Add persisted timing-aware training backend

This commit is contained in:
axiomlogicnexus 2026-04-25 19:36:07 +02:00
parent d382f48048
commit 4c38fcf536
15 changed files with 6107 additions and 20 deletions

View file

@ -2,6 +2,7 @@
#include "HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
#include "JsonObjectConverter.h"
namespace HyperTwistContractLibraryInternal
@ -331,14 +332,13 @@ FHyperTwistTrainingRunStepResult UHyperTwistContractLibrary::MakeSampleTrainingR
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");
Attempt.TimingBreakdown = MakeSampleTrainingTimingBreakdown();
return UHyperTwistTrainingLibrary::ResolveTrainingRunAttempt(RunState, Attempt, Attempt.TotalTimeMs);
return UHyperTwistTrainingLibrary::ResolveTrainingRunAttempt(RunState, Attempt, 0);
}
FHyperTwistTrainingSessionSummary UHyperTwistContractLibrary::MakeSampleTrainingRunSummary()
@ -347,6 +347,291 @@ FHyperTwistTrainingSessionSummary UHyperTwistContractLibrary::MakeSampleTraining
return StepResult.SessionSummary;
}
FHyperTwistTrainingTimingPolicy UHyperTwistContractLibrary::MakeSampleTrainingTimingPolicy()
{
FHyperTwistTrainingTimingPolicy TimingPolicy;
TimingPolicy.bInspectionEnabled = true;
TimingPolicy.InspectionDurationMs = 15000;
TimingPolicy.bInspectionPenaltiesEnabled = true;
TimingPolicy.bAllowManualPenalty = true;
TimingPolicy.bHideRunningTime = false;
FHyperTwistTrainingSplitPhaseDefinition RecognitionPhase;
RecognitionPhase.PhaseId = TEXT("recognition");
RecognitionPhase.Label = TEXT("Recognition");
RecognitionPhase.Order = 0;
FHyperTwistTrainingSplitPhaseDefinition ExecutionPhase;
ExecutionPhase.PhaseId = TEXT("execution");
ExecutionPhase.Label = TEXT("Execution");
ExecutionPhase.Order = 1;
TimingPolicy.SplitPhases = {RecognitionPhase, ExecutionPhase};
return TimingPolicy;
}
FHyperTwistTrainingTimingBreakdown UHyperTwistContractLibrary::MakeSampleTrainingTimingBreakdown()
{
FHyperTwistTrainingTimingBreakdown TimingBreakdown;
TimingBreakdown.InspectionElapsedMs = 14750;
TimingBreakdown.RawSolveTimeMs = 2214;
TimingBreakdown.ManualPenalty = EHyperTwistTrainingPenalty::None;
TimingBreakdown.InspectionPenalty = EHyperTwistTrainingPenalty::None;
TimingBreakdown.AppliedPenalty = EHyperTwistTrainingPenalty::None;
TimingBreakdown.PenaltyOffsetMs = 0;
TimingBreakdown.FinalTimeMs = 2214;
TimingBreakdown.bDidNotFinish = false;
FHyperTwistTrainingSplitCapture RecognitionCapture;
RecognitionCapture.PhaseId = TEXT("recognition");
RecognitionCapture.Label = TEXT("Recognition");
RecognitionCapture.TimestampMs = 620;
TimingBreakdown.SplitCaptures = {RecognitionCapture};
return TimingBreakdown;
}
FHyperTwistTrainingRepositoryState UHyperTwistContractLibrary::MakeSampleTrainingRepositoryState()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return FHyperTwistTrainingRepositoryState();
}
FHyperTwistTrainingRepositoryState RepositoryState;
return UHyperTwistTrainingRepositoryLibrary::RecordRunState(RepositoryState, StepResult.UpdatedRunState);
}
FHyperTwistTrainingDeckHistorySummary UHyperTwistContractLibrary::MakeSampleTrainingDeckHistorySummary()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return FHyperTwistTrainingDeckHistorySummary();
}
const FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState();
return UHyperTwistTrainingRepositoryLibrary::DeriveDeckHistorySummary(
RepositoryState,
StepResult.UpdatedRunState.Session.UserId,
StepResult.UpdatedRunState.Session.DeckId
);
}
FHyperTwistTrainingCaseScopedStats UHyperTwistContractLibrary::MakeSampleTrainingCaseScopedStats()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return FHyperTwistTrainingCaseScopedStats();
}
return UHyperTwistTrainingRepositoryLibrary::DeriveCaseScopedStats(
MakeSampleTrainingRepositoryState(),
StepResult.UpdatedRunState.Session.UserId,
StepResult.UpdatedRunState.Session.DeckId,
StepResult.AttemptResolution.UpdatedProgressionMemory.CaseId,
TEXT("2026-04-28T12:05:00Z")
);
}
FHyperTwistTrainingDeckScopedStats UHyperTwistContractLibrary::MakeSampleTrainingDeckScopedStats()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return FHyperTwistTrainingDeckScopedStats();
}
return UHyperTwistTrainingRepositoryLibrary::DeriveDeckScopedStats(
MakeSampleTrainingRepositoryState(),
StepResult.UpdatedRunState.Session.UserId,
StepResult.UpdatedRunState.Session.DeckId,
TEXT("2026-04-28T12:05:00Z")
);
}
FHyperTwistTrainingCaseLearnerState UHyperTwistContractLibrary::MakeSampleTrainingCaseLearnerState()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid() || !StepResult.AttemptResolution.NextCaseSelection.IsStructurallyValid())
{
return FHyperTwistTrainingCaseLearnerState();
}
const FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState();
FHyperTwistTrainingCaseLearnerState LearnerState;
if (UHyperTwistTrainingRepositoryLibrary::TryGetLearnerState(
RepositoryState,
StepResult.UpdatedRunState.Session.UserId,
StepResult.UpdatedRunState.Session.DeckId,
StepResult.AttemptResolution.UpdatedProgressionMemory.CaseId,
LearnerState
))
{
return LearnerState;
}
return FHyperTwistTrainingCaseLearnerState();
}
FHyperTwistTrainingLearnerDeckStateSummary UHyperTwistContractLibrary::MakeSampleTrainingLearnerDeckStateSummary()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return FHyperTwistTrainingLearnerDeckStateSummary();
}
const FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState();
return UHyperTwistTrainingRepositoryLibrary::DeriveLearnerDeckStateSummary(
RepositoryState,
StepResult.UpdatedRunState.Session.UserId,
StepResult.UpdatedRunState.Session.DeckId,
TEXT("2026-04-28T12:05:00Z")
);
}
FHyperTwistTrainingLearnerProfile UHyperTwistContractLibrary::MakeSampleTrainingLearnerProfile()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return FHyperTwistTrainingLearnerProfile();
}
return UHyperTwistTrainingRepositoryLibrary::DeriveLearnerProfile(
MakeSampleTrainingRepositoryState(),
StepResult.UpdatedRunState.Session.UserId,
TEXT("2026-04-28T12:05:00Z")
);
}
FHyperTwistTrainingCoachMemorySnapshot UHyperTwistContractLibrary::MakeSampleTrainingCoachMemorySnapshot()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return FHyperTwistTrainingCoachMemorySnapshot();
}
return UHyperTwistTrainingRepositoryLibrary::DeriveCoachMemorySnapshot(
MakeSampleTrainingRepositoryState(),
StepResult.UpdatedRunState.Session.UserId,
TEXT("2026-04-28T12:05:00Z")
);
}
TArray<FHyperTwistTrainingLearnerPreset> UHyperTwistContractLibrary::MakeSampleTrainingLearnerPresets()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return TArray<FHyperTwistTrainingLearnerPreset>();
}
return UHyperTwistTrainingRepositoryLibrary::DeriveLearnerPresets(
MakeSampleTrainingRepositoryState(),
StepResult.UpdatedRunState.Session.UserId,
TEXT("2026-04-28T12:05:00Z")
);
}
TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistContractLibrary::MakeSampleTrainingSessionTemplates()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return TArray<FHyperTwistTrainingSessionTemplate>();
}
return UHyperTwistTrainingRepositoryLibrary::DeriveTrainingSessionTemplates(
MakeSampleTrainingRepositoryState(),
StepResult.UpdatedRunState.Session.UserId,
TEXT("2026-04-28T12:05:00Z")
);
}
FHyperTwistTrainingDeck UHyperTwistContractLibrary::MakeSampleTrainingSessionTemplateDeck()
{
const TArray<FHyperTwistTrainingSessionTemplate> Templates = MakeSampleTrainingSessionTemplates();
if (Templates.Num() == 0)
{
return FHyperTwistTrainingDeck();
}
return UHyperTwistTrainingCatalogLibrary::MaterializeTrainingSessionTemplateDeck(
Templates[0],
UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog()
);
}
TArray<FHyperTwistTrainingCaseRecommendation> UHyperTwistContractLibrary::MakeSampleTrainingCaseRecommendations()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return TArray<FHyperTwistTrainingCaseRecommendation>();
}
const FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState();
return UHyperTwistTrainingRepositoryLibrary::ListCaseRecommendationsForDeck(
RepositoryState,
StepResult.UpdatedRunState.ActiveDeck,
StepResult.UpdatedRunState.Session.UserId,
TEXT("2026-04-28T12:05:00Z"),
5
);
}
FHyperTwistTrainingDeck UHyperTwistContractLibrary::MakeSampleTrainingReviewDeck()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return FHyperTwistTrainingDeck();
}
const TArray<FHyperTwistTrainingCaseRecommendation> Recommendations = MakeSampleTrainingCaseRecommendations();
return UHyperTwistTrainingRepositoryLibrary::BuildReviewDeckFromRecommendations(
StepResult.UpdatedRunState.ActiveDeck,
Recommendations,
2
);
}
FHyperTwistTrainingReviewPlanState UHyperTwistContractLibrary::MakeSampleTrainingReviewPlanState()
{
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
if (!StepResult.IsStructurallyValid())
{
return FHyperTwistTrainingReviewPlanState();
}
const TArray<FHyperTwistTrainingCaseRecommendation> Recommendations = MakeSampleTrainingCaseRecommendations();
const FHyperTwistTrainingDeck ReviewDeck = MakeSampleTrainingReviewDeck();
const FHyperTwistTrainingReviewPolicy ReviewPolicy =
UHyperTwistTrainingRepositoryLibrary::GetDefaultReviewPolicy(EHyperTwistTrainingDeliveryMode::Timer);
return UHyperTwistTrainingRepositoryLibrary::BuildReviewPlan(
StepResult.UpdatedRunState.ActiveDeck,
ReviewDeck,
StepResult.UpdatedRunState.Session.UserId,
Recommendations,
ReviewPolicy,
TEXT("review_plan_training_session_01"),
TEXT("2026-04-28T12:05:00Z"),
EHyperTwistTrainingDeliveryMode::Timer
);
}
FHyperTwistTrainingReviewFlowStatus UHyperTwistContractLibrary::MakeSampleTrainingReviewFlowStatus()
{
return UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(
MakeSampleTrainingReviewPlanState()
);
}
bool UHyperTwistContractLibrary::IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State)
{
return State.IsStructurallyValid();
@ -372,6 +657,11 @@ FString UHyperTwistContractLibrary::SerializeContentPackToJson(const FHyperTwist
return HyperTwistContractLibraryInternal::SerializeStructToJson(ContentPack);
}
FString UHyperTwistContractLibrary::SerializeTrainingRepositoryStateToJson(const FHyperTwistTrainingRepositoryState& RepositoryState)
{
return HyperTwistContractLibraryInternal::SerializeStructToJson(RepositoryState);
}
bool UHyperTwistContractLibrary::DeserializePuzzleStateFromJson(const FString& Json, FHyperTwistPuzzleState& OutState)
{
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutState);
@ -386,3 +676,8 @@ bool UHyperTwistContractLibrary::DeserializeContentPackFromJson(const FString& J
{
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutContentPack);
}
bool UHyperTwistContractLibrary::DeserializeTrainingRepositoryStateFromJson(const FString& Json, FHyperTwistTrainingRepositoryState& OutRepositoryState)
{
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutRepositoryState);
}

