Unreal-family demo installers

Status: verified locally on 2026-09-13. These are proprietary demos and remain localOnly candidate-corpus fixtures. No host Wine was used.

Sources

Candidate Archive.org item File Size SHA-1
Unreal Special Edition unreal-special-edition.-7z UnrealSpecialEdition.7z (was Unreal Special Edition.7z until ~2026-10; same bytes) 128,609,961 f3f25896a51cbf37dcdb94833198b86f394d8853
Unreal Tournament unreal-tournament-demo-version-348 UTDEMO348.EXE 55,647,232 faf2c18852a1a53c59db490e044e0d3e100e8fed
Unreal Tournament 2003 UT2K3Demo UT2003Demo2206.exe 148,976,640 372a8b712cb7f2b1539af72430923d290a67e701
Unreal Tournament 2004 UnrealTournament2004Demo Ut2004-NewDemo.exe 296,049,152 5e224a3de711da9085cddd9929499789690043c7
Unreal Tournament 3 setuput3demo setuput3demo.exe 777,027,962 86d4bad740d0c25438a65c48939ee6dcfc88bb02

The host archive reader only unwraps the outer 7-Zip/WinZip container. Candidate preparation then executes the original setup program inside Wine Assembly, exports the VFS it wrote, and boots the installed game. This distinction is enforced by tools/install-unreal-demo.js.

Installer and launch results

Candidate Authentic setup result Installed payload Installed-game result
Unreal Special Edition InstallShield bootstrap launched _INS*.MP; license and destination flow completed 191 MB, System/Unreal.exe SHA-256 5fbc5853a8669a802446ac12e102351053bc6a5ce9f554b03483eb634269f408 Software launch loads SoftDrv, opens WindowsViewport0, initializes the game engine/player, and renders the playable intro
Unreal Tournament 348 Unreal System/Setup.exe completed 104 MB, System/UnrealTournament.exe --app=ut348_demo: first-run wizard, UWindow menu, DM-Morpheus practice match on SoftDrv with walk + mouse-look (see "UT 348 route" below)
UT2003 2206 Unreal System/Setup.exe completed with the shipped MSVCR70.dll 344 MB, System/UT2003.exe SHA-256 97e027dc9765f048beacfa461bc93c71ba1831cd3e8dff0cd7d71c1b478f88a2 The D3D8 wrapper renders textured first-person Antalus gameplay; an authentic dedicated server and direct-connect client exchange the native protocol over vln/1
UT2004 new demo Unreal System/Setup.exe completed with the shipped MSVCR71.dll 525 MB, System/UT2004.exe SHA-256 2a95e2fa8c22ae94eb1c361fdb49ea8ec44c5e2a93faa00831308c01e951db8d Uses the same pre-renderer D3D8 probe; further post-probe launch diagnosis remains

Seeding the bundled Visual C++ runtimes matters. Without MSVCR70.dll, the UT2003 setup appears to require unimplemented CRT imports beginning with wcschr; with its own runtime loaded, setup completes without any new emulator API. The same rule applies to UT2004 and MSVCR71.dll.

OpenGL and software-renderer attempts

UT2003 and UT2004 both ship OpenGLDrv.dll. (Historical: the installer no longer selects OpenGLDrv. See "--app from a fresh candidate fetch" below.) Their installed and default INIs were changed locally to OpenGLDrv.OpenGLRenderDevice, fullscreen was disabled, and the browser launch used -opengl -window with a real WebGL context through SwiftShader. This still does not bypass D3D8: both main executables import and call Direct3DCreate8 during startup hardware detection, before either render driver is loaded.

Direct3DCreate8 now returns a deliberately narrow, non-rendering IDirect3D8 capability object. It preserves the complete 16-slot factory ABI, implements COM lifetime, one adapter, one display mode, identifier and caps queries, and deliberately returns D3DERR_NOTAVAILABLE from format/device creation checks. An authentic UT2003 run successfully called GetDeviceCaps, CheckDeviceFormat, GetAdapterIdentifier, and Release, so the old missing/null-factory branch is gone. It still throws later, before OpenGLDrv loads; this does not establish that an IDirect3DDevice8 is needed.

