Black & White 2 demo
Input and extraction (2026-09-09)
test/binaries/win98-games-a-d/Black and White 2-DX9-D3D.exe is the
647,216,200-byte Black & White 2 demo InstallShield 11 self-extractor.
It is not the original Black & White game.
The outer payload starts at file offset 0x1c200. Each entry is four
NUL-terminated ASCII fields (basename, Disk1\\... path, version, decimal
byte length), immediately followed by exactly that many uncompressed bytes.
Walking nine entries ends exactly at EOF. Measured with xxd and a bounded
field walker against the input, not guessed from cabinet signatures:
| Entry | Data offset | Bytes |
|---|---|---|
| data1.cab | 115243 | 36286822 |
| data1.hdr | 36402105 | 58374 |
| data2.cab | 36460523 | 609467787 |
| engine32.cab | 645928357 | 543481 |
| layout.bin | 646471878 | 559 |
| setup.exe | 646472483 | 121064 |
| setup.ibt | 646593588 | 395955 |
| setup.ini | 646989581 | 480 |
| setup.inx | 646990102 | 226098 |
unshield l data1.cab lists 416 files when its matching header is beside it.
Extraction also needs data2.cab. Earlier /private/tmp/black-white-seed2
emulator overlay contains data1.cab/hdr but omits data2.cab; it is therefore
not a complete installation source. 7z cannot open the outer EXE.
The game EXE is MainApp/BW2Demo.exe, 20,656,128 bytes. The largest asset is
MainApp/Data/Everything.stuff, 432,577,472 bytes. Other file groups provide
English speech/text, splash textures, credits and the readme. DirectX support
includes Apr2005_d3dx9_25_x86.cab. Inspect actual imports with:
node tools/pe-imports.js PATH/MainApp/BW2Demo.exe --all
Installer investigation
The direct extracted setup needs all three runner arguments:
--exe=PATH/setup.exe
--exe-guest-path=c:\windows\temp\byep1.tmp\disk1\setup.exe
--cwd=c:\windows\temp\byep1.tmp\disk1
--args=-deleter
Use --quiet-api --quiet-blocks. Positional EXE arguments are ignored by the
runner; omitting the guest path searches for engine32.cab in the wrong place.
Dialog control sequence: wait 710, click 1; wait 1000, click 1000 and 1;
wait 718, click 1; wait 711, click 1 (Install).
Loading isprobe.tlb imports stdole2. Registering and mounting its raw MSFT
type-library data removes the measured TYPE_E_CANTLOADLIBRARY 0x80029C4A.
This does not establish installer success. The following failure jumps
through zero at iuser original VA 0x10001d7c: an AddRef indirect call where
EDI points to ASCII GUID-path data, not an object. Runtime iuser base in these
runs is 0x01d2f000, original base 0x10000000.
iuser+0x10003391 receives the pointer and forwards it via 0x100010cb to
the AddRef helper 0x10001d6f. Cause is still unproved: argument marshalling,
pointer lifetime and unsupported COM behavior remain possibilities. Earlier
claims that the Win98 OLEAUT library lacks these standard automation helpers
were speculative and must not be used as evidence for native overrides.
Direct executable startup
The complete extraction is currently /private/tmp/black-white-full.ntZDCF.
unshield -d extracted x data1.cab succeeded for all 416 files. The DirectX
cabinet's d3dx9_25.dll was extracted beside MainApp/BW2Demo.exe with 7z e.
Game launches use --vfs-include='**/*' and explicit --dll-seed= paths for
d3dx9_25.dll, binkw32.dll and dbghelp.dll; the ordinary DLL graph supplies CRT
dependencies. English supplemental groups have provisionally been copied
into MainApp's Data, Audio and Data/Art/Textures folders (verify filesystem
traces before treating these destinations as established).
The executable enters real startup code. CPU gaps observed, in sequence:
0x00b65de8: F3 0F59, MULSS (not MULPS).0x0040b656: UCOMISS followed by LAHF and parity tests.0x006013e0: scalar division during startup object construction.
The runner's crash EIP can point after an instruction prefix. Always
disassemble from a known preceding instruction/function entry: disassembling
at reported EIP 0x00b65de9 falsely labels the opcode MULPS. ADDPS/MULPS were
already implemented in dc7bc157. Scalar arithmetic and UCOMISS are being added
with test/test-sse-scalar.js; no SSE feature bit is advertised yet.
The stale region-owner anchors were resolved by the shared integration lane.
Use paired current artifacts: tools/build-compile-wat.js --out=... --compat-out=..., then --no-build --wasm=...; do not mistake an older
build/wine-assembly.wasm for current sources. No gameplay has been verified.
Programmable renderer implementation
The direct game reaches a fatal pixel-shader 1.1 capability check after
creating its LIONHEAD window in the default profile. The experimental
?d3d9-programmable profile enables real shader/texture/buffer rendering after
a GPU pixel probe. No game patch or capability-only bypass is used.
See apps/direct3d9.md for the extension to the existing graphics design.
Real WAT shader objects/constants, guest texture mip storage/level surfaces,
vertex/index buffers, locking/binding/lifetime and programmable UP/buffer
draws now lower through the shared GPU backend. test-d3d9-pipeline-web.js
checks completed canonical BGRA frames, INDEX16 rebasing, INDEX32 above65535,
signed base-vertex offsets and locked/range rejection. These synthetic
rendering passes do not establish game menu or gameplay compatibility.
The genuine browser run with that profile gets past the shader check and
traps at 0x009343b4: the following call at 0x009343c4 is
[device-vtable+0xf4], EndStateBlock (slot61). Return address is
0x009343ca; device pointer 0x07f07030 in the measured run. The immediately
preceding sequence records render states via slot57 (+0xe4). Logs label
these COM thunks <ord>; disassemble the caller to identify the actual slot.
Selective render-state block recording/Capture/Apply is now implemented and
unit-tested; other recording categories fail explicitly until implemented.
Subsequent genuine browser runs advanced through these measured gaps:
0x0093459e, slot69+0x114: SetSamplerState during recording.0x00934664, slot65+0x104: SetTexture during recording (initial null bindings).0x0093468f, slot44+0xb0: SetTransform, texture matrices16..23.0x00936a09, slot22+0x58: GetGammaRamp for implicit chain0.
Selective sampler, internally retained texture bindings and immutable matrix
copies now Capture/Apply without changing live state during recording. Tests
cover replacement/release, caller-memory reuse and independent matrix slots.
Gamma has copied per-device API data, with display capability still disabled.
The initial WORD ramp is0..255 per Microsoft's "Using gamma correction" note,
not0..65535; the game's following loop multiplies each WORD by255.
No menu or gameplay has yet been reached. Probe logs live under
/private/tmp/black-white-*-probe.log; the probe uses the genuine extracted
assets and only the opt-in real GPU capability profile.
Native D3DX then records vertex declarations at original0x0051b0a1
(+0x15c,slot87) and vertex float constants at0x005121be
(+0x178,slot94). Measured DLL base0x02528000, original0x00400000;
runtime failures0x02643099/0x0263a1ac. Declaration/shader references and
per-register float constant state blocks now have lifetime/overlap tests.
The next real failure was ReadFileEx at0x00b532be. It reads175104 bytes
from Everything.stuff offset102306304 into0x4c550000; OVERLAPPED at
0x4fda0300, callback0x00b53100. The callback uses hEvent as a private
context pointer, which must remain untouched. Positional VFS reads now preserve
the shared file cursor and queue completion on the calling WAT instance for
SleepEx/WaitForSingleObjectEx alertable delivery. Lazy residency currently parks
submission through existing IO_WAIT before queueing; this is not fully
asynchronous provider scheduling. Other APC/cancellation APIs remain unsupported.
First integration hung at0x00938963 in the texture-loader polling loop.
Passive callback count stayed0 despite SleepEx(0,TRUE) and completed bytes.
Cause: inline CALL-reg dispatch overwrites redirected EIP unless steps=0;
handler_set_eip only protects the outer thunk-zone path. The APC continuation
now sets both, and test-read-file-ex includes an actual x86 CALL-reg regression
(the original direct-handler callback test did not exercise that path).
WIP checkpoint: the genuine browser run now advances beyond that callback to
native D3DX at runtime 0x025f6e12 (original 0x004cee12), with EDX=0x31545844
(DXT1). This next texture-loading failure is not yet diagnosed. No menu or
gameplay has been verified.
Continuation: disassembly identifies that call as device slot6 GetDirect3D
(call [eax+0x18]), not a DXT decoder failure. Parent identity/AddRef and
device-owned lifetime now have compiled tests, along with the subsequent
GetDisplayMode/GetCreationParameters queries. Genuine browser execution passes
this point and reaches D3DX original 0x0051b59e, runtime 0x0264359e, whose
call [ecx+0x10c] is slot67 SetTextureStageState during state-block recording.
Eight stage rows and selective recording/restoration are being implemented;
fixed-function visual conformance remains unverified.
Stage recording now passes compiled tests and genuine D3DX execution. The next
CPU trap was F3 0F 2A C0 at 0x00a6bb7f (CVTSI2SS xmm0,eax). Both GPR and
memory-source forms now convert signed i32 with default nearest-even rounding
and preserve the upper96 XMM bits; 80-case scalar SSE suite passes. MXCSR
rounding/exception state is still not modeled by this SSE subset.
Next D3DX original 0x00429fc1, runtime 0x02551fc1, calls device slot38
GetRenderTarget (+0x98) then surface slot12 GetDesc (+0x30). The getter now
returns the existing render target through its stable Surface9 wrapper with a
caller reference; GetDesc reports the canonical surface dimensions/format.
Compiled identity/reference-count/output-boundary tests pass.
The genuine run passes those queries, then a native DLL at 0x037bca25
requests ConvertDefaultLocale(LOCALE_USER_DEFAULT=0x400). API3355 now converts
the user/system aliases to the existing emulated en-US locale and preserves
other identifiers and LastError. Thread-locale regression verifies this is
independent of the caller's SetThreadLocale setting.
Further startup continuation (2026-09-09):
- D3DX original
0x0042c58drequests GetFontLanguageInfo. The API now derives legacy kerning/codepage flags from the selected font's actual tables; stock bitmap fonts report no shaping flags. Public GDI regression passes. - Game
0x00937040requests CreateQuery(EVENT=8). A retained Query9 object now implements identity, lifetime, Issue(END) and GetData. Issue uses a real synchronous GPU completion barrier; no asynchronous polling performance is claimed. Unsupported query kinds return NOTAVAILABLE. Compiled API tests and the genuine browser GPU pipeline test verify the barrier and rendered pixels. - An apparent normal exit was actually a null indirect call at
0x0051f024: GetProcAddress(AddFontMemResourceEx) had returned zero. The game decrypts an embedded font into temporary memory, registers it, then frees that buffer. Memory font APIs now copy single-face glyf TrueType data into the existing face cache, select it privately without enumeration, and unregister by a validated handle. Removed faces remain in the existing bounded process cache for live realizations; cache eviction, TTC and CFF are not implemented. Tests overwrite the source and rasterize the retained copy. - Next apparent exit was a missing suffix in LoadLibrary's lookup of
.\\PlugIns\\ScriptLibraryR. The supplied DLL already exists in the asset manifest. WAT now appends.dllwhen the basename has no extension, and honors trailing-dot suppression. The real DLL loads at0x03801000and its script exports resolve; no script-engine substitute is used. - Next CPU trap
0x005fdde2is the prefixed instruction starting at0x005fdde1:F3 0F 2D C1, CVTSS2SI eax,xmm1. Register and four-byte memory forms now implement default nearest-even conversion with the x86 indefinite integer for invalid/overflow values. The scalar suite passes104 cases. Non-default MXCSR modes remain outside the current SSE subset.
Diagnostic logs: /private/tmp/black-white-exit-trace.log,
black-white-memory-font.log, black-white-script-library.log and
black-white-cvtss2si.log. All use the genuine extracted files and the explicit
programmable-GPU profile. Menu and gameplay are still not verified.
The local Microsoft binaries/explorer98/dlls/ole32.dll (4.71.2900) identifies
CoFileTimeToDosDateTime as ordinal23, VA0x7ff8b556. Disassembly validates an
8-byte source and two 2-byte output pointers, then calls KERNEL32's
FileTimeToDosDateTime via IAT0x7ff213a8. BW2 imports this by name; hint25
is not its ordinal. The new wrapper follows that delegation, with compiled
date-range/leap-day/output-boundary/invalid-pointer tests. Genuine startup now
passes 0x0091ee3c, starts its worker threads and loads additional resources.
Next missing import is GetKeyNameTextW at0x0064b860. The bounded UTF-16 wrapper
shares the existing ANSI US scan-code names and has truncation/terminator/ESP
coverage. It inherits that mapper's incomplete extended-key/localization scope;
this is not a complete keyboard-layout implementation. Current diagnostic logs
are /private/tmp/black-white-co-filetime.log and black-white-key-names.log.
The key-name run passes that initialization and reaches D3DX runtime
0x025fa277, original0x004d2277. The call at original0x004d2294 is
call [esi+0x64]: device slot25 CreateCubeTexture, not another 2D texture
getter. Measured size128, levels1, format21 (A8R8G8B8). Cube resources and cube
sampling are not implemented: the shader compiler currently declares only
sampler2D, and the shared GPU texture interface only uploads TEXTURE_2D.
Next work must cover six face/mip stores, CubeTexture9 identity and surface
aliases, plus cube sampling through the shared backend with pixel tests.
Do not alias a cube to one 2D face or advertise cube support before that works.
Cube continuation (2026-09-09): APIs3369..3390 now implement the 22-slot CubeTexture9 interface (private-data/autogen operations still fail loudly). Six independent face/mip stores share locks with retained Surface9 aliases. The shared GPU backend supports cube upload/binding; shader TEX specialization uses the bound resource type. Compiled storage/identity/lock/lifetime tests and actual browser pixels pass for all six faces, lower LOD, return to 2D, and mixed 2D/cube samplers in both orders. Default shader capabilities are unchanged.
The genuine run now passes cube creation and finishes Greek model preloading. It then stops at EIP0, without ExitProcess: captured stack return0052679a identifies game00526795 calling0091ec50, whose tail jump reads a null file object at +4 then its vtable slot12. The preceding file-open operation requests data/art/textures/logo.png, which exists in the extracted MainApp tree. Its open/allocation failure is not yet diagnosed. The trace also contains many DXT1 (31545844) and DXT5 (35545844) CreateTexture requests; those formats remain unimplemented and are rejected, not silently accepted. A late ReadFileEx has a null destination, suggesting allocation pressure, but that is not yet a proven cause of the logo failure. Logs: /private/tmp/black-white-cube.log and black-white-cube-stop.log. No menu or gameplay has been verified.
The focused file-loader trace resolves that failure: CreateFileA returns 700000bc and GetFileSize returns00060af6 for logo.png. CreateFileMappingA returns fb000078, but MapViewOfFile returns0 at00925086. At exit the live map count is410 and backing cursor1bffa000 is only24KiB below its1c000000 limit. That is backing exhaustion, not a missing path or DLL export.
Mapping lifetime audit found UnmapViewOfFile deleting JS view metadata without releasing its guest allocation; failed async provider fills leaked unpublished allocations too. The matching guest_map_free export now releases those sparse views (not HeapFree), and invalid/double unmaps fail. Non-top virtual releases leave holes, so the commit allocator now searches live backing extents for a non-overlapping gap only when the bump cursor cannot satisfy a request. It preserves live addresses and the high-water cursor and zeroes reused bytes. Tests cover1100 real map/release cycles, non-top gap reuse, retained live bytes, consecutive non-overlap, writeback, async failure cleanup and cross-instance reservations. The fixed512MiB total /320MiB backing layout is unchanged.
Diagnostic caveat: black-white-unmap.log and black-white-map-reuse.log still used the preceding compiled browser artifact; they are not post-fix acceptance. The canonical rebuilt pair is1072381/1072841 bytes; the follow-up run is /private/tmp/black-white-map-reuse-built.log.
The rebuilt run passes the logo map:00925086 returns385a0000, then native D3DX processes PNG data. At the next stop there are130 live mappings totaling 169906176 bytes (about162MiB); no memory-layout expansion was needed. New stop00526cf0/return00526d1d is call [ecx+0xc0] at00526d17, device slot48 GetViewport. Viewport now has real24-byte Get/Set state, initial full-target dimensions, bounds/depth validation, and immutable GPU draw snapshot lowering. The browser pipeline verifies default values, preserved state after an invalid setter, cropped rendering, and untouched outside pixels. Viewport state-block recording remains explicitly unsupported.
Viewport continuation reaches D3DX original0042aa4a (runtime02552a4a): device slot79 SetNPatchMode(0) while recording the sprite state block. The old recording guard trapped even on disabled mode; the old getter also incorrectly returned its FLOAT in EAX. The linear-only backend now accepts disabled mode (+0/-0), including recording, rejects every nonzero/NaN request with NOTAVAILABLE, and returns GetNPatchMode through x87 ST(0). This does not implement or advertise N-patch tessellation. State-block and FLOAT/ABI tests pass. Diagnostic log: /private/tmp/black-white-npatch.log.
The next sprite-recording calls are SetIndices(NULL) at D3DX0042ac53 and SetStreamSource(0,NULL,0,0) at0042ac63. Both now use real selective state-block buffer capture rather than the previous recording guard: last write wins, stream offset/stride are copied, retained buffer references transfer and release correctly, and invalid writes preserve prior recorded state. Compiled tests cover non-null replacement, Capture/Apply and release lifetime; this is not a NULL-only bypass. Build1073008/1073468 passes canonical gates.
The subsequent black-window loop actually calls SetVertexShader(NULL) and SetPixelShader(NULL), followed by DrawPrimitive and DrawIndexedPrimitiveUP. Those were rejected before GPU submission. The new bounded fixed-function compiler consumes real state snapshots through the shared GPU backend; no failed guest shader is replaced. WAT-to-browser tests verify world translation, textured diffuse modulation, POSITIONT coverage and alpha rejection. Build 1072939/1073399 passes the canonical gates; state-block regressions also pass. The first run (/private/tmp/black-white-fixed.log) still has a black final frame, with missing-texture and incomplete-mip-chain errors. Two follow-up corrections implement documented null-COLORARG1-texture cascade termination and one-level LOD clamping (non-mip filtering, not generated texture data). Pipeline tests pass. Fresh follow-up log: /private/tmp/black-white-fixed2.log. Gameplay is unverified.
The error-free follow-up remains black. Observational draw snapshots identify a raster bug: TL/TR/BL POSITIONT strips (white diffuse, valid viewport, z0, cull3) write zero colored pixels. The backend selected GL_CCW as its front face, discarding the clockwise sprites D3DCULL_CCW must keep. It now selects GL_CW; browser regressions explicitly verify both culling modes on the same screen-space triangle. Genuine follow-up: /private/tmp/black-white-culling.log; indexed-sprite snapshots are being checked too. No menu acceptance yet.
Corrected-culling snapshots confirm480000 colored pixels in800x600 after the loading strip, and later indexed sprites write pixels too. Completed presentation canvases also contain480000 colored pixels while the whole-window screenshot is black (/private/tmp/black-white-presentation.log). Renderer exclusive composition skips windows with no _backCanvas; this GPU-only client does not request a GDI canvas. D3D9 bridge initialization now asks the existing renderer.getWindowCanvas owner to create that normal backing once. It does not create another compositor or reuse the live GPU context as a window surface. Pipeline regression verifies that acquisition; genuine follow-up is /private/tmp/black-white-compositor.log.
That backing acquisition alone does not fix display. The remaining concrete notification bug is D3D9 Present assigning renderer.needsRepaint, which no renderer code reads. Present now calls renderer.scheduleRepaint, preserving the normal deferred/Worker-safe publication path. Pipeline tests assert exactly one compositor scheduling request per completed Present. Fresh window probe: /private/tmp/black-white-repaint.log. The earlier backing-only explanation was incomplete; neither a colored GPU buffer nor a Present count proves display.
The corrected repaint run visibly renders the genuine Lionhead Studios particle intro. Screenshot personally inspected: /private/var/folders/dz/1fqkk_jd4350qkm91pm9_q3c0000gp/T/black-white-browser-DqPoHR/frame-3.png. This is the first verified visible game-rendered scene, not gameplay or a menu. The probe now accepts explicit timed browser key/click inputs (BW2_INPUTS); the next run sends Escape at50/70 seconds to attempt normal intro skipping. Log /private/tmp/black-white-menu.log; no guest-state patching.
Escape/Space and a real browser click do not skip the intro in180-second probes; the particle animation is still visibly advancing (npCxjk/final.png). Keyboard events log hwnd0 whereas mouse events target10001, so skip-key delivery is not yet proven. Do not classify an animating slow intro as an infinite loop merely from elapsed wall time. DXT1/DXT5 are now real compressed sampled resources: block-row allocation/pitches/locks and small mip tails stay compressed, while the upload snapshot decodes RGB565 and alpha selectors. CPU/compiled tests and WAT-to-GPU pixels pass. A concurrent authorized main merge preserved the dirty work and changed the base layout; canonical build now1073704/1074165, layoute5ea7699f58899e4,source306. Fresh420-second run: /private/tmp/black-white-dxt.log. No menu/gameplay claim.
The intro loop is005260e0, per-frame simulation/render00526cf0. It increments object+20 once per frame; object+0 becomes636 in the observed run. At00528141 the fade starts only after frame > target+350, then object+28 rises until >255; 00529421 sets object+24 = currentFrame+500, and0052943b returns completion only after that frame. Escape is not tested by this loop; mouse and arrow keys alter its particle interaction instead. This is a frame-count animation, so a few FPS means many minutes. Read-only BW2_INTRO_TRACE captures the object from ESI at 00526d93, avoiding a hard-coded guest stack allocation.
However,420-second runs stop submitting frames at1000 (DXT run) or953 (counter run, introFrame918, target636, finishFrame-1, completion0). CPU still executes00868xxx/008c6xxx animation evaluator code. That is not evidence that the intro returned; the earlier beyond-intro inference from EIP alone was premature. A600-second follow-up includes calling-stack observations: /private/tmp/black-white-intro-stack.log. No runtime clock or game state patched.
The600-second stack run stops at introFrame655. Actual saved return addresses 0052618d and00526198 put execution in the100ms catch-up loop, not intro teardown. GetSystemTimeAsFileTime was multiplying ticks by10000 and adding the epoch using i32 arithmetic, then writing a constant high DWORD. Consequently its reported time jumps backwards whenever the low DWORD wraps (~429.497 seconds); the game's unsigned elapsed-time loop then attempts an enormous number of updates. The API now uses an unsigned tick extension and i64 multiply/add/store, preserving the existing simulated epoch and clock source. test-system-filetime covers the exact first carry boundary, subsequent carries, signed tick bit, one clock sample, output bounds and stdcall. It passes, as does the corrected real-device NULL shader test. No guest clock scaling, animation counter or binary patch is used.
BW2_INTRO_TRACE also counts catch-up iterations00526185 vs render-loop0052619d and samples the game's elapsed milliseconds0177cb18. This can distinguish normal slow animation from a new enormous-delta catch-up. Keyboard hwnd0 is the renderer's normal key-event route, not evidence of an input-routing fault; this intro does not check Escape. A fresh long run is needed to establish progression after the FILETIME fix; no menu/gameplay acceptance yet.
Software-worker launch integration (2026-09-10)
The first current-source software CLI probes use all MainApp assets and the
three explicit DLL seeds above, --d3d9-renderer=software --d3d9-programmable,
--batch-size=200000 --real-ticks --max-seconds=45. Diagnostic artifacts are
/private/tmp/bw-software-integration.DqstAK/wine.wasm and its compat pair
(1098688/1099156 bytes, layout f73bfdc3f7f38137). They compile current sources;
the full canonical build still stops at unrelated stale toy-VM browser bundles.
Both ordinary and trace-yield probes stop after 288 batches with STUCK at
thunk07503488, return00a4da46. Disassembly confirms call00a4da40 is device
slot81 DrawPrimitive (+0x144), triangle strip5, start0, count2. The CLI stuck
detector lacks an exemption for outstanding render waits, although it exempts
controlled sessions; consequently the passing controlled worker smoke does not
prove ordinary launch behavior. A scheduler correction and uncontrolled test
are in progress. This observation does not prove a worker deadlock or gameplay.
Logs are run.log and scheduler.log in that diagnostic directory. A bounded
follow-up raises only the diagnostic --stuck-after threshold to1000000;
extended-wait.log will distinguish a false stop from actual renderer errors.
The raised-threshold follow-up does continue submitting real draws. It reports
D3D9 software: only bounded triangle lists are implemented, followed by
D3D9 software: blending is not implemented. These are explicit renderer
implementation gaps, not a silent successful rendering path. Strip/fan
normalization is now the next adapter task; native blending remains required.
This progression supports the false-stop diagnosis, but no visible gameplay
has been verified.
Strip/fan and native blending integration follow-up:
/private/tmp/bw-blend-integration.FJ1QzJ/wine.wasm (1100883 bytes) and its
1101351-byte compat pair compile the new native blend stage and defaults.
run.log no longer reports the strip/blend rejections, but the personally
inspected 800x600 frame.png is solid white. That run reaches its100000-batch
limit; its CLI summary prints the configured60-second budget as elapsed time,
so the summary must not be used as a performance measurement.
timed.log instead uses100000000 max batches and a45-second wall guard, plus
the diagnostic raised stuck threshold. Its more precise backend error is
fixed-function features are not implemented: alphaTest (14 occurrences),
not lighting/fog/specular. It starts five guest worker threads. timed.png is
byte-identical to the inspected white frame. Alpha testing is the next native
pipeline task; this is not menu/gameplay acceptance. Software triangle, blend
and worker unit results alone did not predict this real-game blocker.
Alpha-test integration follow-up: current-source diagnostic pair
/private/tmp/bw-alpha-integration.jT7BVd/wine.wasm and .compat.wasm are
1101235/1101703 bytes. The45-second wall-guard run ends at309117 batches with
no renderer error lines. Both programmed and NULL-shader draws now receive
native alpha-test state; a low8 reference and comparison are copied before
execution and applied before color/blend/depth writes. The personally inspected
800x600 frame.png is predominantly white with one faint yellow spot near the
lower center, rather than the preceding completely white frame. This proves
changed rendered output, not recognizable intro completion, menu or gameplay.
The same five guest workers are present. Logs remain in that directory.
Ordinary CLI wait follow-up: normal-wait.log in the alpha-integration directory
uses the same assets/build and no --stuck-after override. After the live render
request exemption, it reaches the20-second wall guard at312228 batches without
STUCK. Stats now reports measured execution20.009s rather than a configured
deadline. test-cli-elapsed-time.js independently checks the timing correction.
No image was captured in this run; it establishes continued execution, not
visual progress or gameplay. A separate production-worker strip benchmark found
most default draw wall time spent waiting between256-quad timer callbacks;
bounded scheduling improvements are in progress before the next game capture.
Bounded worker batching follow-up: /private/tmp/bw-slice-integration.4NVoSq/
contains current diagnostic1101707/1102175-byte Wasm artifacts, run.log and
frame.png. The ordinary run uses unchanged256-quad native steps grouped into
4ms/64-step worker callbacks, no stuck override and a45-second wall guard. It
ends at22872 batches in measured45.007s and now reports repeated unsupported
single-level2D texture and primitive topology errors. This is additional failing
draw coverage, not evidence that batches/s measures game speed. The inspected
640x480 capture is still white with a faint yellow spot; no gameplay. Capture
the failing texture and primitive descriptors next before selecting mip/cube or
point/line implementation work. The matching synthetic worker benchmark has
identical output before/after batching, but does not establish real-game parity.
Parameter diagnostic follow-up: the old topology error was misleading. The
actual rejected draw is triangle LIST4 with2950 primitives, exceeding the
adapter's256-primitive limit; it is not a new primitive topology. The rejected
texture is stage0,1024x1024,11 mip levels,0 cube faces. parameters60.log records
these values after enriching the existing errors; the preceding30-second run
ended before reaching this phase. Next work is ordered large-draw batching and
real mip-chain sampling, not a point/line or cube workaround. The software
adapter regression now distinguishes capacity failures from topology failures
and checks texture diagnostic fields while preserving allocation ownership.
Large-draw plus mip integration run: /private/tmp/bw-mip-integration.nWAYwR/
contains diagnostic1104018/1104486-byte Wasm pair and run.log/frame.png.
The ordinary60-second launch ends at24379 batches in measured60.019s, with no
renderer errors or STUCK report. The inspected800x600 frame remains white with
a faint yellow spot. Load average was9.16 at launch, so this is not a performance
comparison. No completed-draw/intro-counter capture was collected here; absence
of the previous errors alone does not prove equivalent game progression.
The exact1024x1024 eleven-level shape independently passes software implicit-LOD
pixel tests, and2950-triangle real x86 COM/worker tests pass. Next capture must
record completed draws and intro state alongside images. No gameplay acceptance.
Controlled software probe50895 provides stronger evidence. Artifacts are
/var/folders/dz/1fqkk_jd4350qkm91pm9_q3c0000gp/T/bw-software-probe-65NV0y/.
By60s of its observed attachment window it records168 completed draws and80
completed presents, zero submission failures, and one outstanding draw. At55s
the draw categories change from two loading strips per frame to textured lists
of2/2950/8 triangles using1024x1024 eleven-level textures. The inspected final
640x480 image visibly shows LIONHEAD STUDIOS with falling particles and reflection.
This establishes the software intro, not menu/gameplay or backend parity.
tools/black-white-software-probe.js uses the existing CLI control channel,
Bridge submission promises and EIP tracing without modifying guest state. Its
first run falsely reported traceHits0 because its address regex accepted at most
one leading zero; actual logs contain three00526d93 hits with ESI074d6bdc. The
parser is corrected; that observed stack address must not be hard-coded into
later probes. Frozen mode was rejected with real ticks, so the tool attaches to
an ordinarily running real-time guest and explicitly measures only that window.
Longer probe23014 (artifacts bw-software-probe-pnliVq under the same temp root)
exposes a lifetime failure after initial success. At75s introFrame1/target636 is
observed; by85s/frame5 native raster creation begins failing. Successful DRAW
completions stop at181 while Present and intro counters continue. At120s the
intro is frame62, but346 submissions have failed, including explicit native
allocation failures. This is not healthy progress toward gameplay.
Focused reproduction: node test/test-d3d9-large-draw.js --stress-mips repeats
2950 triangles with the1024x1024 eleven-level chain. Run8381 fails at iteration24;
adapter-owned bytes return to512 after each prior draw and all tracked native
contexts are freed, yet sparse heap reservations advance by roughly12MB per
iteration. Current heap allocation uses first-fit splitting and heap_free only
prepends blocks, without coalescing. Fragmentation is the next hypothesis to test
with native free-list inspection/coalescing; accounting returning to baseline is
not proof that the allocator can reuse the storage.
Follow-up: the primary leak was missing sparse inverse translation in w2g.
Native WASM-pointer frees therefore supplied an unrelated guest address and were
silently rejected by heap validation. Fixed and verified by actual sparse reuse
in test-d3d-render-lifetime.js (51141/20572). Stress93161 now completes32frames
with stable memory after warm-up. Disabling the idle coalescer (41282) passes32
frames but accumulates fragmented free blocks (494->6661), so both inverse
translation and coalescing are retained. These are allocator/render regressions,
not yet evidence that a fresh real-game run progresses through the intro.
Fresh post-fix probe62767 completed180s using diagnostic build
/private/tmp/bw-native-reuse.5Xjp7I/wine.wasm. Artifacts:
/var/folders/dz/1fqkk_jd4350qkm91pm9_q3c0000gp/T/bw-software-probe-frxgg9.
At the final sample: introframe43,414 completedDRAW,123 completedPRESENT,
zero submission failures, queue627/627 completed. The inspected final image
shows Lionhead logo, particles and reflection. Successful rendering continues
beyond the old frame5 allocation failure; no menu/gameplay yet. Initial load
average82.53 makes this unsuitable for performance comparisons. No guest intro
counter or clock patch was used.
Long-run probe20567 completed its1800-second observation window successfully.
Frozen native artifact: /private/tmp/bw-long-native.IxWKnd/wine.wasm;
artifacts: /var/folders/dz/1fqkk_jd4350qkm91pm9_q3c0000gp/T/bw-software-probe-WjjGiJ.
The final sample records introframe560/target636,3514 completed DRAW commands,
563 completed PRESENT commands and zero render failures. One draw remained
pending at sampling, before normal shutdown. This demonstrates sustained intro
rendering, not menu/gameplay. The helper now permits an explicit observation
window up to14400 seconds so later runs can reach beyond the full intro without
patching guest counters or clocks. New source requires a fresh matching native
artifact; this run does not validate subsequent shader/stencil/Reset changes.
The ongoing7200-second probe93079 uses the frozen
/private/tmp/bw-stencil-native.LHj3DN/wine.wasm artifact and
bw-software-probe-pOr4aX capture directory. At2665seconds it reaches
introframe692,4302 completedDRAW and771 completedPRESENT withzero failures.
Live control ping confirms the same process is running. Frame2163.png was
personally inspected: Lionhead particles/logo/reflection, not gameplay. Passing
the recorded target636 does not imply completion: the existing disassembly
above places fade onset after986, then requires the fade threshold and another
500frames. Do not restart or patch the healthy run merely for crossing636.
Later in the same live run, at3839seconds/frame1049, the fade/completion field is53.54997 rather than zero, and the draw categories include10-triangle lists instead of only8-triangle reflection draws. All9682 submitted commands have completed withzero failures at that sample. A subsequent control ping confirms the same process live atframe1052/fade56.09997. Frame3844.png was personally inspected: the logo changes orientation and fades, but this remains the intro. This confirms real progression beyond the documented fade threshold; no guest animation state or clock has been patched.
Probe93079 is now terminal0 after its7200-second guard and graceful controlled
quit; do not treat it as live or send further input. Intro counter reached1787
against finishFrame1786. The final sample records45985 completedDRAW commands,
53558 completed queue commands andzero failures (one draw pending at sampling).
The final bw-software-probe-pOr4aX/frame.png was personally inspected: a
cloud-covered island backdrop with a central panel, empty-looking text fields,
up/down controls and bright polygon artifacts. This is visibly beyond the
Lionhead intro, but neither readable menu text nor interactive gameplay is
verified. New draw categories include1280x960,1024x1024 and256x256 textures.
The run still used frozen bw-stencil-native.LHj3DN/wine.wasm and startup-loaded
JS, predating the later POSITIONT precision/coverage correction, PSIZE and
multi-stage work. A fresh matched artifact is required before attributing the
panel corruption to current source. No guest counters or clocks were patched.
Follow-up probe33057 uses /private/tmp/bw-cascade-native.Ca0YQN/wine.wasm
with startup-loaded JS including the POSITIONT correction. At4553seconds its
intro reaches1787/finish1786 with16323 commands complete and no render failures.
At4660seconds new post-intro draw categories are active. Live controlled capture
/private/tmp/bw-postintro-scene.png was personally inspected: cloud-covered
island, central panel with unreadable text and bright polygons remain. Thus the
POSITIONT correction alone did not resolve this panel corruption. This frozen
artifact still predates later cascade operations, transforms and fog work;
the image is not evidence against all current source. Preserve the live run
for inspection; no guest counters or clocks were patched, gameplay unverified.
The same live run now has a bounded neutral-command capture, armed through its
existing eval control with tools/d3d-command-capture.js. Artifact:
/var/folders/dz/1fqkk_jd4350qkm91pm9_q3c0000gp/T/d3d-command-capture-XfJrDb/frame.v8.
One Present-to-Present interval contains17 DRAW commands plus PRESENT,
77,634,825 serialized command bytes. Capture restored the original submission
hook after completion. It preserves typed arrays/nonfinite values and copied
shared-memory inputs; completion means capture boundaries, not GPU fences.
It does not snapshot the initial color/depth buffers or earlier resource
commands, so replay equivalence must verify those dependencies explicitly.
Draws0–8 include POSITIONT textured strips; later XYZ triangle lists include
empty texture arrays despite stage0 MODULATE/TEXTURE state. This is observed
binding data, not yet proof of which draws are glyphs or why textures are absent.
The capture enables inspecting/replaying the actual panel submissions without
restarting the healthy guest or waiting through another intro.
Current WebGL2 replay32433 of that capture completed17 draws without GL errors.
Tool: node tools/d3d-replay-web.js CAPTURE/frame.v8 OUTPUT.png.
Output /private/tmp/bw-replay-webgl.png was personally inspected at its native
800x600 size: the same unreadable central panel and bright polygons occur.
This reproduces the defects with another executor using the captured inputs;
it points away from an exclusively software-rasterizer defect, not toward any
particular frontend cause yet. Replay starts with transparent color and no
prior depth history; it is not a pixel-identical whole-session comparison.
The host texture loop omits a stage only when its canonical bound pointer is
zero; invalid nonzero resources throw rather than silently disappearing. The
captured untextured XYZ lists use repeated quarter-atlas UV rectangles and
varying 3D positions, consistent with sprites/particles rather than proven
text glyphs. Determine their binding provenance separately from missing text.
Current software replay47696 completes the same17 draws. Comparing with WebGL
initially found479666 alpha differences but only1 RGB pixel beyond tolerance2.
Source inspection found WebGL ignored state.colorWriteMask; applying
COLORWRITEENABLE per draw fixes that independent compatibility bug. All16 masks
plus invalid-mask/no-pixel-change pass on WebGL1/2 in regression76707.
Replayed WebGL32402 versus /private/tmp/bw-replay-software.png now differs
beyond tolerance2 in just1/480000 RGBA pixels (max33, coordinate310,325).
Both still show the unreadable panel and bright particle polygons. This makes
the captured shared draw data/frontend path the next investigation, not a
software-only rasterization explanation. Exact pixel parity is not claimed.
Post-checkpoint investigation on the same live33057 process: the captured
1024x1024 UI atlas (draws2/8) was decoded and personally inspected. Its baked
promotional text is readable, and the panel rectangles are intentionally blank;
the thin divider lines in the menu are present in the source atlas, not damaged
glyphs. No separate glyph-texture draw appears in this captured17-draw interval.
Artifacts /private/tmp/bw-texture-2.png and bw-texture-8.png are diagnostic
extractions from the immutable capture, not native-Windows references.
Microsoft native d3dx9_25.dll disassembly identifies the font constructor at
original0042d202, vtable00401580, and DrawTextA/W entries0042f263/0042f29c.
Both call internal0042d8f7 (runtime025558f7). Existing live EIP tracing confirms
that internal routine executes repeatedly with font object4ed795cc; its common
exit0042ef76 is reached with EDI0 (success). The current process has two loaded
TrueType faces of34044 and134188 bytes. These observations do not prove glyph
generation or successful rendering.
Native global original006298d4/runtime027518d4 reads1, selecting the Uniscribe branch in DrawText; original006298ac/runtime027518ac reads0. Therefore the fallback GetCharacterPlacementA/W calls at0042e14e/0042e156 are not the first path to investigate. Follow the native shaping results and glyph-cache/texture submission boundary next. No Wine implementation source was used; the DLL addresses come from the supplied Microsoft binary. Temporary EIP ranges were restored to the original intro probe range00526d93–00526d97 afterward, without changing guest animation state or restarting the healthy process.
The next inspection found valid shaped glyph IDs/positive advances, but the first six cached glyph records reached through font+0x550 all start with FFFFFFFF followed by zero geometry. Native D3DX0042d32c marks that sentinel at0042d468 when its measured glyph advance is zero. On the Win9x branch it uses MoveToEx, ExtTextOutA(options12hex, NULL rectangle, one WORD glyph index), then MoveToEx to retrieve the updated current position. Our text compositor rejected the NULL opaque rectangle and did not implement direct glyph indices. This explains the observed empty-cache path; fresh corrected game rendering is still required, and cached failures in the old live process are not patched.
Native Win98 oracle gdi-exttextout-glyph now covers32 A/W, character/glyph,
NULL/supplied rectangle and options0/2/4/6 combinations. With Arial height-16,
GCP maps A to glyph36; every case returns1, advances CP to(11,0), and draws28
nonblack pixels. Thus Win98 accepts NULL rectangles with OPAQUE/CLIPPED and
renders ANSI WORD glyph indices. This differs from the current Microsoft
ExtTextOutA documentation
describing ANSI glyph-index calls as a no-op; do not substitute that behavior
for the measured Win98 contract.
Reproduce with node tools/v86-reference/capture.js --online --manifest tools/v86-reference/glyph-apps.json --app gdi-exttextout-glyph --serial-output /private/tmp/win98-exttextout-glyph.serial (one command). Capture54317 exited0
at2026-09-11T00:46:45.542Z using the pinned v86 Win98 image. Probe EXE SHA256:
d6cdcb6a24f744b80d743484f1133412932fe38ac443f43a07c744527a8c6c6c.
Generated metadata is screenshots/v86-reference/generated/gdi-exttextout-glyph.json;
serial transcript has32 CASE lines and EXTTEXTOUT_GLYPH_DONE. Source probe and
isolated manifest are tracked candidates; no Windows binary is added.
Null-rectangle normalization and direct TrueType glyph-index rendering are
implemented using the existing native cache and shared text compositor.
The glyph regression covers A/W WORD inputs (including indices above255),
current-position advances, PDY, clipping, path recording and invalid indices.
Bitmap-font indexed output remains explicitly unsupported.
The standalone native D3DX integration probe now verifies the complete font
path, not just GDI: node tools/d3dx-font-probe.js --dll=/path/to/d3dx9_25.dll --wasm=/path/to/wine-assembly.wasm (one command). It executes the supplied
Microsoft DLL's CreateFontA/DrawTextA, uploads its glyph texture, and checks the
software worker's canonical320x160 frame. Baseline75148 exited the guest
successfully but failed acceptance with zero white pixels and51200 blue pixels.
Corrected run12873 passed with863 white pixels and50337 blue pixels; its PNG
was personally inspected and shows readable white “Black & White 2” text.
The actual browser guest/WebGL2 probe31600 also passes with863 white pixels,
50337 blue pixels and GL error0; its PNG was personally inspected and shows
the same readable text. Reproduce using node tools/d3dx-font-web-probe.js --exe=/path/to/probe.exe --dll=/path/to/d3dx9_25.dll --wasm=/path/to/current.wasm
(one command; the software probe builds the fixture EXE). The harness observes
the real backend's target at Present, before normal guest teardown. Initial
browser attempt2860 selected Notepad due to the global launch wrapper ignoring
arguments; corrected harness selects its explicit registry entry instead.
Full canonical/compat build15499 and repeated software font85829 pass after
concurrent main merges. These are focused native-DLL integration results, not
proof of fresh full-game menu/gameplay. No guest cache was patched.
Fresh current-artifact game run98750 was started after checkpointad3ba44b, using
/private/tmp/bw-glyph-current.pohQXb/wine.wasm (1141556 bytes). It is independent
of the preserved old33057 process whose native glyph cache already contains
failures. At587 seconds the new run reaches intro frame105, with785 completed
draws and zero reported renderer failures; menu/gameplay acceptance is pending.
Artifacts: bw-software-probe-DLH67d under the host temporary directory. This is
a progression observation, not a throughput benchmark (launch loadavg39).
Local Candidates now includes black_white_2_demo (Black & White 2 Demo).
The prepared MainApp tree is copied to the ignored local corpus directory
test/binaries/win98-games-a-d/Black and White 2-DX9-D3D/installed/;
node tools/gen-win98-games-a-d-manifests.js inventories its363 companions.
The entry seeds the same three native DLLs as the CLI probe and opts into the
experimental programmable profile for this app only; renderer selection stays
user-controlled (?d3d9-renderer=software for software, WebGL by default).
CLI: node test/run.js --app=black_white_2_demo --d3d9-renderer=software.
This registers the extracted game, not an installer-success or gameplay claim.
Progression update: current-font run98750 remains live, reaching intro frame1513
at5947 seconds with finishFrame1786 and zero reported renderer failures.
frame-5941.png was visually inspected: complete Lionhead logo/reflection, not
yet the menu. Older pre-font-fix run33057 is now authoritatively terminal with
exit0; its recorded final intro frame1787 and cached missing-glyph state are
historical evidence only. No restart or guest-state patch was used.
Current-font run98750 has now passed intro frame1786 and reached the profile
creation menu. Personally inspected frame-7320.png shows readable “Select
Profile”, “New Profile Name”, “Close”, and the main menu labels over the island
background. At7331 seconds the renderer reports20665 completed draws and zero
failures. This proves the glyph fix reaches the real game's menu, not just the
standalone font probe. Profile selection and changing gameplay remain unverified;
the frozen executable artifact predates lighting/cache/scissor checkpoints.
The same run98750 is now terminal (CLI exit1), not waiting for more input.
Holding Return was accepted at batch591383; at batch591475 the real profile-name
encoding loop called unimplemented PathGetCharTypeW(0x50) and trapped.
The call site is00857c90 (push eax; call ebx), return00857c93, then test al,1:
valid long-filename characters are copied directly; others take the formatting
branch at00857c9e. The preceding sample reports93956 completed draws and zero
renderer failures. Thus keyboard input reaches profile processing; this is not
evidence of gameplay or of a renderer failure. Earlier mouse motion550,400
mapped to native687,500 (640x480 presentation of800x600 window), drained its
queue, and did not visibly dismiss the profile dialog. No guest state was patched.
PathGetCharTypeA/W is now implemented from Microsoft's API contract and the
supplied native test/binaries/explorer98/dlls/shlwapi.dll, version5.00.2614.3500,
SHA256 731b1ffdcb821a87e8ef1aa92fc956a0c385eaa6b8f4ad20c89db8b923f1aeae.
Exports485/486 are70bdde1d/70bf16c2. Native disassembly classifies controls0–31,
quote, less/greater-than and pipe as0; space/comma/semicolon as1; star/question
as4; slash/colon/backslash as8; all remaining BYTE/WORD inputs as3. These are
character flags, not whole-filename validity; do not impose separate DOS name
rules on plus, equals, brackets, DEL or high Unicode values. A truncates toBYTE,
W toWORD, with no codepage conversion. test/test-path-get-char-type.js passes
both entry points over the entire65536-value domain, upper-bit truncation and
stdcall cleanup. This is a disassembly-derived fixture, not a native execution
oracle. A fresh game run with this implementation remains required. No Wine
implementation source was used.
Fresh frozen run24571 now verifies that progression: snapshot
/private/tmp/bw-path-char.ly0IRU/wine.wasm contains the PathGetCharType fix,
and /private/tmp/bw-path-current-screen.png shows the profile-creation dialog.
Normal Return down at batch224427 and up at224621 closes it without trapping;
/private/tmp/bw-path-after-profile.png shows the island main menu headed
“Player”. Renderer completions continue with zero reported failures. This is
successful profile creation, not gameplay. This frozen snapshot predates the
later backbuffer-lock/ownership and programmed-VS pixel-center checkpoints.
A synthetic click at155,445 was accepted at225653, but a later capture
/private/tmp/bw-path-new-game.png still shows the same main menu. A held mouse
down at those coordinates was delivered at226972 to distinguish short-click
polling from menu behavior; mouse-up was delivered at228211. A later settled
capture at229794 (/private/tmp/bw-path-settled-new-game.png) still shows the
main menu, so neither click variant proves New Game activation. No key or mouse
button is left held by these controls. The
live probe remains24571 with artifacts under bw-software-probe-8o44XX in the
system temporary directory. No guest state or instruction stream was patched.
Buffered-mouse investigation on the same frozen run24571: a temporary,
return-value-preserving wrapper around renderer.getMouseButtons counted8
polls with left held and8 after release (down237923/up238468). The wrapper was
restored. Its caller EIP009b0920 belongs to the game's GetDeviceData call at
009b0930 (call [eax+28h], slot10), with20-byte DIDEVICEOBJECTDATA records;
this is not a GetDeviceState caller. The game's switch accepts offsets0/4/8
for axes and12/13/14 for buttons. SetDataFormat remains an implementation gap,
but these observed offsets do not establish a custom-layout mismatch.
After explicit movement155,445 ->156,445 ->155,445 and down243257, the native trace proves real left-down dispatch:009b0a59 tests bit0x80;009b0a5e records that bit set;009b0a67 passes the zero suppress/error byte at01d7a748;009b0a80 pushes event1 and009b0a84 calls009afe60, returning to009b0a89. The receiver is01d733e8. A read-only snapshot also reports error byte0 and mode017792d4=1. Mouse-up244432 is acknowledged and the later009b0a94 ->009afe60 path delivers event4. Thus the native event dispatcher receives both edges; the next question is relative-cursor/hit-test or downstream event handling, not whether a held button ever reaches the guest. This does not yet prove New Game activation. Native event1 target009aff41 copies the receiver's current coordinate words at+c4/+c8 to+114/+118; event4 target009aff6f copies them to+12c/+130. These provide a read-only seam to compare the game's click position with the host's absolute pointer, without changing guest memory. Transient state is at+110, with another dispatch state byte at+15c.
Diagnostic caution: set_trace_eip_range requires (flag,lo,hi), three args.
A mistaken two-argument call enabled broad streaming trace and slowed batches.
The correct restoration (1,0x526d93,0x526d97) was queued after mouse-up; do not
infer completion of that control from its submission. Preserve the healthy
process while its current batch drains; no restart was used for this diagnosis.
The correct trace restoration is now acknowledged, followed by a confirmation
with host mask0/queue0. The native coordinate snapshot found all current/down/up
coordinates at(0,0), while the host pointer was(193,556). Sensitivity is1.0,
insets+100/+104 are0, and remapping+108 is1. A normal mousemove to(0,0) at
batch281124 moved the game's coordinates to(-1,-1); a later move to(156,446)
at285501 moved them to(194,556), verified before pressing. Down287458 and
up288497 at(156,446) recorded both guest edges at(194,556). The settled capture
at290202, /private/tmp/bw-native-new-game-settled.png, shows the four-panel
controls/tutorial screen with Continue. New Game activation is therefore
verified; changing in-world gameplay remains outstanding. No guest memory or
instruction stream was patched, and the frozen process was preserved.
Buffered-input sequencing matters here: motion is accumulated into+cc/+d0 during the FIFO loop and applied to current+c4/+c8 after that loop. A button record encountered in the same poll snapshots the old current position. Move, allow an actual input update, then press; absolute host pointer fields alone are not sufficient evidence of the game's hit-test coordinates.
Continue was then targeted with mousemove(321,451) at292616; a separate native
read verified(400,562), followed by down293970/up294751. Run24571 is now
authoritatively terminal, exit1: batch294774 traps at00925197 decoding0f d0.
This is not yet evidence of a missing SSE instruction: the supplied EXE has
mov eax,3 at00925195, so00925197 is inside its immediate and its runtime bytes
differ from disk. The enclosing00925120 reader copies from a virtual-method
buffer using rep movsd at00925178 / rep movsb at0092517f. Crash registers
include EBP=EDI=00a58780, ESI=00a5877f, EBX=33078b10; saved caller chain includes
009208a6,00920dca,009a81da. Investigate the copy arguments/corruption origin
before adding an opcode implementation. Last sampled renderer failures remain0.
All synthetic buttons were released before the trap. Gameplay remains unverified.
The saved stack identifies the immediate failure:00925120 is thiscall
Read(dest,count,outRead) with ret12. Return009208a6 at074fd034 places
dest=0 at074fd038, count=00a58780 at074fd03c, and outRead=074fd050 at074fd040.
Caller009a81d5 forwards an unchecked NULL from aligned allocator00adbcbf,
requested size00a58780/alignment64/offset0;00adbc2f requests00a587c3 bytes from
underlying00ad566e and propagates NULL. Copying count bytes into address zero
explains final EDI00a58780 and overwritten executable bytes. The reason for the
allocation failure still requires live allocator evidence; do not assume a leak
or enlarge memory solely from this stack. Effective source-1 is an inference
from final ESI/count, not a verified mapping snapshot.
test/test-bw-rep-copy.js executes the exact00925165..00925186 copy/cursor
sequence with valid heap buffers. All16 cold/cached size cases pass on both the
frozen failing-game WASM and current build, plus fresh source77739. It checks
EBP/EDX preservation, ECX0, DF0, final pointers, bytes, guards and cursor update.
No REP defect was reproduced and no instruction implementation was changed.
Both copies of BW2Demo.exe (probe MainApp and local candidate) have SHA256
65130510233cfc9e53758bab8480a3cfeeb6b1043de9774ab6e066191b2bb433.
Fresh reproduction21705 uses frozen current-build snapshot
/private/tmp/bw-allocation-repro.J76xSx/wine.wasm; artifacts are
bw-software-probe-WjsJYY in the system temporary directory. It is a new run
after confirmed exit of24571, not a replacement of a healthy process. Initial
samples report no renderer failures; menu/gameplay progression remains pending.
Read-only main-instance heap snapshots at batches27089 and43976 are identical: 9316 free blocks,7550264 total free bytes,3182896 largest block, no cycle or bad header; low cursor/end71990848/71991296, sparse920016632/920059904, virtual top1214742528. This bounds an intro interval, not the later failed allocation. An isolated120-draw renderer audit likewise found stable live/cache bytes and reuse, but renderer-private free blocks remain unavailable to the guest owner until graceful handoff. Eager multi-batch scratch and owner high-water retention remain pressure hypotheses. Ordinary Escape down22658/up23081 did not skip the intro; the live run has no held key. Current source now reclaims replaced-arena tails and rejects oversized low-heap reservations correctly;21705 predates that change and must not be described as post-fix acceptance.
Allocation-return diagnostic prepared on live run21705
Use breakpoint009a81c9, the aligned allocator's return block, rather than 009a81d5 inside that block. The native debugger checks dispatch boundaries; the later unchecked-argument push is not itself a reliable breakpoint target. At c9, EAX is the allocation result, EDI the requested byte count, ESI the owning object and EBP the input stream. This allows inspection before the subsequent copy corrupts instructions when EAX is zero.
The live CLI now has a one-shot, return-preserving run wrapper installed by the ordinary eval channel. It shadows the instance's inherited exports getter with a configurable property, invokes the original run exactly once, and on the matching debug return copies registers, stack words and allocator cursors into ctx.bwAllocCapture. It then clears the breakpoint and restores the original property descriptor even if observation fails. ctx.bwAllocRestore cancels the hook before a hit. Installation rejected a preexisting breakpoint; a tiny independent Wasm test verified that shadowing/restoration is supported. No guest instructions, registers or allocation state are patched.
The first oversized terminal input did not execute: canonical terminal input overflowed, producing bell characters. Ctrl-U cleared the pending line; allocation_capture_check2 confirmed breakpoint0 and no hook. Three short hook_part commands then assembled the observer text, and hook_install returned true. Keep future relayed commands short. At the last preceding sample the intro had reached frame1098, completion95.2, and zero renderer failures. The allocation has not yet been observed; this is diagnostic readiness, not proof of either allocation failure or success on this preserved pre-fix build.
Run21705 subsequently completed the intro (frame1787, finishFrame1786) and reached profile creation. Short Return694309/up694325 left the dialog visible; held Return696323/up697061 closed it, with Player at the main menu in /private/tmp/bw-profile-settled.png (batch697412). Normal movement698105 to0,0 then698149 to156,446 settled at native195,557. Down698787/up699231 reaches the tutorial Continue screen in /private/tmp/bw-newgame-current.png (batch700341). Move701673 to321,451 settled at native401,563; Continue down703542/up704168 reproduced the copy trap at batch704171, EIP00925197. All inputs were released. The probe is terminal exit1, not a live wait. This was the pre-tail/pre-compaction frozen build, not current-source acceptance.
The observer worked but matched an earlier call to the same allocation site: batch704170, EAX844933376 (nonzero), EDI231184, ESI856098960, EBP856040276. At that point the free list had9972 blocks totaling5389088 bytes, largest22368, with no detected cycle/bad header/truncation. Low cursor/end71990848/71991296; sparse cursor/end845164640/845676544; virtual top856096768. The full registers, 24 stack words and32 stream words are durable in run.log at BW-ALLOC-CAPTURE. The setter logged synchronously before returning to guest execution, so this evidence survives the subsequent crash. It proves fragmentation at this earlier successful call, not the allocator's full state at the later10.3MiB failure.
The one-shot observer cleared itself after that successful call. Next use must retain the breakpoint until EDI equals00a58780 (or explicitly capture every matching return), rather than stop at the first use of the shared call site. The terminal stack again contains destination0/count00a58780 at the copy call. No new CPU opcode defect is demonstrated, and the exact allocation-pressure cause remains open. Do not restart the old session handle21705; it has exited.
Targeted post-compaction reproduction10715
tools/black-white-software-probe.js --allocation-probe now installs a reusable
observer that remains armed through earlier allocations and captures only
EDI00a58780 at009a81c9. Capture is logged synchronously and the exports property
and breakpoint are restored afterwards, including observation/logging failures.
The focused observer test verifies filtering, exactly one guest invocation per
call, unchanged return values/errors, cancellation, serialization and cleanup.
New run10715 is live on the frozen full-build41098 artifact: /private/tmp/bw-compacted-repro.kyBDAg/wine.wasm,1158838 bytes, SHA2567dee27a0cace008de26aa1cc5d4a1ec4d88ba7228ce589190182b5e30f655183. This includes heap-tail reclamation, context compaction and the private VS2 foundation, but predates the later DirectInput lifecycle commit and matrix work. Artifacts: /var/folders/dz/1fqkk_jd4350qkm91pm9_q3c0000gp/T/bw-software-probe-LJrjIm. Launch uses --seconds=14400 --capture-every=60 --control-stdin --allocation-probe and the frozen --wasm path. Observer installation returned armedtrue with address10125769/request10848128. Startup samples through28.95s have no renderer failures; neither gameplay nor the target allocation has yet been reached.
Run10715 subsequently completed the intro at frame1787 with no renderer
failures. Profile Return down218078/up219575 accepted the default Player name;
/private/tmp/bw-compacted-profile-accepted.png shows the main menu. Direct
movement to156,446 initially left native coordinates at0,0. Moving to0,0 first
established(-1,-1); then move224741 to156,446 settled at(194,556), verified before
pressing. New Game down226752/up228520 reaches the tutorial screen, verified by
/private/tmp/bw-compacted-tutorial.png at batch230088. All keys/buttons were
released after these inputs. The original frozen process remains in use, not a
latest-source or full-game acceptance run; the targeted allocation capture is
still pending before Continue.
Continue move232043 to321,451 settled at native(400,562), then down233778 and up235764. All synthetic input was released before the failure. Run10715 is now authoritatively terminal exit1: batch235768 traps at00925197, the same corrupted copy site as the pre-fix run. Do not restart this terminated handle.
Unlike the earlier observer, the targeted capture caught the exact failing
allocation at batch235767: skipped1 earlier allocation, EIP009a81c9,
EDI10848128 (00a58780), EAX0. Heap ptr/end72007784/72011776; sparse ptr/end
852481608/853082112; free-list head877342288; virtual allocation top828637184.
The bounded walk found10248 free blocks, total5678096 bytes, largest44592,
with no bad header or truncation. Full registers/stack/stream are durable at
[BW-ALLOC-CAPTURE] in the run's existing run.log, line3048. The capture proves
NULL allocation precedes the unchecked copy, even with heap-tail reclamation
and shader-context compaction. It does not yet identify why obtaining another
large sparse arena failed: virtual allocation records/backing-space census
were not captured. No missing CPU opcode or new shader failure is demonstrated.
Allocator review: even perfect coalescing of the captured main-instance free blocks cannot satisfy this request. A new sparse arena requires10878976 bytes (166*64KiB), including aligned allocation/header overhead. The next failure branches to distinguish are the shared guest reservation limit,2048 mapping slots, contiguous physical backing/PTE publication, and1024 heap arena slots. The recorded virtual_alloc_top is instance-local, not the authoritative shared reservation cursor. Existing MEM_DECOMMIT/release/failed-reservation lifecycle gaps were found during review, but none is established as this run's cause.
The observer now captures matching-layout VIRTUAL_MAP_STATE, all bounded map records and heap arena records, active/free backing bytes and largest gap. Optional section failures preserve return registers and other diagnostics. Counts are capped by descriptors and2048 maps/1024 arenas; unpublished arena records and best-effort consistency are explicit. The caller loads the same RegionMap mirror after the CLI's WASM-layout check and adds --dump-virtual-maps as a terminal backup. Focused tests and syntax/region-copy/diff gates pass.
New probe89033 is live on the identical frozen7dee27a0... wasm, whose layout stamp was rechecked as9c6027bce1d500a1; this changes diagnostics, not the guest or emulator under comparison. Same14400-second guard,60-second capture interval, control stdin and allocation observer. Artifacts: /var/folders/dz/1fqkk_jd4350qkm91pm9_q3c0000gp/T/bw-software-probe-w9Dofc. Installation reported armedtrue, address10125769/request10848128. No input has been sent. This is the instrumented follow-up to a terminal run, not a restart caused by an observation timeout, and no allocator fix is claimed yet.