wip(step3): 12.5 cm voxels, fall-through fixed, brick evaluator in game, organic tunnels -- speed gate FAILS

Stopped by the owner's decision (in-game lit captures were eating the round); he judges the look in
game instead.
1. Fall-through with the research field: fixed. Deep drop, gate on: 2 holds (0.50 s), min foot
   clearance +43 cm; gate off: penetrates (-278 cm). Stress fixture offsets converted to metres.
2. VOXEL_SIZE 25 -> 12.5 cm. Physical sizes kept in metres (player, steps, gate, insets, research
   field sizes, dig radii, test thresholds); class defaults that count chunks/voxels doubled
   (ClipRadius 6, RenderDistanceChunks 1536, ViewDistance*, StrateViewMargin 6,
   PawnCollisionRadius 2, SkirtCells 4, EdgeSealThickness 128, decoration spacings, density volume
   res 256). Asset values override these defaults: the owner must double his own.
   Ring scale, six workers, idle, step-2f field before the look changes: 64 m cube (519^3) 4.55 s;
   32 m cube (259^3) 0.95 s.
3. In game: tiles go through the brick evaluator (proved by a log marker); profile switch rebuilds
   the loaded world (~5,800 tiles); full-detail fill 12.4 s after spawn in the visible-window test.
4. Look: round winding tunnels, per-column ellipsoid/lattice caches. All step-2f geometry gates pass
   at 12.5 cm (0 crumbs, 1 solid component per profile, solid share 69-87%). 259^3 ring, six workers:
   1.08-2.02 s (worst HyperComplex) -- the 600 ms gate FAILS. Lit captures failed (hidden/off-screen
   window, then blank frames); not pursued.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-01 21:21:45 +02:00
1 parent ca78352e43
commit bc0f9ee791
33 files changed
+2691 -803

No files matched your search