Unreal Special Edition does not ship OpenGLDrv.dll; it has SoftDrv.dll, GlideDrv.dll, and SglDrv.dll. Its former startup exception was an encoded resource-submenu mismatch in DeleteMenu. With resource-menu deletion and compaction implemented, a five-minute authentic software-renderer run loaded SoftDrv.dll, opened WindowsViewport0, logged Game engine initialized, and sustained the render loop. Its gray viewport was a separate USER delivery bug: the viewport is a secondary top-level window, but ShowWindow only queued an initial WM_SIZE for children. Extending first-show sizing to non-main top-level windows made WinDrv allocate its 514x386x32 DIB and produced 159 successful BitBlt frames in a 65-second authentic run. The captured intro is rendered and prompts PRESS ESC TO BEGIN. A subsequent controlled run traversed Game -> New Game -> Easy -> player setup, entered the first-person level with HUD active, and produced distinct before/after movement frames, so interactive gameplay is verified rather than inferred from the intro.

The initial capability facade has since grown into a deliberately bounded D3D8 translation layer over the D3D9 backend. It preserves the exact 97-slot device and 19-slot texture ABIs, translates D3D8 presentation parameters, textures, vertex/index buffers and fixed-function declarations, retains D3D8's BaseVertexIndex from SetIndices for indexed draws, and adapts the implicit swap-chain argument of GetBackBuffer. Unsupported methods remain explicit failures rather than silent successes.

An authentic -d3d -window -nosound run now reaches textured first-person gameplay on DM-Antalus. The no-sound flag isolates graphics from the separate missing Vorbis ov_open import. test/test-ut2003-vlan-candidate.js runs the game's own non-rendering dedicated-server mode beside a direct-connect client. It verifies native UDP in both directions over vln/1, waits for D3D device creation before taking a PNG, rejects the loading screen and spectator join prompt, and rejects the flat-pale weapon signature that exposed the rendering bug.

Getting the textured materials working had two layers. D3D8 sampler controls are texture-stage states 13--21 and 25, but D3D9 moved them into sampler-state slots 1--10. Forwarding the D3D8 numbers directly to the D3D9 texture-stage bank returned D3DERR_INVALIDCALL, leaving filter and address state at defaults. Translating those states activates the mip-atlas shaders; the native headless desktop GLSL 1.10 compiler then rejected their ESSL-only GL_OES_standard_derivatives : require directive even though dFdx/dFdy are core there. The desktop port now removes only that directive. The corrected capture has a textured dark-metal/green assault rifle and textured terrain rather than the former nearly uniform white weapon inside the native GL frame.

The remaining pale-rifle CLI capture was a headless presentation bug, not a D3D8 material bug. WebGL requests an opaque default framebuffer with alpha:false, but the native desktop GL context still stores fragment alpha; the readback path passed those low alpha bytes to the software compositor and blended otherwise-correct rifle RGB toward white. Headless readback now honors the requested WebGL contract by forcing alpha to 255. The focused regression draws RGB with alpha zero and verifies opaque readback, while the frozen VLAN replay verifies the textured rifle survives composition and that held movement changes the first-person scene.

The final acceptance was repeated from a clean worktree at commit fecc3e2f. The authentic dedicated server entered DM-Antalus, the client and server exchanged native UDP through vln/1, the client created its D3D viewport, Fire transitioned it from the join prompt to an owned pawn, and the captured frame showed the HUD plus textured weapon and terrain. Both emulator processes exited cleanly. The committed wall-clock test proves join and rendered gameplay; deterministic held-key movement was additionally proved with a frozen tools/ctl.js replay.

