Walkable/enclosed/refined gates defined; JSONL trace joins request, worker start, build/apply and discards. Coarse-first + dynamic near reserve behind voxel.StreamingCoarseFirst / voxel.StreamingDynamicNearReserve (default 0 = S4 order): matched runs showed no walkability or full-view win and slower LOD0/1 refinement. Instruments settled: 3.1 s standalone reproduced (16.7 s was another generation route); black early captures were clear-only frames. Hashes equal S4; Core-off, M2, SurfaceWorld/FloatingIslands BuildHost pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
176 lines
7.8 KiB
PowerShell
176 lines
7.8 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Label,
|
|
[Parameter(Mandatory = $true)][string[]]$ViewerArguments,
|
|
[ValidateSet('Spawn', 'Flight')][string]$CaptureMode = 'Spawn',
|
|
[switch]$WaitForFlightDrain
|
|
)
|
|
|
|
# Capture through the viewer's D3D back buffer; no desktop or window capture is used.
|
|
# Capturer le tampon D3D du viewer; aucune capture du bureau ou de la fenetre n'est utilisee.
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
Add-Type -AssemblyName System.Drawing
|
|
|
|
$PluginRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../../../..'))
|
|
$ViewerExe = Join-Path $PluginRoot 'Standalone/bin/VoxelForgeCoreStandalone.exe'
|
|
$CaptureRoot = Join-Path $PSScriptRoot 'captures'
|
|
$LogRoot = Join-Path $PSScriptRoot 'viewer_logs'
|
|
[IO.Directory]::CreateDirectory($CaptureRoot) | Out-Null
|
|
[IO.Directory]::CreateDirectory($LogRoot) | Out-Null
|
|
|
|
if (-not ('VoxelForgeR9MemoryProbe' -as [type])) {
|
|
Add-Type -TypeDefinition @'
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
public static class VoxelForgeR9MemoryProbe {
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
|
private struct MEMORYSTATUSEX {
|
|
public uint dwLength, dwMemoryLoad;
|
|
public ulong ullTotalPhys, ullAvailPhys, ullTotalPageFile, ullAvailPageFile;
|
|
public ulong ullTotalVirtual, ullAvailVirtual, ullAvailExtendedVirtual;
|
|
}
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX status);
|
|
public static ulong AvailablePhysicalBytes() {
|
|
var status = new MEMORYSTATUSEX();
|
|
status.dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
|
|
if (!GlobalMemoryStatusEx(ref status)) throw new InvalidOperationException("GlobalMemoryStatusEx failed.");
|
|
return status.ullAvailPhys;
|
|
}
|
|
}
|
|
'@
|
|
}
|
|
|
|
if ([VoxelForgeR9MemoryProbe]::AvailablePhysicalBytes() -lt 4GB) {
|
|
throw 'Viewer launch skipped: less than 4 GiB of free physical memory.'
|
|
}
|
|
if (Get-Process UnrealEditor* -ErrorAction SilentlyContinue) {
|
|
throw 'Viewer launch skipped: an UnrealEditor process is still running.'
|
|
}
|
|
if (-not (Test-Path -LiteralPath $ViewerExe -PathType Leaf)) {
|
|
throw "Standalone viewer is missing: $ViewerExe"
|
|
}
|
|
|
|
Add-Type -AssemblyName System.Drawing
|
|
$SafeLabel = $Label -replace '[^A-Za-z0-9_-]', '_'
|
|
$SequenceSuffix = if ($CaptureMode -eq 'Spawn') { 'spawn' } else { 'mid_sprint' }
|
|
$CapturePrefix = Join-Path $CaptureRoot "${SafeLabel}_${SequenceSuffix}"
|
|
$RunArguments = [Collections.Generic.List[string]]::new()
|
|
foreach ($Argument in $ViewerArguments) { $RunArguments.Add($Argument) }
|
|
if ($CaptureMode -eq 'Spawn') {
|
|
$RunArguments.Add("--capture-spawn-sequence-prefix=$CapturePrefix")
|
|
$RunArguments.Add('--quit-after-spawn-captures')
|
|
$ExpectedIndices = @(0, 1, 2)
|
|
$TimeLimitSeconds = 60
|
|
} else {
|
|
$RunArguments.Add("--capture-flight-sequence-prefix=$CapturePrefix")
|
|
if (-not $WaitForFlightDrain) { $RunArguments.Add('--flight-capture-close-index=1') }
|
|
$ExpectedIndices = @(1)
|
|
$TimeLimitSeconds = if ($WaitForFlightDrain) { 900 } else { 45 }
|
|
}
|
|
$StdoutPath = Join-Path $LogRoot "$SafeLabel`_$SequenceSuffix.stdout.log"
|
|
$StderrPath = Join-Path $LogRoot "$SafeLabel`_$SequenceSuffix.stderr.log"
|
|
$QuotedArguments = @($RunArguments | ForEach-Object { '"' + $_.Replace('"', '\"') + '"' })
|
|
$ArgumentLine = $QuotedArguments -join ' '
|
|
$Process = Start-Process -FilePath $ViewerExe -ArgumentList $ArgumentLine `
|
|
-WorkingDirectory (Split-Path -Parent $ViewerExe) -PassThru -WindowStyle Normal `
|
|
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
|
|
$Started = [Diagnostics.Stopwatch]::StartNew()
|
|
try {
|
|
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal
|
|
$Process.ProcessorAffinity = [IntPtr]0xFFF
|
|
while ($true) {
|
|
$Process.Refresh()
|
|
if ($Process.HasExited) { break }
|
|
if ($Process.PrivateMemorySize64 -gt 8GB) {
|
|
throw "Owned viewer PID $($Process.Id) exceeded the 8 GiB private-memory limit."
|
|
}
|
|
if ($Started.Elapsed.TotalSeconds -gt $TimeLimitSeconds) {
|
|
throw "Owned viewer PID $($Process.Id) exceeded the $TimeLimitSeconds-second capture limit."
|
|
}
|
|
Start-Sleep -Milliseconds 200
|
|
}
|
|
$Process.WaitForExit()
|
|
$Process.Refresh()
|
|
if ($Process.ExitCode -ne 0) {
|
|
throw "Owned viewer PID $($Process.Id) exited with code $($Process.ExitCode)."
|
|
}
|
|
} catch {
|
|
$Process.Refresh()
|
|
if (-not $Process.HasExited) {
|
|
[void]$Process.CloseMainWindow()
|
|
if (-not $Process.WaitForExit(3000)) {
|
|
Stop-Process -Id $Process.Id -Force -ErrorAction SilentlyContinue
|
|
$Process.WaitForExit()
|
|
}
|
|
}
|
|
throw
|
|
}
|
|
|
|
function Save-DownscaledJpeg([string]$Source, [string]$Destination, [long]$Quality) {
|
|
$Original = [Drawing.Image]::FromFile($Source)
|
|
try {
|
|
$Width = [Math]::Min(960, $Original.Width)
|
|
$Height = [int][Math]::Round($Original.Height * $Width / $Original.Width)
|
|
$Small = New-Object Drawing.Bitmap($Width, $Height)
|
|
$Graphics = [Drawing.Graphics]::FromImage($Small)
|
|
try {
|
|
$Graphics.InterpolationMode = [Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
|
$Graphics.PixelOffsetMode = [Drawing.Drawing2D.PixelOffsetMode]::HighQuality
|
|
$Graphics.DrawImage($Original, 0, 0, $Width, $Height)
|
|
$Codec = [Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() |
|
|
Where-Object { $_.MimeType -eq 'image/jpeg' } | Select-Object -First 1
|
|
$Parameters = New-Object Drawing.Imaging.EncoderParameters(1)
|
|
try {
|
|
$Parameters.Param[0] = New-Object Drawing.Imaging.EncoderParameter(
|
|
[Drawing.Imaging.Encoder]::Quality, $Quality)
|
|
$Small.Save($Destination, $Codec, $Parameters)
|
|
} finally {
|
|
$Parameters.Dispose()
|
|
}
|
|
} finally {
|
|
$Graphics.Dispose()
|
|
$Small.Dispose()
|
|
}
|
|
} finally {
|
|
$Original.Dispose()
|
|
}
|
|
}
|
|
|
|
$ViewerLog = Get-Content -LiteralPath $StdoutPath -Raw
|
|
foreach ($Index in $ExpectedIndices) {
|
|
$PngPath = '{0}_{1:D2}.png' -f $CapturePrefix, $Index
|
|
if (-not (Test-Path -LiteralPath $PngPath -PathType Leaf)) {
|
|
throw "Viewer exited normally but did not write required back-buffer capture index $Index."
|
|
}
|
|
if ($CaptureMode -eq 'Spawn') {
|
|
$CaptureTime = @('0.5s', '1.0s', '2.0s')[$Index]
|
|
$JpegPath = Join-Path $CaptureRoot "${SafeLabel}_spawn_$CaptureTime.jpg"
|
|
} else {
|
|
$JpegPath = Join-Path $CaptureRoot "${SafeLabel}_mid_sprint.jpg"
|
|
}
|
|
Save-DownscaledJpeg $PngPath $JpegPath 72
|
|
if ((Get-Item -LiteralPath $JpegPath).Length -ge 300KB) {
|
|
Save-DownscaledJpeg $PngPath $JpegPath 48
|
|
}
|
|
$Jpeg = Get-Item -LiteralPath $JpegPath
|
|
if ($Jpeg.Length -ge 300KB) { throw "Downscaled capture exceeds 300 KB: $($Jpeg.Name)" }
|
|
Remove-Item -LiteralPath $PngPath -Force
|
|
$Timing = [regex]::Match($ViewerLog, "\[(Spawn|Flight)SequenceCapture\] index=$Index elapsed_s=([0-9.]+)")
|
|
$Elapsed = if ($Timing.Success) { $Timing.Groups[2].Value } else { 'missing' }
|
|
Write-Host ('capture={0} elapsed_s={1} size={2} bytes' -f $Jpeg.Name, $Elapsed, $Jpeg.Length)
|
|
}
|
|
if ($CaptureMode -eq 'Flight') {
|
|
# Keep only the reviewed mid-flight JPG; remove this run's unused raw D3D frames.
|
|
# Garder uniquement le JPG de vol examine ; supprimer les tampons D3D bruts inutilises de ce run.
|
|
foreach ($Index in 0..4) {
|
|
$PngPath = '{0}_{1:D2}.png' -f $CapturePrefix, $Index
|
|
if (Test-Path -LiteralPath $PngPath -PathType Leaf) {
|
|
Remove-Item -LiteralPath $PngPath -Force
|
|
}
|
|
}
|
|
}
|
|
Write-Host ('viewer_closed=1 pid={0} exit_code={1} elapsed_s={2:0.000} priority=Normal affinity=0xFFF' -f
|
|
$Process.Id, $Process.ExitCode, $Started.Elapsed.TotalSeconds)
|