Warhammer 40,000: Chaos Gate demo

Telegram saved request #56; compatibility fixture only. No gameplay or public deployment claim. Investigation: 2026-10-13, source base 6aaf65235.

Fixture and extraction

WinG implementation

The executable imports five WinG32 names: WinGRecommendDIBFormat, WinGCreateDC, WinGCreateBitmap, WinGSetDIBColorTable, WinGBitBlt. These were absent from the API table, and ordinary launch stopped at the first.

The implementation returns a complete indexed BI_RGB recommendation, allocates real canonical GDI DCs and DIB sections, copies palette entries into bitmap-owned storage, and performs SRCCOPY through the existing raster engine. Header writes use guest translation across sparse pages. Invalid/unwritable recommendation buffers fail before any write; bitmap creation checks the output pointer and input header/table before allocation. No new JS pixel or palette shadow state.

Contract references: original WinG SDK help mirror and Wine's WinG32 implementation. The recommendation is an output structure (no initialized input biSize requirement), and does not return a palette. The chosen recommendation is 320 × 1, one plane, eight bits per pixel, uncompressed bottom-up; callers replace dimensions and supply their palette before creating the bitmap.

Validation and remaining blocker

Evidence run: 20261013T1145Z-chaos-wing. Complete logs and source patch live at /home/user/chaos-wing-work/scratch/runs/20261013T1145Z-chaos-wing/ on bx_gd476s95. Compact checked logs/captures copied to the persistent repository's same run path. This demo is not yet a registered candidate, so no unrelated candidate/result association was invented.

Ordinary launch command (baseline and candidate):

node test/run.js --exe=/home/user/chaos-fixture/game/WH40KDemo.exe \
  --threads --vfs-include='**/*' --quiet-api --max-seconds=60 --max-batches=60000

Baseline WASM SHA-256 d19c10b798cf71449551fa0387ce5e971c3c3c083aa47ca78f800a0fb7364d58 traps at WinGRecommendDIBFormat, batch 9738. Candidate WASM SHA-256 f7968aa1ed4941ff399c2d87f97451f723e4f98a3f3e7e34e712f6b8618b6f89 passes that call and traps at mmioWrite, batch 35947. Node v24.18.1, threaded CLI, 640 × 480. Both runs report absent optional ole32.dll and comctl32.dll PE fixtures; this shared limitation remains explicit.

A bounded follow-up (--no-build --quiet-blocks --max-batches=35000, with --input=34000:png:.../startup.png --png=.../startup-end.png) captured the ordinary Games Workshop intro. Both captures were inspected. The full launch crash exits before its normal final-PNG path, so the capture is a separate pre-blocker run. It is startup evidence, not a menu or player control test.

The next blocker after the WinG implementation was multimedia I/O writes. At the first trap, guest EIP is 0x004fa8d6, caller return 0x004fa90d, arguments are mmioWrite(0x6d4d0012, 0x074ce2b8, 0x27880).

Fixed memory-file writes (follow-up)

The trapped 0x6d4d0012 handle belongs to the existing FOURCC_MEM slot table. The original EXE opens the output memory file at 0x004fa29d with flags 0x11011 (WRITE, CREATE, ALLOCBUF, exclusive sharing), and compares the write result to the requested count at 0x004fa913 before continuing. mmioWrite now copies into that slot's guest buffer at its cursor, accepts at most the remaining capacity, advances by the actual accepted count, and extends the recorded data length without shrinking it on overwrite. Writes are already materialized in the memory backing, consistent with the existing memory-file mmioGetInfo clear-DIRTY behavior. Expandable memory files still fail fast at open; no resize, custom IOProc, or flush implementation is claimed here.

Negative counts, invalid/closed handles, read-only memory handles, and invalid source/destination ranges return -1. Both ranges are checked before copying, including sparse page permissions, so an inaccessible tail cannot partially change the buffer or cursor. A full fixed buffer returns zero; a request larger than the remaining space returns the short count. Disk handles use the existing host writer and its actual result, with MMIO open access mapped to read/write flags. A zero-byte write validates the handle but never invokes the legacy zero-byte _hwrite truncation behavior.