View file

@ -4,6 +4,25 @@
namespace HyperTwistTrainingCatalogLibraryInternal
{
FString TemplateKindToTag(EHyperTwistTrainingSessionTemplateKind TemplateKind)
{
switch (TemplateKind)
{
case EHyperTwistTrainingSessionTemplateKind::ReviewPlan:
return TEXT("template:review-plan");
case EHyperTwistTrainingSessionTemplateKind::Recovery:
return TEXT("template:recovery");
case EHyperTwistTrainingSessionTemplateKind::Accuracy:
return TEXT("template:accuracy");
case EHyperTwistTrainingSessionTemplateKind::Speed:
return TEXT("template:speed");
case EHyperTwistTrainingSessionTemplateKind::Coach:
return TEXT("template:coach");
default:
return TEXT("template:unknown");
}
}
FHyperTwistTrainingSourceAttribution MakeAlgTrainerAttribution(
const FString& TrackId,
const FString& DeckId,
@ -62,6 +81,29 @@ namespace HyperTwistTrainingCatalogLibraryInternal
);
return TrainingCase;
}
FHyperTwistTrainingTimingPolicy MakeStandardAlgorithmTimingPolicy()
{
FHyperTwistTrainingTimingPolicy TimingPolicy;
TimingPolicy.bInspectionEnabled = true;
TimingPolicy.InspectionDurationMs = 15000;
TimingPolicy.bInspectionPenaltiesEnabled = true;
TimingPolicy.bAllowManualPenalty = true;
TimingPolicy.bHideRunningTime = false;
FHyperTwistTrainingSplitPhaseDefinition RecognitionPhase;
RecognitionPhase.PhaseId = TEXT("recognition");
RecognitionPhase.Label = TEXT("Recognition");
RecognitionPhase.Order = 0;
FHyperTwistTrainingSplitPhaseDefinition ExecutionPhase;
ExecutionPhase.PhaseId = TEXT("execution");
ExecutionPhase.Label = TEXT("Execution");
ExecutionPhase.Order = 1;
TimingPolicy.SplitPhases = {RecognitionPhase, ExecutionPhase};
return TimingPolicy;
}
}
FHyperTwistContentPack UHyperTwistTrainingCatalogLibrary::MakeAlgTrainerStarterContentPack()
@ -102,6 +144,7 @@ FHyperTwistContentPack UHyperTwistTrainingCatalogLibrary::MakeAlgTrainerStarterC
TDeck.bSupportsAlgorithmReveal = true;
TDeck.bSupportsVirtualCube = true;
TDeck.bSupportsSmartCube = true;
TDeck.TimingPolicy = HyperTwistTrainingCatalogLibraryInternal::MakeStandardAlgorithmTimingPolicy();
TDeck.Cases = {OLLT01, OLLT02};
TDeck.DifficultyBand = TEXT("intermediate");
TDeck.Tags = {TEXT("classic"), TEXT("oll"), TEXT("alg-trainer"), TEXT("subset:t")};
@ -144,6 +187,7 @@ FHyperTwistContentPack UHyperTwistTrainingCatalogLibrary::MakeAlgTrainerStarterC
UDeck.bSupportsAlgorithmReveal = true;
UDeck.bSupportsVirtualCube = true;
UDeck.bSupportsSmartCube = true;
UDeck.TimingPolicy = HyperTwistTrainingCatalogLibraryInternal::MakeStandardAlgorithmTimingPolicy();
UDeck.Cases = {OLLU01, OLLU02};
UDeck.DifficultyBand = TEXT("intermediate");
UDeck.Tags = {TEXT("classic"), TEXT("oll"), TEXT("alg-trainer"), TEXT("subset:u")};
@ -192,6 +236,46 @@ TArray<FHyperTwistContentPack> UHyperTwistTrainingCatalogLibrary::MakePhase3Trai
};
}
bool UHyperTwistTrainingCatalogLibrary::TryFindDeckInCatalog(
const TArray<FHyperTwistContentPack>& TrainingCatalog,
const FString& DeckId,
FHyperTwistTrainingDeck& OutDeck
)
{
OutDeck = FHyperTwistTrainingDeck();
if (DeckId.IsEmpty())
{
return false;
}
for (const FHyperTwistContentPack& ContentPack : TrainingCatalog)
{
if (!ContentPack.IsStructurallyValid())
{
continue;
}
for (const FHyperTwistTrainingTrack& Track : ContentPack.Tracks)
{
if (!Track.IsStructurallyValid())
{
continue;
}
for (const FHyperTwistTrainingDeck& Deck : Track.Decks)
{
if (Deck.DeckId == DeckId)
{
OutDeck = Deck;
return OutDeck.IsStructurallyValid();
}
}
}
}
return false;
}
bool UHyperTwistTrainingCatalogLibrary::TryGetTrackFromContentPack(
const FHyperTwistContentPack& ContentPack,
const FString& TrackId,
@ -259,3 +343,69 @@ bool UHyperTwistTrainingCatalogLibrary::TryGetDefaultDeckFromContentPack(
OutDeck = ContentPack.Tracks[0].Decks[0];
return OutDeck.IsStructurallyValid();
}
FHyperTwistTrainingDeck UHyperTwistTrainingCatalogLibrary::MaterializeTrainingSessionTemplateDeck(
const FHyperTwistTrainingSessionTemplate& SessionTemplate,
const TArray<FHyperTwistContentPack>& TrainingCatalog
)
{
FHyperTwistTrainingDeck SourceDeck;
if (!SessionTemplate.IsStructurallyValid()
|| !TryFindDeckInCatalog(TrainingCatalog, SessionTemplate.FocusDeckId, SourceDeck))
{
return FHyperTwistTrainingDeck();
}
FHyperTwistTrainingDeck MaterializedDeck = SourceDeck;
MaterializedDeck.DeckId = FString::Printf(TEXT("%s/template/%s"), *SourceDeck.DeckId, *SessionTemplate.TemplateId);
MaterializedDeck.Title = FString::Printf(TEXT("%s - %s"), *SourceDeck.Title, *SessionTemplate.Title);
MaterializedDeck.SelectionPolicy = SessionTemplate.SuggestedSelectionPolicy;
MaterializedDeck.Tags.AddUnique(TEXT("template"));
MaterializedDeck.Tags.AddUnique(HyperTwistTrainingCatalogLibraryInternal::TemplateKindToTag(SessionTemplate.TemplateKind));
if (SessionTemplate.bPreferReviewRun)
{
MaterializedDeck.Tags.AddUnique(TEXT("review-template"));
}
if (SessionTemplate.bCoachReview)
{
MaterializedDeck.Tags.AddUnique(TEXT("coach-template"));
}
MaterializedDeck.DeliveryModes.AddUnique(SessionTemplate.SuggestedMode);
TArray<FHyperTwistTrainingCase> MaterializedCases;
if (SessionTemplate.FocusCaseIds.Num() > 0)
{
for (const FString& CaseId : SessionTemplate.FocusCaseIds)
{
const FHyperTwistTrainingCase* MatchingCase = SourceDeck.Cases.FindByPredicate(
[&CaseId](const FHyperTwistTrainingCase& Candidate)
{
return Candidate.CaseId == CaseId;
}
);
if (MatchingCase != nullptr)
{
MaterializedCases.Add(*MatchingCase);
}
}
}
else
{
MaterializedCases = SourceDeck.Cases;
}
const int32 RecommendedCaseCount = FMath::Max(1, SessionTemplate.RecommendedCaseCount);
if (MaterializedCases.Num() > RecommendedCaseCount)
{
MaterializedCases.SetNum(RecommendedCaseCount, EAllowShrinking::No);
}
MaterializedDeck.Cases = MoveTemp(MaterializedCases);
if (MaterializedDeck.Cases.Num() == 0)
{
return FHyperTwistTrainingDeck();
}
MaterializedDeck.SourceAttribution.SourceNotes = SessionTemplate.GuidanceLabel;
return MaterializedDeck;
}

View file

@ -5,6 +5,232 @@
namespace HyperTwistTrainingLibraryInternal
{
int32 GetPenaltyOffsetMs(EHyperTwistTrainingPenalty Penalty)
{
switch (Penalty)
{
case EHyperTwistTrainingPenalty::Plus2:
return 2000;
default:
return 0;
}
}
EHyperTwistTrainingPenalty CombinePenalties(
EHyperTwistTrainingPenalty LeftPenalty,
EHyperTwistTrainingPenalty RightPenalty
)
{
if (LeftPenalty == EHyperTwistTrainingPenalty::DNF || RightPenalty == EHyperTwistTrainingPenalty::DNF)
{
return EHyperTwistTrainingPenalty::DNF;
}
if (LeftPenalty == EHyperTwistTrainingPenalty::Plus2 || RightPenalty == EHyperTwistTrainingPenalty::Plus2)
{
return EHyperTwistTrainingPenalty::Plus2;
}
return EHyperTwistTrainingPenalty::None;
}
EHyperTwistTrainingPenalty ComputeInspectionPenalty(
int32 InspectionElapsedMs,
const FHyperTwistTrainingTimingPolicy& TimingPolicy
)
{
if (!TimingPolicy.bInspectionEnabled || !TimingPolicy.bInspectionPenaltiesEnabled)
{
return EHyperTwistTrainingPenalty::None;
}
const int32 LimitMs = FMath::Max(0, TimingPolicy.InspectionDurationMs);
if (LimitMs <= 0 || InspectionElapsedMs <= LimitMs)
{
return EHyperTwistTrainingPenalty::None;
}
if (InspectionElapsedMs <= LimitMs + 2000)
{
return EHyperTwistTrainingPenalty::Plus2;
}
return EHyperTwistTrainingPenalty::DNF;
}
FHyperTwistTrainingTimingPolicy NormalizeTimingPolicyInternal(
const FHyperTwistTrainingTimingPolicy& TimingPolicy
)
{
FHyperTwistTrainingTimingPolicy Normalized = TimingPolicy;
Normalized.InspectionDurationMs = FMath::Max(0, TimingPolicy.InspectionDurationMs);
Normalized.SplitPhases.Reset();
TArray<FHyperTwistTrainingSplitPhaseDefinition> SortedPhases = TimingPolicy.SplitPhases;
SortedPhases.Sort([](const FHyperTwistTrainingSplitPhaseDefinition& Left, const FHyperTwistTrainingSplitPhaseDefinition& Right)
{
if (Left.Order != Right.Order)
{
return Left.Order < Right.Order;
}
return Left.PhaseId < Right.PhaseId;
});
TSet<FString> SeenPhaseIds;
int32 NextOrder = 0;
for (FHyperTwistTrainingSplitPhaseDefinition PhaseDefinition : SortedPhases)
{
if (!PhaseDefinition.IsStructurallyValid() || SeenPhaseIds.Contains(PhaseDefinition.PhaseId))
{
continue;
}
SeenPhaseIds.Add(PhaseDefinition.PhaseId);
PhaseDefinition.Order = NextOrder++;
if (PhaseDefinition.Label.IsEmpty())
{
PhaseDefinition.Label = PhaseDefinition.PhaseId;
}
Normalized.SplitPhases.Add(PhaseDefinition);
}
return Normalized;
}
int32 ResolveSplitOrder(
const FHyperTwistTrainingTimingPolicy& TimingPolicy,
const FHyperTwistTrainingSplitCapture& SplitCapture
)
{
for (const FHyperTwistTrainingSplitPhaseDefinition& PhaseDefinition : TimingPolicy.SplitPhases)
{
if (PhaseDefinition.PhaseId == SplitCapture.PhaseId)
{
return PhaseDefinition.Order;
}
}
return INDEX_NONE;
}
FString ResolveSplitLabel(
const FHyperTwistTrainingTimingPolicy& TimingPolicy,
const FHyperTwistTrainingSplitCapture& SplitCapture
)
{
if (!SplitCapture.Label.IsEmpty())
{
return SplitCapture.Label;
}
for (const FHyperTwistTrainingSplitPhaseDefinition& PhaseDefinition : TimingPolicy.SplitPhases)
{
if (PhaseDefinition.PhaseId == SplitCapture.PhaseId)
{
return PhaseDefinition.Label;
}
}
return SplitCapture.PhaseId;
}
TArray<FHyperTwistTrainingSplitCapture> NormalizeSplitCaptures(
const FHyperTwistTrainingTimingPolicy& TimingPolicy,
const TArray<FHyperTwistTrainingSplitCapture>& SplitCaptures
)
{
TArray<FHyperTwistTrainingSplitCapture> NormalizedCaptures;
TSet<FString> SeenPhaseIds;
for (FHyperTwistTrainingSplitCapture SplitCapture : SplitCaptures)
{
if (!SplitCapture.IsStructurallyValid())
{
continue;
}
if (SeenPhaseIds.Contains(SplitCapture.PhaseId))
{
continue;
}
SplitCapture.TimestampMs = FMath::Max(0, SplitCapture.TimestampMs);
SplitCapture.Label = ResolveSplitLabel(TimingPolicy, SplitCapture);
SeenPhaseIds.Add(SplitCapture.PhaseId);
NormalizedCaptures.Add(SplitCapture);
}
NormalizedCaptures.Sort([&TimingPolicy](const FHyperTwistTrainingSplitCapture& Left, const FHyperTwistTrainingSplitCapture& Right)
{
const int32 LeftOrder = ResolveSplitOrder(TimingPolicy, Left);
const int32 RightOrder = ResolveSplitOrder(TimingPolicy, Right);
if (LeftOrder != INDEX_NONE || RightOrder != INDEX_NONE)
{
if (LeftOrder == INDEX_NONE)
{
return false;
}
if (RightOrder == INDEX_NONE)
{
return true;
}
if (LeftOrder != RightOrder)
{
return LeftOrder < RightOrder;
}
}
return Left.TimestampMs < Right.TimestampMs;
});
return NormalizedCaptures;
}
TArray<FHyperTwistPhaseTiming> BuildDerivedPhaseTimings(
const FHyperTwistTrainingTimingPolicy& TimingPolicy,
const FHyperTwistTrainingTimingBreakdown& TimingBreakdown
)
{
TArray<FHyperTwistPhaseTiming> PhaseTimings;
const TArray<FHyperTwistTrainingSplitCapture> NormalizedCaptures = NormalizeSplitCaptures(
TimingPolicy,
TimingBreakdown.SplitCaptures
);
int32 PhaseStartMs = 0;
int32 ConsumedPhaseCount = 0;
for (const FHyperTwistTrainingSplitCapture& SplitCapture : NormalizedCaptures)
{
const int32 PhaseEndMs = FMath::Max(PhaseStartMs, SplitCapture.TimestampMs);
FHyperTwistPhaseTiming PhaseTiming;
PhaseTiming.PhaseKind = !SplitCapture.Label.IsEmpty() ? SplitCapture.Label : SplitCapture.PhaseId;
PhaseTiming.StartedAtMs = PhaseStartMs;
PhaseTiming.EndedAtMs = PhaseEndMs;
PhaseTiming.DurationMs = FMath::Max(0, PhaseEndMs - PhaseStartMs);
PhaseTimings.Add(PhaseTiming);
PhaseStartMs = PhaseEndMs;
ConsumedPhaseCount += 1;
}
if (TimingBreakdown.RawSolveTimeMs > PhaseStartMs && TimingPolicy.SplitPhases.IsValidIndex(ConsumedPhaseCount))
{
const FHyperTwistTrainingSplitPhaseDefinition& FinalPhaseDefinition = TimingPolicy.SplitPhases[ConsumedPhaseCount];
FHyperTwistPhaseTiming PhaseTiming;
PhaseTiming.PhaseKind = FinalPhaseDefinition.Label;
PhaseTiming.StartedAtMs = PhaseStartMs;
PhaseTiming.EndedAtMs = TimingBreakdown.RawSolveTimeMs;
PhaseTiming.DurationMs = FMath::Max(0, PhaseTiming.EndedAtMs - PhaseTiming.StartedAtMs);
PhaseTimings.Add(PhaseTiming);
}
return PhaseTimings;
}
EHyperTwistCaptureMode ToCaptureMode(EHyperTwistTrainingDeliveryMode Mode)
{
switch (Mode)
@ -159,7 +385,8 @@ namespace HyperTwistTrainingLibraryInternal
FHyperTwistReplayPacket MakeReplayPacketForRun(
const FHyperTwistTrainingSession& Session,
const FHyperTwistTrainingCase& TrainingCase
const FHyperTwistTrainingCase& TrainingCase,
const FHyperTwistTrainingTimingPolicy& TimingPolicy
)
{
FHyperTwistReplayPacket Packet;
@ -170,16 +397,31 @@ namespace HyperTwistTrainingLibraryInternal
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;
const FHyperTwistTrainingTimingPolicy NormalizedTimingPolicy = NormalizeTimingPolicyInternal(TimingPolicy);
FHyperTwistReplayEvent StartEvent;
StartEvent.Sequence = 1;
StartEvent.TimeMs = 0;
StartEvent.EventType = NormalizedTimingPolicy.bInspectionEnabled
? EHyperTwistReplayEventType::InspectionStart
: EHyperTwistReplayEventType::TimerStart;
StartEvent.EventId = NormalizedTimingPolicy.bInspectionEnabled
? TEXT("evt_inspection_start")
: TEXT("evt_timer_start");
Packet = UHyperTwistReplayLibrary::AppendReplayEvent(Packet, TimerStartEvent);
Packet = UHyperTwistReplayLibrary::AppendReplayEvent(Packet, StartEvent);
return Packet;
}
int32 GetObservedAttemptTimeMs(const FHyperTwistTrainingAttempt& Attempt)
{
return FMath::Max(
0,
Attempt.TimingBreakdown.InspectionElapsedMs
+ Attempt.TimingBreakdown.RawSolveTimeMs
+ FMath::Max(0, Attempt.RecognitionTimeMs)
);
}
float ComputeSelectionScore(
const FHyperTwistTrainingCase& TrainingCase,
const FHyperTwistProgressionMemory* Memory,
@ -274,6 +516,17 @@ FHyperTwistContentPack UHyperTwistTrainingLibrary::MakeSampleClassicContentPack(
Deck.bSupportsAlgorithmReveal = true;
Deck.bSupportsVirtualCube = true;
Deck.bSupportsSmartCube = false;
FHyperTwistTrainingSplitPhaseDefinition CrossPhaseDefinition;
CrossPhaseDefinition.PhaseId = TEXT("cross");
CrossPhaseDefinition.Label = TEXT("Cross");
CrossPhaseDefinition.Order = 0;
Deck.TimingPolicy.bInspectionEnabled = true;
Deck.TimingPolicy.InspectionDurationMs = 15000;
Deck.TimingPolicy.bInspectionPenaltiesEnabled = true;
Deck.TimingPolicy.bAllowManualPenalty = true;
Deck.TimingPolicy.SplitPhases = {CrossPhaseDefinition};
Deck.Cases = {CaseA, CaseB};
Deck.DifficultyBand = TEXT("starter");
Deck.Tags = {TEXT("classic"), TEXT("cross")};
@ -403,7 +656,8 @@ FHyperTwistTrainingRunState UHyperTwistTrainingLibrary::StartTrainingRun(
);
RunState.ReplayPacket = HyperTwistTrainingLibraryInternal::MakeReplayPacketForRun(
RunState.Session,
RunState.CurrentSelection.TrainingCase
RunState.CurrentSelection.TrainingCase,
Deck.TimingPolicy
);
if (RunState.ReplayPacket.Events.Num() > 0)
{
@ -519,6 +773,103 @@ FHyperTwistTrainingSession UHyperTwistTrainingLibrary::RecordTrainingAttempt(
return UpdatedSession;
}
FHyperTwistTrainingTimingPolicy UHyperTwistTrainingLibrary::NormalizeTimingPolicy(
const FHyperTwistTrainingTimingPolicy& TimingPolicy
)
{
return HyperTwistTrainingLibraryInternal::NormalizeTimingPolicyInternal(TimingPolicy);
}
FHyperTwistTrainingAttempt UHyperTwistTrainingLibrary::NormalizeTrainingAttemptTiming(
const FHyperTwistTrainingAttempt& Attempt,
const FHyperTwistTrainingTimingPolicy& TimingPolicy
)
{
FHyperTwistTrainingAttempt NormalizedAttempt = Attempt;
FHyperTwistTrainingTimingBreakdown TimingBreakdown = Attempt.TimingBreakdown;
const FHyperTwistTrainingTimingPolicy NormalizedTimingPolicy = NormalizeTimingPolicy(TimingPolicy);
TimingBreakdown.InspectionElapsedMs = NormalizedTimingPolicy.bInspectionEnabled
? FMath::Max(0, TimingBreakdown.InspectionElapsedMs)
: 0;
TimingBreakdown.ManualPenalty = NormalizedTimingPolicy.bAllowManualPenalty
? TimingBreakdown.ManualPenalty
: EHyperTwistTrainingPenalty::None;
TimingBreakdown.InspectionPenalty = HyperTwistTrainingLibraryInternal::ComputeInspectionPenalty(
TimingBreakdown.InspectionElapsedMs,
NormalizedTimingPolicy
);
TimingBreakdown.AppliedPenalty = HyperTwistTrainingLibraryInternal::CombinePenalties(
TimingBreakdown.InspectionPenalty,
TimingBreakdown.ManualPenalty
);
const int32 RecognitionTimeMs = FMath::Max(0, Attempt.RecognitionTimeMs);
int32 RawSolveTimeMs = FMath::Max(0, TimingBreakdown.RawSolveTimeMs);
if (RawSolveTimeMs == 0 && Attempt.ExecutionTimeMs > 0)
{
RawSolveTimeMs = Attempt.ExecutionTimeMs;
}
if (RawSolveTimeMs == 0 && Attempt.TotalTimeMs > 0)
{
RawSolveTimeMs = FMath::Max(
0,
Attempt.TotalTimeMs
- RecognitionTimeMs
- HyperTwistTrainingLibraryInternal::GetPenaltyOffsetMs(TimingBreakdown.AppliedPenalty)
);
}
TimingBreakdown.RawSolveTimeMs = RawSolveTimeMs;
TimingBreakdown.SplitCaptures = HyperTwistTrainingLibraryInternal::NormalizeSplitCaptures(
NormalizedTimingPolicy,
TimingBreakdown.SplitCaptures
);
TimingBreakdown.DerivedPhaseTimings = HyperTwistTrainingLibraryInternal::BuildDerivedPhaseTimings(
NormalizedTimingPolicy,
TimingBreakdown
);
TimingBreakdown.PenaltyOffsetMs = HyperTwistTrainingLibraryInternal::GetPenaltyOffsetMs(
TimingBreakdown.AppliedPenalty
);
if (NormalizedAttempt.Result == EHyperTwistTrainingAttemptResult::Aborted)
{
TimingBreakdown.bDidNotFinish = true;
}
if (TimingBreakdown.AppliedPenalty == EHyperTwistTrainingPenalty::DNF)
{
TimingBreakdown.bDidNotFinish = true;
if (NormalizedAttempt.Result != EHyperTwistTrainingAttemptResult::Aborted)
{
NormalizedAttempt.Result = EHyperTwistTrainingAttemptResult::DNF;
}
}
NormalizedAttempt.ExecutionTimeMs = RawSolveTimeMs;
NormalizedAttempt.TimingBreakdown = TimingBreakdown;
if (TimingBreakdown.bDidNotFinish || NormalizedAttempt.Result == EHyperTwistTrainingAttemptResult::DNF)
{
NormalizedAttempt.TotalTimeMs = 0;
NormalizedAttempt.TimingBreakdown.FinalTimeMs = 0;
}
else
{
const int32 FinalTimeMs = FMath::Max(
0,
RawSolveTimeMs
+ RecognitionTimeMs
+ TimingBreakdown.PenaltyOffsetMs
);
NormalizedAttempt.TotalTimeMs = FinalTimeMs;
NormalizedAttempt.TimingBreakdown.FinalTimeMs = FinalTimeMs;
}
return NormalizedAttempt;
}
FHyperTwistTrainingSession UHyperTwistTrainingLibrary::CompleteTrainingSession(
const FHyperTwistTrainingSession& Session,
bool bAbortSession
@ -609,6 +960,7 @@ FHyperTwistProgressionMemory UHyperTwistTrainingLibrary::UpdateProgressionMemory
UpdatedMemory.ResurfacePriority += 1.25f;
break;
case EHyperTwistTrainingAttemptResult::Timeout:
case EHyperTwistTrainingAttemptResult::DNF:
UpdatedMemory.StrengthScore -= 0.50f;
UpdatedMemory.ResurfacePriority += 1.50f;
break;
@ -713,6 +1065,7 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingLibrary::DeriveTrainingSess
Summary.TotalTimeMs += FMath::Max(0, Attempt.TotalTimeMs);
Summary.TotalExecutionTimeMs += FMath::Max(0, Attempt.ExecutionTimeMs);
Summary.TotalRecognitionTimeMs += FMath::Max(0, Attempt.RecognitionTimeMs);
Summary.TotalInspectionTimeMs += FMath::Max(0, Attempt.TimingBreakdown.InspectionElapsedMs);
Summary.TotalMistakes += FMath::Max(0, Attempt.Mistakes);
if (Attempt.TotalTimeMs > 0)
@ -722,6 +1075,18 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingLibrary::DeriveTrainingSess
: FMath::Min(Summary.BestTotalTimeMs, Attempt.TotalTimeMs);
}
if (Attempt.TimingBreakdown.RawSolveTimeMs > 0)
{
Summary.BestRawSolveTimeMs = Summary.BestRawSolveTimeMs == 0
? Attempt.TimingBreakdown.RawSolveTimeMs
: FMath::Min(Summary.BestRawSolveTimeMs, Attempt.TimingBreakdown.RawSolveTimeMs);
}
if (Attempt.TimingBreakdown.AppliedPenalty == EHyperTwistTrainingPenalty::Plus2)
{
++Summary.Plus2PenaltyCount;
}
switch (Attempt.Result)
{
case EHyperTwistTrainingAttemptResult::Success:
@ -733,6 +1098,9 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingLibrary::DeriveTrainingSess
case EHyperTwistTrainingAttemptResult::Timeout:
++Summary.TimeoutCount;
break;
case EHyperTwistTrainingAttemptResult::DNF:
++Summary.DNFCount;
break;
case EHyperTwistTrainingAttemptResult::Aborted:
++Summary.AbortedCount;
break;
@ -752,6 +1120,8 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingLibrary::DeriveTrainingSess
if (Summary.CompletedAttemptCount > 0)
{
Summary.AverageTotalTimeMs = Summary.TotalTimeMs / Summary.CompletedAttemptCount;
Summary.AverageRawSolveTimeMs = Summary.TotalExecutionTimeMs / Summary.CompletedAttemptCount;
Summary.AverageInspectionTimeMs = Summary.TotalInspectionTimeMs / Summary.CompletedAttemptCount;
Summary.SuccessRate = static_cast<float>(Summary.SuccessCount) / static_cast<float>(Summary.CompletedAttemptCount);
}
@ -781,6 +1151,7 @@ FHyperTwistReplayEvent UHyperTwistTrainingLibrary::MakeTrainingAttemptReplayEven
Payload.TotalTimeMs = Attempt.TotalTimeMs;
Payload.ExecutionTimeMs = Attempt.ExecutionTimeMs;
Payload.RecognitionTimeMs = Attempt.RecognitionTimeMs;
Payload.TimingBreakdown = Attempt.TimingBreakdown;
Payload.Mistakes = Attempt.Mistakes;
Payload.ReplayId = Attempt.ReplayId;
Payload.CompletedAtUtc = Attempt.CompletedAtUtc;
@ -789,7 +1160,10 @@ FHyperTwistReplayEvent UHyperTwistTrainingLibrary::MakeTrainingAttemptReplayEven
? TEXT("training_attempt")
: FString::Printf(TEXT("training_attempt_%s"), *Attempt.AttemptId);
ReplayEvent.Sequence = 0;
ReplayEvent.TimeMs = FMath::Max(0, TimeMs > 0 ? TimeMs : Attempt.TotalTimeMs);
ReplayEvent.TimeMs = FMath::Max(
0,
TimeMs > 0 ? TimeMs : HyperTwistTrainingLibraryInternal::GetObservedAttemptTimeMs(Attempt)
);
ReplayEvent.EventType = EHyperTwistReplayEventType::Annotation;
ReplayEvent.PayloadJson = HyperTwistTrainingLibraryInternal::SerializePayload(Payload);
return ReplayEvent;
@ -802,13 +1176,56 @@ FHyperTwistReplayPacket UHyperTwistTrainingLibrary::AppendTrainingAttemptToRepla
int32 TimeMs
)
{
FHyperTwistReplayPacket UpdatedReplayPacket = ReplayPacket;
const int32 InspectionOffsetMs = FMath::Max(0, Attempt.TimingBreakdown.InspectionElapsedMs);
if (InspectionOffsetMs > 0)
{
FHyperTwistReplayEvent InspectionEndEvent;
InspectionEndEvent.EventType = EHyperTwistReplayEventType::InspectionEnd;
InspectionEndEvent.TimeMs = InspectionOffsetMs;
UpdatedReplayPacket = UHyperTwistReplayLibrary::AppendReplayEvent(UpdatedReplayPacket, InspectionEndEvent);
FHyperTwistReplayEvent TimerStartEvent;
TimerStartEvent.EventType = EHyperTwistReplayEventType::TimerStart;
TimerStartEvent.TimeMs = InspectionOffsetMs;
UpdatedReplayPacket = UHyperTwistReplayLibrary::AppendReplayEvent(UpdatedReplayPacket, TimerStartEvent);
}
for (int32 PhaseIndex = 0; PhaseIndex < Attempt.TimingBreakdown.DerivedPhaseTimings.Num(); ++PhaseIndex)
{
const FHyperTwistPhaseTiming& PhaseTiming = Attempt.TimingBreakdown.DerivedPhaseTimings[PhaseIndex];
const FString PhaseId = FString::Printf(TEXT("phase_%02d"), PhaseIndex + 1);
FHyperTwistReplayPhaseMarkPayload PhaseStartPayload;
PhaseStartPayload.PhaseId = PhaseId;
PhaseStartPayload.PhaseKind = PhaseTiming.PhaseKind;
PhaseStartPayload.PhaseSource = TEXT("training-timing");
PhaseStartPayload.PhaseAction = TEXT("start");
FHyperTwistReplayEvent PhaseStartEvent;
PhaseStartEvent.EventType = EHyperTwistReplayEventType::PhaseMark;
PhaseStartEvent.TimeMs = InspectionOffsetMs + FMath::Max(0, PhaseTiming.StartedAtMs);
PhaseStartEvent.PayloadJson = HyperTwistTrainingLibraryInternal::SerializePayload(PhaseStartPayload);
UpdatedReplayPacket = UHyperTwistReplayLibrary::AppendReplayEvent(UpdatedReplayPacket, PhaseStartEvent);
FHyperTwistReplayPhaseMarkPayload PhaseCompletePayload = PhaseStartPayload;
PhaseCompletePayload.PhaseAction = TEXT("complete");
FHyperTwistReplayEvent PhaseCompleteEvent;
PhaseCompleteEvent.EventType = EHyperTwistReplayEventType::PhaseMark;
PhaseCompleteEvent.TimeMs = InspectionOffsetMs + FMath::Max(0, PhaseTiming.EndedAtMs);
PhaseCompleteEvent.PayloadJson = HyperTwistTrainingLibraryInternal::SerializePayload(PhaseCompletePayload);
UpdatedReplayPacket = UHyperTwistReplayLibrary::AppendReplayEvent(UpdatedReplayPacket, PhaseCompleteEvent);
}
const FHyperTwistReplayEvent ReplayEvent = MakeTrainingAttemptReplayEvent(Session, Attempt, TimeMs);
if (!ReplayEvent.IsStructurallyValid())
{
return ReplayPacket;
return UpdatedReplayPacket;
}
return UHyperTwistReplayLibrary::AppendReplayEvent(ReplayPacket, ReplayEvent);
return UHyperTwistReplayLibrary::AppendReplayEvent(UpdatedReplayPacket, ReplayEvent);
}
FHyperTwistTrainingAttemptResolution UHyperTwistTrainingLibrary::ResolveTrainingAttempt(
@ -826,6 +1243,11 @@ FHyperTwistTrainingAttemptResolution UHyperTwistTrainingLibrary::ResolveTraining
return Resolution;
}
const FHyperTwistTrainingAttempt NormalizedAttempt = NormalizeTrainingAttemptTiming(
Attempt,
Deck.TimingPolicy
);
FHyperTwistProgressionMemory SeedMemory;
bool bFoundExistingMemory = false;
for (const FHyperTwistProgressionMemory& Memory : Memories)
@ -843,8 +1265,12 @@ FHyperTwistTrainingAttemptResolution UHyperTwistTrainingLibrary::ResolveTraining
SeedMemory = MakeInitialProgressionMemory(Session, TrainingCase);
}
Resolution.UpdatedProgressionMemory = UpdateProgressionMemory(Session, TrainingCase, SeedMemory, Attempt);
Resolution.ReplayEvent = MakeTrainingAttemptReplayEvent(Session, Attempt, TimeMs);
Resolution.UpdatedProgressionMemory = UpdateProgressionMemory(Session, TrainingCase, SeedMemory, NormalizedAttempt);
Resolution.ReplayEvent = MakeTrainingAttemptReplayEvent(
Session,
NormalizedAttempt,
TimeMs > 0 ? TimeMs : HyperTwistTrainingLibraryInternal::GetObservedAttemptTimeMs(NormalizedAttempt)
);
TArray<FHyperTwistProgressionMemory> UpdatedMemories = Memories;
bool bReplacedExistingMemory = false;
@ -863,7 +1289,7 @@ FHyperTwistTrainingAttemptResolution UHyperTwistTrainingLibrary::ResolveTraining
UpdatedMemories.Add(Resolution.UpdatedProgressionMemory);
}
const FHyperTwistTrainingSession ReviewSession = RecordTrainingAttempt(Session, Attempt);
const FHyperTwistTrainingSession ReviewSession = RecordTrainingAttempt(Session, NormalizedAttempt);
const FHyperTwistTrainingAdvanceResult AdvanceResult = AdvanceTrainingSessionWithSelection(
ReviewSession,
Deck,
@ -904,6 +1330,10 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingLibrary::ResolveTrainingRunA
{
NormalizedAttempt.ReplayId = RunState.ReplayPacket.ReplayId;
}
NormalizedAttempt = NormalizeTrainingAttemptTiming(NormalizedAttempt, RunState.ActiveDeck.TimingPolicy);
const int32 ObservedAttemptTimeMs = TimeMs > 0
? TimeMs
: HyperTwistTrainingLibraryInternal::GetObservedAttemptTimeMs(NormalizedAttempt);
StepResult.AttemptResolution = ResolveTrainingAttempt(
RunState.Session,
@ -911,7 +1341,7 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingLibrary::ResolveTrainingRunA
*TrainingCase,
RunState.Memories,
NormalizedAttempt,
TimeMs
ObservedAttemptTimeMs
);
if (!StepResult.AttemptResolution.IsStructurallyValid())
{
@ -925,7 +1355,7 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingLibrary::ResolveTrainingRunA
RunState.ReplayPacket,
RunState.Session,
NormalizedAttempt,
TimeMs
ObservedAttemptTimeMs
);
if (StepResult.UpdatedRunState.ReplayPacket.Events.Num() > 0)
{
@ -970,7 +1400,7 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingLibrary::ResolveTrainingRunA
FHyperTwistReplayEvent SolveEndEvent;
SolveEndEvent.EventType = EHyperTwistReplayEventType::SolveEnd;
SolveEndEvent.TimeMs = FMath::Max(TimeMs, NormalizedAttempt.TotalTimeMs);
SolveEndEvent.TimeMs = ObservedAttemptTimeMs;
StepResult.UpdatedRunState.ReplayPacket = UHyperTwistReplayLibrary::AppendReplayEvent(
StepResult.UpdatedRunState.ReplayPacket,
SolveEndEvent

View file

@ -0,0 +1,66 @@
#include "HyperTwistTraining/HyperTwistTrainingPersistenceLibrary.h"
#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
#include "HAL/FileManager.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
namespace HyperTwistTrainingPersistenceLibraryInternal
{
FString ResolvePath(const FString& RepositoryPath)
{
if (RepositoryPath.IsEmpty())
{
return FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("HyperTwist"), TEXT("training-repository.json"));
}
return FPaths::IsRelative(RepositoryPath)
? FPaths::Combine(FPaths::ProjectSavedDir(), RepositoryPath)
: RepositoryPath;
}
}
FString UHyperTwistTrainingPersistenceLibrary::GetDefaultTrainingRepositoryPath()
{
return HyperTwistTrainingPersistenceLibraryInternal::ResolvePath(FString());
}
FString UHyperTwistTrainingPersistenceLibrary::ResolveTrainingRepositoryPath(const FString& RepositoryPath)
{
return HyperTwistTrainingPersistenceLibraryInternal::ResolvePath(RepositoryPath);
}
bool UHyperTwistTrainingPersistenceLibrary::SaveTrainingRepositoryStateToFile(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& RepositoryPath,
FString& OutResolvedPath
)
{
OutResolvedPath = ResolveTrainingRepositoryPath(RepositoryPath);
const FString Directory = FPaths::GetPath(OutResolvedPath);
if (!Directory.IsEmpty())
{
IFileManager::Get().MakeDirectory(*Directory, true);
}
const FString Json = UHyperTwistContractLibrary::SerializeTrainingRepositoryStateToJson(RepositoryState);
return !Json.IsEmpty() && FFileHelper::SaveStringToFile(Json, *OutResolvedPath);
}
bool UHyperTwistTrainingPersistenceLibrary::LoadTrainingRepositoryStateFromFile(
const FString& RepositoryPath,
FHyperTwistTrainingRepositoryState& OutRepositoryState,
FString& OutResolvedPath
)
{
OutRepositoryState = FHyperTwistTrainingRepositoryState();
OutResolvedPath = ResolveTrainingRepositoryPath(RepositoryPath);
FString Json;
if (!FPaths::FileExists(OutResolvedPath) || !FFileHelper::LoadFileToString(Json, *OutResolvedPath))
{
return false;
}
return UHyperTwistContractLibrary::DeserializeTrainingRepositoryStateFromJson(Json, OutRepositoryState);
}

View file

@ -58,6 +58,259 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTr
return FHyperTwistTrainingSessionSummary();
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRuntimeLibrary::GetTrainingRepositoryState(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetTrainingRepositoryState();
}
return FHyperTwistTrainingRepositoryState();
}
FHyperTwistTrainingDeckHistorySummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingDeckHistorySummary(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveDeckHistorySummary();
}
return FHyperTwistTrainingDeckHistorySummary();
}
FHyperTwistTrainingDeckScopedStats UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingDeckScopedStats(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveDeckScopedStats();
}
return FHyperTwistTrainingDeckScopedStats();
}
FHyperTwistTrainingCaseScopedStats UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCurrentCaseScopedStats(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveCurrentCaseScopedStats();
}
return FHyperTwistTrainingCaseScopedStats();
}
FHyperTwistTrainingLearnerDeckStateSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingLearnerDeckStateSummary(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveLearnerDeckStateSummary();
}
return FHyperTwistTrainingLearnerDeckStateSummary();
}
FHyperTwistTrainingLearnerProfile UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingLearnerProfile(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveLearnerProfile();
}
return FHyperTwistTrainingLearnerProfile();
}
FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachMemorySnapshot(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveCoachMemorySnapshot();
}
return FHyperTwistTrainingCoachMemorySnapshot();
}
TArray<FHyperTwistTrainingLearnerPreset> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingLearnerPresets(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveLearnerPresets();
}
return TArray<FHyperTwistTrainingLearnerPreset>();
}
TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingSessionTemplates(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveTrainingSessionTemplates();
}
return TArray<FHyperTwistTrainingSessionTemplate>();
}
TArray<FHyperTwistTrainingCaseRecommendation> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCaseRecommendations(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveCaseRecommendations();
}
return TArray<FHyperTwistTrainingCaseRecommendation>();
}
FHyperTwistTrainingReviewPlanState UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingReviewPlanState(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveReviewPlanState();
}
return FHyperTwistTrainingReviewPlanState();
}
FHyperTwistTrainingReviewFlowStatus UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingReviewFlowStatus(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveReviewFlowStatus();
}
return FHyperTwistTrainingReviewFlowStatus();
}
bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingRunRecord(
UObject* WorldContextObject,
const FString& TrainingSessionId,
FHyperTwistTrainingRunRecord& OutRunRecord
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->TryGetStoredRunRecord(TrainingSessionId, OutRunRecord);
}
OutRunRecord = FHyperTwistTrainingRunRecord();
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingLearnerState(
UObject* WorldContextObject,
const FString& UserId,
const FString& DeckId,
const FString& CaseId,
FHyperTwistTrainingCaseLearnerState& OutLearnerState
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->TryGetStoredLearnerState(UserId, DeckId, CaseId, OutLearnerState);
}
OutLearnerState = FHyperTwistTrainingCaseLearnerState();
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingReviewPlan(
UObject* WorldContextObject,
const FString& PlanId,
FHyperTwistTrainingReviewPlanState& OutReviewPlan
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->TryGetStoredReviewPlan(PlanId, OutReviewPlan);
}
OutReviewPlan = FHyperTwistTrainingReviewPlanState();
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::SaveTrainingRepositoryStateToDefaultPath(
UObject* WorldContextObject,
FString& OutResolvedPath
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->SaveTrainingRepositoryStateToDefaultPath(OutResolvedPath);
}
OutResolvedPath.Reset();
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::LoadTrainingRepositoryStateFromDefaultPath(
UObject* WorldContextObject,
FString& OutResolvedPath
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->LoadTrainingRepositoryStateFromDefaultPath(OutResolvedPath);
}
OutResolvedPath.Reset();
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::TryMaterializeActiveTrainingSessionTemplate(
UObject* WorldContextObject,
const FString& TemplateId,
FHyperTwistTrainingDeck& OutDeck
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->TryMaterializeActiveTrainingSessionTemplate(TemplateId, OutDeck);
}
OutDeck = FHyperTwistTrainingDeck();
return false;
}
FHyperTwistTrainingDeck UHyperTwistTrainingRuntimeLibrary::BuildActiveReviewDeck(
UObject* WorldContextObject,
int32 MaxCases
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->BuildActiveReviewDeck(MaxCases);
}
return FHyperTwistTrainingDeck();
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartTrainingRunFromTemplate(
UObject* WorldContextObject,
const FString& TemplateId,
const FString& UserId,
const FString& SessionId
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->StartTrainingRunFromTemplate(TemplateId, UserId, SessionId);
}
return FHyperTwistTrainingRunState();
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartRecommendedReviewRun(
UObject* WorldContextObject,
const FString& SessionId,
int32 MaxCases,
EHyperTwistTrainingDeliveryMode Mode
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->StartRecommendedReviewRun(SessionId, MaxCases, Mode);
}
return FHyperTwistTrainingRunState();
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartSampleClassicTrainingRun(
UObject* WorldContextObject,
const FString& UserId,

View file

@ -2,6 +2,8 @@
#include "HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingPersistenceLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
bool UHyperTwistTrainingSubsystem::HasActiveRun() const
{
@ -18,6 +20,268 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingSubsystem::GetActiveRunSumm
return ActiveRunSummary;
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingSubsystem::GetTrainingRepositoryState() const
{
return TrainingRepositoryState;
}
FHyperTwistTrainingDeckHistorySummary UHyperTwistTrainingSubsystem::GetActiveDeckHistorySummary() const
{
return ActiveDeckHistorySummary;
}
FHyperTwistTrainingDeckScopedStats UHyperTwistTrainingSubsystem::GetActiveDeckScopedStats() const
{
return ActiveDeckScopedStats;
}
FHyperTwistTrainingCaseScopedStats UHyperTwistTrainingSubsystem::GetActiveCurrentCaseScopedStats() const
{
return ActiveCurrentCaseScopedStats;
}
FHyperTwistTrainingLearnerDeckStateSummary UHyperTwistTrainingSubsystem::GetActiveLearnerDeckStateSummary() const
{
return ActiveLearnerDeckStateSummary;
}
FHyperTwistTrainingLearnerProfile UHyperTwistTrainingSubsystem::GetActiveLearnerProfile() const
{
return ActiveLearnerProfile;
}
FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingSubsystem::GetActiveCoachMemorySnapshot() const
{
return ActiveCoachMemorySnapshot;
}
TArray<FHyperTwistTrainingLearnerPreset> UHyperTwistTrainingSubsystem::GetActiveLearnerPresets() const
{
return ActiveLearnerPresets;
}
TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistTrainingSubsystem::GetActiveTrainingSessionTemplates() const
{
return ActiveTrainingSessionTemplates;
}
TArray<FHyperTwistTrainingCaseRecommendation> UHyperTwistTrainingSubsystem::GetActiveCaseRecommendations() const
{
return ActiveCaseRecommendations;
}
FHyperTwistTrainingReviewPlanState UHyperTwistTrainingSubsystem::GetActiveReviewPlanState() const
{
return ActiveReviewPlanState;
}
FHyperTwistTrainingReviewFlowStatus UHyperTwistTrainingSubsystem::GetActiveReviewFlowStatus() const
{
return ActiveReviewFlowStatus;
}
bool UHyperTwistTrainingSubsystem::TryGetStoredRunRecord(
const FString& TrainingSessionId,
FHyperTwistTrainingRunRecord& OutRunRecord
) const
{
return UHyperTwistTrainingRepositoryLibrary::TryGetRunRecord(
TrainingRepositoryState,
TrainingSessionId,
OutRunRecord
);
}
bool UHyperTwistTrainingSubsystem::TryGetStoredLearnerState(
const FString& UserId,
const FString& DeckId,
const FString& CaseId,
FHyperTwistTrainingCaseLearnerState& OutLearnerState
) const
{
return UHyperTwistTrainingRepositoryLibrary::TryGetLearnerState(
TrainingRepositoryState,
UserId,
DeckId,
CaseId,
OutLearnerState
);
}
bool UHyperTwistTrainingSubsystem::TryGetStoredReviewPlan(
const FString& PlanId,
FHyperTwistTrainingReviewPlanState& OutReviewPlan
) const
{
return UHyperTwistTrainingRepositoryLibrary::TryGetReviewPlan(
TrainingRepositoryState,
PlanId,
OutReviewPlan
);
}
bool UHyperTwistTrainingSubsystem::SaveTrainingRepositoryStateToDefaultPath(FString& OutResolvedPath) const
{
return UHyperTwistTrainingPersistenceLibrary::SaveTrainingRepositoryStateToFile(
TrainingRepositoryState,
FString(),
OutResolvedPath
);
}
bool UHyperTwistTrainingSubsystem::LoadTrainingRepositoryStateFromDefaultPath(FString& OutResolvedPath)
{
FHyperTwistTrainingRepositoryState LoadedState;
if (!UHyperTwistTrainingPersistenceLibrary::LoadTrainingRepositoryStateFromFile(FString(), LoadedState, OutResolvedPath))
{
return false;
}
TrainingRepositoryState = LoadedState;
RefreshRepositoryViews();
return true;
}
bool UHyperTwistTrainingSubsystem::TryMaterializeActiveTrainingSessionTemplate(
const FString& TemplateId,
FHyperTwistTrainingDeck& OutDeck
) const
{
OutDeck = FHyperTwistTrainingDeck();
if (TemplateId.IsEmpty())
{
return false;
}
const FHyperTwistTrainingSessionTemplate* MatchingTemplate = ActiveTrainingSessionTemplates.FindByPredicate(
[&TemplateId](const FHyperTwistTrainingSessionTemplate& Candidate)
{
return Candidate.TemplateId == TemplateId;
}
);
if (MatchingTemplate == nullptr)
{
return false;
}
OutDeck = UHyperTwistTrainingCatalogLibrary::MaterializeTrainingSessionTemplateDeck(
*MatchingTemplate,
UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog()
);
return OutDeck.IsStructurallyValid();
}
FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveReviewDeck(int32 MaxCases) const
{
if (!ActiveRunState.ActiveDeck.IsStructurallyValid())
{
return FHyperTwistTrainingDeck();
}
const FHyperTwistTrainingReviewPolicy ReviewPolicy =
UHyperTwistTrainingRepositoryLibrary::GetDefaultReviewPolicy(ActiveRunState.Session.Mode);
const TArray<FHyperTwistTrainingCaseRecommendation> FilteredRecommendations =
UHyperTwistTrainingRepositoryLibrary::ApplyReviewPolicyToRecommendations(
ActiveCaseRecommendations,
ReviewPolicy,
MaxCases
);
return UHyperTwistTrainingRepositoryLibrary::BuildReviewDeckFromRecommendations(
ActiveRunState.ActiveDeck,
FilteredRecommendations,
MaxCases
);
}
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromTemplate(
const FString& TemplateId,
const FString& UserId,
const FString& SessionId
)
{
FHyperTwistTrainingDeck MaterializedDeck;
if (!TryMaterializeActiveTrainingSessionTemplate(TemplateId, MaterializedDeck))
{
return FHyperTwistTrainingRunState();
}
const FHyperTwistTrainingSessionTemplate* MatchingTemplate = ActiveTrainingSessionTemplates.FindByPredicate(
[&TemplateId](const FHyperTwistTrainingSessionTemplate& Candidate)
{
return Candidate.TemplateId == TemplateId;
}
);
if (MatchingTemplate == nullptr)
{
return FHyperTwistTrainingRunState();
}
return StartTrainingRunFromDeck(
MaterializedDeck,
UserId,
SessionId,
MatchingTemplate->SuggestedMode
);
}
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartRecommendedReviewRun(
const FString& SessionId,
int32 MaxCases,
EHyperTwistTrainingDeliveryMode Mode
)
{
const FHyperTwistTrainingDeck SourceDeck = ActiveRunState.ActiveDeck;
const FHyperTwistTrainingReviewPolicy ReviewPolicy =
UHyperTwistTrainingRepositoryLibrary::GetDefaultReviewPolicy(Mode);
const TArray<FHyperTwistTrainingCaseRecommendation> Recommendations =
UHyperTwistTrainingRepositoryLibrary::ApplyReviewPolicyToRecommendations(
ActiveCaseRecommendations,
ReviewPolicy,
MaxCases
);
const FHyperTwistTrainingDeck ReviewDeck = UHyperTwistTrainingRepositoryLibrary::BuildReviewDeckFromRecommendations(
SourceDeck,
Recommendations,
MaxCases
);
if (!ReviewDeck.IsStructurallyValid())
{
return FHyperTwistTrainingRunState();
}
const FString UserId = ActiveRunState.Session.UserId.IsEmpty() ? TEXT("local-user") : ActiveRunState.Session.UserId;
FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck(ReviewDeck, UserId, SessionId, Mode);
if (!RunState.IsStructurallyValid())
{
return RunState;
}
ActiveReviewPlanState = UHyperTwistTrainingRepositoryLibrary::BuildReviewPlan(
SourceDeck,
ReviewDeck,
UserId,
Recommendations,
ReviewPolicy,
FString::Printf(TEXT("review_plan_%s"), *RunState.Session.TrainingSessionId),
RunState.Session.StartedAtUtc,
Mode
);
if (ActiveReviewPlanState.IsStructurallyValid())
{
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertReviewPlan(
TrainingRepositoryState,
ActiveReviewPlanState
);
ActiveReviewFlowStatus = UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState);
}
else
{
ActiveReviewFlowStatus = FHyperTwistTrainingReviewFlowStatus();
}
return RunState;
}
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromDeck(
const FHyperTwistTrainingDeck& Deck,
const FString& UserId,
@ -25,9 +289,16 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromDe
EHyperTwistTrainingDeliveryMode Mode
)
{
ActiveReviewPlanState = FHyperTwistTrainingReviewPlanState();
ActiveReviewFlowStatus = FHyperTwistTrainingReviewFlowStatus();
ActiveRunState = UHyperTwistTrainingLibrary::StartTrainingRun(Deck, UserId, SessionId, Mode);
bHasActiveRun = ActiveRunState.IsStructurallyValid();
RefreshSummary();
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RecordRunState(
TrainingRepositoryState,
ActiveRunState
);
RefreshRepositoryViews();
return ActiveRunState;
}
@ -74,6 +345,24 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingSubsystem::SubmitTrainingAtt
ActiveRunState = StepResult.UpdatedRunState;
bHasActiveRun = ActiveRunState.IsStructurallyValid();
ActiveRunSummary = StepResult.SessionSummary;
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RecordRunState(
TrainingRepositoryState,
ActiveRunState
);
if (ActiveReviewPlanState.IsStructurallyValid())
{
ActiveReviewPlanState = UHyperTwistTrainingRepositoryLibrary::UpdateReviewPlanAfterAttempt(
ActiveReviewPlanState,
StepResult,
Attempt
);
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertReviewPlan(
TrainingRepositoryState,
ActiveReviewPlanState
);
ActiveReviewFlowStatus = UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState);
}
RefreshRepositoryViews();
return StepResult;
}
@ -94,6 +383,23 @@ FHyperTwistTrainingSession UHyperTwistTrainingSubsystem::CompleteActiveRun(bool
}
RefreshSummary();
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RecordRunState(
TrainingRepositoryState,
ActiveRunState
);
if (ActiveReviewPlanState.IsStructurallyValid())
{
ActiveReviewPlanState = UHyperTwistTrainingRepositoryLibrary::FinalizeReviewPlan(
ActiveReviewPlanState,
ActiveRunState.Session
);
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertReviewPlan(
TrainingRepositoryState,
ActiveReviewPlanState
);
ActiveReviewFlowStatus = UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState);
}
RefreshRepositoryViews();
return ActiveRunState.Session;
}
@ -101,6 +407,17 @@ void UHyperTwistTrainingSubsystem::ClearActiveRun()
{
ActiveRunState = FHyperTwistTrainingRunState();
ActiveRunSummary = FHyperTwistTrainingSessionSummary();
ActiveDeckHistorySummary = FHyperTwistTrainingDeckHistorySummary();
ActiveDeckScopedStats = FHyperTwistTrainingDeckScopedStats();
ActiveCurrentCaseScopedStats = FHyperTwistTrainingCaseScopedStats();
ActiveLearnerDeckStateSummary = FHyperTwistTrainingLearnerDeckStateSummary();
ActiveLearnerProfile = FHyperTwistTrainingLearnerProfile();
ActiveCoachMemorySnapshot = FHyperTwistTrainingCoachMemorySnapshot();
ActiveLearnerPresets.Reset();
ActiveTrainingSessionTemplates.Reset();
ActiveCaseRecommendations.Reset();
ActiveReviewPlanState = FHyperTwistTrainingReviewPlanState();
ActiveReviewFlowStatus = FHyperTwistTrainingReviewFlowStatus();
bHasActiveRun = false;
}
@ -117,3 +434,81 @@ void UHyperTwistTrainingSubsystem::RefreshSummary()
ActiveRunState.Attempts
);
}
void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
{
if (!ActiveRunState.Session.IsStructurallyValid())
{
ActiveDeckHistorySummary = FHyperTwistTrainingDeckHistorySummary();
ActiveDeckScopedStats = FHyperTwistTrainingDeckScopedStats();
ActiveCurrentCaseScopedStats = FHyperTwistTrainingCaseScopedStats();
ActiveLearnerDeckStateSummary = FHyperTwistTrainingLearnerDeckStateSummary();
ActiveLearnerProfile = FHyperTwistTrainingLearnerProfile();
ActiveCoachMemorySnapshot = FHyperTwistTrainingCoachMemorySnapshot();
ActiveLearnerPresets.Reset();
ActiveTrainingSessionTemplates.Reset();
ActiveCaseRecommendations.Reset();
ActiveReviewFlowStatus = FHyperTwistTrainingReviewFlowStatus();
return;
}
ActiveDeckHistorySummary = UHyperTwistTrainingRepositoryLibrary::DeriveDeckHistorySummary(
TrainingRepositoryState,
ActiveRunState.Session.UserId,
ActiveRunState.Session.DeckId
);
ActiveDeckScopedStats = UHyperTwistTrainingRepositoryLibrary::DeriveDeckScopedStats(
TrainingRepositoryState,
ActiveRunState.Session.UserId,
ActiveRunState.Session.DeckId,
FString()
);
const FString CurrentCaseId = ActiveRunState.CurrentSelection.IsStructurallyValid()
? ActiveRunState.CurrentSelection.TrainingCase.CaseId
: ActiveRunState.Session.CurrentCaseId;
ActiveCurrentCaseScopedStats = !CurrentCaseId.IsEmpty()
? UHyperTwistTrainingRepositoryLibrary::DeriveCaseScopedStats(
TrainingRepositoryState,
ActiveRunState.Session.UserId,
ActiveRunState.Session.DeckId,
CurrentCaseId,
ActiveDeckScopedStats.ReferenceUtc
)
: FHyperTwistTrainingCaseScopedStats();
ActiveLearnerDeckStateSummary = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerDeckStateSummary(
TrainingRepositoryState,
ActiveRunState.Session.UserId,
ActiveRunState.Session.DeckId,
FString()
);
ActiveLearnerProfile = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerProfile(
TrainingRepositoryState,
ActiveRunState.Session.UserId,
ActiveLearnerDeckStateSummary.ReferenceUtc
);
ActiveCoachMemorySnapshot = UHyperTwistTrainingRepositoryLibrary::DeriveCoachMemorySnapshot(
TrainingRepositoryState,
ActiveRunState.Session.UserId,
ActiveLearnerDeckStateSummary.ReferenceUtc
);
ActiveLearnerPresets = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerPresets(
TrainingRepositoryState,
ActiveRunState.Session.UserId,
ActiveLearnerDeckStateSummary.ReferenceUtc
);
ActiveTrainingSessionTemplates = UHyperTwistTrainingRepositoryLibrary::DeriveTrainingSessionTemplates(
TrainingRepositoryState,
ActiveRunState.Session.UserId,
ActiveLearnerDeckStateSummary.ReferenceUtc
);
ActiveCaseRecommendations = UHyperTwistTrainingRepositoryLibrary::ListCaseRecommendationsForDeck(
TrainingRepositoryState,
ActiveRunState.ActiveDeck,
ActiveRunState.Session.UserId,
ActiveLearnerDeckStateSummary.ReferenceUtc,
5
);
ActiveReviewFlowStatus = ActiveReviewPlanState.IsStructurallyValid()
? UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState)
: FHyperTwistTrainingReviewFlowStatus();
}

