Complete Phase 6C dedicated map ownership proof

This commit is contained in:
axiomlogicnexus 2026-06-18 10:25:35 +00:00
parent 3da81bb21c
commit fb72fbc2c8
12 changed files with 2204 additions and 4 deletions

View file

@ -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<USceneComponent>(TEXT("SceneRoot"));
SetRootComponent(SceneRoot);
PreviewInstances = CreateDefaultSubobject<UInstancedStaticMeshComponent>(TEXT("PreviewInstances"));
PreviewInstances->SetupAttachment(SceneRoot);
PreviewInstances->SetMobility(EComponentMobility::Static);
PreviewInstances->SetCollisionEnabled(ECollisionEnabled::NoCollision);
PreviewInstances->SetCastShadow(false);
LabelComponent = CreateDefaultSubobject<UTextRenderComponent>(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<UStaticMesh> CubeMeshFinder(
TEXT("/Engine/BasicShapes/Cube.Cube"));
if (CubeMeshFinder.Succeeded())
{
CubePreviewMesh = CubeMeshFinder.Object;
}
static ConstructorHelpers::FObjectFinder<UStaticMesh> 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<FName> 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<float>(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<FVector> 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);
}

View file

@ -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<USceneComponent> SceneRoot = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Shell")
TObjectPtr<UInstancedStaticMeshComponent> PreviewInstances = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Shell")
TObjectPtr<UTextRenderComponent> 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<FString> 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<UStaticMesh> CubePreviewMesh = nullptr;
TObjectPtr<UStaticMesh> 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;
};

View file

@ -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<FPhase6CDedicatedMapManifestEntry> 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<uint8> 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<FJsonObject>& 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<FJsonObject> RootObject;
if (!FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(Json), RootObject)
|| !RootObject.IsValid())
{
return false;
}
const TArray<TSharedPtr<FJsonValue>>* 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<FJsonValue>& EntryValue : *EntryValues)
{
const TSharedPtr<FJsonObject> 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<UWorld>(nullptr, *MapObjectPath);
}
template <typename ActorType>
int32 CountActorsOfClass(UWorld* World)
{
if (World == nullptr || World->PersistentLevel == nullptr)
{
return 0;
}
int32 Count = 0;
for (AActor* Actor : World->PersistentLevel->Actors)
{
if (Cast<ActorType>(Actor) != nullptr)
{
++Count;
}
}
return Count;
}
template <typename ActorType>
ActorType* FindActorOfClass(UWorld* World)
{
if (World == nullptr || World->PersistentLevel == nullptr)
{
return nullptr;
}
for (AActor* Actor : World->PersistentLevel->Actors)
{
if (ActorType* TypedActor = Cast<ActorType>(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",

View file

@ -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"
]
}
]
}

View file

@ -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": []
}
]
}

View file

@ -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

View file

@ -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

View file

@ -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`

View file

@ -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.'

View file

@ -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