+295 -8
View File
@@ -1,6 +1,6 @@
[CmdletBinding()]
param(
[ValidateSet('canonical', 'owner', 'probe', 'perf', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'parity', 'horizon', 'horizon-render', 'determinism', 'symbolize-stacks', 'region-eval', 'step2d', 'step2e', 'step2f', 'step2f-capture')]
[ValidateSet('canonical', 'owner', 'probe', 'perf', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'parity', 'horizon', 'horizon-render', 'determinism', 'symbolize-stacks', 'region-eval', 'step2d', 'step2e', 'step2f', 'step3-scale', 'step2f-capture', 'step3-game')]
[string]$Scenario = 'canonical',
[switch]$Build,
[switch]$EnableRender,
@@ -21,7 +21,7 @@ param(
[ValidateRange(0, 8)]
[int]$HorizonLevel = 5,
[ValidateRange(1, 100000)]
[int]$HorizonRenderDistanceChunks = 768,
[int]$HorizonRenderDistanceChunks = 1536,
[ValidateRange(0, 7)]
[int]$Step2fProfile = 0,
[ValidateSet('inside', 'wall-closeup')]
@@ -64,6 +64,8 @@ $LaunchLedger = [System.Collections.Generic.List[object]]::new()
$BuildRecord = [ordered]@{ requested = [bool]$Build; status = 'not_requested' }
$AssetEvidence = @()
$CommandletMetrics = [ordered]@{}
$Step3ScaleMetrics = [ordered]@{ requested = $Scenario -eq 'step3-scale'; status = 'not_run' }
$Step3GameMetrics = [ordered]@{ requested = $Scenario -eq 'step3-game'; status = 'not_run' }
$GameMetrics = [ordered]@{}
$HorizonMetrics = [ordered]@{
requested = $Scenario -in @('horizon', 'horizon-render', 'step2f-capture')
@@ -257,6 +259,56 @@ public static class VoxelForgeTestMemoryStatus {
'@
}
if (-not ('VoxelForgeTestSystemTimes' -as [type])) {
Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
using System.Threading;
public static class VoxelForgeTestSystemTimes {
[StructLayout(LayoutKind.Sequential)]
public struct FILETIME { public uint Low; public uint High; }
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetSystemTimes(out FILETIME idle, out FILETIME kernel, out FILETIME user);
private static ulong ToUInt64(FILETIME value) { return ((ulong)value.High << 32) | value.Low; }
public static double ReadBusyPercent(int intervalMilliseconds) {
FILETIME idle0, kernel0, user0, idle1, kernel1, user1;
if (!GetSystemTimes(out idle0, out kernel0, out user0)) throw new InvalidOperationException("GetSystemTimes failed before sample.");
Thread.Sleep(intervalMilliseconds);
if (!GetSystemTimes(out idle1, out kernel1, out user1)) throw new InvalidOperationException("GetSystemTimes failed after sample.");
ulong idle = ToUInt64(idle1) - ToUInt64(idle0);
ulong kernel = ToUInt64(kernel1) - ToUInt64(kernel0);
ulong user = ToUInt64(user1) - ToUInt64(user0);
ulong total = kernel + user;
if (total == 0) return 100.0;
ulong busy = total > idle ? total - idle : 0;
return 100.0 * busy / total;
}
}
'@
}
function Wait-ForIdleMachine([string]$When) {
$deadline = [datetime]::UtcNow.AddMinutes(10)
$consecutiveIdleSamples = 0
$lastBusyPercent = 100.0
while ([datetime]::UtcNow -lt $deadline) {
Assert-NoForeignUnreal "$When idle check"
try { $lastBusyPercent = [VoxelForgeTestSystemTimes]::ReadBusyPercent(3000) }
catch { throw "Could not measure system CPU use before $When; refusing to time this run: $($_.Exception.Message)" }
if ($lastBusyPercent -le 15.0) {
++$consecutiveIdleSamples
if ($consecutiveIdleSamples -ge 2) {
Write-Host ("Machine idle before {0}: two 3-second system samples, latest busy={1:N1}%." -f $When, $lastBusyPercent)
return $lastBusyPercent
}
} else {
$consecutiveIdleSamples = 0
Write-Host ("Waiting for idle machine before {0}: 3-second system busy={1:N1}% (limit 15%)." -f $When, $lastBusyPercent)
}
}
throw ("Machine did not stay under 15% CPU busy for two 3-second samples before {0}; last={1:N1}%." -f $When, $lastBusyPercent)
}
function Assert-EnoughFreeMemory([string]$When) {
$deadline = [datetime]::UtcNow.AddMinutes(30)
$didWait = $false
@@ -397,8 +449,9 @@ function Wait-OwnedProcess($Process, [int]$TimeoutSeconds, [switch]$EnforceMemor
}
function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPath,
[string]$StdoutPath, [string]$StderrPath) {
[string]$StdoutPath, [string]$StderrPath, [switch]$ShowWindow) {
Assert-NoForeignUnreal "before $Kind launch"
$idleCpuBusyPercent = Wait-ForIdleMachine "$Kind launch"
Assert-EnoughFreeMemory "$Kind launch"
Ensure-Directory (Split-Path -Parent $LogPath)
Ensure-Directory (Split-Path -Parent $StdoutPath)
@@ -414,6 +467,7 @@ function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPa
exit_code = $null
timed_out = $false
memory_limit_hit = $false
prelaunch_cpu_busy_percent = [Math]::Round($idleCpuBusyPercent, 2)
peak_private_bytes = 0L
crash_reporters_reaped = @()
new_crashes = @()
@@ -426,12 +480,13 @@ function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPa
$isolatedLocalAppData = Join-Path $RunRoot 'LocalAppData'
Ensure-Directory $isolatedLocalAppData
$oldLocalAppData = $env:LOCALAPPDATA
$windowStyle = if ($ShowWindow) { 'Normal' } else { 'Hidden' }
try {
# Unreal resolves several engine-wide stores from LOCALAPPDATA even when -userdir and
# -LocalDataCachePath are supplied. Keep this child process inside the run directory.
$env:LOCALAPPDATA = $isolatedLocalAppData
$process = Start-Process -FilePath $EditorExe -ArgumentList $Arguments -WorkingDirectory $HostRoot `
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath -WindowStyle Hidden `
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath -WindowStyle $windowStyle `
-PassThru
try { $process.PriorityClass = [Diagnostics.ProcessPriorityClass]::BelowNormal }
catch {
@@ -579,7 +634,7 @@ function Sync-OwnerAssets {
$null = Copy-Item -LiteralPath $source -Destination $target -Force -PassThru
[void]$rows.Add([ordered]@{
name = $assetName
used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval', 'step2d', 'step2e', 'step2f', 'step2f-capture')
used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval', 'step2d', 'step2e', 'step2f', 'step3-scale', 'step2f-capture', 'step3-game')
source_path = $source
source_sha256 = Get-Sha256 $source
staged_path = $target
@@ -674,7 +729,7 @@ function Get-AssetEvidence {
$target = Join-Path $HostRoot "Content\VoxelForge\$assetName.uasset"
[void]$rows.Add([ordered]@{
name = $assetName
used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval', 'step2d', 'step2e', 'step2f', 'step2f-capture')
used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval', 'step2d', 'step2e', 'step2f', 'step3-scale', 'step2f-capture', 'step3-game')
source_path = $source
source_sha256 = Get-Sha256 $source
staged_path = $target
@@ -713,7 +768,6 @@ function New-CommonArguments([string]$Kind, [string]$LogPath, $CvarMap,
} else {
[void]$args.Add('-nullrhi')
}
[void]$args.Add('-DDC-ForceMemoryCache')
[void]$args.Add('-unattended')
[void]$args.Add('-nop4')
[void]$args.Add('-nosplash')
@@ -1011,6 +1065,147 @@ function Get-HorizonRenderMetrics($Ledger, [string]$LogPath) {
return $metrics
}
function Get-Step3GameMetrics([string]$LogPath, [string]$TracePath, $Ledger, $RenderMetrics) {
$metrics = [ordered]@{
status = 'missing'
log = $LogPath
startup_trace = $TracePath
l0_tile_count = 0
tile_generation_seconds_p50 = $null
tile_generation_seconds_p95 = $null
tile_generation_seconds_max = $null
full_detail_ring_fill_seconds = $null
full_detail_ring_lod0_tiles = $null
moving_frames = $null
moving_window_seconds = $null
moving_frame_ms_p50 = $null
moving_frame_ms_p95 = $null
moving_frame_ms_p99 = $null
moving_frame_ms_max = $null
brick_evaluator_mesher_route_seen = $false
profile_switch_applied = $false
profile_switch_rebuilt_loaded_tiles = $false
profile_switch_cleared_tile_count = 0
captures = @()
}
if (Test-Path -LiteralPath $LogPath -PathType Leaf) {
$metrics.brick_evaluator_mesher_route_seen = @(
Select-String -LiteralPath $LogPath `
-Pattern '\[VoxelForgeResearchBrickEvaluator\] mesher_grid=1' `
-ErrorAction SilentlyContinue).Count -gt 0
$tileSeconds = [System.Collections.Generic.List[double]]::new()
foreach ($match in @(Select-String -LiteralPath $LogPath `
-Pattern '\[VoxelForgeTileProfile\].*level=0\b.*aborted=0' `
-ErrorAction SilentlyContinue)) {
$seconds = [regex]::Match([string]$match.Line, 'seconds=(?<v>[-+0-9.eE]+)')
if ($seconds.Success) {
$value = [double]$seconds.Groups['v'].Value
if ($value -eq $value -and -not [double]::IsInfinity($value) -and $value -ge 0.0) {
$tileSeconds.Add($value)
}
}
}
$tileSeconds.Sort()
$metrics.l0_tile_count = $tileSeconds.Count
if ($tileSeconds.Count -gt 0) {
$metrics.tile_generation_seconds_p50 = $tileSeconds[[Math]::Max(0, [Math]::Ceiling(0.50 * $tileSeconds.Count) - 1)]
$metrics.tile_generation_seconds_p95 = $tileSeconds[[Math]::Max(0, [Math]::Ceiling(0.95 * $tileSeconds.Count) - 1)]
$metrics.tile_generation_seconds_max = $tileSeconds[$tileSeconds.Count - 1]
}
$perfLine = @(Select-String -LiteralPath $LogPath -Pattern '\[VoxelForgeResearchGamePerf\]' -ErrorAction SilentlyContinue | Select-Object -Last 1)
if ($perfLine.Count -gt 0) {
$line = [string]$perfLine[0].Line
foreach ($key in @('moving_frames', 'moving_window_s', 'p50_ms', 'p95_ms', 'p99_ms', 'max_ms')) {
$value = [regex]::Match($line, ('{0}=(?<v>[-+0-9.eE]+)' -f [regex]::Escape($key)))
if ($value.Success) {
$target = switch ($key) {
'moving_frames' { 'moving_frames' }
'moving_window_s' { 'moving_window_seconds' }
'p50_ms' { 'moving_frame_ms_p50' }
'p95_ms' { 'moving_frame_ms_p95' }
'p99_ms' { 'moving_frame_ms_p99' }
'max_ms' { 'moving_frame_ms_max' }
}
$metrics[$target] = [double]$value.Groups['v'].Value
}
}
}
$switchLine = @(Select-String -LiteralPath $LogPath -Pattern '\[VoxelForgeResearchProfileSwitch\] applied=1' -ErrorAction SilentlyContinue | Select-Object -Last 1)
$rebuildLine = @(Select-String -LiteralPath $LogPath -Pattern 'research_field_selection_changed.*rebuilding loaded world' -ErrorAction SilentlyContinue | Select-Object -Last 1)
if ($switchLine.Count -gt 0) { $metrics.profile_switch_applied = $true }
if ($rebuildLine.Count -gt 0) {
$metrics.profile_switch_rebuilt_loaded_tiles = [regex]::IsMatch(
[string]$switchLine[0].Line, 'loaded_tiles=(?<n>[1-9][0-9]*)')
$regenerate = @(Select-String -LiteralPath $LogPath `
-Pattern '\[VoxelWorld\] RegenerateAllChunks .*cleared (?<n>[0-9]+) tiles' `
-ErrorAction SilentlyContinue |
Where-Object { $_.LineNumber -gt $rebuildLine[0].LineNumber } |
Select-Object -First 1)
if ($regenerate.Count -gt 0) {
$cleared = [regex]::Match([string]$regenerate[0].Line, 'cleared (?<n>[0-9]+) tiles')
if ($cleared.Success) {
$metrics.profile_switch_cleared_tile_count = [int]$cleared.Groups['n'].Value
$metrics.profile_switch_rebuilt_loaded_tiles =
$metrics.profile_switch_rebuilt_loaded_tiles -and
$metrics.profile_switch_cleared_tile_count -gt 0
}
} else {
$metrics.profile_switch_rebuilt_loaded_tiles = $false
}
}
$captureLines = @(Select-String -LiteralPath $LogPath -Pattern '\[VoxelForgeTestCeiling\] capture_index=' -ErrorAction SilentlyContinue)
foreach ($captureLine in $captureLines) {
$index = [regex]::Match([string]$captureLine.Line, 'capture_index=(?<v>[12])')
$accepted = [regex]::Match([string]$captureLine.Line, 'capture_requested=(?<v>[01])')
if ($index.Success) {
$metrics.captures += [ordered]@{
index = [int]$index.Groups['v'].Value
requested = $accepted.Success -and $accepted.Groups['v'].Value -eq '1'
line = [string]$captureLine.Line
}
}
}
}
if (Test-Path -LiteralPath $TracePath -PathType Leaf) {
$ringEvents = [System.Collections.Generic.List[object]]::new()
foreach ($line in @(Get-Content -LiteralPath $TracePath -ErrorAction SilentlyContinue)) {
if ([string]::IsNullOrWhiteSpace($line)) { continue }
try {
$event = $line | ConvertFrom-Json -ErrorAction Stop
if ([string](Get-JsonValue $event 'type' '') -eq 'lod0_ring_ready') {
$ringEvents.Add($event)
}
} catch { }
}
if ($ringEvents.Count -gt 0) {
$metrics.full_detail_ring_fill_seconds = [double](Get-JsonValue $ringEvents[0] 'duration_s' 0.0)
$metrics.full_detail_ring_lod0_tiles = [int](Get-JsonValue $ringEvents[0] 'lod0_tiles' 0)
}
}
$metrics.render = $RenderMetrics
$metrics.streaming = Get-StreamingMetrics $LogPath
$metrics.launch = if ($null -ne $Ledger) {
[ordered]@{
status = Get-JsonValue $Ledger 'status' 'missing'
peak_private_bytes = Get-JsonValue $Ledger 'peak_private_bytes' 0
pid = Get-JsonValue $Ledger 'pid' $null
}
} else { $null }
$step3Passed = ($metrics.l0_tile_count -gt 0 -and
$null -ne $metrics.full_detail_ring_fill_seconds -and
$metrics.moving_frames -gt 0 -and
$metrics.brick_evaluator_mesher_route_seen -and
$metrics.profile_switch_applied -and
$metrics.profile_switch_rebuilt_loaded_tiles -and
$metrics.profile_switch_cleared_tile_count -gt 0 -and
@($metrics.captures | Where-Object { $_.requested }).Count -ge 2 -and
$null -ne $Ledger -and (Get-JsonValue $Ledger 'status' 'failed') -eq 'passed')
$metrics.status = if ($step3Passed) {
'passed'
} else { 'failed' }
return $metrics
}
function Compare-DeterminismDumps([string]$DefaultPath, [string]$CrtFma3OffPath) {
$defaultJson = Read-JsonFile $DefaultPath
$fmaJson = Read-JsonFile $CrtFma3OffPath
@@ -1128,7 +1323,7 @@ function Get-CollisionGateStressMetrics([string]$LogPath) {
'spawn_height_vox', 'deep_drop',
'gate_holds', 'gate_total_s', 'max_gate_s', 'full_stops',
'full_stop_total_s', 'collision_gate', 'gate_progress_m',
'horizontal_gate_probe', 'support_tile',
'full_stop_probe_applied', 'full_stop_probe_observed', 'support_tile',
'support_first_check_to_submit_s',
'support_first_check_to_collision_ready_s', 'test_support_tile',
'test_support_first_check_to_submit_s',
@@ -1612,6 +1807,33 @@ try {
}
}
}
'step3-scale' {
$scaleLog = Join-Path $RunRoot 'Logs\step3-scale.log'
$args = New-CommonArguments 'commandlet' $scaleLog (Merge-Cvars $baseCvars)
$args += New-CommandletArguments $commandletOut 'walk,export' ($Assets -eq 'owner') $false 128
$researchOut = Join-Path $PluginRoot "Saved\Research\step3\scale\$Label"
$args += @('-step3scale', '-regionworkers=6', '-profile=0', ('-researchout="{0}"' -f $researchOut))
$commandletLedger = Invoke-UnrealLaunch 'step3-scale-commandlet' $args `
$scaleLog (Join-Path $RunRoot 'Logs\step3-scale.stdout.log') `
(Join-Path $RunRoot 'Logs\step3-scale.stderr.log')
$scaleReportPath = Join-Path $researchOut 'step3_scale_benchmark.json'
$scaleReport = Read-JsonFile $scaleReportPath
if ($null -eq $scaleReport) {
$Step3ScaleMetrics = [ordered]@{ requested = $true; status = 'missing'; output = $researchOut; report = $scaleReportPath }
Add-Failure "step3 scale benchmark did not produce $scaleReportPath"
} else {
$Step3ScaleMetrics = [ordered]@{
requested = $true
status = if ((Get-JsonValue $scaleReport 'pass' $false) -and $commandletLedger.status -eq 'passed') { 'passed' } else { 'failed' }
output = $researchOut
report = $scaleReportPath
result = $scaleReport
}
if (-not (Get-JsonValue $scaleReport 'pass' $false)) {
Add-Failure 'step3 scale sample, determinism, or six-worker gate failed'
}
}
}
'symbolize-stacks' {
if (([string]::IsNullOrWhiteSpace($StackSamplesPath)) -or -not (Test-Path -LiteralPath $StackSamplesPath -PathType Leaf)) {
throw "-StackSamplesPath must name an existing raw sampler CSV."
@@ -1718,6 +1940,67 @@ try {
}
}
}
'step3-game' {
$tracePath = Join-Path $RunRoot 'step3_game_trace.jsonl'
$gameLog = Join-Path $RunRoot 'Logs\step3-game.log'
$baseCvars['voxel.TestRenderDistanceChunks'] = $HorizonRenderDistanceChunks
$baseCvars['voxel.TestClipRadius'] = 6
$baseCvars['voxel.TestMaxClipLevel'] = $HorizonLevel
$baseCvars['voxel.StartupTraceFile'] = $tracePath
$baseCvars['voxel.StartupTraceMaxTiles'] = 16384
$baseCvars['voxel.OuterClassifierMode'] = 0
$baseCvars['voxel.ResearchParameterizedCave'] = 1
$baseCvars['voxel.ResearchParameterizedCaveSet'] = 0
$baseCvars['voxel.ProfileTileGeneration'] = 1
$baseCvars['voxel.ResearchGamePerf'] = 1
$baseCvars['voxel.TestCeilingCameraX'] = 0
$baseCvars['voxel.TestCeilingCameraY'] = 0
$baseCvars['voxel.TestCeilingCameraZ'] = -8800
$baseCvars['voxel.TestCeilingCameraPitch'] = 0
$baseCvars['voxel.TestCeilingCameraYaw'] = 0
$baseCvars['voxel.TestCeilingView'] = 1
$baseCvars['voxel.TestCeilingCaptureDelaySeconds'] = 25
$baseCvars['voxel.TestCeilingSecondCaptureDelaySeconds'] = 70
$baseCvars['voxel.TestCeilingCameraMoveStartSeconds'] = 35
$baseCvars['voxel.TestCeilingCameraMoveDurationSeconds'] = 25
$baseCvars['voxel.TestCeilingCameraMoveSpeedCmPerSecond'] = 600
$baseCvars['voxel.TestCeilingCameraMoveDirection'] = '1,0,0'
$baseCvars['voxel.TestResearchProfileSwitchAfterSeconds'] = 30
$baseCvars['voxel.TestResearchProfileSwitchTo'] = 1
$baseCvars['voxel.TestExitSeconds'] = 85
$args = New-CommonArguments 'game' $gameLog (Merge-Cvars $baseCvars) $true
# Keep a real RHI game viewport for the two lit screenshots. The offscreen
# viewport accepted high-res requests but only wrote its blank startup frame.
$args = @($args | Where-Object { $_ -ne '-RenderOffscreen' })
$args += '-windowed'
$gameLedger = Invoke-UnrealLaunch 'step3-game-brick-evaluator' $args `
$gameLog (Join-Path $RunRoot 'Logs\step3-game.stdout.log') `
(Join-Path $RunRoot 'Logs\step3-game.stderr.log') -ShowWindow
$GameMetrics = Get-StreamingMetrics $gameLog
$HorizonMetrics = [ordered]@{ requested = $false; status = 'ring_and_tile_metrics_are_in_step3_game' }
$HorizonRenderMetrics = Get-HorizonRenderMetrics $gameLedger $gameLog
$Step3GameMetrics = Get-Step3GameMetrics $gameLog $tracePath $gameLedger $HorizonRenderMetrics
$captureDirectory = Join-Path $PluginRoot "Saved\Research\step3\captures\$Label"
Ensure-Directory $captureDirectory
$images = @($HorizonRenderMetrics.images | Sort-Object modified_utc)
if ($images.Count -lt 2) {
Add-Failure "step3 game path did not produce both lit captures; found $($images.Count)"
} else {
$capturePaths = [System.Collections.Generic.List[string]]::new()
$captureNames = @('OpenVaults_before_switch.png', 'TightNetwork_after_switch.png')
for ($captureIndex = 0; $captureIndex -lt 2; ++$captureIndex) {
$sourceImage = $images[$images.Count - 2 + $captureIndex]
$destination = Join-Path $captureDirectory $captureNames[$captureIndex]
Copy-Item -LiteralPath $sourceImage.path -Destination $destination -Force
$capturePaths.Add($destination)
}
$HorizonRenderMetrics.saved_captures = @($capturePaths)
$Step3GameMetrics.saved_captures = @($capturePaths)
}
if ($Step3GameMetrics.status -ne 'passed') {
Add-Failure "step3 game path, profile rebuild, lit capture, ring, or moving frame metric failed: $($Step3GameMetrics.status)"
}
}
'determinism' {
$defaultDump = Join-Path $RunRoot 'tile_hashes_default.json'
$crtFma3OffDump = Join-Path $RunRoot 'tile_hashes_crt_fma3_off.json'
@@ -2060,6 +2343,8 @@ $result = [ordered]@{
region_eval = if ($Scenario -eq 'region-eval') { $CommandletMetrics } else { $null }
step2e = if ($Scenario -eq 'step2e') { $CommandletMetrics } else { $null }
step2f = if ($Scenario -eq 'step2f') { $CommandletMetrics } else { $null }
step3_scale = $Step3ScaleMetrics
step3_game = $Step3GameMetrics
game = $GameMetrics
horizon = $HorizonMetrics
horizon_render = $HorizonRenderMetrics
@@ -2101,6 +2386,8 @@ try {
[void]$summaryLines.Add("geometry_hash=$($result.geometry_hash)")
[void]$summaryLines.Add("obj_sha256=$($result.obj_sha256)")
[void]$summaryLines.Add("region_eval=$(ConvertTo-CompactJson $result.region_eval)")
[void]$summaryLines.Add("step3_scale=$(ConvertTo-CompactJson $result.step3_scale)")
[void]$summaryLines.Add("step3_game=$(ConvertTo-CompactJson $result.step3_game)")
[void]$summaryLines.Add("surface_fall=$(ConvertTo-CompactJson $result.surface_fall)")
[void]$summaryLines.Add("strate_crossing=$(ConvertTo-CompactJson $result.strate_crossing)")
[void]$summaryLines.Add("visual_staleness=$(ConvertTo-CompactJson $result.visual_staleness)")