Honor queued snapshot through coach queue launch

This commit is contained in:
axiomlogicnexus 2026-05-06 18:20:39 +02:00
parent b08442c335
commit e803631f7a
3 changed files with 199 additions and 95 deletions

View file

@ -9,7 +9,11 @@ Open `UnrealHyperTwist/UnrealHyperTwist.uproject` or `UnrealHyperTwist/UnrealHyp
## Agent Instructions
When Visual Studio MCP tools are available, prefer semantic solution/project/document tools over raw filesystem grep for C# or Unreal symbol work. Default the working scope to `UnrealHyperTwist/` and tracked repo docs before exploring mirrors or archived source material. Do not modify `mirrors/`, `zippedreposource/`, or `docs/refs/` unless the task is explicitly about provenance, intake, or operations. If you encounter Rust content under mirrored dependencies, anchor analysis on the nearest `Cargo.toml`; do not fabricate Rust workspace files for this repo.
When the user says continue, interpret it as continue in logical sequence, not merely resume the nearest open task. If two options are paired and one logically widens or completes the lane needed by the other, do that sequence rather than picking an isolated substep out of order. The default continuation target is: scope the current packet, implement it, validate it, and keep going until the safe package is ready to land unless validation fails or a real decision is required.
When the user says continue, interpret it as continue in logical sequence, not merely resume the nearest open task. If the user says `c`, apply the same rule. If two or more options are paired and one logically widens or completes the lane needed by the others, do that sequence rather than picking an isolated substep out of order. In those cases, prefer the sequence that completes both or all aligned options, except for truly negligible or irrelevant leftovers that can be deferred without distorting the packet. The default continuation target is: scope the current packet, implement it, validate it, and keep going until the safe package is ready to land unless validation fails or a real decision is required.
When continuation includes both implementation and landing work, do the widening and closure work first and commit only once the aligned package is actually ready. Do not stop after the first substep if the logical sequence clearly includes the second.
If the user says continue until the current packet is fully scoped, implemented, validated, committed, and pushed, stay on that packet until one of those states is reached or a real blocker appears. If the user instead says continue for up to `n` aligned steps, keep going through those aligned steps and stop only if tests fail, a real decision is needed, or the specified step budget is exhausted.
When providing a task summary, use the last paragraph to propose and display the next best clean roadmap implementation moves or the next best bounded implementation slice. That closing paragraph should be specific to the latest landed state and should not fall back to generic wrap-up text.

View file

@ -22,6 +22,13 @@ namespace HyperTwistTrainingSubsystemInternal
FString ReasonLine;
};
FResolvedCoachCaseBudget ResolvePreferredCoachCaseBudget(
int32 MaxCases,
EHyperTwistTrainingCoachGuidanceLane DeckLane,
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
bool bQueuedLaunch
);
EHyperTwistCoachPriority CoachPriorityFromScore(const float Score)
{
if (Score >= 85.0f)
@ -186,6 +193,48 @@ namespace HyperTwistTrainingSubsystemInternal
return FString::Printf(TEXT("Launch cases: %d"), CaseCount);
}
FResolvedCoachCaseBudget ResolveQueueLaunchBudgetFromEntry(
const FHyperTwistTrainingCoachSessionQueueStateEntry& QueueEntry,
const FHyperTwistTrainingDeck& QueueDeck,
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
int32 MaxCases
)
{
if (QueueEntry.FocusCaseIds.Num() == 0)
{
return ResolvePreferredCoachCaseBudget(
MaxCases,
GuidanceLaneFromQueueSourceLabel(QueueEntry.SourceLabel),
CoachMemorySnapshot,
true
);
}
FResolvedCoachCaseBudget Result;
Result.RequestedCaseCount = FMath::Max(1, QueueEntry.FocusCaseIds.Num());
Result.ResolvedCaseCount = QueueDeck.Cases.Num() > 0
? QueueDeck.Cases.Num()
: (MaxCases > 0
? FMath::Min(Result.RequestedCaseCount, MaxCases)
: Result.RequestedCaseCount);
if (QueueEntry.bHasTightenedLaunchOverflow && Result.RequestedCaseCount > Result.ResolvedCaseCount)
{
Result.ReasonLine = FString::Printf(
TEXT("queued snapshot keeps the tightened launch scope active (%d deferred cases)"),
FMath::Max(0, QueueEntry.DeferredLaunchCaseCount)
);
}
else if (QueueEntry.bNeedsDeferredLaunchRecovery && Result.RequestedCaseCount > Result.ResolvedCaseCount)
{
Result.ReasonLine = FString::Printf(
TEXT("queued snapshot preserves deferred follow-up recovery (%d deferred cases)"),
FMath::Max(0, QueueEntry.DeferredLaunchCaseCount)
);
}
return Result;
}
FString ResolveGeneratedModeSelectorChoiceLabel(
const FHyperTwistTrainingDeck& Deck,
const FHyperTwistTrainingImportedRuntimeSelectorChoice& Choice
@ -1103,12 +1152,18 @@ namespace HyperTwistTrainingSubsystemInternal
int32 MaxCases
)
{
const FResolvedCoachCaseBudget ResolvedBudget = ResolvePreferredCoachCaseBudget(
MaxCases,
GuidanceLaneFromQueueSourceLabel(QueueEntry.SourceLabel),
CoachMemorySnapshot,
true
);
const FResolvedCoachCaseBudget ResolvedBudget = QueueEntry.FocusCaseIds.Num() > 0
? FResolvedCoachCaseBudget{
FMath::Max(1, QueueEntry.FocusCaseIds.Num()),
MaxCases > 0 ? FMath::Min(QueueEntry.FocusCaseIds.Num(), MaxCases) : QueueEntry.FocusCaseIds.Num(),
FString()
}
: ResolvePreferredCoachCaseBudget(
MaxCases,
GuidanceLaneFromQueueSourceLabel(QueueEntry.SourceLabel),
CoachMemorySnapshot,
true
);
FHyperTwistTrainingDeck SourceDeck;
if (!QueueEntry.FocusDeckId.IsEmpty())
{
@ -2071,27 +2126,22 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP
PanelState.bCanStartCoachFollowUpRun = FollowUpDeck.IsStructurallyValid();
FHyperTwistTrainingDeck QueueLaunchDeck;
HyperTwistTrainingSubsystemInternal::FResolvedCoachCaseBudget QueueLaunchBudget;
if (NextRunnableEntry != nullptr
&& NextRunnableEntry->SourceLabel != TEXT("primary-brief")
&& NextRunnableEntry->SourceLabel != TEXT("follow-up-brief"))
if (NextRunnableEntry != nullptr)
{
QueueLaunchBudget = HyperTwistTrainingSubsystemInternal::ResolvePreferredCoachCaseBudget(
3,
HyperTwistTrainingSubsystemInternal::GuidanceLaneFromQueueSourceLabel(NextRunnableEntry->SourceLabel),
ActiveCoachMemorySnapshot,
true
);
QueueLaunchDeck = HyperTwistTrainingSubsystemInternal::BuildCoachQueueDeckFromEntry(
*NextRunnableEntry,
ActiveRunState,
ActiveCoachMemorySnapshot,
3
);
QueueLaunchBudget = HyperTwistTrainingSubsystemInternal::ResolveQueueLaunchBudgetFromEntry(
*NextRunnableEntry,
QueueLaunchDeck,
ActiveCoachMemorySnapshot,
3
);
}
PanelState.bCanStartQueuedRun = NextRunnableEntry != nullptr
&& ((NextRunnableEntry->SourceLabel == TEXT("primary-brief") && PanelState.bCanStartCoachRecommendedRun)
|| (NextRunnableEntry->SourceLabel == TEXT("follow-up-brief") && PanelState.bCanStartCoachFollowUpRun)
|| QueueLaunchDeck.IsStructurallyValid());
PanelState.bCanStartQueuedRun = NextRunnableEntry != nullptr && QueueLaunchDeck.IsStructurallyValid();
const bool bPreferFollowUpGuidance =
PanelState.bHasFollowUpGuidance
@ -2102,21 +2152,8 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP
const FString ActionLabel = HyperTwistTrainingSubsystemInternal::ResolveQueueActionLabel(
NextRunnableEntry->SourceLabel
);
if (NextRunnableEntry->SourceLabel == TEXT("primary-brief"))
{
PanelState.SuggestedLaunchCaseBudget = RecommendedDeck.Cases.Num();
PanelState.LaunchBudgetReasonLine = RecommendedBudget.ReasonLine;
}
else if (NextRunnableEntry->SourceLabel == TEXT("follow-up-brief"))
{
PanelState.SuggestedLaunchCaseBudget = FollowUpDeck.Cases.Num();
PanelState.LaunchBudgetReasonLine = FollowUpBudget.ReasonLine;
}
else
{
PanelState.SuggestedLaunchCaseBudget = QueueLaunchDeck.Cases.Num();
PanelState.LaunchBudgetReasonLine = QueueLaunchBudget.ReasonLine;
}
PanelState.SuggestedLaunchCaseBudget = QueueLaunchDeck.Cases.Num();
PanelState.LaunchBudgetReasonLine = QueueLaunchBudget.ReasonLine;
PanelState.SecondaryLine = PanelState.SuggestedLaunchCaseBudget > 0
? FString::Printf(
TEXT("%s | %s"),
@ -3554,15 +3591,6 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartNextQueuedCoachRu
return FHyperTwistTrainingRunState();
}
if (NextRunnableEntry->SourceLabel == TEXT("primary-brief"))
{
return StartCoachRecommendedRun(SessionId, MaxCases, StartActionSourceLabel);
}
if (NextRunnableEntry->SourceLabel == TEXT("follow-up-brief"))
{
return StartCoachFollowUpRun(SessionId, MaxCases, StartActionSourceLabel);
}
const FHyperTwistTrainingDeck QueueDeck = HyperTwistTrainingSubsystemInternal::BuildCoachQueueDeckFromEntry(
*NextRunnableEntry,
ActiveRunState,
@ -3584,11 +3612,11 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartNextQueuedCoachRu
QueueStateBeforeStart.ReferenceUtc
);
const HyperTwistTrainingSubsystemInternal::FResolvedCoachCaseBudget LaunchBudget =
HyperTwistTrainingSubsystemInternal::ResolvePreferredCoachCaseBudget(
MaxCases,
HyperTwistTrainingSubsystemInternal::GuidanceLaneFromQueueSourceLabel(NextRunnableEntry->SourceLabel),
HyperTwistTrainingSubsystemInternal::ResolveQueueLaunchBudgetFromEntry(
*NextRunnableEntry,
QueueDeck,
ActiveCoachMemorySnapshot,
true
MaxCases
);
const TArray<FString> LaunchedCaseIds =
HyperTwistTrainingSubsystemInternal::ExtractCaseIdsFromDeck(QueueDeck);
@ -3675,57 +3703,28 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachQueueEntry(
FHyperTwistTrainingDeck QueueDeck;
FHyperTwistCoachBrief QueueBrief;
HyperTwistTrainingSubsystemInternal::FResolvedCoachCaseBudget LaunchBudget;
const bool bHasPrimaryCoachBrief = !ActiveCoachBrief.UserId.IsEmpty() && !ActiveCoachBrief.Headline.IsEmpty();
const bool bHasFollowUpCoachBrief =
!ActiveCoachFollowUpBrief.UserId.IsEmpty() && !ActiveCoachFollowUpBrief.Headline.IsEmpty();
FString UserId = !ActiveCoachSessionQueueState.UserId.IsEmpty()
? ActiveCoachSessionQueueState.UserId
: (ActiveRunState.Session.UserId.IsEmpty() ? TEXT("local-user") : ActiveRunState.Session.UserId);
if (SelectedEntry->SourceLabel == TEXT("primary-brief") && bHasPrimaryCoachBrief)
{
QueueDeck = BuildActiveCoachRecommendedDeck(MaxCases);
LaunchBudget = HyperTwistTrainingSubsystemInternal::ResolvePreferredCoachCaseBudget(
MaxCases,
EHyperTwistTrainingCoachGuidanceLane::Recommended,
ActiveCoachMemorySnapshot,
false
);
QueueBrief = ActiveCoachBrief;
UserId = ActiveCoachBrief.UserId.IsEmpty() ? UserId : ActiveCoachBrief.UserId;
}
else if (SelectedEntry->SourceLabel == TEXT("follow-up-brief") && bHasFollowUpCoachBrief)
{
QueueDeck = BuildActiveCoachFollowUpDeck(MaxCases);
LaunchBudget = HyperTwistTrainingSubsystemInternal::ResolvePreferredCoachCaseBudget(
MaxCases,
EHyperTwistTrainingCoachGuidanceLane::FollowUp,
ActiveCoachMemorySnapshot,
false
);
QueueBrief = ActiveCoachFollowUpBrief;
UserId = ActiveCoachFollowUpBrief.UserId.IsEmpty() ? UserId : ActiveCoachFollowUpBrief.UserId;
}
else
{
QueueDeck = HyperTwistTrainingSubsystemInternal::BuildCoachQueueDeckFromEntry(
*SelectedEntry,
ActiveRunState,
ActiveCoachMemorySnapshot,
MaxCases
);
LaunchBudget = HyperTwistTrainingSubsystemInternal::ResolvePreferredCoachCaseBudget(
MaxCases,
HyperTwistTrainingSubsystemInternal::GuidanceLaneFromQueueSourceLabel(SelectedEntry->SourceLabel),
ActiveCoachMemorySnapshot,
true
);
QueueBrief = HyperTwistTrainingSubsystemInternal::BuildCoachBriefFromQueueEntry(
ActiveCoachSessionQueueState,
*SelectedEntry,
ActiveCoachSessionQueueState.ReferenceUtc
);
}
QueueDeck = HyperTwistTrainingSubsystemInternal::BuildCoachQueueDeckFromEntry(
*SelectedEntry,
ActiveRunState,
ActiveCoachMemorySnapshot,
MaxCases
);
LaunchBudget = HyperTwistTrainingSubsystemInternal::ResolveQueueLaunchBudgetFromEntry(
*SelectedEntry,
QueueDeck,
ActiveCoachMemorySnapshot,
MaxCases
);
QueueBrief = HyperTwistTrainingSubsystemInternal::BuildCoachBriefFromQueueEntry(
ActiveCoachSessionQueueState,
*SelectedEntry,
ActiveCoachSessionQueueState.ReferenceUtc
);
UserId = QueueBrief.UserId.IsEmpty() ? UserId : QueueBrief.UserId;
if (!QueueDeck.IsStructurallyValid())
{

View file

@ -0,0 +1,101 @@
# HyperTwist Phase 4 queue launch continuity packet
Created on `2026-05-06`
Status:
- first-party HyperTwist packet
- bounded Phase `4` coach-to-review validation slice
## Purpose
This packet closes the next end-to-end continuity gap in the live recognition-assisted coach-to-review loop.
The open task is:
- keep queue preview and queue-start execution anchored to the queued snapshot, including `primary-brief` and `follow-up-brief` entries
It is not:
- a new recognition transport packet
- a new review-policy heuristic packet
- a new queue persistence packet
- a broad queue-system rewrite
## Scope
Bounded lane:
- remove the remaining queue-start special cases that bypass queued state for `primary-brief` and `follow-up-brief`
- derive queued launch budget from the queued snapshot when explicit queued focus cases already exist
- keep dashboard queue preview aligned with the same queued deck and queued launch-budget path used for queue execution
Out of scope:
- new queue prioritization rules
- new coach-signal derivation
- new replay scoring
- new review-plan heuristics
- new UI layout work
## Why this was the right next packet
Before this slice:
- recognition replay pressure already flowed into coach guidance
- queue entries already preserved the coach-brief semantics needed for downstream continuity
- active review-policy continuity, dashboard inspection, and recognition-review intervention continuity were already landed
- queue-backed brief reconstruction already restored the right brief fields from persisted queue state
But the queue launch lane still had one honest continuity break:
- queue preview and queue start still special-cased `primary-brief` and `follow-up-brief`
- those special cases could bypass the queued snapshot and fall back to the current live recommended/follow-up deck path
- launch-budget preview for the next queued item could therefore reflect current live brief state rather than the queued entry that was actually about to start
That meant the broad Phase `4` validation lane was still not fully honest once queued work crossed from persisted queue state into previewed and launched runtime execution.
So the next honest move was:
- make queued entries authoritative through preview and launch, instead of preserving queue semantics only to bypass them at execution time
## What landed
Primary code changes:
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp`
- added a queue-launch budget helper that respects queued focus snapshots before falling back to live launch-budget heuristics
- updated queue deck construction so explicit queued focus cases keep their queued case slice instead of being reshaped by current live budget pressure
- updated coach panel state so the next queued launch preview always derives deck validity, case budget, and reason text from the queued entry path
- removed `primary-brief` / `follow-up-brief` queue-start special cases in `StartNextQueuedCoachRun()`
- removed `primary-brief` / `follow-up-brief` queue-entry special cases in `StartCoachQueueEntry()`
- made both queue-start entry points reconstruct their brief, deck, and launch budget from the queued snapshot before action-plan activation
- `AGENTS.md`
- documented the stricter continuation rule for `continue` and `c`, including logical-sequence widening before landing and packet-level completion expectations
## Product effect
Queue-backed coach execution is now more coherent at the runtime handoff boundary:
- `primary-brief` and `follow-up-brief` queue entries now launch from the same queued snapshot path as the rest of the queue lane
- queued focus-case slices remain authoritative when the queue already stored an explicit launch scope
- dashboard queue preview now shows queued launch budget and queued launch viability based on the entry that will actually run
- queue-started action plans stay aligned with the queue-backed brief continuity already landed in the previous packet
## Acceptance criteria
- queue preview no longer falls back to live recommended/follow-up launch state for runnable queued entries
- `StartNextQueuedCoachRun()` no longer bypasses queued state for `primary-brief` or `follow-up-brief`
- `StartCoachQueueEntry()` reconstructs brief, deck, and launch budget from queued state for all runnable entries
- queued focus-case snapshots stay authoritative when present
- full product build succeeds
## Validation checklist
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
2. confirm the next queued panel preview derives its launch deck and launch-budget reason from queued state
3. confirm `primary-brief` and `follow-up-brief` entries now flow through queue-backed brief reconstruction rather than direct live-brief fallback
4. confirm queued focus-case slices remain stable through preview and queue launch
5. confirm no broader review-policy or replay packet is reopened
That is the packet.