A fixed-wall-time profile of the map-load window (batches 6000--12000) retired 938 million handler operations. Raw x87 instructions were 26.85% of them, with no single block over 2.23%, so the load is broad Unreal transform/material work rather than one stuck loop. Enabling the existing experimental x87 fold executed 27.3 million fused regions and advanced 25,179 batches in 140 seconds versus 20,818 without it, about 21% farther on this run. The CLI candidate uses --x87-fusion; browser runs can opt in with ?x87-fold.

SSE is now a separate per-app CPU policy rather than a global claim. The ut2003_demo client and server advertise a Pentium III with CPUID EDX bit 25; the default personality and the not-yet-exercised UT2004 entry still hide SSE. Following the authentic UT2003 path added the instructions it actually reached: exact SFENCE, MOVNTQ, memory MOVLPS, all eight CMPPS predicates, and MOVMSKPS. Unknown SSE encodings continue to trap.

That policy also selects three MSVC/D3DDrv 64-byte MMX copy loops, including a 79-byte three-register pipelined MOVNTQ body at original D3DDrv VA 0x10001100. Each is recognized by a complete address-independent byte hash and lowered through H419 to memory.copy only for proved-disjoint, page-local mappings; overlap and split mappings retain the original instruction ordering. The regression compares ordinary decoding with each lowering for copied bytes, GPRs, flags, final MMX registers, overlap, page splits, and a one-byte near miss. A clean authentic run reaches D3DDrv with no SSE decoder trap, but the shared machine was under heavy concurrent load during the final timing run, so that run is evidence of compatibility, not a defensible wall-time speedup measure.

The CLI software D3D backend reaches the same engine loop without an unimplemented API but rejects its GPU draw opcode; the native/browser WebGL backend remains the authoritative graphics verification.

UT2004: the registered -opengl argument is the whole blocker

UT2004's "further post-probe launch diagnosis" above is resolved. It is not an emulator gap at all — it is the command line lib/apps.js registers for ut2004_demo:

args: '-opengl -window'

How the real error was recovered

The app dies inside MiniDumpWriteDump, which reads as the bug and is not: that is only UE2's crash handler, running after the engine has already decided to die. UE2 reports through appError, which throws, so the message never reaches stdout. It is in memory, though — core.dll exports ?GErrorHist@@3PAGA (ordinal 1036, original VA 0x101ad200), a UTF-16 buffer holding the error text. Dumping it one batch before the crash gives the real complaint:

Missing symbols - aborting. History: UOpenGLRenderDevice::Init

Recipe worth reusing on any UE2 title: resolve ?GErrorHist@@3PAGA to a runtime VA with core+0x101ad200, then --input=N:dump-mem:<va>:512 at a batch just before the crash. --dump= is too late — it fires at exit, after the handler has run.

A GetProcAddress census confirms it: 99 GL/WGL names resolve and 298 return NULL, including core GL 1.1 entry points. OpenGLDrv cannot initialize against that surface, and the engine aborts rather than falling back.

The fix, and what it reaches

Changing ut2004_demo's args to

args: '-d3d -window -nosound'

routes it to the same D3D8 path UT2003 already uses, and the game reaches gameplay: DM-Rankin's lit brick-and-wood interior with a bot in frame.

Applied 2026-09-22. With the D3D9 software path's point/spot lights (fab74ee8), --d3d9-renderer=software --d3d9-programmable also draws the Epic/Digital Extremes/Atari splash with zero refused draws (1.42M batches in 150s at --batch-size=200000); it had refused every lit draw before.

UT2003 under the CLI software backend

The paragraph above ("the CLI software D3D backend ... rejects its GPU draw opcode") is narrower than it reads. With --d3d9-renderer=software --d3d9-programmable on the remote bench box, UT2003 renders its full menu chain — intro logos, the NVIDIA splash, the main menu, and Instant Action | Select Map complete with a live 3D Antalus preview inside the map panel. PNG capture there comes off dx slot 5 640x480, the real primary surface, rather than the canvas the headless-GL path reports.