Contract references: Microsoft mmioWrite and Wine MMIO implementation. The focused regression is test/test-mmio-write.js: actual buffer/readback bytes, cursor/length state, overwrite, short/full/zero writes, invalid counts and handles, non-adjacent sparse pages, missing/read-only/no-access tails, guards, host write errors/short counts, and real dispatch stack cleanup.

Validation run 20261013T1205Z-chaos-mmio executed on bx_gd476s95 at 12:21–12:22 UTC, using the same original fixture and threaded CLI environment. The full build and test-mmio-write, test-wat-mmio, test-mmio-info-sparse, test-mmio-descend-lazy, and test-mmio-ascend-odd-offset all passed. A count-only success replacement was rejected by the first stored-byte assertion; the real source was restored and its hash verified. Whole repository suites were not run.

The prior WinG module hash was verified before building; candidate WASM SHA-256: 0001a582fbfd6ac300aeb42fb0b3a55eb2d6ea4f9d7950df943220ff6a9fc02b. Launch used the command above plus --no-build --quiet-blocks, a batch-40000 capture, and a final capture. It completed the 60000-batch bound in 15.207s without a new API trap. The final image renders the Chaos Gate main menu with five icon-bearing buttons; text labels are absent in this capture. This is menu-rendering evidence, not proof of functioning controls or gameplay. Missing optional ole32.dll/comctl32.dll PE fixtures remain unchanged.

Complete evidence is in the remote checkout's run directory, with a compact copy in the persistent repository's same run path. Next: qualify ordinary menu input into a mission and investigate any actual next failure, including the unverified button text behavior. No subsequent unimplemented API is established by this bounded launch. Expandable memory files/custom IOProcs remain outside this implementation.

Ordinary pre-mission input qualification

Run 20261013T1236Z-chaos-gameplay uses the unchanged MMIO candidate module above, Node v24.18.1, threaded CLI, 640 × 480, and the original extracted fixture on bx_gd476s95. A real pointer click at (320,225) at batch 60000 opens the Cathedral Bridge. Clicking its green briefing display at (320,245) opens a readable mission briefing. Clicking the skull at (320,435) returns to the hub; its far-right door at (580,330) opens the squad loadout with rendered marine, weapons, statistics, and character name. These are actual renderer input events with DOWN/UP delivery recorded by --trace-input; no guest-state writes.

The fixture's Chaos_Demo.txt documents the required route: read the briefing, click its skull to return, enter the far-right Configure Squads door, leave loadout through its right-hand doors, then enter the center-left Enter Battle door. The center foreground floor is not the deployment control. Main-menu button labels remain absent, but the hub tooltip and briefing text do render.

