Files
VoxelForge/Tools/TestVoxelForgeCoreAdapterHost.ps1
Fr0zkaandClaude Opus 5.5 874c796c65 wip(assembler): M1 round 1 -- manifest format, block contract, bound audit, native Heightfield2D
Luna, M1 round 1 of 4 (plan in Standalone/ASSEMBLER_M1_REPORT.md):
- Versioned fixed-scale world manifest (the shipped plan format) and the Core block
  contract: point evaluation, interval-or-unknown, influence bound, detail envelope, channels.
- Interval audit: 256 boxes / 6,912 samples, 0 missed, 0 unknown; a deliberately false bound
  is detected (negative control).
- Native Heightfield2D with biome input: 4,096-point hash fae2721c052aec5c identical at
  W1/W8/W16; point queries 2.059 ms vs 62.115 ms legacy -- but NOT a match yet (mean density
  error 274 voxels, 2,261 sign mismatches vs the legacy surface). 45 mesh-free Core queries
  match direct evaluation exactly. Existing cave world unchanged (walkability, 64/64 tiles).
- Open: collision readiness, a valid full-view timing (viewer captures were occluded/empty;
  top-down height maps are the only usable pair).
Also carries the design doc's owner decisions (fixed water; player edits unrestricted, route
law for automatic changes only) and three plugin files + BuildHost scripts that earlier round
commits had missed (also fixed on unreal/core-integration as 2574b56).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-10-09 00:04:30 +02:00

204 lines
9.8 KiB
PowerShell