Three things cost a session each and are worth writing down:

"Stuck forever in the first software DrawIndexedPrimitive" (2026-09-29)

Reported for both demos: a bounded run (--d3d9-renderer=software --d3d9-programmable --max-batches=1500 --batch-size=200000 --trace-api=IDirect3DDevice8_DrawIndexedPrimitive,IDirect3DDevice8_Present) ends with EIP parked on the D3D thunk after 30 DIPs and 2 Presents, and looks like a rasterizer that never returns. It is not a guest or rasterizer hang. Ruled out by reading and by probing: the D3D8 DIP frontend (09ac, SetIndices base at state+1692), $d3d9_draw_buffer's validation (09ae), and the software prepare/step tile loop (09ah, bbox clamped to the viewport, non-finite vertices marked bad) all terminate.

What happens: every software draw/Present returns a negative render token, $d3d_render_park sets yield 16, and the CLI main thread waits on the render worker (lib/d3d9-host.js; Node has no crossOriginIsolated, so the CLI always takes the async worker path). test/run.js checked "still parked?" once per batch: run() was skipped, the batch was counted and the batch clock advanced, so a --max-batches budget drained one event-loop turn at a time while the guest executed nothing. A ctl.js eval on the parked run showed the request {"t":-6,"done":true,"yr":16} completing and moving on to -8, and frozen stepping walked UT2004 into its NVIDIA intro with a software-rendered 3D character — slowly, not stuck.

Fix (test/run.js, awaitMainRenderPark): before each batch, if main is parked on yield 16, await that request in wall time (bounded by --max-seconds and stop), then run the batch. --no-render-park-wait restores the old loop for an A/B. The exit summary prints render park: main waited on N software D3D requests (Nms wall), N batches skipped while parked, and ctx.renderParkStats exposes the same to --control evals. test/test-d3d-render-park-batches.js drives the shape through real COM thunks (512x512 software device, DrawPrimitiveUP + Present in one 40-batch step): 0 batches skipped with the fix, 39 of 40 skipped with --no-render-park-wait.

Same UT2003 command line, same box, after the fix: 866 DIPs and 722 Presents in 712 batches, and the capture is the UT2003 main menu (dx slot 5, 1024x768). Before it was 30 DIPs and 2 Presents in 1500 batches. The run hit its --max-seconds=600 guard rather than its batch budget, and render park says why: 362 waits, 588s of the 600 on the software worker, so about 1.6s per parked request at 1024x768. The wall-clock ceiling is now the software rasterizer's throughput; batch accounting no longer hides it. Use --max-seconds rather than --max-batches for UT routes on this backend.

UT2004 with the same flags makes no D3D8 call in its first 1500 batches, because it is still streaming packages in 1KB ReadFiles (humanmalea.ukx and others) before it opens the device. Given --max-seconds=600 --max-batches=100000000 --stuck-after=100000000, it reaches the UT2004 main menu (dx slot 5, 640x480, 845 Presents, 5195 batches). 562s of the 600 were 2518 render waits. Note that --stuck-after=0 does not disable the stuck detector: the test is stuckCount > STUCK_AFTER, so 0 fires on the first repeat. Pass a huge value instead.

The browser never had this bug: host.js yields to the event loop rather than spending a counted unit per check.

The fixed UT3 installer was executed directly in Wine Assembly. Its verified first runtime blocker is:

Static PE import comparison against src/api_table.json found nine imported names which are not implemented:

Installer strings also name seven MSI APIs resolved dynamically, so they do not appear in the static import table:

Only MsiQueryProductStateW currently exists in the API table. UuidCreate is already implemented through CoCreateGuid. This is an analysis inventory only; none of the UT3 gaps were implemented.

Call-form census and the software-D3D8 draw stall (2026-09-29)