View file

@ -65,6 +65,57 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingSessionSummary MakeSampleTrainingRunSummary();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingTimingPolicy MakeSampleTrainingTimingPolicy();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingTimingBreakdown MakeSampleTrainingTimingBreakdown();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingRepositoryState MakeSampleTrainingRepositoryState();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingDeckHistorySummary MakeSampleTrainingDeckHistorySummary();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingCaseScopedStats MakeSampleTrainingCaseScopedStats();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingDeckScopedStats MakeSampleTrainingDeckScopedStats();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingCaseLearnerState MakeSampleTrainingCaseLearnerState();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingLearnerDeckStateSummary MakeSampleTrainingLearnerDeckStateSummary();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingLearnerProfile MakeSampleTrainingLearnerProfile();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingCoachMemorySnapshot MakeSampleTrainingCoachMemorySnapshot();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static TArray<FHyperTwistTrainingLearnerPreset> MakeSampleTrainingLearnerPresets();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static TArray<FHyperTwistTrainingSessionTemplate> MakeSampleTrainingSessionTemplates();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingDeck MakeSampleTrainingSessionTemplateDeck();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static TArray<FHyperTwistTrainingCaseRecommendation> MakeSampleTrainingCaseRecommendations();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingDeck MakeSampleTrainingReviewDeck();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingReviewPlanState MakeSampleTrainingReviewPlanState();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingReviewFlowStatus MakeSampleTrainingReviewFlowStatus();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Validation")
static bool IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State);
@ -80,6 +131,9 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Serialization")
static FString SerializeContentPackToJson(const FHyperTwistContentPack& ContentPack);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Serialization")
static FString SerializeTrainingRepositoryStateToJson(const FHyperTwistTrainingRepositoryState& RepositoryState);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
static bool DeserializePuzzleStateFromJson(const FString& Json, FHyperTwistPuzzleState& OutState);
@ -88,4 +142,7 @@ public:
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
static bool DeserializeContentPackFromJson(const FString& Json, FHyperTwistContentPack& OutContentPack);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
static bool DeserializeTrainingRepositoryStateFromJson(const FString& Json, FHyperTwistTrainingRepositoryState& OutRepositoryState);
};

