From 24736650ae92ab21406d09b48230d971fea8e473 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Fri, 1 May 2026 22:00:33 +0200 Subject: [PATCH] Overlay imported training catalog packs and finite bundles --- .../HyperTwistTrainingCatalogLibrary.cpp | 232 ++++++++++++++++-- 1 file changed, 216 insertions(+), 16 deletions(-) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.cpp index 40cfb2c..f294cc3 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.cpp @@ -1297,11 +1297,19 @@ namespace HyperTwistTrainingCatalogMaterializationInternal return false; } - bool IsSupportedFiniteRepoBackedBundle(const FHyperTwistTrainingCatalogRuntimeContentBundle& RuntimeBundle) + bool IsSupportedFiniteImportedExerciseBundle(const FHyperTwistTrainingCatalogRuntimeContentBundle& RuntimeBundle) { + const bool bIsSupportedFinitePolicy = + RuntimeBundle.RuntimeMaterializationPolicy.Equals(TEXT("instantiate-algorithm-cases"), ESearchCase::IgnoreCase) + || RuntimeBundle.RuntimeMaterializationPolicy.Equals(TEXT("instantiate-scramble-drill-cases"), ESearchCase::IgnoreCase); + if (!bIsSupportedFinitePolicy) + { + return false; + } + return RuntimeBundle.ContentSourceFile.EndsWith(TEXT("repo-backed-exercises.jsonl"), ESearchCase::IgnoreCase) - && (RuntimeBundle.RuntimeMaterializationPolicy.Equals(TEXT("instantiate-algorithm-cases"), ESearchCase::IgnoreCase) - || RuntimeBundle.RuntimeMaterializationPolicy.Equals(TEXT("instantiate-scramble-drill-cases"), ESearchCase::IgnoreCase)); + || RuntimeBundle.ContentSourceFile.EndsWith(TEXT("httrack-retained-exercises.jsonl"), ESearchCase::IgnoreCase) + || RuntimeBundle.ContentSourceFile.EndsWith(TEXT("restrictive-static-exercises.jsonl"), ESearchCase::IgnoreCase); } bool TryBuildTrainingCaseFromRawRecord( @@ -1392,10 +1400,11 @@ namespace HyperTwistTrainingCatalogMaterializationInternal return OutCase.IsStructurallyValid(); } - bool TryMaterializeFiniteRepoBackedDeck( + bool TryMaterializeFiniteImportedDeck( const FString& CatalogDirectory, const FHyperTwistTrainingCatalogDeckImportSpec& DeckImportSpec, const FHyperTwistTrainingCatalogRuntimeContentBundle& RuntimeBundle, + TMap>& RawExerciseRecordCache, FHyperTwistTrainingDeck& OutDeck, TArray& InOutNotes ) @@ -1406,10 +1415,10 @@ namespace HyperTwistTrainingCatalogMaterializationInternal InOutNotes.Add(TEXT("Imported deck materialization failed because the deck spec or runtime bundle was invalid.")); return false; } - if (!IsSupportedFiniteRepoBackedBundle(RuntimeBundle)) + if (!IsSupportedFiniteImportedExerciseBundle(RuntimeBundle)) { InOutNotes.Add(FString::Printf( - TEXT("Imported deck %s remains deferred because runtime policy %s from %s is outside the current finite repo-backed slice."), + TEXT("Imported deck %s remains deferred because runtime policy %s from %s is outside the current finite imported-exercise slice."), *DeckImportSpec.DeckId, *RuntimeBundle.RuntimeMaterializationPolicy, *RuntimeBundle.ContentSourceFile @@ -1459,19 +1468,25 @@ namespace HyperTwistTrainingCatalogMaterializationInternal } const FString SourcePath = ResolveBundledContentSourcePath(CatalogDirectory, RuntimeBundle.ContentSourceFile); - TArray RawRecords; - if (!LoadRawExerciseRecordsFromJsonLinesFile( - SourcePath, - RawRecords, - InOutNotes, - FString::Printf(TEXT("Imported raw exercise source for %s"), *DeckImportSpec.DeckId))) + TArray* RawRecords = RawExerciseRecordCache.Find(SourcePath); + if (RawRecords == nullptr) { - return false; + TArray LoadedRawRecords; + if (!LoadRawExerciseRecordsFromJsonLinesFile( + SourcePath, + LoadedRawRecords, + InOutNotes, + FString::Printf(TEXT("Imported raw exercise source for %s"), *DeckImportSpec.DeckId))) + { + return false; + } + + RawRecords = &RawExerciseRecordCache.Add(SourcePath, MoveTemp(LoadedRawRecords)); } const FString PuzzleId = ResolveImportedPuzzleId(DeckImportSpec); TArray Cases; - for (const FHyperTwistTrainingCatalogRawExerciseRecord& RawRecord : RawRecords) + for (const FHyperTwistTrainingCatalogRawExerciseRecord& RawRecord : *RawRecords) { if (!MatchesRuntimeBundleFilter(RuntimeBundle, RawRecord)) { @@ -1543,17 +1558,200 @@ namespace HyperTwistTrainingCatalogMaterializationInternal ); return OutDeck.IsStructurallyValid(); } + + bool TryFindDeckImportSpecByImportSpecId( + const FHyperTwistTrainingCatalogMaterializedBundle& MaterializedBundle, + const FString& DeckImportSpecId, + FHyperTwistTrainingCatalogDeckImportSpec& OutDeckImportSpec + ) + { + OutDeckImportSpec = FHyperTwistTrainingCatalogDeckImportSpec(); + if (!MaterializedBundle.IsStructurallyValid() || DeckImportSpecId.IsEmpty()) + { + return false; + } + + for (const FHyperTwistTrainingCatalogDeckImportSpec& DeckImportSpec + : MaterializedBundle.DeckImportSpecs.deck_import_specs) + { + if (DeckImportSpec.DeckImportSpecId == DeckImportSpecId) + { + OutDeckImportSpec = DeckImportSpec; + return OutDeckImportSpec.IsStructurallyValid(); + } + } + + return false; + } + + bool TryBuildImportedTrackFromManifest( + const FString& CatalogDirectory, + const FHyperTwistTrainingCatalogMaterializedBundle& MaterializedBundle, + const FHyperTwistTrainingCatalogImportedTrackManifest& TrackManifest, + TMap>& RawExerciseRecordCache, + FHyperTwistTrainingTrack& OutTrack + ) + { + OutTrack = FHyperTwistTrainingTrack(); + if (!MaterializedBundle.IsStructurallyValid() || !TrackManifest.IsStructurallyValid()) + { + return false; + } + + FHyperTwistTrainingSourceAttribution TrackSourceAttribution; + if (!UHyperTwistTrainingCatalogLibrary::TryConvertImportedSourceAttribution( + TrackManifest.SourceAttribution, + TrackSourceAttribution)) + { + return false; + } + + TArray Decks; + for (const FString& DeckImportSpecId : TrackManifest.DeckImportSpecIds) + { + FHyperTwistTrainingCatalogDeckImportSpec DeckImportSpec; + if (!TryFindDeckImportSpecByImportSpecId(MaterializedBundle, DeckImportSpecId, DeckImportSpec)) + { + continue; + } + + FHyperTwistTrainingCatalogRuntimeContentBundle RuntimeBundle; + if (!UHyperTwistTrainingCatalogLibrary::TryFindRuntimeContentBundleForDeckImportSpec( + MaterializedBundle, + DeckImportSpec, + RuntimeBundle)) + { + continue; + } + + FHyperTwistTrainingDeck Deck; + TArray MaterializationNotes; + if (!TryMaterializeFiniteImportedDeck( + CatalogDirectory, + DeckImportSpec, + RuntimeBundle, + RawExerciseRecordCache, + Deck, + MaterializationNotes)) + { + continue; + } + + Decks.Add(MoveTemp(Deck)); + } + + if (Decks.Num() == 0) + { + return false; + } + + OutTrack.TrackId = TrackManifest.TrackId; + OutTrack.Title = TrackManifest.Title; + OutTrack.Goal = TrackManifest.Goal; + OutTrack.Decks = MoveTemp(Decks); + OutTrack.SourceAttribution = TrackSourceAttribution; + return OutTrack.IsStructurallyValid(); + } + + bool TryBuildImportedContentPackFromManifest( + const FString& CatalogDirectory, + const FHyperTwistTrainingCatalogMaterializedBundle& MaterializedBundle, + const FHyperTwistTrainingCatalogImportedContentPackManifest& ContentPackManifest, + TMap>& RawExerciseRecordCache, + FHyperTwistContentPack& OutContentPack + ) + { + OutContentPack = FHyperTwistContentPack(); + if (!MaterializedBundle.IsStructurallyValid() || !ContentPackManifest.IsStructurallyValid()) + { + return false; + } + + FHyperTwistTrainingSourceAttribution ContentPackSourceAttribution; + if (!UHyperTwistTrainingCatalogLibrary::TryConvertImportedSourceAttribution( + ContentPackManifest.SourceAttribution, + ContentPackSourceAttribution)) + { + return false; + } + + EHyperTwistPuzzleFamily PuzzleFamily = EHyperTwistPuzzleFamily::ClassicCube; + if (!UHyperTwistTrainingCatalogLibrary::TryConvertImportedPuzzleFamily( + ContentPackManifest.PuzzleFamily, + PuzzleFamily)) + { + return false; + } + + TArray Tracks; + for (const FHyperTwistTrainingCatalogImportedTrackManifest& TrackManifest : ContentPackManifest.TrackManifests) + { + FHyperTwistTrainingTrack Track; + if (TryBuildImportedTrackFromManifest( + CatalogDirectory, + MaterializedBundle, + TrackManifest, + RawExerciseRecordCache, + Track)) + { + Tracks.Add(MoveTemp(Track)); + } + } + + if (Tracks.Num() == 0) + { + return false; + } + + OutContentPack.ContentPackId = ContentPackManifest.ContentPackId; + OutContentPack.Title = ContentPackManifest.Title; + OutContentPack.Version = ContentPackManifest.Version.IsEmpty() ? OutContentPack.Version : ContentPackManifest.Version; + OutContentPack.PuzzleFamily = PuzzleFamily; + OutContentPack.NotationProfile = + ContentPackManifest.NotationProfile.IsEmpty() ? OutContentPack.NotationProfile : ContentPackManifest.NotationProfile; + OutContentPack.Tracks = MoveTemp(Tracks); + OutContentPack.SourceAttribution = ContentPackSourceAttribution; + return OutContentPack.IsStructurallyValid(); + } } TArray UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog() { - return + TArray Catalog = { MakeAlgTrainerStarterContentPack(), MakeCrossTrainerStarterContentPack(), MakeFiveStyleStarterContentPack(), UHyperTwistTrainingLibrary::MakeSampleClassicContentPack() }; + + FHyperTwistTrainingCatalogMaterializedBundle MaterializedBundle; + FHyperTwistTrainingCatalogImportLoadReport LoadReport; + if (!LoadBundledCatalogMaterializationBundle(MaterializedBundle, LoadReport)) + { + return Catalog; + } + + TMap> RawExerciseRecordCache; + const FString CatalogDirectory = GetBundledCatalogMaterializationDirectory(); + for (const FHyperTwistTrainingCatalogImportedContentPackManifest& ContentPackManifest + : MaterializedBundle.ContentPackManifests.content_packs) + { + FHyperTwistContentPack ImportedContentPack; + if (!HyperTwistTrainingCatalogMaterializationInternal::TryBuildImportedContentPackFromManifest( + CatalogDirectory, + MaterializedBundle, + ContentPackManifest, + RawExerciseRecordCache, + ImportedContentPack)) + { + continue; + } + + Catalog.Add(MoveTemp(ImportedContentPack)); + } + + return Catalog; } FString UHyperTwistTrainingCatalogLibrary::GetBundledCatalogMaterializationDirectory() @@ -1772,10 +1970,12 @@ bool UHyperTwistTrainingCatalogLibrary::TryMaterializeImportedDeckFromMaterializ return false; } - const bool bMaterialized = HyperTwistTrainingCatalogMaterializationInternal::TryMaterializeFiniteRepoBackedDeck( + TMap> RawExerciseRecordCache; + const bool bMaterialized = HyperTwistTrainingCatalogMaterializationInternal::TryMaterializeFiniteImportedDeck( GetBundledCatalogMaterializationDirectory(), DeckImportSpec, RuntimeBundle, + RawExerciseRecordCache, OutDeck, OutLoadReport.Notes );