[CmdletBinding()]
param(
[ValidateRange(60, 900)][int]$TimeoutSeconds = 900
)
# Run the owner-asset adapter and Core parity gate in the isolated BuildHost only.
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$PluginRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
$HostRoot = Join-Path $PluginRoot 'Saved\BuildHost\HostProject'
$HostProject = Join-Path $HostRoot 'HostProject.uproject'
$StagePlugin = Join-Path $HostRoot 'Plugins\VoxelForge'
$OwnerAssetRoot = Join-Path ([IO.Path]::GetFullPath((Join-Path $PluginRoot '..\..'))) 'Content\VoxelForge'
$OutputRoot = Join-Path $StagePlugin 'Saved\CoreAdapterValidation'
$OutputDirectory = Join-Path $OutputRoot 'Output'
$LogDirectory = Join-Path $OutputRoot 'Logs'
$LocalAppData = Join-Path $OutputRoot 'LocalAppData'
$UserDirectory = Join-Path $OutputRoot 'User'
$DerivedDataCache = Join-Path $PluginRoot 'Saved\BuildHost\VFCoreAdapterDDC'
$ZenData = Join-Path $OutputRoot 'ZenData'
$EditorCmd = 'E:\Program Files\Epic Games\UE_5.7\Engine\Binaries\Win64\UnrealEditor-Cmd.exe'
$LogPath = Join-Path $LogDirectory 'CoreAdapterValidation.log'
$StdoutPath = Join-Path $LogDirectory 'CoreAdapterValidation.stdout.log'
$StderrPath = Join-Path $LogDirectory 'CoreAdapterValidation.stderr.log'
$WorldJson = Join-Path $OutputDirectory 'owner_world.json'
$MemoryLimitBytes = 8GB
function Assert-UnderPath([string]$Candidate, [string]$Root) {
$candidateFull = [IO.Path]::GetFullPath($Candidate).TrimEnd('\', '/')
$rootFull = [IO.Path]::GetFullPath($Root).TrimEnd('\', '/')
if (-not ($candidateFull.Equals($rootFull, [StringComparison]::OrdinalIgnoreCase) -or
$candidateFull.StartsWith($rootFull + '\', [StringComparison]::OrdinalIgnoreCase))) {
throw "Refusing a commandlet path outside BuildHost: $candidateFull"
}
}
Assert-UnderPath $HostRoot (Join-Path $PluginRoot 'Saved\BuildHost')
Assert-UnderPath $OutputRoot (Join-Path $PluginRoot 'Saved\BuildHost')
foreach ($required in @($HostProject, $EditorCmd)) {
if (-not (Test-Path -LiteralPath $required -PathType Leaf)) {
throw "Required BuildHost commandlet input is missing: $required"
}
}
$foreignEditors = @(Get-Process -ErrorAction SilentlyContinue |
Where-Object { $_.ProcessName -like 'UnrealEditor*' })
if ($foreignEditors.Count -gt 0) {
$ids = ($foreignEditors | ForEach-Object { "$($_.ProcessName):$($_.Id)" }) -join ', '
throw "An UnrealEditor process is already running; leaving it untouched: $ids"
}
foreach ($assetName in @('DA_Settings', 'DA_Strate1', 'DA_Strate2', 'DA_Strate3', 'DA_Strate4')) {
$ownerAsset = Join-Path $OwnerAssetRoot "$assetName.uasset"
$stagedAsset = Join-Path $HostRoot "Content\VoxelForge\$assetName.uasset"
if (-not (Test-Path -LiteralPath $ownerAsset -PathType Leaf) -or
-not (Test-Path -LiteralPath $stagedAsset -PathType Leaf)) {
throw "Owner or BuildHost asset is missing: $assetName"
}
if ((Get-FileHash -LiteralPath $ownerAsset -Algorithm SHA256).Hash -ne
(Get-FileHash -LiteralPath $stagedAsset -Algorithm SHA256).Hash) {
throw "BuildHost asset copy differs from the owner asset: $assetName"
}
}
foreach ($directory in @($OutputDirectory, $LogDirectory, $LocalAppData,
$UserDirectory, $DerivedDataCache, $ZenData)) {
[IO.Directory]::CreateDirectory($directory) | Out-Null
}
$CrashRoots = @((Join-Path $HostRoot 'Saved\Crashes'), (Join-Path $StagePlugin 'Saved\Crashes'))
$CrashBefore = @{}
foreach ($CrashRoot in $CrashRoots) {
if (Test-Path -LiteralPath $CrashRoot -PathType Container) {
foreach ($Crash in @(Get-ChildItem -LiteralPath $CrashRoot -Directory -Filter 'UECC-*' -Recurse -Force)) {
$CrashBefore[$Crash.FullName.ToLowerInvariant()] = $Crash.LastWriteTimeUtc.Ticks
}
}
}
function Get-NewCrashFolders {
$Found = [Collections.Generic.List[string]]::new()
foreach ($CrashRoot in $CrashRoots) {
if (Test-Path -LiteralPath $CrashRoot -PathType Container) {
foreach ($Crash in @(Get-ChildItem -LiteralPath $CrashRoot -Directory -Filter 'UECC-*' -Recurse -Force)) {
if (-not $CrashBefore.ContainsKey($Crash.FullName.ToLowerInvariant())) {
[void]$Found.Add($Crash.FullName)
}
}
}
}
return @($Found.ToArray())
}
$Arguments = @(
('-project="{0}"' -f $HostProject),
'-nullrhi', '-unattended', '-nop4', '-nosplash', '-NoSound',
'-stdout', '-FullStdOutLogOutput', '-forcelogflush', '-NoZenAutoLaunch',
'-notraceserver', '-nocrashreports', '-ddc=NoZenLocalFallback',
('-userdir="{0}"' -f $UserDirectory),
('-ZenDataPath="{0}"' -f $ZenData),
('-LocalDataCachePath="{0}"' -f $DerivedDataCache),
'-run=VoxelForgeExplore', '-seed=0', '-archetype=TunnelNetwork', '-slot=0',
'-modes=export', '-blockearlyout=0', '-gameconfig=1', '-standaloneworldexport=1',
'-settingsref=/Game/VoxelForge/DA_Settings.DA_Settings',
('-out="{0}"' -f $OutputDirectory)
)
$OldLocalAppData = $env:LOCALAPPDATA
$env:LOCALAPPDATA = $LocalAppData
try {
$PeakPrivateBytes = [int64]0
$Succeeded = $false
for ($Attempt = 1; $Attempt -le 2 -and -not $Succeeded; ++$Attempt) {
$AttemptLogPath = if ($Attempt -eq 1) { $LogPath } else {
Join-Path $LogDirectory 'CoreAdapterValidation.retry.log'
}
$AttemptStdoutPath = if ($Attempt -eq 1) { $StdoutPath } else {
Join-Path $LogDirectory 'CoreAdapterValidation.retry.stdout.log'
}
$AttemptStderrPath = if ($Attempt -eq 1) { $StderrPath } else {
Join-Path $LogDirectory 'CoreAdapterValidation.retry.stderr.log'
}
$AttemptArguments = @($Arguments) + ('-abslog="{0}"' -f $AttemptLogPath)
Write-Host "BuildHost adapter commandlet attempt $Attempt of 2; normal priority."
$Process = Start-Process -FilePath $EditorCmd -ArgumentList $AttemptArguments -WorkingDirectory $HostRoot `
-RedirectStandardOutput $AttemptStdoutPath -RedirectStandardError $AttemptStderrPath `
-WindowStyle Hidden -PassThru
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal
$StartedUtc = [datetime]::UtcNow
while (-not $Process.HasExited) {
Start-Sleep -Seconds 2
$Process.Refresh()
if (-not $Process.HasExited) {
$PeakPrivateBytes = [Math]::Max($PeakPrivateBytes, [int64]$Process.PrivateMemorySize64)
}
if (([datetime]::UtcNow - $StartedUtc).TotalSeconds -gt $TimeoutSeconds) {
Stop-Process -Id $Process.Id -Force -ErrorAction SilentlyContinue
throw "BuildHost adapter commandlet exceeded the $TimeoutSeconds-second time box."
}
if (-not $Process.HasExited -and $Process.PrivateMemorySize64 -gt $MemoryLimitBytes) {
Stop-Process -Id $Process.Id -Force -ErrorAction SilentlyContinue
throw 'BuildHost adapter commandlet exceeded the 8 GiB private-memory limit.'
}
}
$Process.WaitForExit()
$Process.Refresh()
$ProcessExitCode = $Process.ExitCode
if ([string]::IsNullOrEmpty([string]$ProcessExitCode) -and (Test-Path -LiteralPath $AttemptLogPath -PathType Leaf)) {
$CompletedLog = Get-Content -LiteralPath $AttemptLogPath -Raw
if ((($CompletedLog -match 'Commandlet->Main return this error code: 0') -or
($CompletedLog -match 'Commandlet VoxelForgeExploreCommandlet_\d+ finished execution \(result 0\)')) -and
($CompletedLog -match '\[VoxelForgeCoreAdapter\] pass schema=1')) {
# Unreal returned its commandlet result through the log, but Start-Process did not
# retain an ExitCode value for this editor process handle.
$ProcessExitCode = 0
}
}
if ($ProcessExitCode -eq -1073741502 -and $Attempt -eq 1) {
Write-Warning 'BuildHost adapter commandlet exited with 0xC0000142; retrying once.'
continue
}
if ($ProcessExitCode -ne 0) {
$FailedRunCrashes = @(Get-NewCrashFolders)
$CrashDetail = if ($FailedRunCrashes.Count -gt 0) {
" New UECC folders: $($FailedRunCrashes -join '; ')."
} else { '' }
throw "BuildHost adapter commandlet attempt $Attempt failed with exit code '$ProcessExitCode'.$CrashDetail See $AttemptLogPath."
}
$LogPath = $AttemptLogPath
$Succeeded = $true
}
if (-not $Succeeded) {
throw 'BuildHost adapter commandlet did not complete successfully.'
}
} finally {
if ($null -eq $OldLocalAppData) {
Remove-Item Env:LOCALAPPDATA -ErrorAction SilentlyContinue
} else {
$env:LOCALAPPDATA = $OldLocalAppData
}
}
if (-not (Test-Path -LiteralPath $WorldJson -PathType Leaf)) {
throw "The adapter commandlet did not write $WorldJson."
}
$BuildLogText = Get-Content -LiteralPath $LogPath -Raw
foreach ($WorkerCount in @(1, 8, 16)) {
$ExpectedAdapterPass = "\[VoxelForgeCoreAdapter\] pass schema=1 workers=$WorkerCount asset_clip_radius=\d+ asset_max_level=\d+ reference_clip_radius=3 reference_max_level=8 tiles=2226 density=0xdb2991c73215af4c mesh=0x8236c5edd7fc8e4f payload=0x4c48fa75a543d58e topology=0xbea22e76b34a4dfc walk=5642/5642"
if ($BuildLogText -notmatch $ExpectedAdapterPass) {
throw "The BuildHost log is missing the exact Core adapter parity result for workers=$WorkerCount. See $LogPath."
}
}
$NewCrashes = @(Get-NewCrashFolders)
if ($NewCrashes.Count -gt 0) {
throw "New UECC crash folders appeared during adapter validation: $($NewCrashes -join '; ')"
}
Write-Host 'VOXELFORGE_CORE_ADAPTER_PASS tiles=2226 walkability=5642/5642'
Write-Host "Core adapter log: $LogPath"
Write-Host "Schema-v1 JSON: $WorldJson"
Write-Host "Peak commandlet private bytes sampled: $PeakPrivateBytes"