View file

@ -17,6 +17,13 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Catalog")
static TArray<FHyperTwistContentPack> MakePhase3TrainingCatalog();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Catalog")
static bool TryFindDeckInCatalog(
const TArray<FHyperTwistContentPack>& TrainingCatalog,
const FString& DeckId,
FHyperTwistTrainingDeck& OutDeck
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Catalog")
static bool TryGetTrackFromContentPack(
const FHyperTwistContentPack& ContentPack,
@ -37,4 +44,10 @@ public:
const FHyperTwistContentPack& ContentPack,
FHyperTwistTrainingDeck& OutDeck
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Catalog")
static FHyperTwistTrainingDeck MaterializeTrainingSessionTemplateDeck(
const FHyperTwistTrainingSessionTemplate& SessionTemplate,
const TArray<FHyperTwistContentPack>& TrainingCatalog
);
};

View file

@ -70,6 +70,17 @@ public:
const FHyperTwistTrainingAttempt& Attempt
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingTimingPolicy NormalizeTimingPolicy(
const FHyperTwistTrainingTimingPolicy& TimingPolicy
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingAttempt NormalizeTrainingAttemptTiming(
const FHyperTwistTrainingAttempt& Attempt,
const FHyperTwistTrainingTimingPolicy& TimingPolicy
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingSession CompleteTrainingSession(
const FHyperTwistTrainingSession& Session,

View file

@ -0,0 +1,33 @@
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
#include "HyperTwistTrainingPersistenceLibrary.generated.h"
UCLASS()
class UNREALHYPERTWIST_API UHyperTwistTrainingPersistenceLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Persistence")
static FString GetDefaultTrainingRepositoryPath();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Persistence")
static FString ResolveTrainingRepositoryPath(const FString& RepositoryPath);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Persistence")
static bool SaveTrainingRepositoryStateToFile(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& RepositoryPath,
FString& OutResolvedPath
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Persistence")
static bool LoadTrainingRepositoryStateFromFile(
const FString& RepositoryPath,
FHyperTwistTrainingRepositoryState& OutRepositoryState,
FString& OutResolvedPath
);
};

View file

@ -0,0 +1,238 @@
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
#include "HyperTwistTrainingRepositoryLibrary.generated.h"
UCLASS()
class UNREALHYPERTWIST_API UHyperTwistTrainingRepositoryLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingAttemptHistoryEntry MakeAttemptHistoryEntry(
const FHyperTwistTrainingSession& Session,
const FHyperTwistTrainingAttempt& Attempt
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static EHyperTwistTrainingReviewGrade DeriveReviewGrade(
const FHyperTwistTrainingAttemptHistoryEntry& AttemptHistoryEntry,
const TArray<FHyperTwistTrainingReviewHistoryEntry>& ExistingReviewHistory
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingReviewHistoryEntry MakeReviewHistoryEntry(
const FHyperTwistTrainingSession& Session,
const FHyperTwistTrainingAttempt& Attempt,
const TArray<FHyperTwistTrainingReviewHistoryEntry>& ExistingReviewHistory
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingSolveHistoryEntry MakeSolveHistoryEntry(
const FHyperTwistTrainingAttempt& Attempt
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingRunRecord BuildRunRecord(
const FHyperTwistTrainingRunState& RunState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingRepositoryState UpsertRunRecord(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingRunRecord& RunRecord
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingRepositoryState RecordRunState(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingRunState& RunState
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository")
static bool TryGetRunRecord(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& TrainingSessionId,
FHyperTwistTrainingRunRecord& OutRunRecord
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static TArray<FHyperTwistTrainingRunRecord> ListRunRecordsForDeck(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& DeckId
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingCaseLearnerState DeriveLearnerStateForCase(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& DeckId,
const FString& CaseId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository")
static bool TryGetLearnerState(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& DeckId,
const FString& CaseId,
FHyperTwistTrainingCaseLearnerState& OutLearnerState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static TArray<FHyperTwistTrainingCaseLearnerState> ListLearnerStatesForDeck(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& DeckId
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingDeckHistorySummary DeriveDeckHistorySummary(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& DeckId
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingCaseScopedStats DeriveCaseScopedStats(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& DeckId,
const FString& CaseId,
const FString& ReferenceUtc
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static TArray<FHyperTwistTrainingCaseScopedStats> ListCaseScopedStatsForDeck(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& DeckId,
const FString& ReferenceUtc
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingDeckScopedStats DeriveDeckScopedStats(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& DeckId,
const FString& ReferenceUtc
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingLearnerDeckStateSummary DeriveLearnerDeckStateSummary(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& DeckId,
const FString& ReferenceUtc
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingLearnerProfile DeriveLearnerProfile(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& ReferenceUtc
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingCoachMemorySnapshot DeriveCoachMemorySnapshot(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& ReferenceUtc
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static TArray<FHyperTwistTrainingLearnerPreset> DeriveLearnerPresets(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& ReferenceUtc
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static TArray<FHyperTwistTrainingSessionTemplate> DeriveTrainingSessionTemplates(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& ReferenceUtc
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingCaseRecommendation MakeCaseRecommendation(
const FHyperTwistTrainingCase& TrainingCase,
const FHyperTwistTrainingCaseLearnerState& LearnerState,
const FString& ReferenceUtc
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingReviewPolicy GetDefaultReviewPolicy(
EHyperTwistTrainingDeliveryMode DeliveryMode
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static TArray<FHyperTwistTrainingCaseRecommendation> ListCaseRecommendationsForDeck(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingDeck& Deck,
const FString& UserId,
const FString& ReferenceUtc,
int32 MaxRecommendations
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static TArray<FHyperTwistTrainingCaseRecommendation> ApplyReviewPolicyToRecommendations(
const TArray<FHyperTwistTrainingCaseRecommendation>& Recommendations,
const FHyperTwistTrainingReviewPolicy& ReviewPolicy,
int32 MaxRecommendations
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingDeck BuildReviewDeckFromRecommendations(
const FHyperTwistTrainingDeck& SourceDeck,
const TArray<FHyperTwistTrainingCaseRecommendation>& Recommendations,
int32 MaxCases
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingReviewPlanState BuildReviewPlan(
const FHyperTwistTrainingDeck& SourceDeck,
const FHyperTwistTrainingDeck& ReviewDeck,
const FString& UserId,
const TArray<FHyperTwistTrainingCaseRecommendation>& Recommendations,
const FHyperTwistTrainingReviewPolicy& ReviewPolicy,
const FString& PlanId,
const FString& ReferenceUtc,
EHyperTwistTrainingDeliveryMode DeliveryMode
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingRepositoryState UpsertReviewPlan(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingReviewPlanState& ReviewPlan
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository")
static bool TryGetReviewPlan(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& PlanId,
FHyperTwistTrainingReviewPlanState& OutReviewPlan
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingReviewPlanState UpdateReviewPlanAfterAttempt(
const FHyperTwistTrainingReviewPlanState& ReviewPlan,
const FHyperTwistTrainingRunStepResult& StepResult,
const FHyperTwistTrainingAttempt& Attempt
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingReviewPlanState FinalizeReviewPlan(
const FHyperTwistTrainingReviewPlanState& ReviewPlan,
const FHyperTwistTrainingSession& Session
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingReviewFlowStatus DeriveReviewFlowStatus(
const FHyperTwistTrainingReviewPlanState& ReviewPlan
);
};

View file

@ -25,6 +25,106 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingSessionSummary GetActiveTrainingRunSummary(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRepositoryState GetTrainingRepositoryState(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingDeckHistorySummary GetActiveTrainingDeckHistorySummary(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingDeckScopedStats GetActiveTrainingDeckScopedStats(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingCaseScopedStats GetActiveTrainingCurrentCaseScopedStats(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingLearnerDeckStateSummary GetActiveTrainingLearnerDeckStateSummary(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingLearnerProfile GetActiveTrainingLearnerProfile(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingCoachMemorySnapshot GetActiveTrainingCoachMemorySnapshot(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static TArray<FHyperTwistTrainingLearnerPreset> GetActiveTrainingLearnerPresets(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static TArray<FHyperTwistTrainingSessionTemplate> GetActiveTrainingSessionTemplates(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static TArray<FHyperTwistTrainingCaseRecommendation> GetActiveTrainingCaseRecommendations(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingReviewPlanState GetActiveTrainingReviewPlanState(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingReviewFlowStatus GetActiveTrainingReviewFlowStatus(UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static bool TryGetStoredTrainingRunRecord(
UObject* WorldContextObject,
const FString& TrainingSessionId,
FHyperTwistTrainingRunRecord& OutRunRecord
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static bool TryGetStoredTrainingLearnerState(
UObject* WorldContextObject,
const FString& UserId,
const FString& DeckId,
const FString& CaseId,
FHyperTwistTrainingCaseLearnerState& OutLearnerState
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static bool TryGetStoredTrainingReviewPlan(
UObject* WorldContextObject,
const FString& PlanId,
FHyperTwistTrainingReviewPlanState& OutReviewPlan
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static bool SaveTrainingRepositoryStateToDefaultPath(
UObject* WorldContextObject,
FString& OutResolvedPath
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static bool LoadTrainingRepositoryStateFromDefaultPath(
UObject* WorldContextObject,
FString& OutResolvedPath
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static bool TryMaterializeActiveTrainingSessionTemplate(
UObject* WorldContextObject,
const FString& TemplateId,
FHyperTwistTrainingDeck& OutDeck
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingDeck BuildActiveReviewDeck(
UObject* WorldContextObject,
int32 MaxCases
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState StartTrainingRunFromTemplate(
UObject* WorldContextObject,
const FString& TemplateId,
const FString& UserId,
const FString& SessionId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState StartRecommendedReviewRun(
UObject* WorldContextObject,
const FString& SessionId,
int32 MaxCases,
EHyperTwistTrainingDeliveryMode Mode
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState StartSampleClassicTrainingRun(
UObject* WorldContextObject,

View file

@ -20,6 +20,91 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingSessionSummary GetActiveRunSummary() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingRepositoryState GetTrainingRepositoryState() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingDeckHistorySummary GetActiveDeckHistorySummary() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingDeckScopedStats GetActiveDeckScopedStats() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingCaseScopedStats GetActiveCurrentCaseScopedStats() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingLearnerDeckStateSummary GetActiveLearnerDeckStateSummary() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingLearnerProfile GetActiveLearnerProfile() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingCoachMemorySnapshot GetActiveCoachMemorySnapshot() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
TArray<FHyperTwistTrainingLearnerPreset> GetActiveLearnerPresets() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
TArray<FHyperTwistTrainingSessionTemplate> GetActiveTrainingSessionTemplates() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
TArray<FHyperTwistTrainingCaseRecommendation> GetActiveCaseRecommendations() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingReviewPlanState GetActiveReviewPlanState() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingReviewFlowStatus GetActiveReviewFlowStatus() const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
bool TryGetStoredRunRecord(
const FString& TrainingSessionId,
FHyperTwistTrainingRunRecord& OutRunRecord
) const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
bool TryGetStoredLearnerState(
const FString& UserId,
const FString& DeckId,
const FString& CaseId,
FHyperTwistTrainingCaseLearnerState& OutLearnerState
) const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
bool TryGetStoredReviewPlan(
const FString& PlanId,
FHyperTwistTrainingReviewPlanState& OutReviewPlan
) const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
bool SaveTrainingRepositoryStateToDefaultPath(FString& OutResolvedPath) const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
bool LoadTrainingRepositoryStateFromDefaultPath(FString& OutResolvedPath);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
bool TryMaterializeActiveTrainingSessionTemplate(
const FString& TemplateId,
FHyperTwistTrainingDeck& OutDeck
) const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingDeck BuildActiveReviewDeck(int32 MaxCases) const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
FHyperTwistTrainingRunState StartTrainingRunFromTemplate(
const FString& TemplateId,
const FString& UserId,
const FString& SessionId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
FHyperTwistTrainingRunState StartRecommendedReviewRun(
const FString& SessionId,
int32 MaxCases,
EHyperTwistTrainingDeliveryMode Mode
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
FHyperTwistTrainingRunState StartTrainingRunFromDeck(
const FHyperTwistTrainingDeck& Deck,
@ -49,6 +134,7 @@ public:
private:
void RefreshSummary();
void RefreshRepositoryViews();
UPROPERTY()
FHyperTwistTrainingRunState ActiveRunState;
@ -56,6 +142,42 @@ private:
UPROPERTY()
FHyperTwistTrainingSessionSummary ActiveRunSummary;
UPROPERTY()
FHyperTwistTrainingRepositoryState TrainingRepositoryState;
UPROPERTY()
FHyperTwistTrainingDeckHistorySummary ActiveDeckHistorySummary;
UPROPERTY()
FHyperTwistTrainingDeckScopedStats ActiveDeckScopedStats;
UPROPERTY()
FHyperTwistTrainingCaseScopedStats ActiveCurrentCaseScopedStats;
UPROPERTY()
FHyperTwistTrainingLearnerDeckStateSummary ActiveLearnerDeckStateSummary;
UPROPERTY()
FHyperTwistTrainingLearnerProfile ActiveLearnerProfile;
UPROPERTY()
FHyperTwistTrainingCoachMemorySnapshot ActiveCoachMemorySnapshot;
UPROPERTY()
TArray<FHyperTwistTrainingLearnerPreset> ActiveLearnerPresets;
UPROPERTY()
TArray<FHyperTwistTrainingSessionTemplate> ActiveTrainingSessionTemplates;
UPROPERTY()
TArray<FHyperTwistTrainingCaseRecommendation> ActiveCaseRecommendations;
UPROPERTY()
FHyperTwistTrainingReviewPlanState ActiveReviewPlanState;
UPROPERTY()
FHyperTwistTrainingReviewFlowStatus ActiveReviewFlowStatus;
UPROPERTY()
bool bHasActiveRun = false;
};