Measured with test/run.js --edge-hist and tools/call-form-weighted.js (docs/uop-tier-design.md §15.1). Box1 ran --d3d9-renderer=software --d3d9-programmable --batch-size=200000 --screen=1100x840. On that tree (2a632d73), neither UT2003 nor UT2004 gets past the first 3D draw: the main thread never returns from IDirect3DDevice8_DrawIndexedPrimitive.

That run did not use --headless-gl, and box1 has no display for it. So the earlier gameplay verification stands for its own path only.

The load phase itself is the most indirect-call-heavy code measured in the corpus. Guest indirect transfers are 4.5-8.4% of block entries, and vtable/reg calls are 2-6.6%. The sites are low-polymorphic:

Unreal SE's Nyleve flyby on SoftDrv: under 1% guest indirect. The threaded remainder there is SoftDrv MMX (pxor/movq/pmulhw/psraw) refused as head-unsupported. galaxy+0x105085d2 call [0x1054c260] calls a runtime-built mixer in heap memory (0xc49394).

MMX fill fold (2026-09-29). Unreal SE's SoftDrv has the same movq [r],mm; add r,8; dec c; jnz clear loops as Deus Ex, at 0x10931ed0 and 0x10931ff0. The fold (docs/loop-idiom-superops-design.md §23) matches them zero times in the flyby, batches 900-1800. The flyby never clears the frame.

The flyby's MMX share is compute. SoftDrv loads at a runtime base of 0x0289b000 (preferred 0x10900000), so softdrv+0x10924747 is the block at runtime 0x028bf747. The hot blocks:

That is a uop-tier MMX coverage problem, not a fold.

Covered (2026-09-29, docs/uop-tier-design.md §16). Every head above is now installed or live in the uop tier. Threaded block entries over batches 900..1800 went from 116.1M to 68.8M. User CPU for 1800 batches went from 28.42/28.93 s to 18.45/19.01 s (−35.1%), with frames md5-identical at 900/1200/1500/1790/end.

CRT exports and the native overrides (2026-09-29, docs/crt-native-overrides.md)

UT2003 imports msvcr70.dll and UT2004 imports msvcr71.dll, both through thin wrappers in core.dll at core+0x101139xx..0x10113cxx. Which CRT export is hot depends on the phase:

floor, wcslen, wcscpy, wcscat, wcsstr, _wcsicmp and _wcsnicmp are now native when imported from these DLLs. With them, msvcr70 falls from 11.8-14.7% to 1.4% of UT2003's block entries in 420..520. What is left is _vsnwprintf, mbtowc and memmove. rand and _vsnwprintf are the remaining candidates, and neither is done: rand keeps its seed in the ptd, and _vsnwprintf needs a byte-exact formatter.

A run of the same build is deterministic. For UT2003 with the software backend at 200000 blocks per batch:

UT2004 over batches 600..1100: msvcr71 drops from 16.1% to 6.9% of block entries (docs/crt-native-overrides.md, Verdict).

UT2004 reaches a DeathMatch (2026-09-29)

