param( [string]$ProjectRoot = 'C:\HyperTwist', [string]$UnrealEditorCmdPath = 'C:\Program Files\Epic Games\UE_5.7\Engine\Binaries\Win64\UnrealEditor-Cmd.exe', [string]$PythonScriptPath, [ValidateSet('d3d11', 'nullrhi')] [string]$RenderBackend = 'd3d11' ) $ErrorActionPreference = 'Stop' if ([string]::IsNullOrWhiteSpace($PythonScriptPath)) { $PythonScriptPath = Join-Path $ProjectRoot 'scripts\hypertwist_author_classic_cube_training_maps.py' } $UProjectPath = Join-Path $ProjectRoot 'UnrealHyperTwist\UnrealHyperTwist.uproject' $AuthoringStateRoot = Join-Path $ProjectRoot 'UnrealHyperTwist\Saved\HyperTwistMapAuthoring' $ClassicMapPath = Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Maps\L_HyperTwist_ClassicTraining.umap' $FollowAlongMapPath = Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Maps\L_HyperTwist_FollowAlongTraining.umap' $ExpectedMaterialPaths = @( (Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Materials\M_HT_ClassicCubeFaceMaster.uasset'), (Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Materials\MI_HT_ClassicCube_Up.uasset'), (Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Materials\MI_HT_ClassicCube_Down.uasset'), (Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Materials\MI_HT_ClassicCube_Front.uasset'), (Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Materials\MI_HT_ClassicCube_Back.uasset'), (Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Materials\MI_HT_ClassicCube_Left.uasset'), (Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Materials\MI_HT_ClassicCube_Right.uasset'), (Join-Path $ProjectRoot 'UnrealHyperTwist\Content\HyperTwistTraining\Materials\MI_HT_ClassicCube_Internal.uasset') ) 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 "Classic-cube map authoring script was not found at '$PythonScriptPath'." } [System.IO.Directory]::CreateDirectory($AuthoringStateRoot) | Out-Null $NormalizedPythonScriptPath = $PythonScriptPath -replace '\\', '/' $AuthoredTargets = @( @{ Label = 'classic' ExpectedMapPath = $ClassicMapPath }, @{ Label = 'followalong' ExpectedMapPath = $FollowAlongMapPath } ) foreach ($Target in $AuthoredTargets) { $MarkerPath = Join-Path $AuthoringStateRoot "$($Target.Label)-complete.json" $BackupPath = Join-Path $AuthoringStateRoot "$($Target.Label)-pre-authoring.umap" $HadOriginalMap = Test-Path $Target.ExpectedMapPath $AuthoringSucceeded = $false Remove-Item $MarkerPath, $BackupPath -Force -ErrorAction SilentlyContinue if ($HadOriginalMap) { Copy-Item $Target.ExpectedMapPath $BackupPath -Force } try { $env:HYPERTWIST_CLASSIC_CUBE_MAP_KIND = $Target.Label $env:HYPERTWIST_CLASSIC_CUBE_AUTHORING_MARKER = $MarkerPath $AuthoringStartedAtUtc = [DateTime]::UtcNow $BackendArguments = if ($RenderBackend -eq 'nullrhi') { @('-nullrhi') } else { @('-d3d11', '-RenderOffscreen') } Write-Host "Authoring HyperTwist classic cube training map target '$($Target.Label)' through UnrealEditor-Cmd ($RenderBackend)..." & $UnrealEditorCmdPath ` $UProjectPath ` "-ExecutePythonScript=$NormalizedPythonScriptPath" ` -unattended ` -nop4 ` @BackendArguments ` -nosound ` -nosplash ` -stdout ` -FullStdOutLogOutput ` -log $EditorExitCode = $LASTEXITCODE $FreshMapExists = (Test-Path $Target.ExpectedMapPath) -and ( (Get-Item $Target.ExpectedMapPath).LastWriteTimeUtc -ge $AuthoringStartedAtUtc.AddSeconds(-2) ) $CompletionMarkerIsValid = $false if (Test-Path $MarkerPath) { try { $CompletionMarker = Get-Content $MarkerPath -Raw | ConvertFrom-Json $CompletionMarkerIsValid = ` $CompletionMarker.schemaVersion -eq 1 -and ` $CompletionMarker.stage -eq 'complete' -and ` $CompletionMarker.target -eq $Target.Label -and ` $CompletionMarker.mapAssetPath -eq ( "/Game/HyperTwistTraining/Maps/" + [System.IO.Path]::GetFileNameWithoutExtension($Target.ExpectedMapPath) ) -and ` @($CompletionMarker.actorLabels).Count -ge 7 } catch { Write-Warning "Ignoring malformed authoring completion marker '$MarkerPath': $($_.Exception.Message)" } } if (-not $FreshMapExists -or -not $CompletionMarkerIsValid) { throw "Classic-cube map authoring did not produce a fresh, fully validated '$($Target.Label)' map (editor exit code $EditorExitCode)." } if ($EditorExitCode -ne 0) { Write-Warning "UnrealEditor-Cmd exited with code $EditorExitCode after the complete map marker was durably written. The validated authored map is retained." } $AuthoringSucceeded = $true } finally { Remove-Item Env:\HYPERTWIST_CLASSIC_CUBE_MAP_KIND -ErrorAction SilentlyContinue Remove-Item Env:\HYPERTWIST_CLASSIC_CUBE_AUTHORING_MARKER -ErrorAction SilentlyContinue if (-not $AuthoringSucceeded) { if ($HadOriginalMap -and (Test-Path $BackupPath)) { Copy-Item $BackupPath $Target.ExpectedMapPath -Force Write-Warning "Restored the pre-authoring map after an incomplete '$($Target.Label)' authoring run." } elseif (Test-Path $Target.ExpectedMapPath) { Remove-Item $Target.ExpectedMapPath -Force } } Remove-Item $BackupPath -Force -ErrorAction SilentlyContinue } } foreach ($ExpectedMapPath in @($ClassicMapPath, $FollowAlongMapPath)) { if (-not (Test-Path $ExpectedMapPath)) { throw "Expected authored map was not found at '$ExpectedMapPath'." } } foreach ($ExpectedMaterialPath in $ExpectedMaterialPaths) { if (-not (Test-Path $ExpectedMaterialPath)) { throw "Expected classic-cube material asset was not found at '$ExpectedMaterialPath'." } } Write-Host 'Classic-cube authored maps verified.'