Add HyperTwist AI bootstrap guidance
This commit is contained in:
parent
262f193e05
commit
3aeaf16f8b
9 changed files with 672 additions and 0 deletions
10
.github/copilot-instructions.md
vendored
Normal file
10
.github/copilot-instructions.md
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# HyperTwist AI Instructions
|
||||
|
||||
- Treat `UnrealHyperTwist/` as the live project root and `UnrealHyperTwist/Source/UnrealHyperTwist/` as the primary owned code surface.
|
||||
- Prefer Visual Studio MCP semantic tools when available:
|
||||
- use solution/project inspection instead of manual tree walks
|
||||
- use symbol/navigation tools instead of grep for C# and Unreal code
|
||||
- use document open/read/write tools for files already inside the loaded solution
|
||||
- Only `UnrealHyperTwist/Plugins/UnrealMCPChong/` is a maintained in-repo plugin fork. Leave the other plugin folders alone unless the task explicitly targets them.
|
||||
- Do not treat `mirrors/`, `zippedreposource/`, or `docs/refs/` as default edit surfaces.
|
||||
- If Rust code appears inside dependencies or mirrors, use the nearest `Cargo.toml`; do not invent Rust project markers at repo root.
|
||||
10
AGENTS.md
Normal file
10
AGENTS.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
`UnrealHyperTwist/` is the live application root. Treat `UnrealHyperTwist/Source/UnrealHyperTwist/` as the primary first-party code surface and `UnrealHyperTwist/Config/` plus `Content/` as the active project shell. `UnrealHyperTwist/Plugins/UnrealMCPChong/` is the only maintained in-repo plugin fork; the other plugin folders are external copies and should stay untouched unless a plugin task explicitly names them. `docs/` contains working documentation, while `docs/refs/` is reference-only and may contain sensitive ops material. `mirrors/`, `zippedreposource/`, and the archived content folders at repo root are custody/reference storage, not the default edit target.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
Open `UnrealHyperTwist/UnrealHyperTwist.uproject` or `UnrealHyperTwist/UnrealHyperTwist.sln` in Visual Studio 2022 / Unreal Engine 5.7 for normal work. Use `git -C C:\HyperTwist status --short` before and after changes. The bootstrap CI check is defined in `.woodpecker/hypertwist-bootstrap.yml` and validates the presence of the Unreal project, source module root, and docs tree.
|
||||
|
||||
## 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.
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
|
||||
|
||||
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
|
||||
namespace HyperTwistContractLibraryInternal
|
||||
|
|
@ -294,6 +295,51 @@ FHyperTwistVisionServiceHealth UHyperTwistContractLibrary::MakeMockVisionService
|
|||
return Health;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistContractLibrary::MakeSampleTrainingRunState()
|
||||
{
|
||||
const FHyperTwistContentPack ContentPack = UHyperTwistTrainingLibrary::MakeSampleClassicContentPack();
|
||||
if (!ContentPack.IsStructurallyValid() || ContentPack.Tracks.Num() == 0 || ContentPack.Tracks[0].Decks.Num() == 0)
|
||||
{
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
return UHyperTwistTrainingLibrary::StartTrainingRun(
|
||||
ContentPack.Tracks[0].Decks[0],
|
||||
TEXT("local-user"),
|
||||
TEXT("training_session_01"),
|
||||
EHyperTwistTrainingDeliveryMode::Timer
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunStepResult UHyperTwistContractLibrary::MakeSampleTrainingRunStepResult()
|
||||
{
|
||||
const FHyperTwistTrainingRunState RunState = MakeSampleTrainingRunState();
|
||||
if (!RunState.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingRunStepResult();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingAttempt Attempt;
|
||||
Attempt.AttemptId = TEXT("attempt_01");
|
||||
Attempt.TrainingSessionId = RunState.Session.TrainingSessionId;
|
||||
Attempt.CaseId = RunState.Session.CurrentCaseId;
|
||||
Attempt.Result = EHyperTwistTrainingAttemptResult::Success;
|
||||
Attempt.TotalTimeMs = 2214;
|
||||
Attempt.ExecutionTimeMs = 2214;
|
||||
Attempt.RecognitionTimeMs = 0;
|
||||
Attempt.Mistakes = 0;
|
||||
Attempt.ReplayId = RunState.ReplayPacket.ReplayId;
|
||||
Attempt.CompletedAtUtc = TEXT("2026-04-25T12:02:14Z");
|
||||
|
||||
return UHyperTwistTrainingLibrary::ResolveTrainingRunAttempt(RunState, Attempt, Attempt.TotalTimeMs);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingSessionSummary UHyperTwistContractLibrary::MakeSampleTrainingRunSummary()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
return StepResult.SessionSummary;
|
||||
}
|
||||
|
||||
bool UHyperTwistContractLibrary::IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State)
|
||||
{
|
||||
return State.IsStructurallyValid();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,19 @@
|
|||
|
||||
namespace HyperTwistTrainingLibraryInternal
|
||||
{
|
||||
EHyperTwistCaptureMode ToCaptureMode(EHyperTwistTrainingDeliveryMode Mode)
|
||||
{
|
||||
switch (Mode)
|
||||
{
|
||||
case EHyperTwistTrainingDeliveryMode::RecognitionAssisted:
|
||||
return EHyperTwistCaptureMode::Recognition;
|
||||
case EHyperTwistTrainingDeliveryMode::SmartDevice:
|
||||
return EHyperTwistCaptureMode::SmartDevice;
|
||||
default:
|
||||
return EHyperTwistCaptureMode::Runtime;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsModeAllowed(
|
||||
const FHyperTwistTrainingCase& TrainingCase,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
|
|
@ -29,6 +42,65 @@ namespace HyperTwistTrainingLibraryInternal
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCase* FindCaseById(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FString& CaseId
|
||||
)
|
||||
{
|
||||
for (const FHyperTwistTrainingCase& TrainingCase : Deck.Cases)
|
||||
{
|
||||
if (TrainingCase.CaseId == CaseId)
|
||||
{
|
||||
return &TrainingCase;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ContainsCaseId(const TArray<FString>& CaseIds, const FString& CaseId)
|
||||
{
|
||||
return CaseIds.ContainsByPredicate([&CaseId](const FString& Value)
|
||||
{
|
||||
return Value == CaseId;
|
||||
});
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCase* FindFirstCompatibleCase(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
)
|
||||
{
|
||||
for (const FHyperTwistTrainingCase& TrainingCase : Deck.Cases)
|
||||
{
|
||||
if (TrainingCase.IsStructurallyValid() && IsModeAllowed(TrainingCase, Mode))
|
||||
{
|
||||
return &TrainingCase;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck FilterDeckToCaseIds(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const TArray<FString>& AllowedCaseIds
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingDeck FilteredDeck = Deck;
|
||||
FilteredDeck.Cases.Reset();
|
||||
|
||||
for (const FHyperTwistTrainingCase& TrainingCase : Deck.Cases)
|
||||
{
|
||||
if (ContainsCaseId(AllowedCaseIds, TrainingCase.CaseId))
|
||||
{
|
||||
FilteredDeck.Cases.Add(TrainingCase);
|
||||
}
|
||||
}
|
||||
|
||||
return FilteredDeck;
|
||||
}
|
||||
|
||||
float GetBaseDifficultyWeight(const FHyperTwistTrainingCase& TrainingCase)
|
||||
{
|
||||
return TrainingCase.Difficulty > 0.0f ? TrainingCase.Difficulty : 1.0f;
|
||||
|
|
@ -39,6 +111,75 @@ namespace HyperTwistTrainingLibraryInternal
|
|||
return FMath::Max(1.0f, GetBaseDifficultyWeight(TrainingCase) * 10.0f);
|
||||
}
|
||||
|
||||
FHyperTwistPuzzleDefinitionRef MakeDefinitionForCase(const FHyperTwistTrainingCase& TrainingCase)
|
||||
{
|
||||
FHyperTwistPuzzleDefinitionRef Definition;
|
||||
Definition.PuzzleId = TrainingCase.PuzzleId;
|
||||
Definition.DefinitionVersion = TEXT("2026.04");
|
||||
|
||||
if (TrainingCase.PuzzleId.StartsWith(TEXT("hypercube/")))
|
||||
{
|
||||
Definition.PuzzleFamily = EHyperTwistPuzzleFamily::Hypercube;
|
||||
Definition.NotationProfile = TEXT("hyper-4d");
|
||||
Definition.SizeVector = {2, 2, 2, 2};
|
||||
Definition.Dimension = 4;
|
||||
return Definition;
|
||||
}
|
||||
|
||||
Definition.PuzzleFamily = EHyperTwistPuzzleFamily::ClassicCube;
|
||||
Definition.NotationProfile = TEXT("classic-wca");
|
||||
Definition.SizeVector = {3, 3, 3};
|
||||
Definition.Dimension = 3;
|
||||
|
||||
const FString Prefix = TEXT("cube/");
|
||||
if (TrainingCase.PuzzleId.StartsWith(Prefix))
|
||||
{
|
||||
const FString SizePart = TrainingCase.PuzzleId.RightChop(Prefix.Len());
|
||||
TArray<FString> Tokens;
|
||||
SizePart.ParseIntoArray(Tokens, TEXT("x"), true);
|
||||
TArray<int32> ParsedSizes;
|
||||
for (const FString& Token : Tokens)
|
||||
{
|
||||
const int32 ParsedValue = FCString::Atoi(*Token);
|
||||
if (ParsedValue > 0)
|
||||
{
|
||||
ParsedSizes.Add(ParsedValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (ParsedSizes.Num() > 0)
|
||||
{
|
||||
Definition.SizeVector = ParsedSizes;
|
||||
Definition.Dimension = ParsedSizes.Num();
|
||||
}
|
||||
}
|
||||
|
||||
return Definition;
|
||||
}
|
||||
|
||||
FHyperTwistReplayPacket MakeReplayPacketForRun(
|
||||
const FHyperTwistTrainingSession& Session,
|
||||
const FHyperTwistTrainingCase& TrainingCase
|
||||
)
|
||||
{
|
||||
FHyperTwistReplayPacket Packet;
|
||||
Packet.ReplayId = FString::Printf(TEXT("replay_%s"), *Session.TrainingSessionId);
|
||||
Packet.PuzzleDefinition = MakeDefinitionForCase(TrainingCase);
|
||||
Packet.CaptureMode = ToCaptureMode(Session.Mode);
|
||||
Packet.SessionId = Session.TrainingSessionId;
|
||||
Packet.StartedAtUtc = Session.StartedAtUtc;
|
||||
Packet.EndedAtUtc = Session.EndedAtUtc;
|
||||
|
||||
FHyperTwistReplayEvent TimerStartEvent;
|
||||
TimerStartEvent.EventId = TEXT("evt_timer_start");
|
||||
TimerStartEvent.Sequence = 1;
|
||||
TimerStartEvent.TimeMs = 0;
|
||||
TimerStartEvent.EventType = EHyperTwistReplayEventType::TimerStart;
|
||||
|
||||
Packet = UHyperTwistReplayLibrary::AppendReplayEvent(Packet, TimerStartEvent);
|
||||
return Packet;
|
||||
}
|
||||
|
||||
float ComputeSelectionScore(
|
||||
const FHyperTwistTrainingCase& TrainingCase,
|
||||
const FHyperTwistProgressionMemory* Memory,
|
||||
|
|
@ -193,6 +334,66 @@ FHyperTwistTrainingSession UHyperTwistTrainingLibrary::StartTrainingSession(
|
|||
return UpdatedSession;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingLibrary::StartTrainingRun(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FString& UserId,
|
||||
const FString& SessionId,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingRunState RunState;
|
||||
if (!Deck.IsStructurallyValid())
|
||||
{
|
||||
return RunState;
|
||||
}
|
||||
|
||||
RunState.ActiveDeck = Deck;
|
||||
RunState.Session = StartTrainingSession(Deck, UserId, SessionId, Mode);
|
||||
if (!RunState.Session.IsStructurallyValid())
|
||||
{
|
||||
return RunState;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingCase& TrainingCase : Deck.Cases)
|
||||
{
|
||||
if (!TrainingCase.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RunState.RemainingCaseIds.Add(TrainingCase.CaseId);
|
||||
RunState.Memories.Add(MakeInitialProgressionMemory(RunState.Session, TrainingCase));
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCase* InitialCase = HyperTwistTrainingLibraryInternal::FindFirstCompatibleCase(Deck, Mode);
|
||||
if (InitialCase != nullptr)
|
||||
{
|
||||
FHyperTwistTrainingDeck InitialDeck = Deck;
|
||||
InitialDeck.Cases.Reset();
|
||||
InitialDeck.Cases.Add(*InitialCase);
|
||||
RunState.CurrentSelection = SelectNextTrainingCase(InitialDeck, RunState.Memories, Mode, FString());
|
||||
}
|
||||
|
||||
if (RunState.CurrentSelection.IsStructurallyValid())
|
||||
{
|
||||
RunState.Session = AdvanceTrainingSession(
|
||||
RunState.Session,
|
||||
EHyperTwistTrainingSessionState::Active,
|
||||
RunState.CurrentSelection.TrainingCase.CaseId
|
||||
);
|
||||
RunState.ReplayPacket = HyperTwistTrainingLibraryInternal::MakeReplayPacketForRun(
|
||||
RunState.Session,
|
||||
RunState.CurrentSelection.TrainingCase
|
||||
);
|
||||
if (RunState.ReplayPacket.Events.Num() > 0)
|
||||
{
|
||||
RunState.LastReplayEvent = RunState.ReplayPacket.Events.Last();
|
||||
}
|
||||
}
|
||||
|
||||
return RunState;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingLibrary::CanTransitionTrainingSession(
|
||||
EHyperTwistTrainingSessionState FromState,
|
||||
EHyperTwistTrainingSessionState ToState
|
||||
|
|
@ -654,3 +855,147 @@ FHyperTwistTrainingAttemptResolution UHyperTwistTrainingLibrary::ResolveTraining
|
|||
Resolution.NextCaseSelection = AdvanceResult.NextCaseSelection;
|
||||
return Resolution;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunStepResult UHyperTwistTrainingLibrary::ResolveTrainingRunAttempt(
|
||||
const FHyperTwistTrainingRunState& RunState,
|
||||
const FHyperTwistTrainingAttempt& Attempt,
|
||||
int32 TimeMs
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingRunStepResult StepResult;
|
||||
StepResult.UpdatedRunState = RunState;
|
||||
|
||||
if (!RunState.IsStructurallyValid() || !Attempt.IsStructurallyValid())
|
||||
{
|
||||
return StepResult;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCase* TrainingCase = HyperTwistTrainingLibraryInternal::FindCaseById(
|
||||
RunState.ActiveDeck,
|
||||
Attempt.CaseId
|
||||
);
|
||||
if (TrainingCase == nullptr || !TrainingCase->IsStructurallyValid())
|
||||
{
|
||||
return StepResult;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingAttempt NormalizedAttempt = Attempt;
|
||||
if (NormalizedAttempt.ReplayId.IsEmpty() && !RunState.ReplayPacket.ReplayId.IsEmpty())
|
||||
{
|
||||
NormalizedAttempt.ReplayId = RunState.ReplayPacket.ReplayId;
|
||||
}
|
||||
|
||||
StepResult.AttemptResolution = ResolveTrainingAttempt(
|
||||
RunState.Session,
|
||||
RunState.ActiveDeck,
|
||||
*TrainingCase,
|
||||
RunState.Memories,
|
||||
NormalizedAttempt,
|
||||
TimeMs
|
||||
);
|
||||
if (!StepResult.AttemptResolution.IsStructurallyValid())
|
||||
{
|
||||
return StepResult;
|
||||
}
|
||||
|
||||
StepResult.UpdatedRunState.Session = StepResult.AttemptResolution.UpdatedSession;
|
||||
StepResult.UpdatedRunState.CurrentSelection = StepResult.AttemptResolution.NextCaseSelection;
|
||||
StepResult.UpdatedRunState.Attempts.Add(NormalizedAttempt);
|
||||
StepResult.UpdatedRunState.ReplayPacket = AppendTrainingAttemptToReplay(
|
||||
RunState.ReplayPacket,
|
||||
RunState.Session,
|
||||
NormalizedAttempt,
|
||||
TimeMs
|
||||
);
|
||||
if (StepResult.UpdatedRunState.ReplayPacket.Events.Num() > 0)
|
||||
{
|
||||
StepResult.UpdatedRunState.LastReplayEvent = StepResult.UpdatedRunState.ReplayPacket.Events.Last();
|
||||
}
|
||||
else
|
||||
{
|
||||
StepResult.UpdatedRunState.LastReplayEvent = StepResult.AttemptResolution.ReplayEvent;
|
||||
}
|
||||
|
||||
bool bUpdatedExistingMemory = false;
|
||||
for (FHyperTwistProgressionMemory& Memory : StepResult.UpdatedRunState.Memories)
|
||||
{
|
||||
if (Memory.CaseId == StepResult.AttemptResolution.UpdatedProgressionMemory.CaseId)
|
||||
{
|
||||
Memory = StepResult.AttemptResolution.UpdatedProgressionMemory;
|
||||
bUpdatedExistingMemory = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bUpdatedExistingMemory)
|
||||
{
|
||||
StepResult.UpdatedRunState.Memories.Add(StepResult.AttemptResolution.UpdatedProgressionMemory);
|
||||
}
|
||||
|
||||
StepResult.UpdatedRunState.RemainingCaseIds = RunState.RemainingCaseIds;
|
||||
if (NormalizedAttempt.Result == EHyperTwistTrainingAttemptResult::Success)
|
||||
{
|
||||
StepResult.UpdatedRunState.RemainingCaseIds.Remove(NormalizedAttempt.CaseId);
|
||||
}
|
||||
|
||||
if (StepResult.UpdatedRunState.RemainingCaseIds.Num() == 0)
|
||||
{
|
||||
StepResult.UpdatedRunState.Session = CompleteTrainingSession(
|
||||
StepResult.UpdatedRunState.Session,
|
||||
false
|
||||
);
|
||||
StepResult.UpdatedRunState.ReplayPacket.EndedAtUtc = !NormalizedAttempt.CompletedAtUtc.IsEmpty()
|
||||
? NormalizedAttempt.CompletedAtUtc
|
||||
: StepResult.UpdatedRunState.Session.EndedAtUtc;
|
||||
|
||||
FHyperTwistReplayEvent SolveEndEvent;
|
||||
SolveEndEvent.EventType = EHyperTwistReplayEventType::SolveEnd;
|
||||
SolveEndEvent.TimeMs = FMath::Max(TimeMs, NormalizedAttempt.TotalTimeMs);
|
||||
StepResult.UpdatedRunState.ReplayPacket = UHyperTwistReplayLibrary::AppendReplayEvent(
|
||||
StepResult.UpdatedRunState.ReplayPacket,
|
||||
SolveEndEvent
|
||||
);
|
||||
if (StepResult.UpdatedRunState.ReplayPacket.Events.Num() > 0)
|
||||
{
|
||||
StepResult.UpdatedRunState.LastReplayEvent = StepResult.UpdatedRunState.ReplayPacket.Events.Last();
|
||||
}
|
||||
StepResult.UpdatedRunState.CurrentSelection = FHyperTwistTrainingCaseSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
const FHyperTwistTrainingDeck RemainingDeck = HyperTwistTrainingLibraryInternal::FilterDeckToCaseIds(
|
||||
RunState.ActiveDeck,
|
||||
StepResult.UpdatedRunState.RemainingCaseIds
|
||||
);
|
||||
const FHyperTwistTrainingCaseSelection RemainingSelection = SelectNextTrainingCase(
|
||||
RemainingDeck,
|
||||
StepResult.UpdatedRunState.Memories,
|
||||
RunState.Session.Mode,
|
||||
NormalizedAttempt.CaseId
|
||||
);
|
||||
|
||||
if (RemainingSelection.IsStructurallyValid())
|
||||
{
|
||||
StepResult.UpdatedRunState.CurrentSelection = RemainingSelection;
|
||||
StepResult.UpdatedRunState.Session.CurrentCaseId = RemainingSelection.TrainingCase.CaseId;
|
||||
StepResult.UpdatedRunState.Session.SessionState = EHyperTwistTrainingSessionState::Active;
|
||||
}
|
||||
else
|
||||
{
|
||||
StepResult.UpdatedRunState.Session = CompleteTrainingSession(
|
||||
StepResult.UpdatedRunState.Session,
|
||||
false
|
||||
);
|
||||
StepResult.UpdatedRunState.CurrentSelection = FHyperTwistTrainingCaseSelection();
|
||||
StepResult.UpdatedRunState.ReplayPacket.EndedAtUtc = !NormalizedAttempt.CompletedAtUtc.IsEmpty()
|
||||
? NormalizedAttempt.CompletedAtUtc
|
||||
: StepResult.UpdatedRunState.Session.EndedAtUtc;
|
||||
}
|
||||
}
|
||||
|
||||
StepResult.SessionSummary = DeriveTrainingSessionSummary(
|
||||
StepResult.UpdatedRunState.Session,
|
||||
StepResult.UpdatedRunState.Attempts
|
||||
);
|
||||
return StepResult;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
|
||||
|
||||
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::HasActiveRun() const
|
||||
{
|
||||
return bHasActiveRun && ActiveRunState.IsStructurallyValid();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::GetActiveRunState() const
|
||||
{
|
||||
return ActiveRunState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingSessionSummary UHyperTwistTrainingSubsystem::GetActiveRunSummary() const
|
||||
{
|
||||
return ActiveRunSummary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromDeck(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FString& UserId,
|
||||
const FString& SessionId,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
)
|
||||
{
|
||||
ActiveRunState = UHyperTwistTrainingLibrary::StartTrainingRun(Deck, UserId, SessionId, Mode);
|
||||
bHasActiveRun = ActiveRunState.IsStructurallyValid();
|
||||
RefreshSummary();
|
||||
return ActiveRunState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartSampleClassicTrainingRun(
|
||||
const FString& UserId,
|
||||
const FString& SessionId,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
)
|
||||
{
|
||||
const FHyperTwistContentPack ContentPack = UHyperTwistTrainingLibrary::MakeSampleClassicContentPack();
|
||||
if (!ContentPack.IsStructurallyValid() || ContentPack.Tracks.Num() == 0 || ContentPack.Tracks[0].Decks.Num() == 0)
|
||||
{
|
||||
ClearActiveRun();
|
||||
return ActiveRunState;
|
||||
}
|
||||
|
||||
return StartTrainingRunFromDeck(ContentPack.Tracks[0].Decks[0], UserId, SessionId, Mode);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunStepResult UHyperTwistTrainingSubsystem::SubmitTrainingAttempt(
|
||||
const FHyperTwistTrainingAttempt& Attempt,
|
||||
int32 TimeMs
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingRunStepResult StepResult;
|
||||
StepResult.UpdatedRunState = ActiveRunState;
|
||||
|
||||
if (!HasActiveRun())
|
||||
{
|
||||
return StepResult;
|
||||
}
|
||||
|
||||
StepResult = UHyperTwistTrainingLibrary::ResolveTrainingRunAttempt(
|
||||
ActiveRunState,
|
||||
Attempt,
|
||||
TimeMs
|
||||
);
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return StepResult;
|
||||
}
|
||||
|
||||
ActiveRunState = StepResult.UpdatedRunState;
|
||||
bHasActiveRun = ActiveRunState.IsStructurallyValid();
|
||||
ActiveRunSummary = StepResult.SessionSummary;
|
||||
return StepResult;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingSession UHyperTwistTrainingSubsystem::CompleteActiveRun(bool bAbortSession)
|
||||
{
|
||||
if (!HasActiveRun())
|
||||
{
|
||||
return FHyperTwistTrainingSession();
|
||||
}
|
||||
|
||||
ActiveRunState.Session = UHyperTwistTrainingLibrary::CompleteTrainingSession(ActiveRunState.Session, bAbortSession);
|
||||
ActiveRunState.CurrentSelection = FHyperTwistTrainingCaseSelection();
|
||||
ActiveRunState.RemainingCaseIds.Reset();
|
||||
|
||||
if (ActiveRunState.ReplayPacket.ReplayId.Len() > 0)
|
||||
{
|
||||
ActiveRunState.ReplayPacket.EndedAtUtc = ActiveRunState.Session.EndedAtUtc;
|
||||
}
|
||||
|
||||
RefreshSummary();
|
||||
return ActiveRunState.Session;
|
||||
}
|
||||
|
||||
void UHyperTwistTrainingSubsystem::ClearActiveRun()
|
||||
{
|
||||
ActiveRunState = FHyperTwistTrainingRunState();
|
||||
ActiveRunSummary = FHyperTwistTrainingSessionSummary();
|
||||
bHasActiveRun = false;
|
||||
}
|
||||
|
||||
void UHyperTwistTrainingSubsystem::RefreshSummary()
|
||||
{
|
||||
if (!ActiveRunState.Session.IsStructurallyValid())
|
||||
{
|
||||
ActiveRunSummary = FHyperTwistTrainingSessionSummary();
|
||||
return;
|
||||
}
|
||||
|
||||
ActiveRunSummary = UHyperTwistTrainingLibrary::DeriveTrainingSessionSummary(
|
||||
ActiveRunState.Session,
|
||||
ActiveRunState.Attempts
|
||||
);
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include "HyperTwistCore/HyperTwistCoreTypes.h"
|
||||
#include "HyperTwistReplay/HyperTwistReplayTypes.h"
|
||||
#include "HyperTwistRecognition/HyperTwistRecognitionTypes.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
|
||||
#include "HyperTwistContractLibrary.generated.h"
|
||||
|
||||
UCLASS()
|
||||
|
|
@ -52,6 +53,15 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
|
||||
static FHyperTwistVisionServiceHealth MakeMockVisionServiceHealth();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingRunState MakeSampleTrainingRunState();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingRunStepResult MakeSampleTrainingRunStepResult();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingSessionSummary MakeSampleTrainingRunSummary();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Validation")
|
||||
static bool IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,14 @@ public:
|
|||
EHyperTwistTrainingDeliveryMode Mode
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingRunState StartTrainingRun(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FString& UserId,
|
||||
const FString& SessionId,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static bool CanTransitionTrainingSession(
|
||||
EHyperTwistTrainingSessionState FromState,
|
||||
|
|
@ -120,4 +128,11 @@ public:
|
|||
const FHyperTwistTrainingAttempt& Attempt,
|
||||
int32 TimeMs
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingRunStepResult ResolveTrainingRunAttempt(
|
||||
const FHyperTwistTrainingRunState& RunState,
|
||||
const FHyperTwistTrainingAttempt& Attempt,
|
||||
int32 TimeMs
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
|
||||
#include "HyperTwistTrainingSubsystem.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UNREALHYPERTWIST_API UHyperTwistTrainingSubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
bool HasActiveRun() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRunState GetActiveRunState() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingSessionSummary GetActiveRunSummary() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRunState StartTrainingRunFromDeck(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FString& UserId,
|
||||
const FString& SessionId,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRunState StartSampleClassicTrainingRun(
|
||||
const FString& UserId,
|
||||
const FString& SessionId,
|
||||
EHyperTwistTrainingDeliveryMode Mode
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRunStepResult SubmitTrainingAttempt(
|
||||
const FHyperTwistTrainingAttempt& Attempt,
|
||||
int32 TimeMs
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingSession CompleteActiveRun(bool bAbortSession);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
void ClearActiveRun();
|
||||
|
||||
private:
|
||||
void RefreshSummary();
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingRunState ActiveRunState;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingSessionSummary ActiveRunSummary;
|
||||
|
||||
UPROPERTY()
|
||||
bool bHasActiveRun = false;
|
||||
};
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "CoreMinimal.h"
|
||||
#include "HyperTwistCore/HyperTwistCoreTypes.h"
|
||||
#include "HyperTwistReplay/HyperTwistReplayTypes.h"
|
||||
#include "HyperTwistTrainingTypes.generated.h"
|
||||
|
||||
UENUM(BlueprintType)
|
||||
|
|
@ -483,3 +484,60 @@ struct FHyperTwistTrainingAttemptResolution
|
|||
&& UpdatedProgressionMemory.CaseId.Len() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingRunState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingDeck ActiveDeck;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingSession Session;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingCaseSelection CurrentSelection;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> RemainingCaseIds;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistProgressionMemory> Memories;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingAttempt> Attempts;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistReplayPacket ReplayPacket;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistReplayEvent LastReplayEvent;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return ActiveDeck.IsStructurallyValid()
|
||||
&& Session.IsStructurallyValid()
|
||||
&& (!ReplayPacket.ReplayId.IsEmpty() ? ReplayPacket.IsStructurallyValid() : true);
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingRunStepResult
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingRunState UpdatedRunState;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingAttemptResolution AttemptResolution;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingSessionSummary SessionSummary;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return UpdatedRunState.IsStructurallyValid();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue