1:1 source port of the engine and game code; Windows build and system layer; docs. No game files or SDK headers.
This commit is contained in:
parent
c3b032a45a
commit
0ecb47e9cb
3871 changed files with 91080 additions and 1640063 deletions
211
tools/port/smoke_win.ps1
Normal file
211
tools/port/smoke_win.ps1
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
# Scripted smoke test of the Windows port (not 1:1 tooling). Starts choc3port.exe ONCE (silent, windowed, without taking the
|
||||
# keyboard focus), sends window messages to the game window only (never the real mouse or keyboard) and waits for
|
||||
# readiness by polling choc3port.log instead of fixed sleeps:
|
||||
# - the platform layer logs "load <script>.lua" for every Lua file the engine opens, and one "fps ..." line per second;
|
||||
# - a step is ready when an "fps" line that arrived after the action AND after the last "load" line shows a quiet
|
||||
# frame (max frame time < 150 ms), i.e. loading and fades are over and the game takes input again.
|
||||
# Coordinates are the 800x600 logical space (mapped through the same letterbox the platform layer uses).
|
||||
# powershell -File tools/port/smoke_win.ps1 -Steps "name,kind,args;name,kind,args" [-Shots "name,name"] [-Dev] [-HeapCheck]
|
||||
# Step kinds: click,x,y | drag,x1,y1,x2,y2 (button-down drag, 12 moves) | hover,x,y | key,Escape|Return|Space|Tab|Back|Left|Up|Right|Down|F1..F12|<vk number> | text,abc |
|
||||
# lua,<code> (runs Lua through CHOC3_LUACMD, see port/src/win/testaids_win.cpp) | wait,seconds | none
|
||||
# -StepsFile gives one step per line (use it when Lua code contains ;)
|
||||
# Before the steps the script waits for the main menu (load ui/mainmenu.lua + a quiet frame). -Dev sets CHOC3_DEV=1
|
||||
# (the game's cheat mode: dev menu bar with money, ports, factories, product/sign designer). Screenshots (-Shots names
|
||||
# only) are the game view only, 640x480, in port/build-win/smoke/. Per step it prints the Lua files loaded, the time to
|
||||
# ready, and a coarse brightness figure. The log and stderr of the run are kept in port/build-win/smoke/<Tag>.log.
|
||||
# Kills only its own PID.
|
||||
param(
|
||||
[string]$Steps = "start,click,400,345",
|
||||
[string]$StepsFile = "", # one step per line (name,kind,args; # comments); needed when Lua code contains ; characters
|
||||
[string]$Shots = "",
|
||||
[string]$Demo = "", # step names to save as FULL-resolution game-view PNGs in port/build-win/demo/demo-<name>.png
|
||||
[string]$Tag = "run",
|
||||
[int]$MaxSeconds = 240,
|
||||
[int]$ReadyTimeout = 30,
|
||||
[switch]$Dev,
|
||||
[switch]$HeapCheck,
|
||||
[switch]$Focus
|
||||
)
|
||||
$root = Resolve-Path "$PSScriptRoot\..\.."
|
||||
$bin = "$root\port\build-win\bin"
|
||||
$out = "$root\port\build-win\smoke"
|
||||
$logPath = "$bin\choc3port.log"
|
||||
New-Item -ItemType Directory -Force $out | Out-Null
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
Add-Type @"
|
||||
using System; using System.Runtime.InteropServices;
|
||||
public class SW {
|
||||
[DllImport("user32.dll")] public static extern bool PostMessage(IntPtr h, uint m, IntPtr w, IntPtr l);
|
||||
[DllImport("user32.dll")] public static extern bool SetProcessDpiAwarenessContext(IntPtr c);
|
||||
[DllImport("user32.dll")] public static extern bool GetClientRect(IntPtr h, out RECT r);
|
||||
[DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h, IntPtr dc, uint f);
|
||||
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int L,T,R,B; } }
|
||||
"@
|
||||
[SW]::SetProcessDpiAwarenessContext([IntPtr](-4)) | Out-Null
|
||||
if (Test-Path $logPath) { Remove-Item $logPath -Force }
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = "$bin\choc3port.exe"; $psi.WorkingDirectory = $bin; $psi.UseShellExecute = $false
|
||||
$psi.RedirectStandardError = $true; $psi.RedirectStandardOutput = $true
|
||||
$psi.EnvironmentVariables["ALSOFT_DRIVERS"] = "null"
|
||||
if (-not $Focus) { $psi.EnvironmentVariables["CHOC3_NOFOCUS"] = "1" }
|
||||
$psi.EnvironmentVariables["CHOC3_LUACMD"] = "1"
|
||||
$psi.EnvironmentVariables["CHOC3_HEARTBEAT"] = "1" # "hb <polls> <swaps> <ms since swap>" every 500 ms
|
||||
$psi.EnvironmentVariables["CHOC3_BLOCKREAL"] = "1" # real mouse/keyboard input never reaches the game
|
||||
if ($Dev) { $psi.EnvironmentVariables["CHOC3_DEV"] = "1" }
|
||||
if ($HeapCheck) { $psi.EnvironmentVariables["CHOC3_HEAPCHECK"] = "1" }
|
||||
$p = [System.Diagnostics.Process]::Start($psi)
|
||||
$so = $p.StandardOutput.ReadToEndAsync(); $se = $p.StandardError.ReadToEndAsync()
|
||||
"pid $($p.Id) tag $Tag"
|
||||
$sw = [Diagnostics.Stopwatch]::StartNew()
|
||||
|
||||
# ---- log polling -------------------------------------------------------------------------------------------------------
|
||||
$script:pos = 0L; $script:partial = ""; $script:seq = 0; $script:lastLoadSeq = 0; $script:fps = New-Object System.Collections.ArrayList; $script:hb = New-Object System.Collections.ArrayList
|
||||
$script:loads = New-Object System.Collections.ArrayList; $script:crash = New-Object System.Collections.ArrayList
|
||||
function Pump {
|
||||
if (-not (Test-Path $logPath)) { return }
|
||||
$fs = [IO.File]::Open($logPath, 'Open', 'Read', 'ReadWrite')
|
||||
try {
|
||||
if ($fs.Length -le $script:pos) { return }
|
||||
$fs.Position = $script:pos
|
||||
$buf = New-Object byte[] ($fs.Length - $script:pos); $n = $fs.Read($buf, 0, $buf.Length); $script:pos += $n
|
||||
} finally { $fs.Dispose() }
|
||||
$text = $script:partial + [Text.Encoding]::ASCII.GetString($buf, 0, $n)
|
||||
$parts = $text -split "`n"; $script:partial = $parts[-1]
|
||||
for ($i = 0; $i -lt $parts.Length - 1; $i++) {
|
||||
$l = $parts[$i].TrimEnd("`r"); $script:seq++
|
||||
if ($l -match '^load (.+)$') { $script:lastLoadSeq = $script:seq; [void]$script:loads.Add($Matches[1]) }
|
||||
elseif ($l -match '^hb (\d+) (\d+) (\d+)') { [void]$script:hb.Add(@{ seq = $script:seq; polls = [int]$Matches[1]; swaps = [int]$Matches[2] }) }
|
||||
elseif ($l -match '^fps (\d+) \| frame avg ([\d.]+) max ([\d.]+) ms') {
|
||||
[void]$script:fps.Add(@{ seq = $script:seq; fps = [int]$Matches[1]; avg = [double]$Matches[2]; max = [double]$Matches[3] })
|
||||
}
|
||||
elseif ($l -match 'CRASH|^ bt |CHOC3_DEV') { [void]$script:crash.Add($l) }
|
||||
}
|
||||
}
|
||||
# ready = the main thread answered the event poll in 3 consecutive heartbeats (1.5 s) after the action and after the last
|
||||
# "load" line (a window or script load keeps the main thread busy: polls = 0), then a short settle for fades
|
||||
function WaitReady([int]$timeout) {
|
||||
Pump; $actSeq = $script:seq; $t = [Diagnostics.Stopwatch]::StartNew()
|
||||
while ($t.Elapsed.TotalSeconds -lt $timeout) {
|
||||
Start-Sleep -Milliseconds 150; $p.Refresh(); if ($p.HasExited) { Pump; return $false }
|
||||
Pump
|
||||
$from = $(if ($actSeq -gt $script:lastLoadSeq) { $actSeq } else { $script:lastLoadSeq }); $good = 0
|
||||
foreach ($h in $script:hb) { if ($h.seq -gt $from) { if ($h.polls -ge 10) { $good++ } else { $good = 0 } } }
|
||||
if ($good -ge 3) { Start-Sleep -Milliseconds 700; Pump; return $true }
|
||||
}
|
||||
return $false
|
||||
}
|
||||
# ---- view geometry (same letterbox as the platform layer's ViewRect) ----------------------------------------------------------
|
||||
function ViewRect {
|
||||
$r = New-Object SW+RECT; [SW]::GetClientRect($p.MainWindowHandle, [ref]$r) | Out-Null
|
||||
$s = [Math]::Min($r.R / 800.0, $r.B / 600.0); if ($s -le 0) { $s = 1 }
|
||||
return @{ cw = $r.R; ch = $r.B; s = $s; ox = ($r.R - 800 * $s) / 2; oy = ($r.B - 600 * $s) / 2 }
|
||||
}
|
||||
function MakeLp([int]$lx, [int]$ly) {
|
||||
$v = ViewRect; $cx = [int][Math]::Floor($lx * $v.s + $v.ox + 0.5); $cy = [int][Math]::Floor($ly * $v.s + $v.oy + 0.5)
|
||||
return [IntPtr](($cy -shl 16) -bor $cx)
|
||||
}
|
||||
function Hover([int]$lx, [int]$ly) { [SW]::PostMessage($p.MainWindowHandle, 0x200, [IntPtr]0, (MakeLp $lx $ly)) | Out-Null }
|
||||
function Click([int]$lx, [int]$ly) {
|
||||
$h = $p.MainWindowHandle; $lp = MakeLp $lx $ly
|
||||
[SW]::PostMessage($h, 0x200, [IntPtr]0, $lp) | Out-Null; Start-Sleep -Milliseconds 250
|
||||
[SW]::PostMessage($h, 0x201, [IntPtr]1, $lp) | Out-Null; Start-Sleep -Milliseconds 90
|
||||
[SW]::PostMessage($h, 0x202, [IntPtr]0, $lp) | Out-Null
|
||||
}
|
||||
function Drag([int]$x1, [int]$y1, [int]$x2, [int]$y2, [int]$n) {
|
||||
# button-down drag as the real mouse produces it: move, down, n moves with MK_LBUTTON, up at the end point
|
||||
$h = $p.MainWindowHandle
|
||||
[SW]::PostMessage($h, 0x200, [IntPtr]0, (MakeLp $x1 $y1)) | Out-Null; Start-Sleep -Milliseconds 250
|
||||
[SW]::PostMessage($h, 0x201, [IntPtr]1, (MakeLp $x1 $y1)) | Out-Null; Start-Sleep -Milliseconds 90
|
||||
for ($i = 1; $i -le $n; $i++) {
|
||||
$lx = [int]($x1 + ($x2 - $x1) * $i / $n); $ly = [int]($y1 + ($y2 - $y1) * $i / $n)
|
||||
[SW]::PostMessage($h, 0x200, [IntPtr]1, (MakeLp $lx $ly)) | Out-Null; Start-Sleep -Milliseconds 40
|
||||
}
|
||||
Start-Sleep -Milliseconds 90
|
||||
[SW]::PostMessage($h, 0x202, [IntPtr]0, (MakeLp $x2 $y2)) | Out-Null
|
||||
}
|
||||
$vk = @{ Escape = 0x1B; Return = 0x0D; Space = 0x20; Tab = 9; Back = 8; Left = 0x25; Up = 0x26; Right = 0x27; Down = 0x28 }
|
||||
1..12 | ForEach-Object { $vk["F$_"] = 0x6F + $_ }
|
||||
function Key([string]$k) {
|
||||
$code = if ($vk.ContainsKey($k)) { $vk[$k] } else { [int]$k }
|
||||
$h = $p.MainWindowHandle; $scan = 0
|
||||
[SW]::PostMessage($h, 0x100, [IntPtr]$code, [IntPtr](1 -bor ($scan -shl 16))) | Out-Null; Start-Sleep -Milliseconds 70
|
||||
[SW]::PostMessage($h, 0x101, [IntPtr]$code, [IntPtr](-1073741823)) | Out-Null
|
||||
}
|
||||
function Text([string]$s) {
|
||||
foreach ($c in $s.ToCharArray()) { [SW]::PostMessage($p.MainWindowHandle, 0x102, [IntPtr][int]$c, [IntPtr]1) | Out-Null; Start-Sleep -Milliseconds 60 }
|
||||
}
|
||||
function Shot([string]$name) {
|
||||
$p.Refresh(); if ($p.HasExited -or $p.MainWindowHandle -eq 0) { return "no window" }
|
||||
$v = ViewRect; if ($v.cw -le 0) { return "no client" }
|
||||
$bmp = New-Object System.Drawing.Bitmap $v.cw, $v.ch
|
||||
$g = [System.Drawing.Graphics]::FromImage($bmp); $dc = $g.GetHdc(); [SW]::PrintWindow($p.MainWindowHandle, $dc, 3) | Out-Null; $g.ReleaseHdc($dc)
|
||||
if ($Demo.Split(",") -contains $name) {
|
||||
New-Item -ItemType Directory -Force "$root/port/build-win/demo" | Out-Null
|
||||
$full = New-Object System.Drawing.Bitmap ([int](800 * $v.s)), ([int](600 * $v.s))
|
||||
$gf = [System.Drawing.Graphics]::FromImage($full)
|
||||
$gf.DrawImage($bmp, (New-Object System.Drawing.Rectangle 0, 0, $full.Width, $full.Height), [int]$v.ox, [int]$v.oy, $full.Width, $full.Height, 'Pixel')
|
||||
$full.Save("$root/port/build-win/demo/demo-$name.png", [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
" demo-$name.png $($full.Width)x$($full.Height)"; $gf.Dispose(); $full.Dispose()
|
||||
}
|
||||
$s = New-Object System.Drawing.Bitmap 640, 480
|
||||
$g2 = [System.Drawing.Graphics]::FromImage($s); $g2.InterpolationMode = 'HighQualityBicubic'
|
||||
$g2.DrawImage($bmp, (New-Object System.Drawing.Rectangle 0, 0, 640, 480), [int]$v.ox, [int]$v.oy, [int](800 * $v.s), [int](600 * $v.s), 'Pixel')
|
||||
$n = 0; $lit = 0; for ($y = 12; $y -lt 480; $y += 24) { for ($x = 16; $x -lt 640; $x += 32) { $c = $s.GetPixel($x, $y); $n++; if (($c.R + $c.G + $c.B) -gt 30) { $lit++ } } }
|
||||
if ($Shots.Split(",") -contains $name) { $s.Save("$out\$Tag-$name.png", [System.Drawing.Imaging.ImageFormat]::Png); $lit = "$lit/$n saved" } else { $lit = "$lit/$n" }
|
||||
$g.Dispose(); $g2.Dispose(); $bmp.Dispose(); $s.Dispose()
|
||||
return "lit $lit"
|
||||
}
|
||||
function Finish([string]$why) {
|
||||
$p.Refresh()
|
||||
if (-not $p.HasExited) { "stopping own pid ($why)"; Stop-Process -Id $p.Id -Force; $p.WaitForExit(3000) | Out-Null } else { "EXITED code {0:X} ($why)" -f $p.ExitCode }
|
||||
Pump
|
||||
Copy-Item $logPath "$out\$Tag.log" -Force -ErrorAction SilentlyContinue
|
||||
"--- stderr (tail)"; ($se.Result -split "`n" | Select-Object -Last 30) -join "`n"
|
||||
"--- stdout (tail)"; ($so.Result -split "`n" | Select-Object -Last 8) -join "`n"
|
||||
"--- crash lines"; $script:crash -join "`n"
|
||||
"--- log tail (non-fps)"; if (Test-Path $logPath) { Get-Content $logPath | Where-Object { $_ -notmatch '^fps |^load |^hb ' } | Select-Object -Last 12 }
|
||||
"leftover: " + ((Get-Process choc3port, WerFault -ErrorAction SilentlyContinue | Measure-Object).Count)
|
||||
}
|
||||
|
||||
# without the keyboard focus Windows never sends WM_ACTIVATEAPP and the game idles at ~0.3 fps: tell it it is active
|
||||
function Activate { if (-not $Focus) { [SW]::PostMessage($p.MainWindowHandle, 0x1C, [IntPtr]1, [IntPtr]0) | Out-Null } }
|
||||
# ---- wait for the main menu ---------------------------------------------------------------------------------------------
|
||||
$ok = $false; $jig = 0
|
||||
while ($sw.Elapsed.TotalSeconds -lt 60) {
|
||||
Start-Sleep -Milliseconds 300; Pump; $p.Refresh()
|
||||
if ($p.HasExited) { break }
|
||||
if ($p.MainWindowHandle -ne 0) { Hover (390 + ($jig % 20)) 300; $jig++ } # the game redraws on pointer activity
|
||||
if ($script:loads -contains "assets/ui/mainmenu.lua" -or ($script:loads | Where-Object { $_ -like "*ui/mainmenu.lua" })) { $ok = $true; break }
|
||||
}
|
||||
if (-not $ok) { "main menu never loaded"; Finish "no menu"; return }
|
||||
$script:loads.Clear()
|
||||
if (-not (WaitReady $ReadyTimeout)) { "menu not quiet"; }
|
||||
"menu ready after $([int]$sw.Elapsed.TotalSeconds)s"
|
||||
|
||||
# ---- steps --------------------------------------------------------------------------------------------------------------
|
||||
$i = 0
|
||||
$stepList = if ($StepsFile) { Get-Content $StepsFile | Where-Object { $_.Trim() -and -not $_.Trim().StartsWith("#") } } else { $Steps.Split(";") }
|
||||
foreach ($st in $stepList) {
|
||||
$f = $st.Trim().Split(","); $i++; $name = $f[0]
|
||||
if ($sw.Elapsed.TotalSeconds -gt $MaxSeconds) { "time budget used, stopping before step $name"; break }
|
||||
$p.Refresh(); if ($p.HasExited) { "EXITED before step $name"; break }
|
||||
$script:loads.Clear(); $t0 = [Diagnostics.Stopwatch]::StartNew()
|
||||
switch ($f[1]) {
|
||||
"click" { Click ([int]$f[2]) ([int]$f[3]) }
|
||||
"drag" { Drag ([int]$f[2]) ([int]$f[3]) ([int]$f[4]) ([int]$f[5]) 12 }
|
||||
"hover" { Hover ([int]$f[2]) ([int]$f[3]) }
|
||||
"key" { Key $f[2] }
|
||||
"text" { Text $f[2] }
|
||||
"lua" { Set-Content -Path "$bin\luacmd.txt" -Value (($f[2..($f.Length - 1)]) -join ",") -Encoding ASCII; $w = [Diagnostics.Stopwatch]::StartNew(); while ((Test-Path "$bin\luacmd.txt") -and $w.Elapsed.TotalSeconds -lt 10) { Start-Sleep -Milliseconds 100 } }
|
||||
"wait" { Start-Sleep -Seconds ([int]$f[2]) }
|
||||
default { }
|
||||
}
|
||||
$ready = WaitReady $ReadyTimeout
|
||||
if ($p.HasExited) { " EXITED during step $name, code {0:X}" -f $p.ExitCode; break }
|
||||
$info = Shot $name
|
||||
$ld = ($script:loads | ForEach-Object { $_ -replace '^assets/', '' } | Select-Object -Unique) -join " "
|
||||
$last = if ($script:fps.Count) { $script:fps[$script:fps.Count - 1] } else { @{ fps = 0; max = 0 } }; $hbn = if ($script:hb.Count) { $script:hb[$script:hb.Count - 1].swaps * 2 } else { 0 }
|
||||
"{0,2} {1} [{2}] ready={3} {4:N1}s swaps/s={5} {7} | {8}" -f $i, $name, ($f[1..($f.Length - 1)] -join ","), $ready, $t0.Elapsed.TotalSeconds, $hbn, 0, $info, $ld
|
||||
}
|
||||
$p.Refresh()
|
||||
if ($p.HasExited) { Finish "ended" } else { Finish "done" }
|
||||
Loading…
Add table
Add a link
Reference in a new issue