UT2004 now gets into a match: DM-Rankin, HUD at 100/100, and the clock counting down from 20:00. It took three emulator fixes.

  1. 95b1b8c9: DestroyWindow no longer quits. UT2004's splash dialog is the first top-level window, so it became $main_hwnd. Destroying it set $quit_flag while the game kept running. Now $main_hwnd passes to the next top-level window, and WM_QUIT comes only from PostQuitMessage.

  2. 906cfe27: a real RtlUnwind. The old handler unlinked frames without calling their handlers with EXCEPTION_UNWINDING, so msvcr71's C++ EH never ran its unwind funclets. The first .PAG <- .PAX throw after PLAY left a stale FRAMEINFO chain, and __CxxFrameHandler looped. The replacement follows NT x86 semantics:

    • each frame below the target gets its handler called with EXCEPTION_UNWINDING (plus EXIT_UNWIND when the target is NULL), then is unlinked;
    • a collided unwind (disposition 3) resumes from the dispatcher context;
    • EAX returns ReturnValue.
  3. 4eb870e1: a dispatcher registration node. msvcr71's _UnwindNestedFrames (orig 0x7c359b25) runs the code below.

    saved = FS:[0]
    RtlUnwind(pRN)
    saved->next = FS:[0]
    FS:[0] = saved
    

    It relies on NT's RtlpExecuteHandlerForException having linked its own node on top of FS:[0] for the duration of the handler call. Without the node, saved is the catching frame itself, and the relink made it point to itself: the SEH chain read 0x179fb6d8 next=0x179fb6d8 and the next throw walked it forever. The emulator now links a node {next, handler=0xCACA003A thunk, establisher} around each handler call:

    • the node's handler returns ExceptionCollidedUnwind and fills the dispatcher context on an unwind, and ContinueSearch otherwise;
    • $seh_walk_from and $rtl_unwind_step skip the node.

    test/test-rtl-unwind-handlers.js covers the whole relink. The dump helper sehchain.js (a ctl.js eval that walks FS:[0] and flags a cycle) is what found it.

Route (box2; flags as for UT2003 above):

  1. Step the frozen run to batch 3500 for the main menu, 640x480, at client origin (24,44).
  2. The mouse is DirectInput-relative, so every click needs a sync. Move to (24,44), step 3 batches, move to (300,300), step 3, move to the target, step 3, then click. The hover highlight can lag behind; the clicks still land.
  3. Click INSTANT ACTION at (400,362). The Gametype page is up by batch 4400.
  4. Click DeathMatch at (195,221). Select Map (DM-RANKIN) is up by 4550.
  5. Click PLAY at (448,514) (batch ~4565). The load shows no Present:
    • captures keep the typewriter menu frame;
    • EIP samples sit in msvcr71 _woutput and friends at 0x115a..-0x115c.. (runtime base 0x11591000);
    • eight [C++ throw] .PAG <- .PAX lines are logged and are harmless now.
    • Check progress with the snapshot EIP and the SEH chain, not the picture.
  6. By batch 8200: DM-Rankin with "Press [Fire] to join the match!".
  7. Click in the viewport (340,300). By 8600 the player has spawned, with the HUD, the weapon bar and the assault rifle.

UT 348 route (2026-10-06)

