Three independent concurrency bugs caused 10-15% failure rates when
running replay tests at -j 4+ (and ~13% at -j 16 for the worms2d suite):
1. Exclusive file locking: WA.exe opens data files (.img, .wav, .bmp)
with dwShareMode=0. Force FILE_SHARE_READ on all CreateFileA calls
in headless mode so concurrent instances can read shared game assets.
2. _findfirst directory contention: WA's file-existence check for
steam.dat uses _findfirst64i32 which does FindFirstFileA directory
enumeration. Under high concurrency, NTFS directory contention from
.openwa_tmp operations causes transient failures. Hook the check
function (0x4DFA30) to always return "exists" in headless mode.
3. thm.prv race condition: MAP_VIEW_LOAD creates, reads, and deletes
a temporary thm.prv file during terrain processing. Concurrent
instances fighting over this file caused heap corruption (crash in
ntdll RtlAllocateHeap). Extend file isolation to hook FindFirstFileA
and DeleteFileA in ad…
feat: add generate-baseline subcommand and -d flag to test runner
Adds `generate-baseline` for bulk expected log generation from replays
without baselines, and `-d DIR` to both headless and generate-baseline
modes for targeting subdirectories (e.g. testdata/replays/worms2d).
Extension matching is now case-insensitive (.WAgame and .wagame).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add PixelGrid (pure-Rust test grid) and PixelWriter trait for unit
testing line algorithms without WA runtime. Add snapshot capture in
DLL (OPENWA_CAPTURE_LINE_SNAPSHOTS=1, triggered at frame 10) that
draws test lines on a BitGrid using WA's native functions and saves
pixel data for comparison.
Also fix headful test crash: remove --minimized flag which became
incompatible with WA's DirectDraw initialization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: rename openwa-wormkit crate to openwa-dll, output openwa.dll
The project hasn't been a WormKit DLL for a long time — it requires a
custom launcher. Rename the crate to openwa-dll with output openwa.dll
to match the rest of the naming convention (openwa-core, openwa-launcher).
Updates all references across Cargo.toml, Rust sources, PowerShell scripts,
CLAUDE.md, skill files, and docs. Replaces stale "wormkit hook/shim"
comments with accurate descriptions. Three remaining "WormKit" references
are legitimate (refer to the actual WA modding framework).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merge replay-test.ps1's headful mode into openwa-test as a new
`headful` subcommand. Runs with graphics+sound, checks for crashes,
panics, and [GAMEPLAY PASS/FAIL] markers. Supports filter by name
or direct .WAgame path, with configurable --timeout (default 150s).
replay-test.ps1 is now a thin wrapper like run-tests.ps1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: add openwa-config crate with WA installation locator
Centralizes game path discovery into a new openwa-config crate.
find_wa_dir() checks OPENWA_WA_PATH env var then falls back to
HKCU\Software\Team17SoftwareLTD\WormsArmageddon (PATH value).
Removes all hardcoded Steam library paths from launcher, test runner,
and scheme_parse integration test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docs: split CLAUDE.md into focused per-crate files and skills
Delete the 2100-line validation/ module (stale checks, wrong vtable slot,
no-op hooks, sleep-based dumps) and replace with ~120-line startup_checks.rs
that auto-generates all checks from define_addresses! and #[vtable(...)]
metadata. Checks run unconditionally at DLL load — no OPENWA_VALIDATE env
var needed. Test runner scans OpenWA.log for [CHECK FAIL] and reports once.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: add trace-desync tool for per-frame checksum comparison
Hook WA's GameFrameChecksumProcessor (0x5329C0) to capture the game's
own network sync checksums after each simulation frame. The test runner's
new `trace-desync` subcommand runs a replay twice — once with minimal
hooks (baseline) and once with all hooks — then diffs the per-frame
hashes to pinpoint the exact frame where simulation diverges.
New env vars: OPENWA_TRACE_DESYNC, OPENWA_TRACE_BASELINE,
OPENWA_TRACE_HASH_PATH. Baseline mode skips all gameplay hooks,
keeping only headless/file-isolation/input/replay infrastructure.
RE: identified and labeled ComputeStateChecksum (0x546140),
ComputeStateChecksum_Multi (0x546170), SerializeGameState (0x532330),
GameFrameChecksumProcessor (0x5329C0). Exposed DDGameWrapper checksum
fields at offsets 0x268/0x26C/0x270.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: scope MutexGuard in worker thread to not hold lock during test
The original code held the work_rx mutex for the entire recv() call.
While harmless when items are pre-buffered, this is incorrect — scope
the guard so it drops after recv() returns but before running the test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: isolate named semaphore for full test concurrency
Hook CreateSemaphoreA in headless mode to rename the "Worms Armageddon"
instance-detection semaphore to "Worms Armageddon_{pid}". Without this,
concurrent WA.exe instances detect each other via GetLastError() ==
ERROR_ALREADY_EXISTS and skip initialization, causing silent failures.
This was the last concurrency hazard — tests now pass reliably at j=16
(10/10 runs, vs 1/17 before the session's fixes).
Also:
- Expose temp_dir_path() from file_isolation for cross-module use
- Reduce test stagger from 200ms to 50ms (just enough for I/O smoothing)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: high-concurrency test crashes from premature thread resume
Root cause: the launcher's hooks-ready event had a 10s timeout. Under
high concurrency (j=16), DLL loading + hook installation exceeds 10s
due to I/O contention, causing the launcher to resume WA.exe's main
thread before all hooks are installed. The FireWeapon trap then fires
because WeaponRelease (last hook in the chain) was never installed.
Fix: increase hooks-ready timeout to 120s (matching test runner timeout).
Also:
- Redirect all temp files to per-PID .openwa_tmp/{pid}/ directory
instead of individual per-PID suffixed filenames. Now covers
writetest.txt, mono.tmp, custom.dat, current.thm, playback.thm
in addition to existing land.dat, landgen.svg
- Clean up temp directory on DLL detach and in test runner
- Remove 200ms test stagger (no longer needed with proper isolation)
- Add .gitignore for replay output logs (only *_expected.log tracked)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: test runner crash detection, ERRORLOG capture, and dialog suppression
- Launcher passes /silentcrash to WA.exe in headless mode, preventing
the crash handler from relaunching with /handlecrash and showing the
"Fatal error" dialog that blocks test execution
- Hook ERRORLOG.TXT creation via OPENWA_ERRORLOG_PATH env var to
redirect crash logs to per-test paths in the run directory
- Detect crashes by NTSTATUS exit codes or ERRORLOG.TXT presence,
show CRASH status with exception details and first 20 lines of
the error log (using lossy UTF-8 for binary memory dumps)
- Strip ANSI color codes when stdout is not a terminal
- Add RE notes for Frontend__MainNavigationLoop (crash handler mechanism)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused DETACHED_PROCESS constant and unused `output` variable
- Remove unused DDGame import in fire_surrender
- Rename LPSECURITY_ATTRIBUTES to LpSecurityAttributes (camel case)
- Remove unused BOOL type alias in file_isolation
- Prefix unused `esp` with underscore in debug_watchpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: use chrono for timestamp in test runner
- Launcher: pass CREATE_NO_WINDOW | CREATE_SUSPENDED when creating
WA.exe in headless mode, plus SW_HIDE in STARTUPINFO
- Test runner: use CREATE_NO_WINDOW when spawning launcher
- run-tests.ps1: build test runner then run binary directly
Console/terminal windows no longer appear during headless tests.
WA's own "Loading" GUI dialog still flashes briefly (suppressing it
via ShowWindow hook causes WA to hang or throttle).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: suppress windows during headless test execution
- Launcher: use SW_HIDE for WA.exe window when OPENWA_HEADLESS=1 is set
- Test runner: use CREATE_NO_WINDOW when spawning launcher processes
Eliminates console windows and most WA windows during test runs.
The brief "Loading" dialog still flashes — hooking ShowWindow causes
WA to throttle (minimized) or hang (hidden), so it's left as-is.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: hook CreateFileA for per-instance landscape file isolation
Hook kernel32!CreateFileA in headless mode to redirect landscape
generation temp files (mono.tmp, land.dat, landgen.svg) to per-PID
paths. This prevents concurrent WA.exe instances from clobbering
each other's landscape data during parallel replay testing.
Also: update test runner to default j=4, clean up per-PID temp files
after test runs, remove unnecessary launch stagger comment.
Concurrent test results: 7 tests in ~12s (j=4) vs ~36s (serial).
Occasional flakiness at j=7 from unidentified remaining shared resource.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: add Rust replay test runner with concurrent execution support
New crate: openwa-test-runner — discovers and runs all headless replay
tests, compares output logs byte-for-byte, prints pass/fail summary.
Concurrency infrastructure:
- Per-instance event names (OpenWA_HooksReady_{pid}) in launcher + DLL
- Configurable OpenWA.log path via OPENWA_LOG_PATH env var
- Per-instance playback.thm paths to avoid replay map data races
- 1s launch stagger for concurrent mode stability
Usage: openwa-test [-j N] [--no-build] [filter]
Default is serial (j=1). Concurrent mode (-j 4) gives ~3x speedup
but has a known flaky race in WA.exe startup (needs procmon investigation).
Also normalizes CRLF in log comparison for cross-platform robustness.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>