From fb72fbc2c847ecd5b3dce210d43eaef0804d2414 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Thu, 18 Jun 2026 10:25:35 +0000 Subject: [PATCH] Complete Phase 6C dedicated map ownership proof --- .../L_HyperTwist_Magic120CellTraining.umap | Bin 8808 -> 14055 bytes .../L_HyperTwist_MagicCube5DTraining.umap | Bin 8808 -> 14031 bytes ...istHigherDimensionalTrainingShellActor.cpp | 221 +++++ ...TwistHigherDimensionalTrainingShellActor.h | 106 +++ ...mensionalPhase6CActivationContractTest.cpp | 793 ++++++++++++++++++ ...phase6c_dedicated_family_map_manifest.json | 68 ++ ...cated_map_ownership_automation_report.json | 224 +++++ ...NSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md | 14 + .../HyperTwist/FEATURE_REGISTRY.md | 2 +- .../HyperTwist/ROADMAP.md | 21 +- ...yperTwistHigherDimensionalMapAuthoring.ps1 | 180 ++++ ...author_higher_dimensional_training_maps.py | 579 +++++++++++++ 12 files changed, 2204 insertions(+), 4 deletions(-) create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistHigherDimensionalTrainingShellActor.cpp create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistHigherDimensionalTrainingShellActor.h create mode 100644 docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json create mode 100644 docs/generated/higher_dimensional_training_maps/phase6c_dedicated_map_ownership_automation_report.json create mode 100644 scripts/Invoke-HyperTwistHigherDimensionalMapAuthoring.ps1 create mode 100644 scripts/hypertwist_author_higher_dimensional_training_maps.py diff --git a/UnrealHyperTwist/Content/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining.umap b/UnrealHyperTwist/Content/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining.umap index 028fa9f1746211bb7b91d7125ac9bf1fc34e0b53..9564d082e23baf3cebb6dde85bb126c578ba4022 100644 GIT binary patch literal 14055 zcmeG@33MFAl?66nlK{pGa=;wI7_hNMni*NLB{0^^Xl5iKjjp+Yn4amDrlp?lad*#1 zqZdFBoS2v->u@diE+m)-BpAqIcv*u92`sy0!?6aEO@QSzgezdcfI<6zch#upuq1<% z_x3&hdp%Y4>({^P*RQH>>7nnQdc(&P6BFmoBIMxx3HcOCgjb( zUj1X$ckK{+jyRE!L!j@*4ZG&=?#QnH<{!Ru%hIjQD;O1I!%b&RD zms}0tp82!bU1w_h~>gqPOe`jgYY0k~rpzI^Xnhu`?rx*N}5_fqC+1$9}i@BaOG z>t@djM{K-It3GzYNq}>I``YJxU0WC5ddK{W^9%SFRsrtq)aF;-a9_Uj8-H}%V5SdJCG1^9uUui^4Flv(NNxvGXwDJZ%vj+gKcD9dlVs!@c7_i@X?bKw^RjI zv|aej(xVsWU;hN@kS8j206B=vCcbTl&zcXdWy?a|hGl(%BygHwYp7je^b_*#+1vN8 z+z*2cIH~vc53zDS$i1i0#Iaw9y9o>`FCey9`i_8hU4K#)GkTpzQH4-CbQ@a+j!S5P4vEE2nd0{w~|< zIY`hZ^1L9E^B-?NxC+|=MfWMHM-qW?x7shLIYA{yp0N3ssG?g@hSQunY^X{GgGX;b zT>vW;^qx0+^A8S0q?*-Ir1rxq9<4B1lUCdp8w?pE^SV$N`C7oX4Urx$-L2{=B@S*T zId#>2-!zduiYDTeku9F8brjd9=*Dc3MK3-gqNibSD8qBQz!Uz1lOM%}=z+DCm4r#@ zRpZ@jF^F4*0WK@)xJJ7aULZl&)_0Jvmlt6I!=f;vk!xbdSEEv1Ih!tM+WYMFEfAE% zYk1a}T#q72q6XTvaWXG~qq_t!fhScjKK?8&$LUF%n_bcfm)8uQWZU+a>M+O$xU?wc zi-M52mp!u)XTv8coL*4svrW%`jvnwU3X1|xczW;vL}4s?dOl69fJREubeon{2e_or zoJqlIZA{i#gS?J~ww01JMGl+5EP}{pUf{(fI@xCGXG?P#^3TEBw=M;W$)wgM4yFXP z6_$yNuHg!atv>~ei9GUxLu-ctqO1$5v5o|rmK0?8ol3nuk`tM~eo zhcCeigiQ$y$Qi~M31ofGku|u0`V>Wklx%fHGwvlu8l*0b~lk9vcxS?#}vqS^!&EbsFXZo5)x8P=#e`3?Sd1`}?yF z0zP2~9hj_Jj+xE(K{E%-5vIiO`CdD%(qMALBcJU;Ga`yA@%@6XLvq*1>`ShEv<0wV z^@O+mXIDOqG0ILRAsNmEl9wknv#*+CJ+hb`lev{_xa)wmm}`s!0?~JWaWiHHwmTT6SJNq>s z$-g+#itdrPyrA~$oT?M~`nMjzbLynJ9tDmot(=xJlq+nxT)lGYA|RapDBO0y!7^%V zBwaF8v+;7&7P6=d9JZ!A0oef#XXMj0sSHMRX?lf>Y@7j%H2T}OF3y|1| z9dg1sXI0@G8Li?~@pi3q7|!?x)X7r(JTDsU2bm&TiH-TT5S*^$qClFyb&~=#AigdUkBA|YqX0?cpqR5%Ff^wPd zR(mVaG25r3V>V4k$84I8j@dLFUGEI-T{1(u@C@l9Go*{pkgjzGbZ@}CVGc)_$}dRQ zZ<0+(w*%g&@SO-=GUf8z6$+WS{LWA;HPO-{!{|)7_++>xP*@yD* zZ5Xo~lRxGL^bzKqR_d%9D#(Lw17_;a(<$Lc7J0LQ1Iv|G`(9d1nL~lY*n`-a%475Q zp+z3fAIrHYZv+_v%sa^}t-nkTE3pq)6?|o-#kxWfJ1sPL(h5;L`tuyh?E4kuQL-7^ zcQGCCfkhshKkPzzn<&jI%o((8EoN%pTso*!-V(qg539Tlr&uKv*Y{T5+ZOwbLpqg* zTvbm}oh7`gl71I@-Jmbhu3wLo7`S53M8UZ=d?waCNy zYo*N4zB!c94Gk1opV@WF z{R>O|b4wlN9W z9*CsX-cVh7h0k~9T2XZBD;gTGOCO1MMe0&##`1k5IY-u2&$l|pdRIb^$M1JL22V4CJ|}+umspXX8#O8}D^>VMoY6ni3-6e5k%Jm6Us3;b_l**3+s4Ly>G#S1us+ z^mUJ-{Fy+HtIj0zxCWEu14jIO0lBi(&1$pi6R6(9^&+-bQ`n*`p;7@;>G!l?9avh;bATkNbyj1M*H%yh?E81a%VK4#3Q4ctJMx#xl^(Fz6_uC*||tlwlnI^ zC!Fz2!XIi(M0_$A=^E`Cat|SWXC$D-Bfe}h-IPo8JMw&_0me9_&WLY__e;)L6l`%W zcLi|1{i$5CzE@Ayhx75YZzvwjjBtM6SUeaX>U26%5Tip~!S<1u6KwP}S$LI<>vj9l zcL~4Gkw^y=(A|+t$5Sz9=*!t28BKxSL!8Gk#z#BSu}DC2`P$JBPA(Fz2ODz zW~}>gJkpnmIeqqcq*u4j6Q7nOKJOTUHEWMYQ+A8)u6{JCEg&W91L(7v`e~1{lFsm$ z5ksQIF7Ghfl|g%wFb6nC@qWh`_&kdxv%gz*E3s%m3Z%PoWpfU>1mXu`Ct&BIaS3dI zSZKhK&4a547epJyPx^1l`GGhDpLEdJ3qedn%%($n%-MwUmO~~PKo=$3!i_?DR017y zi8g77^EW|!E-%y5)r!<^OB^{M{wu|B+<|KcAiI)=Guz#fCRqI|LjAMhK@$r#v^#dt0AZPYC#$YjAU20%(GGZ_rlCDK9`ZfVC|uOlShSua@l+xrX+p0HgV$Ba10%-f;DR*=`C+u7 zJ6*EYTrLjj!H;8nYo;ik*qF|UBW0#{uZ*<)zP^D{-J|wCDejksV9iT$AB%;s8`miK z6^HroCzx)+z33~zs#P_{2P8b|!07sV=X*lQh4wT^mH z_zvdA&peuU6ut)OTZ6eAdK|p4Gdvv+x9l zj{)pxX+4D-Hvv3kmA&)%TT-(_DW2@CFps^lEK?DWwd|Ty)`{m|eC3T$%95OJ;tiKz z6%V`kJrSYEJL^jqw8eaX%$;dafP6zZNqP~ubsOo^UAFq5A66LdfIKd zm)V`Kj5`K_RhL=NMjXx*Iu`w!H9lHF#r!}~XI?wZKXM8R=?r{+6|}nUj3Apg5rYzb zxZ}*YVe5n8|Nf=b!s(=++hS5ombZ0AcZ}UT+`<+PD=DEtj+2dmemjJgtc@jGKW0Ec z>bZUC7n>Z}FFv3B;sv!IT!C&co(Kc+rGA)ttZcDxv8svvY(cWJG??1|u~&ZQ%!f`r zd%Nd5KfQk4PJ@-P;GkBTK<2TRB-SuJjY==&j1moJ6wM8DPg$JeaNIi($~tk$;+}+1 z)`?l{V56%-C;EMl84KAm$5>6Orrb(RGFDzldtvi?JG#8T*!iEoyL@Nhx0N#1GGuTH zJZ-@>)<3LR)5`wvrr-Pv85jJW3w-@vO$Fj&L_onyjhgv$DHfRX>DvV2GV#O=D|6W* z>LiNy(6X79i)q znjozi8*?oJCQf`9T#Ts)_Z+n^<^D^G?V8#Y?T1Mo<*=ST4AD!BqM4{M);Q` z2*;U)@oP>(%Q3zxiY(oM3u*Ohdn|6VR{y`w&kb)p`LX2R*a9K2OY-+_{oT7i5ofJa zTlU%>{MtM*VcYgEyMO%Y#03M}Hg9Qoc16!!4}2YO7$z)LeZ79Hi^fvl>v*$_EvkMU zZ|DvdZ>&Td+r+R=jH?2aW!oNJY1nP9tn)9h#1F;c0E``OV^+c%>|_@stWO8@2vD@O zRntMGd!%s{diMUu&iSwN``d4J?bmwBAF3KEU4@MINT{mu>Y(uVw^n}`BCLpq;9u6R j`siolwWs~&$^D_1y?Ua$klgbtduRX0gG=ocf%N|elU literal 8808 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 XY5Dbpi>*GMG$Y{8`$k(#0{H(2-(sd) diff --git a/UnrealHyperTwist/Content/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining.umap b/UnrealHyperTwist/Content/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining.umap index 028fa9f1746211bb7b91d7125ac9bf1fc34e0b53..d7e3b4f75c2d8f94f88f634f793c1561330c34a5 100644 GIT binary patch literal 14031 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 KU2bLwr2ikti^vrK literal 8808 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 XY5Dbpi>*GMG$Y{8`$k(#0{H(2-(sd) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistHigherDimensionalTrainingShellActor.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistHigherDimensionalTrainingShellActor.cpp new file mode 100644 index 0000000..a311ab2 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistHigherDimensionalTrainingShellActor.cpp @@ -0,0 +1,221 @@ +#include "HyperTwistTraining/HyperTwistHigherDimensionalTrainingShellActor.h" + +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/SceneComponent.h" +#include "Components/TextRenderComponent.h" +#include "Engine/StaticMesh.h" +#include "UObject/ConstructorHelpers.h" + +namespace HyperTwistHigherDimensionalTrainingShellActorInternal +{ + const TCHAR* Magic120CellFamilyKey = TEXT("magic120cell"); +} + +AHyperTwistHigherDimensionalTrainingShellActor::AHyperTwistHigherDimensionalTrainingShellActor() +{ + PrimaryActorTick.bCanEverTick = false; + + SceneRoot = CreateDefaultSubobject(TEXT("SceneRoot")); + SetRootComponent(SceneRoot); + + PreviewInstances = CreateDefaultSubobject(TEXT("PreviewInstances")); + PreviewInstances->SetupAttachment(SceneRoot); + PreviewInstances->SetMobility(EComponentMobility::Static); + PreviewInstances->SetCollisionEnabled(ECollisionEnabled::NoCollision); + PreviewInstances->SetCastShadow(false); + + LabelComponent = CreateDefaultSubobject(TEXT("LabelComponent")); + LabelComponent->SetupAttachment(SceneRoot); + LabelComponent->SetHorizontalAlignment(EHorizTextAligment::EHTA_Center); + LabelComponent->SetVerticalAlignment(EVerticalTextAligment::EVRTA_TextCenter); + LabelComponent->SetWorldSize(56.0f); + LabelComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 220.0f)); + + static ConstructorHelpers::FObjectFinder CubeMeshFinder( + TEXT("/Engine/BasicShapes/Cube.Cube")); + if (CubeMeshFinder.Succeeded()) + { + CubePreviewMesh = CubeMeshFinder.Object; + } + + static ConstructorHelpers::FObjectFinder SphereMeshFinder( + TEXT("/Engine/BasicShapes/Sphere.Sphere")); + if (SphereMeshFinder.Succeeded()) + { + SpherePreviewMesh = SphereMeshFinder.Object; + } +} + +void AHyperTwistHigherDimensionalTrainingShellActor::OnConstruction(const FTransform& Transform) +{ + Super::OnConstruction(Transform); + + RefreshPreview(); + RefreshLabel(); + RefreshMetadataTags(); +} + +void AHyperTwistHigherDimensionalTrainingShellActor::RefreshPreview() +{ + if (PreviewInstances == nullptr) + { + return; + } + + PreviewInstances->ClearInstances(); + + UStaticMesh* PreviewMesh = UsesMagic120CellPreview() ? SpherePreviewMesh : CubePreviewMesh; + if (PreviewMesh == nullptr) + { + PreviewMesh = CubePreviewMesh != nullptr ? CubePreviewMesh : SpherePreviewMesh; + } + + if (PreviewMesh == nullptr) + { + return; + } + + PreviewInstances->SetStaticMesh(PreviewMesh); + if (UsesMagic120CellPreview()) + { + BuildMagic120CellPreview(); + } + else + { + BuildMagicCube5DPreview(); + } +} + +void AHyperTwistHigherDimensionalTrainingShellActor::RefreshLabel() +{ + if (LabelComponent == nullptr) + { + return; + } + + LabelComponent->SetText(BuildLabelText()); + LabelComponent->SetTextRenderColor( + UsesMagic120CellPreview() ? FColor(96, 208, 255) : FColor(255, 164, 96)); +} + +void AHyperTwistHigherDimensionalTrainingShellActor::RefreshMetadataTags() +{ + TArray NewTags; + + const auto AddTag = [&NewTags](const FString& Tag) + { + if (!Tag.IsEmpty()) + { + NewTags.AddUnique(FName(*Tag)); + } + }; + + AddTag(TEXT("HyperTwistHigherDimensionalTrainingShell")); + AddTag(FString::Printf(TEXT("training-shell-id:%s"), *TrainingShellId)); + AddTag(FString::Printf(TEXT("activation-profile:%s"), *ActivationProfileId)); + AddTag(FString::Printf(TEXT("host-surface:%s"), *HostSurfaceId)); + AddTag(FString::Printf(TEXT("launch-surface:%s"), *LaunchSurfaceId)); + AddTag(FString::Printf(TEXT("view-context-surface:%s"), *ViewContextSurfaceId)); + AddTag(FString::Printf(TEXT("session-surface:%s"), *SessionSurfaceId)); + AddTag(FString::Printf(TEXT("interactive-scene-surface:%s"), *InteractiveSceneSurfaceId)); + AddTag(FString::Printf(TEXT("scene-context:%s"), *SceneContextId)); + AddTag(FString::Printf(TEXT("puzzle:%s"), *PuzzleId)); + AddTag(FString::Printf(TEXT("runtime-mode:%s"), *RuntimeModeId)); + AddTag(FString::Printf(TEXT("projection:%s"), *ProjectionProfileId)); + AddTag(FString::Printf(TEXT("persistence:%s"), *PrimaryPersistenceBoundaryId)); + AddTag(FString::Printf(TEXT("manifest:%s"), *AuthoringManifestRelativePath)); + AddTag(FString::Printf(TEXT("family:%s"), *FamilyKey)); + AddTag(bDedicatedFamilyOwnership + ? TEXT("ownership:dedicated-family") + : TEXT("ownership:shared-or-unspecified")); + + for (const FString& TrainingShellTag : TrainingShellTags) + { + AddTag(TrainingShellTag); + } + + Tags = MoveTemp(NewTags); +} + +void AHyperTwistHigherDimensionalTrainingShellActor::BuildMagic120CellPreview() +{ + AddPreviewInstance(FVector(0.0f, 0.0f, PreviewHeightOffset), 1.1f); + + for (int32 Index = 0; Index < 12; ++Index) + { + const float AngleRadians = + (static_cast(Index) / 12.0f) * 2.0f * PI; + const float AlternatingHeight = + PreviewHeightOffset + ((Index % 2 == 0) ? 45.0f : -45.0f); + const FVector Position( + FMath::Cos(AngleRadians) * PreviewRadius, + FMath::Sin(AngleRadians) * PreviewRadius, + AlternatingHeight); + AddPreviewInstance(Position, 0.48f); + } + + AddPreviewInstance(FVector(0.0f, 0.0f, PreviewHeightOffset + 190.0f), 0.42f); + AddPreviewInstance(FVector(0.0f, 0.0f, PreviewHeightOffset - 190.0f), 0.42f); +} + +void AHyperTwistHigherDimensionalTrainingShellActor::BuildMagicCube5DPreview() +{ + AddPreviewInstance(FVector(0.0f, 0.0f, PreviewHeightOffset), 1.0f); + + const TArray AxisOffsets = + { + FVector(PreviewRadius, 0.0f, PreviewHeightOffset), + FVector(-PreviewRadius, 0.0f, PreviewHeightOffset), + FVector(0.0f, PreviewRadius, PreviewHeightOffset), + FVector(0.0f, -PreviewRadius, PreviewHeightOffset), + FVector(0.0f, 0.0f, PreviewHeightOffset + PreviewRadius), + FVector(0.0f, 0.0f, PreviewHeightOffset - PreviewRadius), + FVector(PreviewRadius * 0.78f, PreviewRadius * 0.78f, PreviewHeightOffset), + FVector(-PreviewRadius * 0.78f, -PreviewRadius * 0.78f, PreviewHeightOffset), + FVector(PreviewRadius * 0.78f, -PreviewRadius * 0.78f, PreviewHeightOffset), + FVector(-PreviewRadius * 0.78f, PreviewRadius * 0.78f, PreviewHeightOffset) + }; + + for (const FVector& Offset : AxisOffsets) + { + AddPreviewInstance(Offset, 0.42f); + } +} + +void AHyperTwistHigherDimensionalTrainingShellActor::AddPreviewInstance( + const FVector& RelativeLocation, + const float UniformScale) +{ + if (PreviewInstances == nullptr) + { + return; + } + + FTransform InstanceTransform; + InstanceTransform.SetLocation(RelativeLocation); + InstanceTransform.SetScale3D(FVector(UniformScale)); + PreviewInstances->AddInstance(InstanceTransform); +} + +FText AHyperTwistHigherDimensionalTrainingShellActor::BuildLabelText() const +{ + const FString EffectiveTitle = !Title.IsEmpty() + ? Title + : (UsesMagic120CellPreview() ? TEXT("Magic120Cell Training Shell") : TEXT("MagicCube5D Training Shell")); + + const FString EffectiveSceneContextId = !SceneContextId.IsEmpty() + ? SceneContextId + : TEXT("scene-context/pending"); + + return FText::FromString(FString::Printf( + TEXT("%s\n%s"), + *EffectiveTitle, + *EffectiveSceneContextId)); +} + +bool AHyperTwistHigherDimensionalTrainingShellActor::UsesMagic120CellPreview() const +{ + return FamilyKey.Equals( + HyperTwistHigherDimensionalTrainingShellActorInternal::Magic120CellFamilyKey, + ESearchCase::IgnoreCase); +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistHigherDimensionalTrainingShellActor.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistHigherDimensionalTrainingShellActor.h new file mode 100644 index 0000000..41d0e43 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistHigherDimensionalTrainingShellActor.h @@ -0,0 +1,106 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "HyperTwistHigherDimensionalTrainingShellActor.generated.h" + +class UInstancedStaticMeshComponent; +class USceneComponent; +class UStaticMesh; +class UTextRenderComponent; + +UCLASS(BlueprintType) +class UNREALHYPERTWIST_API AHyperTwistHigherDimensionalTrainingShellActor : public AActor +{ + GENERATED_BODY() + +public: + AHyperTwistHigherDimensionalTrainingShellActor(); + + virtual void OnConstruction(const FTransform& Transform) override; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Shell") + TObjectPtr SceneRoot = nullptr; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Shell") + TObjectPtr PreviewInstances = nullptr; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Shell") + TObjectPtr LabelComponent = nullptr; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString FamilyKey = TEXT("magic120cell"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString TrainingShellId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString Title; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell", meta = (MultiLine = "true")) + FString Summary; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString MapAssetPath; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString ActivationProfileId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString HostSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString LaunchSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString ViewContextSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString SessionSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString InteractiveSceneSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString SceneContextId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString PuzzleId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString RuntimeModeId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString ProjectionProfileId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString PrimaryPersistenceBoundaryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + FString AuthoringManifestRelativePath; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + bool bDedicatedFamilyOwnership = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + TArray TrainingShellTags; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + float PreviewRadius = 260.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Shell") + float PreviewHeightOffset = 90.0f; + +private: + TObjectPtr CubePreviewMesh = nullptr; + TObjectPtr SpherePreviewMesh = nullptr; + + void RefreshPreview(); + void RefreshLabel(); + void RefreshMetadataTags(); + void BuildMagic120CellPreview(); + void BuildMagicCube5DPreview(); + void AddPreviewInstance(const FVector& RelativeLocation, float UniformScale); + FText BuildLabelText() const; + bool UsesMagic120CellPreview() const; +}; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistHigherDimensionalPhase6CActivationContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistHigherDimensionalPhase6CActivationContractTest.cpp index b4ac842..65d966a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistHigherDimensionalPhase6CActivationContractTest.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistHigherDimensionalPhase6CActivationContractTest.cpp @@ -1,10 +1,21 @@ // Copyright HyperTwist, Inc. All Rights Reserved. #include "Engine/GameInstance.h" +#include "Engine/Level.h" +#include "Engine/World.h" +#include "GameFramework/PlayerStart.h" +#include "GameFramework/WorldSettings.h" +#include "Misc/FileHelper.h" #include "Misc/AutomationTest.h" #include "Misc/PackageName.h" +#include "Misc/Paths.h" +#include "Misc/SecureHash.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" #include "HyperTwistSimulation/HyperTwistSimulationLibrary.h" +#include "HyperTwistTraining/HyperTwistCoachDashboardGameMode.h" +#include "HyperTwistTraining/HyperTwistHigherDimensionalTrainingShellActor.h" #include "HyperTwistTraining/HyperTwistTrainingLibrary.h" #include "HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h" #include "HyperTwistTraining/HyperTwistTrainingSubsystem.h" @@ -13,6 +24,35 @@ namespace HyperTwistHigherDimensionalPhase6CTestInternal { + struct FPhase6CDedicatedMapManifestEntry + { + FString MapKind; + FString FamilyKey; + FString MapAssetPath; + FString MapFileRelativePath; + FString MapHashMd5; + FString TrainingShellId; + FString ActivationProfileId; + FString HostSurfaceId; + FString LaunchSurfaceId; + FString ViewContextSurfaceId; + FString SessionSurfaceId; + FString InteractiveSceneSurfaceId; + FString SceneContextId; + FString PuzzleId; + FString RuntimeModeId; + FString ProjectionProfileId; + FString PrimaryPersistenceBoundaryId; + }; + + struct FPhase6CDedicatedMapManifest + { + FString ManifestId; + FString ManifestVersion; + FString ClassicReferenceMapHashMd5; + TArray Entries; + }; + FHyperTwistTrainingDeck MakeDeck(const FString& DeckId, const FString& PuzzleId) { FHyperTwistTrainingDeck Deck; @@ -86,6 +126,193 @@ namespace HyperTwistHigherDimensionalPhase6CTestInternal TrainingSubsystem->RefreshCompanionSpeechServiceHealthForAutomation(); return TrainingSubsystem; } + + FString GetManifestPath() + { + return FPaths::ConvertRelativePathToFull(FPaths::Combine( + FPaths::ProjectDir(), + TEXT("../docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json"))); + } + + FString GetMapFilePathFromRelativePath(const FString& RelativePath) + { + return FPaths::ConvertRelativePathToFull(FPaths::Combine( + FPaths::ProjectDir(), + TEXT("../"), + RelativePath)); + } + + FString ComputeFileMd5(const FString& FilePath) + { + if (!FPaths::FileExists(FilePath)) + { + return FString(); + } + + TArray FileBytes; + if (!FFileHelper::LoadFileToArray(FileBytes, *FilePath)) + { + return FString(); + } + + FMD5 Md5; + if (FileBytes.Num() > 0) + { + Md5.Update(FileBytes.GetData(), FileBytes.Num()); + } + + uint8 Digest[16]; + Md5.Final(Digest); + return BytesToHex(Digest, UE_ARRAY_COUNT(Digest)).ToLower(); + } + + bool ParseManifestEntry( + const TSharedPtr& JsonObject, + FPhase6CDedicatedMapManifestEntry& OutEntry + ) + { + return JsonObject.IsValid() + && JsonObject->TryGetStringField(TEXT("mapKind"), OutEntry.MapKind) + && JsonObject->TryGetStringField(TEXT("familyKey"), OutEntry.FamilyKey) + && JsonObject->TryGetStringField(TEXT("mapAssetPath"), OutEntry.MapAssetPath) + && JsonObject->TryGetStringField(TEXT("mapFileRelativePath"), OutEntry.MapFileRelativePath) + && JsonObject->TryGetStringField(TEXT("mapHashMd5"), OutEntry.MapHashMd5) + && JsonObject->TryGetStringField(TEXT("trainingShellId"), OutEntry.TrainingShellId) + && JsonObject->TryGetStringField(TEXT("activationProfileId"), OutEntry.ActivationProfileId) + && JsonObject->TryGetStringField(TEXT("hostSurfaceId"), OutEntry.HostSurfaceId) + && JsonObject->TryGetStringField(TEXT("launchSurfaceId"), OutEntry.LaunchSurfaceId) + && JsonObject->TryGetStringField(TEXT("viewContextSurfaceId"), OutEntry.ViewContextSurfaceId) + && JsonObject->TryGetStringField(TEXT("sessionSurfaceId"), OutEntry.SessionSurfaceId) + && JsonObject->TryGetStringField(TEXT("interactiveSceneSurfaceId"), OutEntry.InteractiveSceneSurfaceId) + && JsonObject->TryGetStringField(TEXT("sceneContextId"), OutEntry.SceneContextId) + && JsonObject->TryGetStringField(TEXT("puzzleId"), OutEntry.PuzzleId) + && JsonObject->TryGetStringField(TEXT("runtimeModeId"), OutEntry.RuntimeModeId) + && JsonObject->TryGetStringField(TEXT("projectionProfileId"), OutEntry.ProjectionProfileId) + && JsonObject->TryGetStringField(TEXT("primaryPersistenceBoundaryId"), OutEntry.PrimaryPersistenceBoundaryId); + } + + bool TryLoadDedicatedMapManifest(FPhase6CDedicatedMapManifest& OutManifest) + { + const FString ManifestPath = GetManifestPath(); + FString Json; + if (!FFileHelper::LoadFileToString(Json, *ManifestPath)) + { + return false; + } + + TSharedPtr RootObject; + if (!FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(Json), RootObject) + || !RootObject.IsValid()) + { + return false; + } + + const TArray>* EntryValues = nullptr; + if (!RootObject->TryGetStringField(TEXT("manifestId"), OutManifest.ManifestId) + || !RootObject->TryGetStringField(TEXT("manifestVersion"), OutManifest.ManifestVersion) + || !RootObject->TryGetStringField( + TEXT("classicReferenceMapHashMd5"), + OutManifest.ClassicReferenceMapHashMd5) + || !RootObject->TryGetArrayField(TEXT("entries"), EntryValues) + || EntryValues == nullptr) + { + return false; + } + + OutManifest.Entries.Reset(); + for (const TSharedPtr& EntryValue : *EntryValues) + { + const TSharedPtr EntryObject = EntryValue.IsValid() + ? EntryValue->AsObject() + : nullptr; + FPhase6CDedicatedMapManifestEntry Entry; + if (!ParseManifestEntry(EntryObject, Entry)) + { + return false; + } + + OutManifest.Entries.Add(Entry); + } + + return true; + } + + const FPhase6CDedicatedMapManifestEntry* FindManifestEntryByFamilyKey( + const FPhase6CDedicatedMapManifest& Manifest, + const FString& FamilyKey + ) + { + for (const FPhase6CDedicatedMapManifestEntry& Entry : Manifest.Entries) + { + if (Entry.FamilyKey.Equals(FamilyKey, ESearchCase::IgnoreCase)) + { + return &Entry; + } + } + + return nullptr; + } + + FString BuildMapObjectPath(const FString& MapAssetPath) + { + if (MapAssetPath.IsEmpty()) + { + return FString(); + } + + const FString MapName = FPackageName::GetShortName(MapAssetPath); + return FString::Printf(TEXT("%s.%s"), *MapAssetPath, *MapName); + } + + UWorld* LoadDedicatedMapWorld(const FString& MapAssetPath) + { + const FString MapObjectPath = BuildMapObjectPath(MapAssetPath); + if (MapObjectPath.IsEmpty()) + { + return nullptr; + } + + return LoadObject(nullptr, *MapObjectPath); + } + + template + int32 CountActorsOfClass(UWorld* World) + { + if (World == nullptr || World->PersistentLevel == nullptr) + { + return 0; + } + + int32 Count = 0; + for (AActor* Actor : World->PersistentLevel->Actors) + { + if (Cast(Actor) != nullptr) + { + ++Count; + } + } + + return Count; + } + + template + ActorType* FindActorOfClass(UWorld* World) + { + if (World == nullptr || World->PersistentLevel == nullptr) + { + return nullptr; + } + + for (AActor* Actor : World->PersistentLevel->Actors) + { + if (ActorType* TypedActor = Cast(Actor)) + { + return TypedActor; + } + } + + return nullptr; + } } IMPLEMENT_SIMPLE_AUTOMATION_TEST( @@ -188,6 +415,572 @@ bool FHyperTwistHigherDimensionalPhase6CHostCatalogTest::RunTest(const FString& return true; } +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistHigherDimensionalPhase6CDedicatedMapAuthoringManifestTest, + "HyperTwist.FirstParty.HigherDimensional.Phase6C.DedicatedMapAuthoringManifest", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistHigherDimensionalPhase6CDedicatedMapAuthoringManifestTest::RunTest( + const FString& Parameters) +{ + using namespace HyperTwistHigherDimensionalPhase6CTestInternal; + + FPhase6CDedicatedMapManifest Manifest; + TestTrue( + TEXT("The Phase 6C dedicated-family map authoring manifest must load."), + TryLoadDedicatedMapManifest(Manifest) + ); + if (Manifest.Entries.Num() == 0) + { + return false; + } + + TestEqual( + TEXT("The dedicated-family map authoring manifest id must stay stable."), + Manifest.ManifestId, + TEXT("phase6c/dedicated-family-training-map-authoring") + ); + TestEqual( + TEXT("The dedicated-family map authoring manifest must expose both authored family maps."), + Manifest.Entries.Num(), + 2 + ); + + const FString ClassicMapFilePath = GetMapFilePathFromRelativePath( + TEXT("UnrealHyperTwist/Content/HyperTwistTraining/Maps/L_HyperTwist_ClassicTraining.umap")); + const FString ClassicMapHashMd5 = ComputeFileMd5(ClassicMapFilePath); + TestTrue( + TEXT("The classic reference training map file must exist for dedicated-map differentiation checks."), + !ClassicMapHashMd5.IsEmpty() + ); + TestEqual( + TEXT("The dedicated-family map manifest must track the current classic reference hash."), + Manifest.ClassicReferenceMapHashMd5, + ClassicMapHashMd5 + ); + + const FPhase6CDedicatedMapManifestEntry* Magic120CellEntry = + FindManifestEntryByFamilyKey(Manifest, TEXT("magic120cell")); + const FPhase6CDedicatedMapManifestEntry* MagicCube5DEntry = + FindManifestEntryByFamilyKey(Manifest, TEXT("magiccube5d")); + TestNotNull( + TEXT("The dedicated-family map manifest must contain the Magic120Cell authored entry."), + Magic120CellEntry + ); + TestNotNull( + TEXT("The dedicated-family map manifest must contain the MagicCube5D authored entry."), + MagicCube5DEntry + ); + if (Magic120CellEntry == nullptr || MagicCube5DEntry == nullptr) + { + return false; + } + + const FString Magic120CellMapFilePath = GetMapFilePathFromRelativePath( + Magic120CellEntry->MapFileRelativePath); + const FString MagicCube5DMapFilePath = GetMapFilePathFromRelativePath( + MagicCube5DEntry->MapFileRelativePath); + const FString Magic120CellMapHashMd5 = ComputeFileMd5(Magic120CellMapFilePath); + const FString MagicCube5DMapHashMd5 = ComputeFileMd5(MagicCube5DMapFilePath); + TestTrue( + TEXT("The Magic120Cell dedicated-family map file must exist."), + !Magic120CellMapHashMd5.IsEmpty() + ); + TestTrue( + TEXT("The MagicCube5D dedicated-family map file must exist."), + !MagicCube5DMapHashMd5.IsEmpty() + ); + TestEqual( + TEXT("The Magic120Cell dedicated-family map hash must match the manifest."), + Magic120CellEntry->MapHashMd5, + Magic120CellMapHashMd5 + ); + TestEqual( + TEXT("The MagicCube5D dedicated-family map hash must match the manifest."), + MagicCube5DEntry->MapHashMd5, + MagicCube5DMapHashMd5 + ); + TestNotEqual( + TEXT("The Magic120Cell dedicated-family map must no longer be byte-identical to the classic training map."), + Magic120CellMapHashMd5, + ClassicMapHashMd5 + ); + TestNotEqual( + TEXT("The MagicCube5D dedicated-family map must no longer be byte-identical to the classic training map."), + MagicCube5DMapHashMd5, + ClassicMapHashMd5 + ); + TestNotEqual( + TEXT("The two dedicated-family maps must no longer be byte-identical to each other."), + Magic120CellMapHashMd5, + MagicCube5DMapHashMd5 + ); + + FHyperTwistTrainingHigherDimensionalRuntimeHostSurface Magic120CellHostSurface; + TestTrue( + TEXT("The Magic120Cell host surface must resolve for map-manifest reconciliation."), + UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalRuntimeHostSurfaceByActivationProfileId( + Magic120CellEntry->ActivationProfileId, + Magic120CellHostSurface + ) + ); + TestEqual( + TEXT("The Magic120Cell manifest must stay aligned with the host-surface map asset path."), + Magic120CellEntry->MapAssetPath, + Magic120CellHostSurface.EffectiveHostMapPath + ); + TestEqual( + TEXT("The Magic120Cell manifest must stay aligned with the host-surface id."), + Magic120CellEntry->HostSurfaceId, + Magic120CellHostSurface.HostSurfaceId + ); + + FHyperTwistTrainingHigherDimensionalRuntimeLaunchSurface Magic120CellLaunchSurface; + TestTrue( + TEXT("The Magic120Cell launch surface must resolve for map-manifest reconciliation."), + UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalRuntimeLaunchSurfaceByActivationProfileId( + Magic120CellEntry->ActivationProfileId, + Magic120CellLaunchSurface + ) + ); + TestEqual( + TEXT("The Magic120Cell manifest must stay aligned with the launch-surface id."), + Magic120CellEntry->LaunchSurfaceId, + Magic120CellLaunchSurface.LaunchSurfaceId + ); + TestEqual( + TEXT("The Magic120Cell manifest must stay aligned with the launch-surface persistence boundary."), + Magic120CellEntry->PrimaryPersistenceBoundaryId, + Magic120CellLaunchSurface.PrimaryPersistenceBoundaryId + ); + + FHyperTwistTrainingHigherDimensionalRuntimeViewContextSurface Magic120CellViewContextSurface; + TestTrue( + TEXT("The Magic120Cell view-context surface must resolve for map-manifest reconciliation."), + UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalRuntimeViewContextSurfaceByActivationProfileId( + Magic120CellEntry->ActivationProfileId, + Magic120CellViewContextSurface + ) + ); + TestEqual( + TEXT("The Magic120Cell manifest must stay aligned with the view-context surface id."), + Magic120CellEntry->ViewContextSurfaceId, + Magic120CellViewContextSurface.ViewContextSurfaceId + ); + + FHyperTwistTrainingHigherDimensionalRuntimeSessionSurface Magic120CellSessionSurface; + TestTrue( + TEXT("The Magic120Cell session surface must resolve for map-manifest reconciliation."), + UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalRuntimeSessionSurfaceByActivationProfileId( + Magic120CellEntry->ActivationProfileId, + Magic120CellSessionSurface + ) + ); + TestEqual( + TEXT("The Magic120Cell manifest must stay aligned with the session surface id."), + Magic120CellEntry->SessionSurfaceId, + Magic120CellSessionSurface.SessionSurfaceId + ); + + FHyperTwistTrainingHigherDimensionalInteractiveSceneSurface Magic120CellSceneSurface; + TestTrue( + TEXT("The Magic120Cell interactive scene surface must resolve for map-manifest reconciliation."), + UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalInteractiveSceneSurfaceByActivationProfileId( + Magic120CellEntry->ActivationProfileId, + Magic120CellSceneSurface + ) + ); + TestEqual( + TEXT("The Magic120Cell manifest must stay aligned with the interactive-scene surface id."), + Magic120CellEntry->InteractiveSceneSurfaceId, + Magic120CellSceneSurface.SceneSurfaceId + ); + TestEqual( + TEXT("The Magic120Cell manifest must stay aligned with the scene-context id."), + Magic120CellEntry->SceneContextId, + Magic120CellSceneSurface.SceneContext.SceneContextId + ); + TestEqual( + TEXT("The Magic120Cell manifest must stay aligned with the interactive-scene projection profile."), + Magic120CellEntry->ProjectionProfileId, + Magic120CellSceneSurface.SceneContext.ProjectionSettings.ProjectionProfile + ); + + FHyperTwistTrainingHigherDimensionalRuntimeHostSurface MagicCube5DHostSurface; + TestTrue( + TEXT("The MagicCube5D host surface must resolve for map-manifest reconciliation."), + UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalRuntimeHostSurfaceByActivationProfileId( + MagicCube5DEntry->ActivationProfileId, + MagicCube5DHostSurface + ) + ); + TestEqual( + TEXT("The MagicCube5D manifest must stay aligned with the host-surface map asset path."), + MagicCube5DEntry->MapAssetPath, + MagicCube5DHostSurface.EffectiveHostMapPath + ); + + FHyperTwistTrainingHigherDimensionalRuntimeLaunchSurface MagicCube5DLaunchSurface; + TestTrue( + TEXT("The MagicCube5D launch surface must resolve for map-manifest reconciliation."), + UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalRuntimeLaunchSurfaceByActivationProfileId( + MagicCube5DEntry->ActivationProfileId, + MagicCube5DLaunchSurface + ) + ); + TestEqual( + TEXT("The MagicCube5D manifest must stay aligned with the launch-surface id."), + MagicCube5DEntry->LaunchSurfaceId, + MagicCube5DLaunchSurface.LaunchSurfaceId + ); + TestEqual( + TEXT("The MagicCube5D manifest must stay aligned with the persistence boundary."), + MagicCube5DEntry->PrimaryPersistenceBoundaryId, + MagicCube5DLaunchSurface.PrimaryPersistenceBoundaryId + ); + + FHyperTwistTrainingHigherDimensionalInteractiveSceneSurface MagicCube5DSceneSurface; + TestTrue( + TEXT("The MagicCube5D interactive scene surface must resolve for map-manifest reconciliation."), + UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalInteractiveSceneSurfaceByActivationProfileId( + MagicCube5DEntry->ActivationProfileId, + MagicCube5DSceneSurface + ) + ); + TestEqual( + TEXT("The MagicCube5D manifest must stay aligned with the interactive-scene surface id."), + MagicCube5DEntry->InteractiveSceneSurfaceId, + MagicCube5DSceneSurface.SceneSurfaceId + ); + TestEqual( + TEXT("The MagicCube5D manifest must stay aligned with the scene-context id."), + MagicCube5DEntry->SceneContextId, + MagicCube5DSceneSurface.SceneContext.SceneContextId + ); + TestEqual( + TEXT("The MagicCube5D manifest must stay aligned with the interactive-scene projection profile."), + MagicCube5DEntry->ProjectionProfileId, + MagicCube5DSceneSurface.SceneContext.ProjectionSettings.ProjectionProfile + ); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistHigherDimensionalPhase6CDedicatedMapOwnedShellTest, + "HyperTwist.FirstParty.HigherDimensional.Phase6C.DedicatedMapOwnedShell", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistHigherDimensionalPhase6CDedicatedMapOwnedShellTest::RunTest( + const FString& Parameters) +{ + using namespace HyperTwistHigherDimensionalPhase6CTestInternal; + + FPhase6CDedicatedMapManifest Manifest; + TestTrue( + TEXT("The Phase 6C dedicated-family map manifest must load for owned-shell checks."), + TryLoadDedicatedMapManifest(Manifest) + ); + if (Manifest.Entries.Num() == 0) + { + return false; + } + + const FPhase6CDedicatedMapManifestEntry* Magic120CellEntry = + FindManifestEntryByFamilyKey(Manifest, TEXT("magic120cell")); + const FPhase6CDedicatedMapManifestEntry* MagicCube5DEntry = + FindManifestEntryByFamilyKey(Manifest, TEXT("magiccube5d")); + TestNotNull( + TEXT("The owned-shell checks must find the Magic120Cell manifest entry."), + Magic120CellEntry + ); + TestNotNull( + TEXT("The owned-shell checks must find the MagicCube5D manifest entry."), + MagicCube5DEntry + ); + if (Magic120CellEntry == nullptr || MagicCube5DEntry == nullptr) + { + return false; + } + + const auto VerifyDedicatedMapShell = + [this](const FPhase6CDedicatedMapManifestEntry& Entry) -> bool + { + UWorld* MapWorld = + HyperTwistHigherDimensionalPhase6CTestInternal::LoadDedicatedMapWorld( + Entry.MapAssetPath); + TestNotNull( + *FString::Printf( + TEXT("The dedicated-family map world asset must load for %s."), + *Entry.FamilyKey), + MapWorld + ); + if (MapWorld == nullptr) + { + return false; + } + + AWorldSettings* WorldSettings = MapWorld->GetWorldSettings(); + TestNotNull( + *FString::Printf( + TEXT("The dedicated-family world settings must load for %s."), + *Entry.FamilyKey), + WorldSettings + ); + if (WorldSettings == nullptr) + { + return false; + } + + const int32 TrainingShellActorCount = + HyperTwistHigherDimensionalPhase6CTestInternal::CountActorsOfClass< + AHyperTwistHigherDimensionalTrainingShellActor>(MapWorld); + TestTrue( + *FString::Printf( + TEXT("The dedicated-family map must contain at most one optional training shell actor for %s."), + *Entry.FamilyKey), + TrainingShellActorCount <= 1 + ); + TestEqual( + *FString::Printf( + TEXT("The dedicated-family map must contain exactly one PlayerStart anchor for %s."), + *Entry.FamilyKey), + HyperTwistHigherDimensionalPhase6CTestInternal::CountActorsOfClass< + APlayerStart>(MapWorld), + 1 + ); + + AHyperTwistHigherDimensionalTrainingShellActor* TrainingShellActor = + HyperTwistHigherDimensionalPhase6CTestInternal::FindActorOfClass< + AHyperTwistHigherDimensionalTrainingShellActor>(MapWorld); + APlayerStart* PlayerStart = + HyperTwistHigherDimensionalPhase6CTestInternal::FindActorOfClass< + APlayerStart>(MapWorld); + TestNotNull( + *FString::Printf( + TEXT("The dedicated-family PlayerStart anchor must load for %s."), + *Entry.FamilyKey), + PlayerStart + ); + if (PlayerStart == nullptr) + { + return false; + } + + const FString ExpectedTitle = Entry.FamilyKey.Equals( + TEXT("magic120cell"), + ESearchCase::IgnoreCase) + ? TEXT("Magic120Cell dedicated training shell") + : TEXT("MagicCube5D dedicated training shell"); + const FString ExpectedManifestPath = + TEXT("docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json"); + const FString ExpectedHostTag = + FString::Printf(TEXT("host-surface:%s"), *Entry.HostSurfaceId); + const FString ExpectedLaunchTag = + FString::Printf(TEXT("launch-surface:%s"), *Entry.LaunchSurfaceId); + const FString ExpectedViewContextTag = + FString::Printf(TEXT("view-context-surface:%s"), *Entry.ViewContextSurfaceId); + const FString ExpectedSessionTag = + FString::Printf(TEXT("session-surface:%s"), *Entry.SessionSurfaceId); + const FString ExpectedInteractiveSceneTag = + FString::Printf( + TEXT("interactive-scene-surface:%s"), + *Entry.InteractiveSceneSurfaceId); + const FString ExpectedSceneContextTag = + FString::Printf(TEXT("scene-context:%s"), *Entry.SceneContextId); + const FString ExpectedProjectionTag = + FString::Printf(TEXT("projection:%s"), *Entry.ProjectionProfileId); + const FString ExpectedPersistenceTag = + FString::Printf(TEXT("persistence:%s"), *Entry.PrimaryPersistenceBoundaryId); + const FString ExpectedTrainingShellTag = + FString::Printf(TEXT("training-shell-id:%s"), *Entry.TrainingShellId); + const FString ExpectedActivationTag = + FString::Printf(TEXT("activation-profile:%s"), *Entry.ActivationProfileId); + const FString ExpectedPuzzleTag = + FString::Printf(TEXT("puzzle:%s"), *Entry.PuzzleId); + const FString ExpectedRuntimeModeTag = + FString::Printf(TEXT("runtime-mode:%s"), *Entry.RuntimeModeId); + const auto HasActorTag = [](const AActor* Actor, const FString& Tag) -> bool + { + return Actor != nullptr && Actor->ActorHasTag(FName(*Tag)); + }; + + TestEqual( + *FString::Printf( + TEXT("The dedicated-family world settings must preserve the coach dashboard game mode for %s."), + *Entry.FamilyKey), + WorldSettings->DefaultGameMode != nullptr + ? WorldSettings->DefaultGameMode->GetPathName() + : FString(), + AHyperTwistCoachDashboardGameMode::StaticClass()->GetPathName() + ); + if (TrainingShellActor != nullptr) + { + TestEqual( + *FString::Printf( + TEXT("The optional dedicated-family training shell must preserve the family key for %s."), + *Entry.FamilyKey), + TrainingShellActor->FamilyKey, + Entry.FamilyKey + ); + TestEqual( + *FString::Printf( + TEXT("The optional dedicated-family training shell must preserve the title for %s."), + *Entry.FamilyKey), + TrainingShellActor->Title, + ExpectedTitle + ); + TestEqual( + *FString::Printf( + TEXT("The optional dedicated-family training shell must preserve the map asset path for %s."), + *Entry.FamilyKey), + TrainingShellActor->MapAssetPath, + Entry.MapAssetPath + ); + TestEqual( + *FString::Printf( + TEXT("The optional dedicated-family training shell must preserve the training-shell id for %s."), + *Entry.FamilyKey), + TrainingShellActor->TrainingShellId, + Entry.TrainingShellId + ); + TestEqual( + *FString::Printf( + TEXT("The optional dedicated-family training shell must preserve the activation profile id for %s."), + *Entry.FamilyKey), + TrainingShellActor->ActivationProfileId, + Entry.ActivationProfileId + ); + TestEqual( + *FString::Printf( + TEXT("The optional dedicated-family training shell must preserve the scene-context id for %s."), + *Entry.FamilyKey), + TrainingShellActor->SceneContextId, + Entry.SceneContextId + ); + TestEqual( + *FString::Printf( + TEXT("The optional dedicated-family training shell must preserve the projection profile id for %s."), + *Entry.FamilyKey), + TrainingShellActor->ProjectionProfileId, + Entry.ProjectionProfileId + ); + TestEqual( + *FString::Printf( + TEXT("The optional dedicated-family training shell must preserve the persistence boundary id for %s."), + *Entry.FamilyKey), + TrainingShellActor->PrimaryPersistenceBoundaryId, + Entry.PrimaryPersistenceBoundaryId + ); + TestEqual( + *FString::Printf( + TEXT("The optional dedicated-family training shell must preserve the authoring manifest path for %s."), + *Entry.FamilyKey), + TrainingShellActor->AuthoringManifestRelativePath, + ExpectedManifestPath + ); + TestTrue( + *FString::Printf( + TEXT("The optional dedicated-family training shell must preserve dedicated ownership for %s."), + *Entry.FamilyKey), + TrainingShellActor->bDedicatedFamilyOwnership + ); + TestTrue( + *FString::Printf( + TEXT("The optional dedicated-family training shell must carry the authored shell tag for %s."), + *Entry.FamilyKey), + HasActorTag( + TrainingShellActor, + TEXT("HyperTwistHigherDimensionalTrainingShell")) + ); + TestTrue( + *FString::Printf( + TEXT("The optional dedicated-family training shell must carry the authored training-shell id tag for %s."), + *Entry.FamilyKey), + HasActorTag(TrainingShellActor, ExpectedTrainingShellTag) + ); + TestTrue( + *FString::Printf( + TEXT("The optional dedicated-family training shell must carry the authored host-surface tag for %s."), + *Entry.FamilyKey), + HasActorTag(TrainingShellActor, ExpectedHostTag) + ); + TestTrue( + *FString::Printf( + TEXT("The optional dedicated-family training shell must carry the authored launch-surface tag for %s."), + *Entry.FamilyKey), + HasActorTag(TrainingShellActor, ExpectedLaunchTag) + ); + TestTrue( + *FString::Printf( + TEXT("The optional dedicated-family training shell must carry the authored scene-context tag for %s."), + *Entry.FamilyKey), + HasActorTag(TrainingShellActor, ExpectedSceneContextTag) + ); + TestTrue( + *FString::Printf( + TEXT("The optional dedicated-family training shell must carry the authored projection tag for %s."), + *Entry.FamilyKey), + HasActorTag(TrainingShellActor, ExpectedProjectionTag) + ); + TestTrue( + *FString::Printf( + TEXT("The optional dedicated-family training shell must carry the authored persistence tag for %s."), + *Entry.FamilyKey), + HasActorTag(TrainingShellActor, ExpectedPersistenceTag) + ); + TestTrue( + *FString::Printf( + TEXT("The optional dedicated-family training shell must carry the dedicated ownership tag for %s."), + *Entry.FamilyKey), + HasActorTag(TrainingShellActor, TEXT("ownership:dedicated-family")) + ); + } + TestTrue( + *FString::Printf( + TEXT("The dedicated-family PlayerStart anchor must carry the activation-profile tag for %s."), + *Entry.FamilyKey), + HasActorTag(PlayerStart, ExpectedActivationTag) + ); + TestTrue( + *FString::Printf( + TEXT("The dedicated-family PlayerStart anchor must carry the view-context tag for %s."), + *Entry.FamilyKey), + HasActorTag(PlayerStart, ExpectedViewContextTag) + ); + TestTrue( + *FString::Printf( + TEXT("The dedicated-family PlayerStart anchor must carry the session-surface tag for %s."), + *Entry.FamilyKey), + HasActorTag(PlayerStart, ExpectedSessionTag) + ); + TestTrue( + *FString::Printf( + TEXT("The dedicated-family PlayerStart anchor must carry the interactive-scene tag for %s."), + *Entry.FamilyKey), + HasActorTag(PlayerStart, ExpectedInteractiveSceneTag) + ); + TestTrue( + *FString::Printf( + TEXT("The dedicated-family PlayerStart anchor must carry the puzzle tag for %s."), + *Entry.FamilyKey), + HasActorTag(PlayerStart, ExpectedPuzzleTag) + ); + TestTrue( + *FString::Printf( + TEXT("The dedicated-family PlayerStart anchor must carry the runtime-mode tag for %s."), + *Entry.FamilyKey), + HasActorTag(PlayerStart, ExpectedRuntimeModeTag) + ); + + return true; + }; + + return VerifyDedicatedMapShell(*Magic120CellEntry) + && VerifyDedicatedMapShell(*MagicCube5DEntry); +} + IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHyperTwistHigherDimensionalPhase6CLaunchCatalogTest, "HyperTwist.FirstParty.HigherDimensional.Phase6C.LaunchCatalog", diff --git a/docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json b/docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json new file mode 100644 index 0000000..1a4b612 --- /dev/null +++ b/docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json @@ -0,0 +1,68 @@ +{ + "manifestId": "phase6c/dedicated-family-training-map-authoring", + "manifestVersion": "2026.06.18", + "authorTag": "HyperTwistHigherDimensionalTrainingShell", + "authoringScriptRelativePath": "scripts/hypertwist_author_higher_dimensional_training_maps.py", + "authoredThroughGameModeClassPath": "/Script/UnrealHyperTwist.HyperTwistCoachDashboardGameMode", + "classicReferenceMapHashMd5": "7772cc42fd9fd129cb6d99c0b918a24e", + "entries": [ + { + "mapKind": "magic120cell", + "familyKey": "magic120cell", + "mapAssetPath": "/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining", + "mapFileRelativePath": "UnrealHyperTwist/Content/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining.umap", + "mapHashMd5": "9d9b9301f14cdad8c263d2c57b5f2c7f", + "trainingShellId": "phase6c/magic120cell/dedicated-training-shell", + "activationProfileId": "magic120cell-cleanroom-runtime-activation", + "hostSurfaceId": "phase6c/magic120cell/runtime-host-surface", + "launchSurfaceId": "phase6c/magic120cell/dedicated-training-launch-surface", + "viewContextSurfaceId": "phase6c/magic120cell/dedicated-training-view-context-surface", + "sessionSurfaceId": "phase6c/magic120cell/dedicated-training-session-surface", + "interactiveSceneSurfaceId": "phase6c/magic120cell/interactive-scene-surface", + "sceneContextId": "phase6c/magic120cell/interactive-scene-context", + "puzzleId": "polychoron/magic120cell", + "runtimeModeId": "magic120cell-full-color-runtime-v1", + "projectionProfileId": "magic120cell-4d-projection-distance-v1", + "primaryPersistenceBoundaryId": "magic120cell-persistence-boundary", + "authorTag": "HyperTwistHigherDimensionalTrainingShell", + "authoringManifestRelativePath": "docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json", + "authoredViaGameModeClassPath": "/Script/UnrealHyperTwist.HyperTwistCoachDashboardGameMode", + "trainingShellTags": [ + "phase6c", + "family:magic120cell", + "host:dedicated-family-map", + "projection:magic120cell-4d-projection-distance-v1", + "persistence:magic120cell-persistence-boundary" + ] + }, + { + "mapKind": "magiccube5d", + "familyKey": "magiccube5d", + "mapAssetPath": "/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining", + "mapFileRelativePath": "UnrealHyperTwist/Content/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining.umap", + "mapHashMd5": "15d2f7acfdce43402ad2fd291fc5c781", + "trainingShellId": "phase6c/magiccube5d/dedicated-training-shell", + "activationProfileId": "magiccube5d-cleanroom-runtime-activation", + "hostSurfaceId": "phase6c/magiccube5d/runtime-host-surface", + "launchSurfaceId": "phase6c/magiccube5d/dedicated-training-launch-surface", + "viewContextSurfaceId": "phase6c/magiccube5d/dedicated-training-view-context-surface", + "sessionSurfaceId": "phase6c/magiccube5d/dedicated-training-session-surface", + "interactiveSceneSurfaceId": "phase6c/magiccube5d/interactive-scene-surface", + "sceneContextId": "phase6c/magiccube5d/interactive-scene-context", + "puzzleId": "hypercube/magiccube5d/order3", + "runtimeModeId": "magiccube5d-order3-runtime-v1", + "projectionProfileId": "magiccube5d-5d-projection-distance-v1", + "primaryPersistenceBoundaryId": "magiccube5d-persistence-boundary", + "authorTag": "HyperTwistHigherDimensionalTrainingShell", + "authoringManifestRelativePath": "docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json", + "authoredViaGameModeClassPath": "/Script/UnrealHyperTwist.HyperTwistCoachDashboardGameMode", + "trainingShellTags": [ + "phase6c", + "family:magiccube5d", + "host:dedicated-family-map", + "projection:magiccube5d-5d-projection-distance-v1", + "persistence:magiccube5d-persistence-boundary" + ] + } + ] +} diff --git a/docs/generated/higher_dimensional_training_maps/phase6c_dedicated_map_ownership_automation_report.json b/docs/generated/higher_dimensional_training_maps/phase6c_dedicated_map_ownership_automation_report.json new file mode 100644 index 0000000..f4320d6 --- /dev/null +++ b/docs/generated/higher_dimensional_training_maps/phase6c_dedicated_map_ownership_automation_report.json @@ -0,0 +1,224 @@ +{ + "devices": [ + { + "deviceName": "DESKTOP-KS3VGHU", + "instance": "8A15E1D540837281594FD7AB21A7E713", + "instanceName": "DESKTOP-KS3VGHU-33204", + "platform": "WindowsEditor", + "oSVersion": "Windows 11 (23H2) [10.0.22631.4037] ", + "model": "Default", + "gPU": "GenericGPUBrand", + "cPUModel": "12th Gen Intel(R) Core(TM) i7-12700H", + "rAMInGB": 16, + "renderMode": "SM6", + "rHI": "", + "appInstanceLog": "" + } + ], + "reportCreatedOn": "2026.06.18-10.22.23", + "succeeded": 13, + "succeededWithWarnings": 0, + "failed": 0, + "notRun": 0, + "inProcess": 0, + "totalDuration": 43.566970825195312, + "comparisonExported": false, + "comparisonExportDirectory": "", + "tests": [ + { + "testDisplayName": "ActivationCatalog", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.ActivationCatalog", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.016251299530267715, + "dateTime": "2026.06.18-10.21.36", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "DedicatedMapAuthoringManifest", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.DedicatedMapAuthoringManifest", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.13871729373931885, + "dateTime": "2026.06.18-10.21.36", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "DedicatedMapOwnedShell", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.DedicatedMapOwnedShell", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.018342901021242142, + "dateTime": "2026.06.18-10.21.36", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "HostCatalog", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.HostCatalog", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.016522001475095749, + "dateTime": "2026.06.18-10.21.36", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "InteractiveSceneCatalog", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.InteractiveSceneCatalog", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.15781599283218384, + "dateTime": "2026.06.18-10.21.36", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "LaunchCatalog", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.LaunchCatalog", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.017018400132656097, + "dateTime": "2026.06.18-10.21.36", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "LaunchExecution", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.LaunchExecution", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 43.018119812011719, + "dateTime": "2026.06.18-10.21.36", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "Magic120CellActivationProfile", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.Magic120CellActivationProfile", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.014199301600456238, + "dateTime": "2026.06.18-10.22.23", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "MagicCube5DActivationProfile", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.MagicCube5DActivationProfile", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.015723902732133865, + "dateTime": "2026.06.18-10.22.23", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "RunStateActivationResolution", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.RunStateActivationResolution", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.01621440052986145, + "dateTime": "2026.06.18-10.22.23", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "SessionCatalog", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.SessionCatalog", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.098687797784805298, + "dateTime": "2026.06.18-10.22.23", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "TrainingRunDefinitionBridge", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.TrainingRunDefinitionBridge", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.015297800302505493, + "dateTime": "2026.06.18-10.22.23", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + }, + { + "testDisplayName": "ViewContextCatalog", + "fullTestPath": "HyperTwist.FirstParty.HigherDimensional.Phase6C.ViewContextCatalog", + "tags": [], + "state": "Success", + "deviceInstance": [ + "8A15E1D540837281594FD7AB21A7E713" + ], + "duration": 0.024057600647211075, + "dateTime": "2026.06.18-10.22.23", + "entries": [], + "warnings": 0, + "errors": 0, + "artifacts": [] + } + ] +} \ No newline at end of file diff --git a/docs/ops/HYPERTWIST_COMPREHENSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md b/docs/ops/HYPERTWIST_COMPREHENSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md index 46677b4..2720039 100644 --- a/docs/ops/HYPERTWIST_COMPREHENSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md +++ b/docs/ops/HYPERTWIST_COMPREHENSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md @@ -367,6 +367,8 @@ Closure read: - `2026-06-18` Windows checkpoint: the same isolated worktree `C:\HyperTwist_worktrees\phase10validate` rebuilt the dedicated-family runtime-state slice with `Result: Succeeded` / UnrealBuildTool `Total execution time: 1648.33 seconds`; after the selector-batching hardening rebuild with `Result: Succeeded` / UnrealBuildTool `Total execution time: 93.44 seconds`, targeted `UnrealEditor-Cmd` automation exported `C:\HyperTwist_worktrees\phase10validate\UnrealHyperTwist\Saved\AutomationReports\Phase6C-DedicatedFamily-Optimized\index.json` with all `11` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests passing, `totalDuration` `196.08790588378906`, and `LaunchExecution` duration `195.65591430664062`; the commandlet still emitted `Ignoring very large delta of 195.58 seconds`, but that quiet interval is materially down from the immediately prior `385.00 seconds` proof on this lane while retaining `**** TEST COMPLETE. EXIT CODE: 0 ****` - `2026-06-18` continuation proof: current code now also defers repository-view projection across the internal generated-mode launch handoff itself, so the seed-run, launch-request persistence, and generated-run transition keep their repository state intact without forcing intermediate view rebuilds that the caller never reads - `2026-06-18` Windows checkpoint: after that projection-deferral hardening rebuild with `Result: Succeeded` / UnrealBuildTool `Total execution time: 400.77 seconds`, targeted `UnrealEditor-Cmd` automation exported `C:\HyperTwist_worktrees\phase10validate\UnrealHyperTwist\Saved\AutomationReports\Phase6C-DedicatedFamily-Optimized2\index.json` with all `11` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests passing, `totalDuration` `41.88494873046875`, and `LaunchExecution` duration `41.53509521484375`; the controller quiet interval fell again to `41.52 seconds` while retaining `**** TEST COMPLETE. EXIT CODE: 0 ****` +- `2026-06-18` recovery proof: current code now deterministically reauthors the dedicated-family `Magic120Cell` / `MagicCube5D` maps through `scripts/hypertwist_author_higher_dimensional_training_maps.py`, stamps `AHyperTwistCoachDashboardGameMode` plus manifest-backed `PlayerStart` ownership tags for activation, host, launch, view-context, session, interactive-scene, scene-context, puzzle, runtime-mode, projection, persistence, and dedicated-family ownership, and records the refreshed owned-map hashes in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json`; the optional `AHyperTwistHigherDimensionalTrainingShellActor` remains available for manual/editor authoring, but the headless `UnrealEditor-Cmd` lane now intentionally skips spawning it because that path still crashes the commandlet with `EXCEPTION_INT_DIVIDE_BY_ZERO` +- `2026-06-18` Windows checkpoint: after patching the missing dedicated-anchor puzzle tag, the same isolated worktree `C:\HyperTwist_worktrees\phase10validate` reran `scripts\Invoke-HyperTwistHigherDimensionalMapAuthoring.ps1` to `Higher-dimensional authored maps verified.`, then targeted `UnrealEditor-Cmd` automation exported `C:\HyperTwist_worktrees\phase10validate\UnrealHyperTwist\Saved\AutomationReports\Phase6C-DedicatedMapOwnership\index.json`; the pulled local proof at `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_map_ownership_automation_report.json` now shows all `13` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests passing, including `DedicatedMapAuthoringManifest` and `DedicatedMapOwnedShell`, with `**** TEST COMPLETE. EXIT CODE: 0 ****` - [x] Replace the current placeholder interactive-scene envelopes with full owned `120-cell` and `5D` simulation-state, projection, and persistence ownership in Unreal above the landed higher-dimensional scene-context catalog - [x] Replace the current shared-map execution bridge with full owned dedicated-family interactive launch, map, and persistence surfaces for `120-cell` and `5D` @@ -582,4 +584,16 @@ Recommended next widening order: the same `localhost:22022` lane still passed all `11` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests while dropping the `LaunchExecution` quiet interval again from `195.58` to `41.52` seconds +- `2026-06-18`: the next `Phase 6C` recovery-proof pass then turned the new + dedicated-family maps into deterministically regenerated headless-owned + artifacts through `scripts/hypertwist_author_higher_dimensional_training_maps.py`, + stamped explicit `PlayerStart` puzzle and ownership tags plus the rest of + the higher-dimensional ownership contract into those maps, preserved the + optional `AHyperTwistHigherDimensionalTrainingShellActor` only for manual or + editor authoring because `UnrealEditor-Cmd` still crashes when spawning it, + and revalidated the corrected slice on `localhost:22022` with all `13` + `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests passing, including + `DedicatedMapAuthoringManifest` and `DedicatedMapOwnedShell`; pulled proof + now lives in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json` + and `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_map_ownership_automation_report.json` - later classic-cube polish only when a concrete presentation gap remains, not as the default next move diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index ad48d6f..bbfc068 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -271,7 +271,7 @@ repo. | Higher-dimensional initial shared-map launch and view-context execution bridge | Implemented now | landed `Phase 6C` continuation packet | First-party current code first bundled higher-dimensional launch surfaces and view-context surfaces for `Magic120Cell` and `MagicCube5D`, resolved those surfaces through the training runtime library plus the live training panel/session-actor caches, and let the training subsystem start the shared-map seed run, apply default selector posture, persist the generated-mode launch request, and start the owned generated run without widening into external-process ownership. After recovering an interrupted follow-up, the primary reverse-SSH `localhost:22022` Windows lane rebuilt that cleaned slice on `2026-06-18` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 124.81 seconds`, and passed all `8` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests, including `LaunchCatalog`, `ViewContextCatalog`, `LaunchExecution`, and `RunStateActivationResolution`; that initial bridge is now superseded by the later dedicated-family map ownership continuation. | | Higher-dimensional explicit dedicated-host seam | Implemented now | landed `Phase 6C` host-surface continuation packet | First-party current code then recorded the preferred dedicated-family `Magic120Cell` / `MagicCube5D` host-map targets in an explicit higher-dimensional host-surface catalog while the slice still kept the shared training map as the interim effective runtime fallback, mirrored that resolved host seam through the training runtime library plus the panel/session caches, and threaded the host-surface id into the launch/view-context surfaces and generated-mode lineage. The primary reverse-SSH `localhost:22022` Windows lane rebuilt this widened slice on `2026-06-18` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 2858.02 seconds`, and passed all `9` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests, including the new `HostCatalog`; that interim fallback is now superseded by the later dedicated-family map ownership continuation. | | Higher-dimensional executed runtime session surface | Implemented now | landed `Phase 6C` session-surface continuation packet | First-party current code now merges the higher-dimensional launch, host, and view-context seams into an explicit runtime-session catalog for `Magic120Cell` and `MagicCube5D`, mirrors that session surface through the training runtime library plus the panel/session caches, upgrades generated-mode deck lineage to record the resolved session surface, and applies the merged launch-plus-view selector posture at start time instead of only the narrower launch defaults. The primary reverse-SSH `localhost:22022` Windows lane rebuilt this widened slice on `2026-06-18` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 3541.98 seconds`, and passed all `10` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests, including the new `SessionCatalog`; the longer `LaunchExecution` quiet interval of `573.84 seconds` remained known-good. | -| Higher-dimensional dedicated-family interactive scene ownership | Implemented now | landed `Phase 6C` interactive-scene and dedicated-host continuation packet | First-party current code now promotes the higher-dimensional runtime-session seams into explicit `Magic120Cell` and `MagicCube5D` interactive-scene surfaces with family-owned runtime-state, projection, and persistence envelopes, replaces the shared-map bridge with dedicated-family training-map assets plus dedicated host/launch/view-context/session surfaces, exposes explicit scene-envelope decoders through the runtime library, batches default selector application so generated-mode launch execution no longer rebuilds repository views once per selector, and then defers repository-view projection across the internal generated-mode handoff itself so only the final launch state forces a view refresh. The primary reverse-SSH `localhost:22022` Windows lane rebuilt the dedicated-family ownership slice on `2026-06-18` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 1648.33 seconds`; the later projection-deferral hardening rebuild completed in `400.77 seconds`, and the exported automation report passed all `11` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests with `totalDuration` `41.88494873046875`, `LaunchExecution` duration `41.53509521484375`, and the controller quiet interval reduced to `41.52 seconds`. | +| Higher-dimensional dedicated-family interactive scene ownership | Implemented now | landed `Phase 6C` interactive-scene and dedicated-host continuation packet | First-party current code now promotes the higher-dimensional runtime-session seams into explicit `Magic120Cell` and `MagicCube5D` interactive-scene surfaces with family-owned runtime-state, projection, and persistence envelopes, replaces the shared-map bridge with dedicated-family training-map assets plus dedicated host/launch/view-context/session surfaces, exposes explicit scene-envelope decoders through the runtime library, batches default selector application so generated-mode launch execution no longer rebuilds repository views once per selector, and then defers repository-view projection across the internal generated-mode handoff itself so only the final launch state forces a view refresh. The primary reverse-SSH `localhost:22022` Windows lane rebuilt the dedicated-family ownership slice on `2026-06-18` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 1648.33 seconds`; the later projection-deferral hardening rebuild completed in `400.77 seconds`, and the exported automation report passed all `11` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests with `totalDuration` `41.88494873046875`, `LaunchExecution` duration `41.53509521484375`, and the controller quiet interval reduced to `41.52 seconds`. The current recovery-proof continuation now also deterministically reauthors the dedicated-family maps through `scripts/hypertwist_author_higher_dimensional_training_maps.py`, records manifest-backed owned hashes in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json`, preserves the optional `AHyperTwistHigherDimensionalTrainingShellActor` for manual/editor authoring while intentionally skipping it on the headless commandlet lane because that spawn path still crashes `UnrealEditor-Cmd`, and passes all `13` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests through pulled proof in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_map_ownership_automation_report.json`, including `DedicatedMapAuthoringManifest` and `DedicatedMapOwnedShell`. | | Broad non-Euclidean interaction shell and host ownership | Deep-source grounded retained | `MagicTile` retained remainder | Broad interaction-shell, WinForms/OpenTK host ownership, and generic runtime replacement remain deferred after the landed `Phase 6R-T` slice. | ### 7. Speech input and voice sidecars diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index 08a81c1..083440d 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -179,10 +179,25 @@ Current consolidated milestone snapshot: again passed all `11` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests with `totalDuration` `41.88494873046875` and `LaunchExecution` duration `41.53509521484375` +- `2026-06-18`: the next `Phase 6C` recovery-proof pass then made those + dedicated-family maps deterministic headless-owned artifacts through + `scripts/hypertwist_author_higher_dimensional_training_maps.py`, stamped + explicit `PlayerStart` ownership tags for activation, host, launch, + view-context, session, interactive-scene, scene-context, puzzle, + runtime-mode, projection, persistence, and dedicated-family ownership, + preserved the optional `AHyperTwistHigherDimensionalTrainingShellActor` + only for manual/editor authoring because `UnrealEditor-Cmd` still crashes + when spawning it, and revalidated the corrected slice with pulled proof in + `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json` + plus `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_map_ownership_automation_report.json`; + the same `localhost:22022` lane passed all `13` + `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests, including + `DedicatedMapAuthoringManifest` and `DedicatedMapOwnedShell` - the functional `Phase 6C` ownership gap is now closed; the remaining work - in this lane is optional further micro-profiling and richer dedicated-family - authored map content, not missing activation, host, session, - interactive-scene, runtime-state, or persistence ownership seams + in this lane is optional further micro-profiling and richer non-headless + decorative/manual-authored family map dressing, not missing activation, + host, session, interactive-scene, runtime-state, or persistence ownership + seams - the canonical HyperTwist repo-row portfolio is now treated as `75` rows, not `71` - currently implemented rows are now `35`, not `20` diff --git a/scripts/Invoke-HyperTwistHigherDimensionalMapAuthoring.ps1 b/scripts/Invoke-HyperTwistHigherDimensionalMapAuthoring.ps1 new file mode 100644 index 0000000..d0a203d --- /dev/null +++ b/scripts/Invoke-HyperTwistHigherDimensionalMapAuthoring.ps1 @@ -0,0 +1,180 @@ +param( + [string]$ProjectRoot = 'C:\HyperTwist', + [string]$UnrealEditorCmdPath = 'C:\Program Files\Epic Games\UE_5.7\Engine\Binaries\Win64\UnrealEditor-Cmd.exe', + [string]$PythonScriptPath +) + +$ErrorActionPreference = 'Stop' + +if ([string]::IsNullOrWhiteSpace($PythonScriptPath)) +{ + $PythonScriptPath = Join-Path $ProjectRoot 'scripts\hypertwist_author_higher_dimensional_training_maps.py' +} + +$UProjectPath = Join-Path $ProjectRoot 'UnrealHyperTwist\UnrealHyperTwist.uproject' +$Magic120CellMapPath = Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Maps\L_HyperTwist_Magic120CellTraining.umap' +$MagicCube5DMapPath = Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Maps\L_HyperTwist_MagicCube5DTraining.umap' +$ClassicMapPath = Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Maps\L_HyperTwist_ClassicTraining.umap' +$ManifestPath = Join-Path $ProjectRoot 'docs\generated\higher_dimensional_training_maps\phase6c_dedicated_family_map_manifest.json' + +if (-not (Test-Path $UnrealEditorCmdPath)) +{ + throw "UnrealEditor-Cmd.exe was not found at '$UnrealEditorCmdPath'." +} + +if (-not (Test-Path $UProjectPath)) +{ + throw "UnrealHyperTwist project file was not found at '$UProjectPath'." +} + +if (-not (Test-Path $PythonScriptPath)) +{ + throw "Higher-dimensional map authoring script was not found at '$PythonScriptPath'." +} + +$NormalizedPythonScriptPath = $PythonScriptPath -replace '\\', '/' +$AuthoredTargets = @( + @{ + Label = 'magic120cell' + ExpectedMapPath = $Magic120CellMapPath + }, + @{ + Label = 'magiccube5d' + ExpectedMapPath = $MagicCube5DMapPath + } +) + +foreach ($Target in $AuthoredTargets) +{ + $env:HYPERTWIST_HIGHER_DIMENSIONAL_MAP_KIND = $Target.Label + $AuthoringStartedAtUtc = [DateTime]::UtcNow + Write-Host "Authoring HyperTwist higher-dimensional training map target '$($Target.Label)' through UnrealEditor-Cmd..." + & $UnrealEditorCmdPath ` + $UProjectPath ` + "-ExecutePythonScript=$NormalizedPythonScriptPath" ` + -unattended ` + -nop4 ` + -nullrhi ` + -nosound ` + -nosplash ` + -stdout ` + -FullStdOutLogOutput ` + -log + + if ($LASTEXITCODE -ne 0) + { + $FreshMapExists = $false + if (Test-Path $Target.ExpectedMapPath) + { + $FreshMapExists = (Get-Item $Target.ExpectedMapPath).LastWriteTimeUtc -ge $AuthoringStartedAtUtc.AddSeconds(-2) + } + + if ($FreshMapExists) + { + Write-Warning "UnrealEditor-Cmd exited with code $LASTEXITCODE after freshly writing '$($Target.ExpectedMapPath)'. Continuing because this host may still crash during post-save shutdown on the headless authoring lane." + } + else + { + Remove-Item Env:\HYPERTWIST_HIGHER_DIMENSIONAL_MAP_KIND -ErrorAction SilentlyContinue + throw "Higher-dimensional map authoring failed with exit code $LASTEXITCODE." + } + } + + if (-not (Test-Path $Target.ExpectedMapPath)) + { + Remove-Item Env:\HYPERTWIST_HIGHER_DIMENSIONAL_MAP_KIND -ErrorAction SilentlyContinue + throw "Expected authored map was not found at '$($Target.ExpectedMapPath)'." + } +} + +Remove-Item Env:\HYPERTWIST_HIGHER_DIMENSIONAL_MAP_KIND -ErrorAction SilentlyContinue + +foreach ($ExpectedMapPath in @($Magic120CellMapPath, $MagicCube5DMapPath)) +{ + if (-not (Test-Path $ExpectedMapPath)) + { + throw "Expected authored map was not found at '$ExpectedMapPath'." + } +} + +if (-not (Test-Path $ClassicMapPath)) +{ + throw "Classic reference map was not found at '$ClassicMapPath'." +} + +New-Item -ItemType Directory -Force -Path ([System.IO.Path]::GetDirectoryName($ManifestPath)) | Out-Null + +$ClassicReferenceHashMd5 = (Get-FileHash $ClassicMapPath -Algorithm MD5).Hash.ToLowerInvariant() +$ManifestEntries = @( + [ordered]@{ + mapKind = 'magic120cell' + familyKey = 'magic120cell' + mapAssetPath = '/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining' + mapFileRelativePath = 'UnrealHyperTwist/Content/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining.umap' + mapHashMd5 = (Get-FileHash $Magic120CellMapPath -Algorithm MD5).Hash.ToLowerInvariant() + trainingShellId = 'phase6c/magic120cell/dedicated-training-shell' + activationProfileId = 'magic120cell-cleanroom-runtime-activation' + hostSurfaceId = 'phase6c/magic120cell/runtime-host-surface' + launchSurfaceId = 'phase6c/magic120cell/dedicated-training-launch-surface' + viewContextSurfaceId = 'phase6c/magic120cell/dedicated-training-view-context-surface' + sessionSurfaceId = 'phase6c/magic120cell/dedicated-training-session-surface' + interactiveSceneSurfaceId = 'phase6c/magic120cell/interactive-scene-surface' + sceneContextId = 'phase6c/magic120cell/interactive-scene-context' + puzzleId = 'polychoron/magic120cell' + runtimeModeId = 'magic120cell-full-color-runtime-v1' + projectionProfileId = 'magic120cell-4d-projection-distance-v1' + primaryPersistenceBoundaryId = 'magic120cell-persistence-boundary' + authorTag = 'HyperTwistHigherDimensionalTrainingShell' + authoringManifestRelativePath = 'docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json' + authoredViaGameModeClassPath = '/Script/UnrealHyperTwist.HyperTwistCoachDashboardGameMode' + trainingShellTags = @( + 'phase6c', + 'family:magic120cell', + 'host:dedicated-family-map', + 'projection:magic120cell-4d-projection-distance-v1', + 'persistence:magic120cell-persistence-boundary' + ) + }, + [ordered]@{ + mapKind = 'magiccube5d' + familyKey = 'magiccube5d' + mapAssetPath = '/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining' + mapFileRelativePath = 'UnrealHyperTwist/Content/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining.umap' + mapHashMd5 = (Get-FileHash $MagicCube5DMapPath -Algorithm MD5).Hash.ToLowerInvariant() + trainingShellId = 'phase6c/magiccube5d/dedicated-training-shell' + activationProfileId = 'magiccube5d-cleanroom-runtime-activation' + hostSurfaceId = 'phase6c/magiccube5d/runtime-host-surface' + launchSurfaceId = 'phase6c/magiccube5d/dedicated-training-launch-surface' + viewContextSurfaceId = 'phase6c/magiccube5d/dedicated-training-view-context-surface' + sessionSurfaceId = 'phase6c/magiccube5d/dedicated-training-session-surface' + interactiveSceneSurfaceId = 'phase6c/magiccube5d/interactive-scene-surface' + sceneContextId = 'phase6c/magiccube5d/interactive-scene-context' + puzzleId = 'hypercube/magiccube5d/order3' + runtimeModeId = 'magiccube5d-order3-runtime-v1' + projectionProfileId = 'magiccube5d-5d-projection-distance-v1' + primaryPersistenceBoundaryId = 'magiccube5d-persistence-boundary' + authorTag = 'HyperTwistHigherDimensionalTrainingShell' + authoringManifestRelativePath = 'docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json' + authoredViaGameModeClassPath = '/Script/UnrealHyperTwist.HyperTwistCoachDashboardGameMode' + trainingShellTags = @( + 'phase6c', + 'family:magiccube5d', + 'host:dedicated-family-map', + 'projection:magiccube5d-5d-projection-distance-v1', + 'persistence:magiccube5d-persistence-boundary' + ) + } +) + +$Manifest = [ordered]@{ + manifestId = 'phase6c/dedicated-family-training-map-authoring' + manifestVersion = '2026.06.18' + authorTag = 'HyperTwistHigherDimensionalTrainingShell' + authoringScriptRelativePath = 'scripts/hypertwist_author_higher_dimensional_training_maps.py' + authoredThroughGameModeClassPath = '/Script/UnrealHyperTwist.HyperTwistCoachDashboardGameMode' + classicReferenceMapHashMd5 = $ClassicReferenceHashMd5 + entries = $ManifestEntries +} + +$Manifest | ConvertTo-Json -Depth 6 | Set-Content -Path $ManifestPath -Encoding UTF8 +Write-Host 'Higher-dimensional authored maps verified.' diff --git a/scripts/hypertwist_author_higher_dimensional_training_maps.py b/scripts/hypertwist_author_higher_dimensional_training_maps.py new file mode 100644 index 0000000..4c4eeaf --- /dev/null +++ b/scripts/hypertwist_author_higher_dimensional_training_maps.py @@ -0,0 +1,579 @@ +import hashlib +import json +import os +import traceback + +import unreal + + +PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +MAP_ROOT = "/Game/HyperTwistTraining/Maps" +SAFE_TRANSIENT_LEVEL_PATH = "/Engine/Maps/Entry" +AUTHOR_TAG = "HyperTwistHigherDimensionalTrainingShell" +LEGACY_CLASSIC_AUTHOR_TAG = "HyperTwistClassicCubeTrainingMap" +TRAINING_SHELL_CLASS_PATH = "/Script/UnrealHyperTwist.HyperTwistHigherDimensionalTrainingShellActor" +COACH_DASHBOARD_GAME_MODE_PATH = "/Script/UnrealHyperTwist.HyperTwistCoachDashboardGameMode" +ENABLE_HEADLESS_TRAINING_SHELL_ACTOR = ( + os.getenv("HYPERTWIST_ENABLE_TRAINING_SHELL_ACTOR", "").strip().lower() + in {"1", "true", "yes"} +) +PRESERVED_LEVEL_FRAMEWORK_CLASS_NAMES = { + "Brush", + "DefaultPhysicsVolume", + "LevelScriptActor", + "WorldSettings", +} +MANIFEST_RELATIVE_PATH = os.path.join( + "docs", + "generated", + "higher_dimensional_training_maps", + "phase6c_dedicated_family_map_manifest.json", +) +MANIFEST_ABSOLUTE_PATH = os.path.join(PROJECT_ROOT, MANIFEST_RELATIVE_PATH) + +MAP_CONFIGS = ( + { + "map_kind": "magic120cell", + "family_key": "magic120cell", + "map_asset_path": f"{MAP_ROOT}/L_HyperTwist_Magic120CellTraining", + "map_file_relative_path": os.path.join( + "UnrealHyperTwist", + "Content", + "HyperTwistTraining", + "Maps", + "L_HyperTwist_Magic120CellTraining.umap", + ), + "training_shell_id": "phase6c/magic120cell/dedicated-training-shell", + "title": "Magic120Cell dedicated training shell", + "summary": ( + "First-party authored dedicated-family training shell for the owned " + "Magic120Cell higher-dimensional activation lane." + ), + "activation_profile_id": "magic120cell-cleanroom-runtime-activation", + "host_surface_id": "phase6c/magic120cell/runtime-host-surface", + "launch_surface_id": "phase6c/magic120cell/dedicated-training-launch-surface", + "view_context_surface_id": "phase6c/magic120cell/dedicated-training-view-context-surface", + "session_surface_id": "phase6c/magic120cell/dedicated-training-session-surface", + "interactive_scene_surface_id": "phase6c/magic120cell/interactive-scene-surface", + "scene_context_id": "phase6c/magic120cell/interactive-scene-context", + "puzzle_id": "polychoron/magic120cell", + "runtime_mode_id": "magic120cell-full-color-runtime-v1", + "projection_profile_id": "magic120cell-4d-projection-distance-v1", + "primary_persistence_boundary_id": "magic120cell-persistence-boundary", + "player_start_location": unreal.Vector(-560.0, 0.0, 190.0), + "sun_location": unreal.Vector(-340.0, 120.0, 470.0), + "sun_rotation": unreal.Rotator(-34.0, -22.0, 0.0), + "sun_intensity": 7.6, + "floor_scale": unreal.Vector(18.0, 18.0, 1.0), + "shell_location": unreal.Vector(0.0, 0.0, 0.0), + "shell_rotation": unreal.Rotator(0.0, 22.0, 0.0), + "training_shell_tags": [ + "phase6c", + "family:magic120cell", + "host:dedicated-family-map", + "projection:magic120cell-4d-projection-distance-v1", + "persistence:magic120cell-persistence-boundary", + ], + }, + { + "map_kind": "magiccube5d", + "family_key": "magiccube5d", + "map_asset_path": f"{MAP_ROOT}/L_HyperTwist_MagicCube5DTraining", + "map_file_relative_path": os.path.join( + "UnrealHyperTwist", + "Content", + "HyperTwistTraining", + "Maps", + "L_HyperTwist_MagicCube5DTraining.umap", + ), + "training_shell_id": "phase6c/magiccube5d/dedicated-training-shell", + "title": "MagicCube5D dedicated training shell", + "summary": ( + "First-party authored dedicated-family training shell for the owned " + "MagicCube5D higher-dimensional activation lane." + ), + "activation_profile_id": "magiccube5d-cleanroom-runtime-activation", + "host_surface_id": "phase6c/magiccube5d/runtime-host-surface", + "launch_surface_id": "phase6c/magiccube5d/dedicated-training-launch-surface", + "view_context_surface_id": "phase6c/magiccube5d/dedicated-training-view-context-surface", + "session_surface_id": "phase6c/magiccube5d/dedicated-training-session-surface", + "interactive_scene_surface_id": "phase6c/magiccube5d/interactive-scene-surface", + "scene_context_id": "phase6c/magiccube5d/interactive-scene-context", + "puzzle_id": "hypercube/magiccube5d/order3", + "runtime_mode_id": "magiccube5d-order3-runtime-v1", + "projection_profile_id": "magiccube5d-5d-projection-distance-v1", + "primary_persistence_boundary_id": "magiccube5d-persistence-boundary", + "player_start_location": unreal.Vector(-620.0, -120.0, 200.0), + "sun_location": unreal.Vector(-260.0, -200.0, 500.0), + "sun_rotation": unreal.Rotator(-38.0, 34.0, 0.0), + "sun_intensity": 8.8, + "floor_scale": unreal.Vector(20.0, 20.0, 1.0), + "shell_location": unreal.Vector(0.0, 0.0, 0.0), + "shell_rotation": unreal.Rotator(0.0, -18.0, 0.0), + "training_shell_tags": [ + "phase6c", + "family:magiccube5d", + "host:dedicated-family-map", + "projection:magiccube5d-5d-projection-distance-v1", + "persistence:magiccube5d-persistence-boundary", + ], + }, +) + + +def log(message: str) -> None: + unreal.log(f"[HyperTwistHigherDimensionalMapAuthoring] {message}") + + +def require_asset(asset_path: str): + asset = unreal.EditorAssetLibrary.load_asset(asset_path) + if asset is None: + raise RuntimeError(f"Required asset was not found: {asset_path}") + return asset + + +def require_class(class_path: str): + loaded_class = unreal.load_class(None, class_path) + if loaded_class is None: + raise RuntimeError(f"Required class was not found: {class_path}") + return loaded_class + + +def ensure_directory(directory_path: str) -> None: + if not unreal.EditorAssetLibrary.does_directory_exist(directory_path): + if not unreal.EditorAssetLibrary.make_directory(directory_path): + raise RuntimeError(f"Failed to create content directory: {directory_path}") + + +def spawn_actor(actor_class, label: str, location: unreal.Vector, rotation: unreal.Rotator): + actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) + if actor_subsystem is None: + raise RuntimeError("EditorActorSubsystem was not available.") + + actor = actor_subsystem.spawn_actor_from_class(actor_class, location, rotation) + if actor is None: + raise RuntimeError(f"Failed to spawn actor: {label}") + + actor.set_actor_label(label) + actor.set_editor_property("tags", [unreal.Name(AUTHOR_TAG)]) + return actor + + +def build_map_object_path(map_asset_path: str) -> str: + asset_name = map_asset_path.rsplit("/", 1)[-1] + return f"{map_asset_path}.{asset_name}" + + +def map_package_exists(config) -> bool: + map_asset_path = config["map_asset_path"] + map_file_absolute_path = resolve_map_file_absolute_path(config) + map_object_path = build_map_object_path(map_asset_path) + return ( + os.path.exists(map_file_absolute_path) + or unreal.EditorAssetLibrary.does_asset_exist(map_asset_path) + or unreal.EditorAssetLibrary.does_asset_exist(map_object_path) + ) + + +def delete_existing_level(level_subsystem, config) -> bool: + map_asset_path = config["map_asset_path"] + if not map_package_exists(config): + return False + + if not level_subsystem.load_level(SAFE_TRANSIENT_LEVEL_PATH): + raise RuntimeError( + f"Failed to load transient level '{SAFE_TRANSIENT_LEVEL_PATH}' before rebuilding " + f"'{map_asset_path}'." + ) + + map_object_path = build_map_object_path(map_asset_path) + deleted_via_asset_api = False + for candidate_path in (map_object_path, map_asset_path): + if unreal.EditorAssetLibrary.does_asset_exist(candidate_path): + deleted_via_asset_api = unreal.EditorAssetLibrary.delete_asset(candidate_path) + if deleted_via_asset_api: + log(f"Deleted existing level asset {candidate_path} before rebuild") + break + + map_file_absolute_path = resolve_map_file_absolute_path(config) + if os.path.exists(map_file_absolute_path): + os.remove(map_file_absolute_path) + log(f"Removed existing level file {map_file_absolute_path} before rebuild") + return True + + if deleted_via_asset_api: + return True + + raise RuntimeError(f"Failed to delete existing level package: {map_asset_path}") + + +def recreate_level(level_subsystem, config) -> None: + map_asset_path = config["map_asset_path"] + delete_existing_level(level_subsystem, config) + + if not level_subsystem.new_level(map_asset_path): + raise RuntimeError(f"Failed to create level: {map_asset_path}") + + log(f"Created new level {map_asset_path}") + + +def should_preserve_existing_actor(actor) -> bool: + actor_class = actor.get_class() + actor_class_name = actor_class.get_name() if actor_class is not None else "" + return actor_class_name in PRESERVED_LEVEL_FRAMEWORK_CLASS_NAMES + + +def clear_existing_training_map_actors() -> None: + actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) + if actor_subsystem is None: + raise RuntimeError("EditorActorSubsystem was not available.") + + author_tags = {AUTHOR_TAG, LEGACY_CLASSIC_AUTHOR_TAG} + destroyed_tagged_count = 0 + destroyed_fallback_count = 0 + + for actor in actor_subsystem.get_all_level_actors(): + if actor is None: + continue + + if should_preserve_existing_actor(actor): + continue + + actor_tags = {str(tag) for tag in actor.tags} + if not actor_tags.isdisjoint(author_tags): + if actor_subsystem.destroy_actor(actor): + destroyed_tagged_count += 1 + continue + + if actor_subsystem.destroy_actor(actor): + destroyed_fallback_count += 1 + + log( + "Removed " + f"{destroyed_tagged_count} tagged and {destroyed_fallback_count} fallback actors " + "before reauthoring" + ) + + +def save_current_level_or_raise(level_subsystem, label: str) -> None: + if not level_subsystem.save_current_level(): + raise RuntimeError(f"Failed to save level after {label}.") + log(f"Saved current level after {label}") + + +def configure_world_settings(game_mode_class_path: str) -> None: + editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem) + if editor_subsystem is None: + raise RuntimeError("UnrealEditorSubsystem was not available.") + + world = editor_subsystem.get_editor_world() + if world is None: + raise RuntimeError("Editor world was not available after loading the target level.") + + world_settings = world.get_world_settings() + if world_settings is None: + raise RuntimeError("World settings were not available for the target level.") + + world_settings.set_editor_property("default_game_mode", require_class(game_mode_class_path)) + + +def ensure_floor_plane(config) -> None: + floor_actor = spawn_actor( + unreal.StaticMeshActor, + f"HT_{config['family_key']}_Floor", + unreal.Vector(0.0, 0.0, -20.0), + unreal.Rotator(0.0, 0.0, 0.0), + ) + static_mesh_component = floor_actor.static_mesh_component + static_mesh_component.set_editor_property( + "static_mesh", + require_asset("/Engine/BasicShapes/Plane.Plane"), + ) + static_mesh_component.set_editor_property("mobility", unreal.ComponentMobility.STATIC) + floor_actor.set_actor_scale3d(config["floor_scale"]) + + +def ensure_directional_light(config) -> None: + directional_light = spawn_actor( + unreal.DirectionalLight, + f"HT_{config['family_key']}_Sun", + config["sun_location"], + config["sun_rotation"], + ) + light_component = directional_light.get_component_by_class(unreal.DirectionalLightComponent) + if light_component is not None: + light_component.set_editor_property("mobility", unreal.ComponentMobility.MOVABLE) + light_component.set_editor_property("intensity", config["sun_intensity"]) + + +def ensure_skylight(config) -> None: + skylight = spawn_actor( + unreal.SkyLight, + f"HT_{config['family_key']}_Sky", + unreal.Vector(0.0, 0.0, 260.0), + unreal.Rotator(0.0, 0.0, 0.0), + ) + skylight_component = skylight.get_component_by_class(unreal.SkyLightComponent) + if skylight_component is not None: + skylight_component.set_editor_property("mobility", unreal.ComponentMobility.MOVABLE) + skylight_component.set_editor_property("real_time_capture", True) + skylight_component.set_editor_property( + "intensity", + 1.35 if config["family_key"] == "magic120cell" else 1.55, + ) + + +def ensure_reflection_capture(config) -> None: + reflection_capture = spawn_actor( + unreal.SphereReflectionCapture, + f"HT_{config['family_key']}_Reflection", + unreal.Vector(0.0, 0.0, 150.0), + unreal.Rotator(0.0, 0.0, 0.0), + ) + reflection_capture.set_actor_scale3d(unreal.Vector(14.0, 14.0, 14.0)) + + +def ensure_player_start(config) -> None: + player_start = spawn_actor( + unreal.PlayerStart, + f"HT_{config['family_key']}_DedicatedSurfaceAnchor", + config["player_start_location"], + unreal.Rotator(0.0, 0.0, 0.0), + ) + anchor_tags = [ + AUTHOR_TAG, + f"training-shell-id:{config['training_shell_id']}", + f"activation-profile:{config['activation_profile_id']}", + f"host-surface:{config['host_surface_id']}", + f"launch-surface:{config['launch_surface_id']}", + f"view-context-surface:{config['view_context_surface_id']}", + f"session-surface:{config['session_surface_id']}", + f"interactive-scene-surface:{config['interactive_scene_surface_id']}", + f"scene-context:{config['scene_context_id']}", + f"puzzle:{config['puzzle_id']}", + f"runtime-mode:{config['runtime_mode_id']}", + f"projection:{config['projection_profile_id']}", + f"persistence:{config['primary_persistence_boundary_id']}", + "ownership:dedicated-family", + ] + anchor_tags.extend(config["training_shell_tags"]) + player_start.set_editor_property( + "tags", + [unreal.Name(tag) for tag in dict.fromkeys(anchor_tags)], + ) + + +def ensure_training_shell_actor(config) -> None: + shell_class = require_class(TRAINING_SHELL_CLASS_PATH) + shell_actor = unreal.EditorLevelLibrary.spawn_actor_from_class( + shell_class, + config["shell_location"], + config["shell_rotation"], + ) + if shell_actor is None: + raise RuntimeError( + f"Failed to spawn dedicated training shell actor for {config['family_key']}." + ) + + shell_actor.set_actor_label(f"HT_{config['family_key']}_TrainingShell") + shell_actor.set_editor_property("family_key", config["family_key"]) + shell_actor.set_editor_property("training_shell_id", config["training_shell_id"]) + shell_actor.set_editor_property("title", config["title"]) + shell_actor.set_editor_property("summary", config["summary"]) + shell_actor.set_editor_property("map_asset_path", config["map_asset_path"]) + shell_actor.set_editor_property("activation_profile_id", config["activation_profile_id"]) + shell_actor.set_editor_property("host_surface_id", config["host_surface_id"]) + shell_actor.set_editor_property("launch_surface_id", config["launch_surface_id"]) + shell_actor.set_editor_property("view_context_surface_id", config["view_context_surface_id"]) + shell_actor.set_editor_property("session_surface_id", config["session_surface_id"]) + shell_actor.set_editor_property( + "interactive_scene_surface_id", + config["interactive_scene_surface_id"], + ) + shell_actor.set_editor_property("scene_context_id", config["scene_context_id"]) + shell_actor.set_editor_property("puzzle_id", config["puzzle_id"]) + shell_actor.set_editor_property("runtime_mode_id", config["runtime_mode_id"]) + shell_actor.set_editor_property("projection_profile_id", config["projection_profile_id"]) + shell_actor.set_editor_property( + "primary_persistence_boundary_id", + config["primary_persistence_boundary_id"], + ) + shell_actor.set_editor_property( + "authoring_manifest_relative_path", + MANIFEST_RELATIVE_PATH.replace("\\", "/"), + ) + shell_actor.set_editor_property("dedicated_family_ownership", True) + shell_actor.set_editor_property("training_shell_tags", config["training_shell_tags"]) + shell_tags = [ + AUTHOR_TAG, + f"training-shell-id:{config['training_shell_id']}", + f"activation-profile:{config['activation_profile_id']}", + f"host-surface:{config['host_surface_id']}", + f"launch-surface:{config['launch_surface_id']}", + f"view-context-surface:{config['view_context_surface_id']}", + f"session-surface:{config['session_surface_id']}", + f"interactive-scene-surface:{config['interactive_scene_surface_id']}", + f"scene-context:{config['scene_context_id']}", + f"puzzle:{config['puzzle_id']}", + f"runtime-mode:{config['runtime_mode_id']}", + f"projection:{config['projection_profile_id']}", + f"persistence:{config['primary_persistence_boundary_id']}", + "ownership:dedicated-family", + ] + shell_tags.extend(config["training_shell_tags"]) + shell_actor.set_editor_property( + "tags", + [unreal.Name(tag) for tag in dict.fromkeys(shell_tags)], + ) + try: + shell_actor.rerun_construction_scripts() + except Exception: + log( + "Training shell actor construction rerun was unavailable; " + "continuing with direct metadata state." + ) + + +def resolve_map_file_absolute_path(config) -> str: + return os.path.join(PROJECT_ROOT, config["map_file_relative_path"]) + + +def compute_md5(file_path: str) -> str: + digest = hashlib.md5() + with open(file_path, "rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def ensure_manifest_directory() -> None: + os.makedirs(os.path.dirname(MANIFEST_ABSOLUTE_PATH), exist_ok=True) + + +def build_manifest_entries(): + entries = [] + for config in MAP_CONFIGS: + map_file_absolute_path = resolve_map_file_absolute_path(config) + if not os.path.exists(map_file_absolute_path): + continue + + entries.append( + { + "mapKind": config["map_kind"], + "familyKey": config["family_key"], + "mapAssetPath": config["map_asset_path"], + "mapFileRelativePath": config["map_file_relative_path"].replace("\\", "/"), + "mapHashMd5": compute_md5(map_file_absolute_path), + "trainingShellId": config["training_shell_id"], + "activationProfileId": config["activation_profile_id"], + "hostSurfaceId": config["host_surface_id"], + "launchSurfaceId": config["launch_surface_id"], + "viewContextSurfaceId": config["view_context_surface_id"], + "sessionSurfaceId": config["session_surface_id"], + "interactiveSceneSurfaceId": config["interactive_scene_surface_id"], + "sceneContextId": config["scene_context_id"], + "puzzleId": config["puzzle_id"], + "runtimeModeId": config["runtime_mode_id"], + "projectionProfileId": config["projection_profile_id"], + "primaryPersistenceBoundaryId": config["primary_persistence_boundary_id"], + "authorTag": AUTHOR_TAG, + "authoringManifestRelativePath": MANIFEST_RELATIVE_PATH.replace("\\", "/"), + "authoredViaGameModeClassPath": COACH_DASHBOARD_GAME_MODE_PATH, + "trainingShellTags": config["training_shell_tags"], + } + ) + return entries + + +def write_manifest() -> None: + ensure_manifest_directory() + classic_map_absolute_path = os.path.join( + PROJECT_ROOT, + "UnrealHyperTwist", + "Content", + "HyperTwistTraining", + "Maps", + "L_HyperTwist_ClassicTraining.umap", + ) + manifest = { + "manifestId": "phase6c/dedicated-family-training-map-authoring", + "manifestVersion": "2026.06.18", + "authorTag": AUTHOR_TAG, + "authoringScriptRelativePath": "scripts/hypertwist_author_higher_dimensional_training_maps.py", + "authoredThroughGameModeClassPath": COACH_DASHBOARD_GAME_MODE_PATH, + "classicReferenceMapHashMd5": ( + compute_md5(classic_map_absolute_path) + if os.path.exists(classic_map_absolute_path) + else "" + ), + "entries": build_manifest_entries(), + } + with open(MANIFEST_ABSOLUTE_PATH, "w", encoding="utf-8") as handle: + json.dump(manifest, handle, indent=2) + handle.write("\n") + log(f"Wrote higher-dimensional training-map manifest to {MANIFEST_ABSOLUTE_PATH}") + + +def author_map(config) -> None: + level_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem) + if level_subsystem is None: + raise RuntimeError("LevelEditorSubsystem was not available.") + + log(f"Starting authored rebuild for {config['map_asset_path']}") + recreate_level(level_subsystem, config) + log("Created clean target level") + configure_world_settings(COACH_DASHBOARD_GAME_MODE_PATH) + log("Configured dedicated training-shell game mode") + save_current_level_or_raise( + level_subsystem, + f"{config['family_key']} dedicated training-shell game mode", + ) + if ENABLE_HEADLESS_TRAINING_SHELL_ACTOR: + ensure_training_shell_actor(config) + log("Placed dedicated training shell actor") + else: + log( + "Skipped dedicated training shell actor on the headless authoring lane " + "because custom actor placement still crashes UnrealEditor-Cmd here" + ) + ensure_player_start(config) + log("Placed dedicated surface anchor") + save_current_level_or_raise( + level_subsystem, + f"{config['family_key']} dedicated shell surfaces", + ) + log(f"Finished authoring {config['map_asset_path']}") + + +def main() -> None: + ensure_directory(MAP_ROOT) + requested_map_kind = os.getenv("HYPERTWIST_HIGHER_DIMENSIONAL_MAP_KIND", "").strip().lower() + write_manifest_requested = requested_map_kind in ("", "all", "both") + if requested_map_kind in ("", "all", "both"): + targets = MAP_CONFIGS + else: + targets = tuple( + config for config in MAP_CONFIGS if config["map_kind"] == requested_map_kind + ) + if not targets: + raise RuntimeError( + f"Unsupported HYPERTWIST_HIGHER_DIMENSIONAL_MAP_KIND value: {requested_map_kind}" + ) + + for config in targets: + author_map(config) + + if write_manifest_requested: + write_manifest() + + log("Higher-dimensional dedicated-family authored maps are ready.") + + +if __name__ == "__main__": + try: + main() + except Exception as error: + unreal.log_error( + f"[HyperTwistHigherDimensionalMapAuthoring] {error}\n{traceback.format_exc()}" + ) + raise