--app=ut348_demo mounts test/binaries/candidates/unreal-tournament-348-demo/extracted/ through a manifest written by node tools/gen-tree-manifest.js <root> --exe=System/UnrealTournament.exe --flatten=System. The game opens UnrealTournament.ini relative to the exe and its packages through ..\System, ..\Maps etc., so System\ files are mounted at both c:\<name> and c:\System\<name>. Without the mapping it dies early with "Can't find file for package 'Engine'" (an appThrowf; Core's static message buffer is at core+0x101f65fc, readable with --dump after the exit).

Evidence: scratch/runs/20261006T001500Z-ut348-demo-claude202b4b39-dm-morpheus.

Deus Ex demo on OpenGlDrv (2026-10-06, OPENGLDRV-GL11-SURFACE)

OpenGlDrv resolves the whole GL 1.1 + WGL table by name and aborts ("Missing symbols") if one is absent; since 5f17bb07 every GL 1.1 name is an API (unimplemented ones fail fast by name) and it binds. Select it locally by setting deusExRenderer in lib/apps.js to OpenGlDrv.OpenGLRenderDevice (the committed default stays SoftDrv) and run the CLI with --gl-renderer=software.

Correction and narrowing (same day, watchpoints): the object's vtable is NOT overwritten -- --watch=0x7e2f17d0 --watch-log shows only its construction (batches 45598-45599). engine+0x1030cb70 is a lazy loader's Load: this = 0x7e2f17d0 (a two-slot vtable whose slot 0 is this very function), [this+4] = the FArchive (0x7e9b46a4, vtable in Core), [this+8] = the saved file position. It calls Tell (+0x28), Seek (+0x34), serializes the array at this+0xc (engine 0x10303904), then Seeks back. So the jump into zeros happens while OpenGlDrv's texture upload lazily reads texture data out of a package: the suspect is the serialized data (a count or size from the file) smashing the stack, i.e. a file-read difference, not a GL call. SoftDrv may never touch that texture. Next: --trace-fs on the package reads in batches 211000-211300 and a stack-guard watch on the caller's frame (EBP 0x179ff694).

Static follow-up (same day): engine 0x10303904 -> 0x1030cc60 is operator<<(FArchive&, TArray<BYTE>&) (compact-index count, Realloc, then Serialize into the heap buffer) -- nothing there writes the stack. The FArchive at [this+4] has Core's vtable 0x1017a3f8 (file 0x10c443f8 at runtime), and its slot 10 (Tell, 0x101634b0) and slot 13 (Seek, 0x10163410) are real functions, so the loader's two virtual calls are sound. The jump into zeros therefore happens later, inside Seek/Serialize or after the loader returns; the next runtime step is --trace-at=core+0x10163410 on the last hits before batch 211297 and a --trace-stack-scan at the crash.

Boat runs (2026-10-06, main 99e861e6, boat bx_n53xdjmt, fresh npm ci; deusExRenderer = OpenGlDrv and OpenGlDrv.dll added to the DLL list as a boat-local edit; --gl-renderer=software --quiet-api --quiet-blocks --stuck-after=0 --input=200000:keydown:27,200100:keyup:27). Module bases in that build: engine 0x10e82000 (orig 0x10300000), core 0x10bca000 (orig 0x10100000), opengldrv 0x120d0000.

CORRECTION and narrowing (same day, boat bx_rdw8tsqd, main 85141552; evidence scratch/runs/20261006T1810Z-deusex-opengldrv-esi/key-lines.txt):

The overwrite (same day, boat bx_e35gh894, main 8c360029; evidence appended to scratch/runs/20261006T1810Z-deusex-opengldrv-esi/key-lines.txt):

Thread 3 (same day, boat bx_tdvuwpfj, main 1945acdd; boat-local probes in lib/thread-manager.js and src/03-registers.wat, never committed):

ROOT CAUSE, and a correction to the Galaxy attribution above (same day, boat bx_k5x5vqk5): the writer is our own software GL, not Galaxy and not the guest.

Deus Ex demo on GlideDrv in the page (2026-10-06, DEUSEX-GLIDE-PAGE-EXIT)

GlideDrv played the 3D intro on the CLI but "exited to the desktop" in the browser. The page actually showed Critical Error Assertion failed: RenDev [File:C:\Unreal\WinDrv\Src\WinViewport.cpp] [Line: 345], with the software Glide backend (?glide-renderer=software) as well as WebGL, and its console never logged a LoadLibrary of glidedrv.dll. UE1 loads the device class from the guest's own C:\System; lib/apps.js mounted GlideDrv.int but not GlideDrv.dll, and the CLI hid that by finding the DLL on the host disk beside the exe. 7229c754 mounts the Glide, OpenGl, MeTaL and SGL driver DLLs beside their .int files; GlideDrv then plays the intro in the page on the WebGL Glide backend. test/test-unreal-renderer-dll-mounts.js checks that every registry app mounting a *Drv.int mounts the DLL too. Evidence: scratch/runs/20261006T1815Z-deusex-glide-page-w6.

Repro trap on a fresh clone or boat: the gitignored test/binaries/dlls (msvcrt, comctl32) and fonts/*.fon are absent and there is no top-level binaries -> test/binaries link, so the page falls back to stubs and fails for an unrelated reason. Ship those first.

--app from a fresh candidate fetch (2026-10-10, UT2004-FRESH-FETCH-MANIFEST)

tools/fetch-candidate-corpus.js --id=<candidate> writes .wine-assembly-browser.json at the candidate root, not under installed/. Its urls start installed/..., its vfsPaths are c:\<path under installed>, and it omits the exe. Before eb349d0a7 the registry read installed/.wine-assembly-browser.json and mounted the exe and its DLLs at C:\ with cwd C:\, so a fresh fetch died at batch 14 with a C++ throw. eb349d0a7 points unreal_special_demo, ut2003_demo(_server) and ut2004_demo at the root manifest, with exeGuestPath/workingDirectory set to c:\system. DLLs found beside the exe now get guest paths beside it, in both run.js and the page.

Evidence: scratch/runs/20261010T2335Z-ut2004-fresh-fetch-app-w5.

Unreal Special menu labels (2026-10-11, TEST-FILES-OBJECT-ENTRIES)

The menu bar showed raw ID_FileMenu ID_ViewMenu ID_WindowMenu. The menu resource stores placeholder keys, and Window.dll localizes them at runtime. The localizer is the function at original VA 0x10b01370, which recurses into submenus. Its loop runs over GetMenuItemCount positions, last to first:

It runs on the LoadMenuA handle before SetMenu. Our Get/SetMenuItemInfo handled only heap menus and returned FALSE for the 0x00BE resource handle, and SetMenu reloaded the untouched resource anyway. aeedc00f0 resolves an unattached LoadMenu handle through its detached alias, and SetMenu attaches an existing alias. The bar now reads File / View / Window. Evidence: scratch/runs/20261011T0200Z-unreal-special-menu-labels-w5.

Lazy .int files in the page (2026-10-11, w5; Telegram #30/#31). In the browser, unreal_special_demo crashed at batch 0 with VfsPendingError: c:\system\core.int ... async-only provider from ini_get_string.

UT2003 registered native COMCTL closure (2026-10-13)

An isolated --app=ut2003_demo_server launch asserted in WControl::RegisterWindowClass, Window.cpp:1070. Targeted API tracing identified the actual failing GetClassInfoExA(NULL, "SysTabControl32", ...), after three LISTBOX and two BUTTON superclass registrations. The original Window.dll call is at VA 0x11018c8e; the tab superclass caller is 0x1101ac67.

The restored installation already contains comctl32.dll and windows/system/comctl32.dll, both 577808 bytes, SHA-256 28a41e6a35c4509924a32a99841a815cf49e1d3ee6912ec26a823e45472edfa4. These are preserved installed-fixture files, not newly acquired game payloads. The local manifest mounts them in the VFS, but native startup discovery uses the explicit DLL seeds and entry.files, not that expanded manifest. Its host search from installed/system misses both paths. Both UT2003 registry entries now explicitly seed the existing windows/system/comctl32.dll.

Candidate a4d487337 loads native COMCTL at 0x11729000, binds Window.dll's import, and its DllMain returns 1. The original class assertion clears without an API, INI, or fixture change. This is a dependency correction, not a gameplay pass: software startup then reports invalid native free list, followed by the game's LockRect failed: D3DERR_INVALIDCALL in UD3DRenderDevice::ReadPixels <- Present. The device queue completes 2829 commands with queue error null, but bridge lastError is nonempty. No usable world or player input was established. Existing SHELL32/OLE32 availability warnings remain separately unqualified.

Evidence: 20261013T2240Z-ut2003-registered-software-control, 20261013T2245Z-ut2003-registered-class-trace, and 20261013T2247Z-ut2003-registered-comctl-software under scratch/runs/. All use pinned WASM SHA-256 9b3ef6f173daaa8be90733a4b8449733d74ca861062d0383a709f4be02ce1c77; the candidate changes registry JS separately, with tracked WAT and renderer sources unchanged from the preceding 3540d8304 checkout. This does not certify a fresh current-main build. All owned processes/groups were confirmed absent; movement/reverse/idle and browser/WebGL/audio/network/FPS remain untested.