Loadout exits through (565,105). Hovering (130,330) on the hub then shows Enter Combat <E> and highlights the center-left door. Clicking it at batch 115000 starts mission loading and creates guest thread 3 at 0x004b8e60 with CREATE_SUSPENDED, parameter 0x00978fc4, handle 0x000e1002. The run subsequently stops returning from main instance.exports.run(BATCH_SIZE): no batch-140000 image, final image, API trap, or CLI 70-second timeout appeared. It remained CPU-active beyond 90 seconds and did not service SIGTERM. A Node inspector pause at 12:42:09 UTC captured $func546 ($th_block_retired, mapped using this module's combined WAT), $run, and the main JS run call. This identifies an active interpreter run, not the guest instruction causing it. The process was explicitly killed at 12:42:28 UTC (exit 137), and the remote runtime slot released. No battlefield or player control is qualified.

Reproduction (append image actions from evidence.json for the saved route):

node test/run.js --exe=/home/user/chaos-fixture/game/WH40KDemo.exe \
  --threads --vfs-include='**/*' --no-build --quiet-api --quiet-blocks \
  --max-seconds=70 --max-batches=150000 --trace-input \
  --input=60000:click:320:225,70000:click:320:245,80000:click:320:435,90000:click:580:330,100000:click:565:105,115000:click:130:330

Use an external process deadline as well as the CLI guard for this reproduction. Next investigation: capture guest EIP/registers and thread state while paused inside this non-yielding run; test whether the default uop path or mission synchronization is responsible. Neither is established by the captured stack. No emulator source changed in this input-qualification follow-up, so no build or regression suite was rerun. The prior build/test evidence remains separate. Missing optional ole32.dll and comctl32.dll PE fixtures remain explicit.

Mission stall: bounded inspector comparison

Run 20261013T1254Z-chaos-stall, 12:55–13:00 UTC, retained the same fixture, module hash, remote checkout, Node version and ordinary six-click route above. supervise.js records the full argument vectors, launches a separate Node child with inspector port 9231, reads state at 40 seconds, and independently kills that child at 80 seconds. All owned processes were stopped before runtime release. No emulator source, build or test changes were made.

The default run paused at batch 124508, guest EIP 0x00507330, with dbg_prev_eip and dbg_prev2_eip also 0x00507330, block budget 1000, and yield reason zero. Exported EAX/ECX/EDX/EBX/ESP/EBP/ESI were all zero; EDI was 0xff659b68. The sampled stack contains $branch_end_at and $run; the prior run's $th_block_retired sample is consistent with the same dispatch cycle. Thread state was T1 waiting at 0x004fb9cc, T2 idle at 0x07503ed0, and T3 exited at 0x004b8e7f. Thus the original suspended worker had already run and exited; its creation is not itself the established stall cause. The supervisor killed the child at 12:56:54.716 UTC.

Disassembly identifies 0x00507330 as a guest memset-like routine: destination, fill byte, and count are stack arguments; it aligns the destination, executes rep stosd at 0x00507371, and returns at 0x00507383. The pre-corruption caller and arguments have not been captured. The repeated retired-block entry and refunded block budget explain the observed non-yielding symptom, but the cause of the zero register file is not established.

The otherwise matching --no-uop diagnostic is not a fix. At its 40-second pause (batch 124334), main was at 0x00507371 with all exported GPRs zero and budget 74. Its stack sampled $gs32, $rep_stos_mem, and $th_rep_stosd. Its final log reports STUCK at EIP=0x00507383 after 11 batches, ESP zero, EDI 0xff659b68, a worker-1 zero-EIP exit, and a worker-2 unreachable trap at 0x07503ed0. GDI DC/object counts were zero. The inspected final 640 × 480 image is black. CLI exit code zero at 12:57:47.527 UTC reflects its stop/report path, not a successful game exit or mission. The corruption symptoms therefore occur with both dispatch configurations.

A third, bounded --trace-eip-range=0x507330-0x507330 --trace-eip-from=115000 --trace-eip-detail attempt was inconclusive: worker tracing produced entry-only lines from startup, and its inspector sample was still at batch 111175, before Enter Combat. It was killed at 12:59:34.386 UTC. Do not use that sample as mission-state evidence. An experimental unnamed-WASM-global index mapping was also unvalidated and is excluded from conclusions; exported getters above are the state evidence.

Next useful probe: capture the last valid main-thread stack and the arguments to 0x00507330 immediately before the shared register/GDI state is lost, with thread-filtered tracing or a conditional inspector breakpoint. Investigate the destination/count and the actual memory writer before changing cache retirement or guest synchronization. Neither changing the retired-block budget nor disabling uops would repair the demonstrated loss of guest state.

The compact local evidence directory contains both run logs, inspector stacks and getter results, supervisor exit records and exact commands, the reviewed black diagnostic frame, and the explicitly inconclusive trace. The fixture remains unregistered; no unrelated dashboard candidate was assigned.