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' $ReceiptDirectory = Join-Path $ProjectRoot 'UnrealHyperTwist\Saved\HyperTwistMapAuthoring' 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 ReceiptPath = Join-Path $ReceiptDirectory 'phase6c_magic120cell_complete.json' }, @{ Label = 'magiccube5d' ExpectedMapPath = $MagicCube5DMapPath ReceiptPath = Join-Path $ReceiptDirectory 'phase6c_magiccube5d_complete.json' } ) foreach ($Target in $AuthoredTargets) { $env:HYPERTWIST_HIGHER_DIMENSIONAL_MAP_KIND = $Target.Label $AuthoringStartedAtUtc = [DateTime]::UtcNow Remove-Item -LiteralPath $Target.ReceiptPath -Force -ErrorAction SilentlyContinue 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 $AuthoringExitCode = $LASTEXITCODE $Receipt = $null if (Test-Path -LiteralPath $Target.ReceiptPath -PathType Leaf) { $Receipt = Get-Content -LiteralPath $Target.ReceiptPath -Raw | ConvertFrom-Json } $ExpectedMapHash = if (Test-Path -LiteralPath $Target.ExpectedMapPath -PathType Leaf) { (Get-FileHash -LiteralPath $Target.ExpectedMapPath -Algorithm MD5).Hash.ToLowerInvariant() } else { '' } $ReceiptIsFresh = $Receipt -and ` (Get-Item -LiteralPath $Target.ReceiptPath).LastWriteTimeUtc -ge $AuthoringStartedAtUtc.AddSeconds(-2) $ReceiptIsValid = $ReceiptIsFresh -and ` $Receipt.authoringComplete -eq $true -and ` $Receipt.mapKind -eq $Target.Label -and ` $Receipt.gameModeClassPath -eq '/Script/UnrealHyperTwist.HyperTwistHigherDimensionalTrainingGameMode' -and ` $Receipt.presentationEnvironmentOwnership -eq 'runtime-game-mode-and-shell-components' -and ` $Receipt.mapHashMd5 -eq $ExpectedMapHash -and ` @($Receipt.validatedActorLabels).Count -eq 1 if (-not $ReceiptIsValid) { Remove-Item Env:\HYPERTWIST_HIGHER_DIMENSIONAL_MAP_KIND -ErrorAction SilentlyContinue if ($AuthoringExitCode -ne 0) { throw "Higher-dimensional map authoring failed with exit code $AuthoringExitCode before a valid completion receipt was written for '$($Target.Label)'." } throw "Higher-dimensional map authoring exited without a valid hash-bound completion receipt for '$($Target.Label)'." } if ($AuthoringExitCode -ne 0) { Write-Warning "UnrealEditor-Cmd exited with code $AuthoringExitCode only after '$($Target.Label)' completed, validated, and wrote its hash-bound receipt. Accepting the known post-completion headless shutdown crash." } 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.HyperTwistHigherDimensionalTrainingGameMode' runtimePresentationClassPath = '/Script/UnrealHyperTwist.HyperTwistHigherDimensionalTrainingShellActor' presentationEnvironmentOwnership = 'runtime-game-mode-and-shell-components' canonicalRenderableElementCount = 120 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.HyperTwistHigherDimensionalTrainingGameMode' runtimePresentationClassPath = '/Script/UnrealHyperTwist.HyperTwistHigherDimensionalTrainingShellActor' presentationEnvironmentOwnership = 'runtime-game-mode-and-shell-components' canonicalRenderableElementCount = 242 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.07.19' authorTag = 'HyperTwistHigherDimensionalTrainingShell' authoringScriptRelativePath = 'scripts/hypertwist_author_higher_dimensional_training_maps.py' authoredThroughGameModeClassPath = '/Script/UnrealHyperTwist.HyperTwistHigherDimensionalTrainingGameMode' classicReferenceMapHashMd5 = $ClassicReferenceHashMd5 entries = $ManifestEntries } $Manifest | ConvertTo-Json -Depth 6 | Set-Content -Path $ManifestPath -Encoding UTF8 Write-Host 'Higher-dimensional authored maps verified.'