Re-Volt demo (Acclaim / Iguana London, 1999)
App id re_volt_demo; candidate re-volt-demo in
test/candidate-corpus/manifest.json. Direct3D 6 (IDirect3DDevice3) R/C
racer, PC Gamer cover-disc build (cars/pcgamer), one track.
Licence
Not redistributable. The game's own startup legal screen says the demo "is
intended for personal use only and may not be lent, copied, sold, published,
duplicated onto CD, etc, without prior written approval from Acclaim". Neither
the archive.org item nor readme.txt says otherwise. It is registered strictly
as a localOnly fixture: fetched by tools/fetch-candidate-corpus.js --id=re-volt-demo, never shipped or deployed.
Install layout
Re-voltDemo.zip (archive.org Re-Volt, SHA-1 in the manifest) holds an
InstallShield 5 setup. data1.cab is unpacked with unshield per file group
(game, cars, gfx, levels, models, wavs), and the groups are copied
into installed/ with game at the root. That gives 443 files: revolt.exe at
the root and Mss32.dll beside it, loaded as a real PE. Fetching on a fresh
boat needs unshield from apt.
Startup sequence (headless, --batch-size=50000 --tick-ms-per-batch=1000)
IDirectPlayLobby3_RegisterApplication. It writesHKLM\Software\Microsoft\DirectPlay\Applications\Revolt(Guid{6BB78285-71DF-11D2-B46C-0C780CC10840}, Filerevolt.exe, Path/CurrentDirectoryC:\). Before 2026-10-11 this returned E_NOTIMPL, and the game stopped with the MessageBox "DDERR_UNSUPPORTED: Can't register for lobby support".- It reads settings from
HKLM\software\Acclaim\Re-Volt Demo\1.0(all values are absent on a fresh run, so the game uses its defaults). - CRC32 integrity pass, about 150 batches of 50000 blocks with a black
screen. This is the bitwise CRC loop at
0x444581–0x44459e(polynomial0x04C11DB7), called from0x444562. The cost is real interpreter work and it is not a hang. - The legal screen, then the Acclaim logo, both drawn every frame by
IDirectDrawSurface_GetDC+StretchBltof a 640×480 bitmap followed byFlip. One 50000-block batch runs hundreds of these frames, and the screen is timed on guest time. Headless, this is the slowest wall-clock phase (~12 s per batch); a larger--tick-ms-per-batchgets through it sooner. - The "RE-VOLT loading" screen, captioned with the file being loaded
(
levels\frontend\frontend.inf, thenC:\WINDOWS\TEMP\revolt.log). During it the game bubble-sorts model polygons at0x44bc2a–0x44bc56. That is finite but O(n²), and it holds one unchanged frame from batch ~196 to ~300. - The 3D main menu (shop-counter scene; Start Race / Best Times /
Options / Quit;
V0.01pcgamerin the corner) by batch ~312, about 7 minutes of wall time on a 4-vCPU boat. Pressing Enter at batches 170 and 175 (keydown:13/keyup:13) skips the legal screen.
Route to a race (reviewed 2026-10-11)
Run scratch/runs/20261011T0038Z-re_volt_demo-race-claude1863 (route.sh
there). From the menu, Enter every 15 batches from 330 to 510 takes these
defaults: Start Race → Single Race (b345) → car select → "Toys in the Hood
1" (b400) → loading levels\NHood1 (b445). The track load is slow; while it
runs the game places 64 objects by rejection sampling at 0x45a7c0 (CRT
rand at 0x482c69, outer cmp esi, 0x40 at 0x45aa65). The race starts
around b674. Holding Up (VK 38) from b670 drives the buggy. The run reaches
b810 in about 16 minutes.
Texture loading and the colour fix (2026-10-11)
Texture pages are 256×256 A1R5G5B5: the DDSD pixel format is flags 0x41
with masks 0x7C00/0x03E0/0x001F/0x8000, copied from the format the game picked
at 0x726c78. The creation loop near 0x46cea0 makes a system-memory page and
the video-memory pages, then Texture2::Loads each one. A BMP (24-bit,
levels\nhood1\nhood1a.bmp...) gets into a page through LoadImageA and
CreateCompatibleDC, then GetDC on the system-memory page, StretchBlt,
Lock and Load (GetDC returns at 0x46d67f, Lock at 0x46d71d). The only
GDI imports are LoadImageA, CreateCompatibleDC, SelectObject and StretchBlt.
Until 2026-10-11, GDI on a 16bpp DirectDraw surface always wrote RGB565
($gdi_dx_dc_bind and $gdi_raster_channel_mask). Both D3DIM arms then read
those texels as 1555. The result was a grey road drawn purple, the wooden fence
green, the grey manhole yellow-green, and rainbow speckle wherever green's low
bits vary. This was not mip aliasing and not a sampler bug: the software and
WebGL frames matched. GDI now uses each surface's own layout. Before/after on
both arms, plus the source BMPs: run
scratch/runs/20261011T0150Z-re_volt_demo-edge-speckle-claude1863.
API profile (legal screen, 60 s)
IDirect3DDevice3_SetRenderState 22.5k, IDirectInputDevice_Acquire +
GetDeviceState 7.6k each, SetTextureStageState 5.4k,
IDirect3DViewport3_SetViewport2 5.4k, and Flip/BeginScene/EndScene 3.9k each.
Every frame also does the GDI sequence GetDC, CreateCompatibleDC,
SelectObject, StretchBlt, DeleteDC and ReleaseDC. Input is DirectInput
keyboard polling (GetDeviceState), so --input=B:keydown:VK reaches it.
Command: --trace-api-counts.
Browser loading-stall report (2026-10-13)
User report TG60-REVOLT-LOADING-20261013 shows loading
models\\sphere.ncp, EIP near 0x45a8xx / 0x482c69, and repeated ESI
0x3f. Original EXE SHA-256
58ba4835e6607eae62be4e023ef989172cb519c78f34811acba547e9e0e3581a
confirms the placement routine above: rejected separation branches at
0x45aa17; acceptance increments ESI at 0x45aa5a and compares with 64
at 0x45aa65. ESI 63 is consistent with the last object. Changing EIP or
random state alone does not prove that object will ever be accepted.
CRT rand at 0x482c69 updates its original state at guest 0x49bb88.
Run scratch/runs/20261013T2305Z-revolt-original-browser used exact clean
source a116a574c7411671b1042013b641ac0765471ef5, an independent canonical
build (WASM SHA-256
46c1e23aabca26de5e459f21463d3e1167f3a71d95e573ffedecefe7b31765f7),
and all 443 original installed files plus the browser manifest verified by
hash. Registered browser route was
index.html?app=re_volt_demo&debug&no-threads&d3d-renderer=webgl on
bx_yqnspudj. Live controls confirmed WebGL, RLE/x87/uop enabled, Lazy Sync
enabled, and Threads unchecked, matching the report. Five-second samples
read registers and the random state; they did not write guest memory, change
the clock, restore a snapshot, or change random state/configuration.
Launch was 23:04:49 UTC; reviewed main menu was 23:06:18 (89 seconds).
Ordinary Enter selected Start Race, Single Race, default player name,
RC Bandit, Toys in the Hood 1, then the summary at 23:08:17. The log sampled
placement at 23:09:03, EIP 0x45aa1d, ESI 62. It then passed that routine
and reached the reviewed starting grid at 23:09:24, about 67 seconds after
the summary confirmation and 276 seconds after launch. No sustained ESI 63
was observed in this run. loading-003.png is the starting grid;
race-001.png and race-002.png show later race timers 21.799 and 39.762
seconds after ordinary Up input, with the latter showing 8 mph. Up without
steering took the car off the intended route into a dark tunnel/wall area;
this is limited evidence of movement and input response, not a completed
lap or a separate physics/rendering diagnosis. Browser closed normally at
23:10:19; supervisor/driver/Chrome PIDs and groups were absent at 23:10:50.
The reported prolonged stall was not reproduced, and no emulator fix is claimed. The user's exact loaded module, elapsed stall duration and random state are unknown; a successful independent run does not establish that all placements terminate. If it recurs, preserve the exact module and elapsed ESI/random-state series before changing any optimization settings.
Loading-time profile (2026-10-13, TG62)
Run scratch/runs/20261013T2327Z-revolt-loading-profile used the same original
443 installed files plus manifest, clean a116a574, canonical module
46c1e23aabca26de5e459f21463d3e1167f3a71d95e573ffedecefe7b31765f7, Chrome
151.0.7922.108, WebGL selected, and cooperative guest threads. No game state,
random seed, clock, or settings were changed. The added diagnostics were the
existing performance HUD, 2 ms CDP sampling, CDP network timestamps, and
read-only provider-fill/main-IO-poll wrappers. Startup and track profiles each
had a 120 s cap, inside a 420 s driver and independent 440 s group supervisor.
The earlier 275.572 s launch-to-grid observation includes 118.819 s of menu navigation and review. Its final confirmation-to-grid capture was 67.432 s; calling the whole 4.5 minutes loading is incorrect. Captures bound screen availability rather than marking exact transitions.
The new profiles identify substantial software rendering on a cooperative
guest thread, despite the WebGL selection. To exclude time spent reviewing
menus and the grid, analysis.json crops the full profiles to the console's
thread-2 spawn/exit timestamps. The thread starts at guest 0x444800; a live
read-only thread snapshot confirms it was active during track loading alongside
the Miles audio thread at 0x8624b0. The software-draw profile call chain is
step → runBudgeted → runSlice → DrawPrimitive → rasterize_triangle_textured → viewport_draw_textured_span. The function map comes from the exact build;
WASM function 6821's body starts at byte 939163, matching its profile position.
| Loading-thread window | Window | Textured spans | Rectangle clears | Depth clears |
|---|---|---|---|---|
| Frontend | 62.526 s | 40.756 s | 5.928 s | 5.618 s |
| Track | 71.553 s | 45.420 s | 6.811 s | 6.295 s |
Those three functions account for approximately 84% and 82% of sampled
elapsed time, respectively. These are sampling-based time attributions,
not hardware CPU-utilization measurements. The full startup profile also
contains 18.6 s attributed to the WebGL fence method, predominantly outside
the loading-thread window; synchronous GPU readback can block there. It must
not be presented as the loading bottleneck from this profile.
During the track window, 152 provider fills took a median 244.7 ms, while 156 HTTP range requests took a median 6.75 ms. The union of network-request intervals was 1.136 s, compared with 40.855 s for provider-fill intervals and 41.020 s for main-thread IO parking. These intervals overlap and must not be added. The large gap is consistent with browser-thread contention delaying provider completion while the cooperative thread renders; the loopback network transfer itself does not explain the observed loading time. Guest sorting and rejection placement still execute, but sampling main EIP alone misses this secondary-thread rendering cost. This run sampled ESI 63 in placement and subsequently reached the grid; it did not reproduce a lasting placement hang.
Source explains the backend mismatch: host.js::_startD3DIMGpu explicitly
binds the executor and d3dim_gpu_enable(1) to the main instance. Cooperative
threads receive separate instances in ThreadManager.spawnPending; their
per-instance $d3dim_gpu_on starts at zero, and the manager has no corresponding
GPU-enable/executor setup. The host bridge deliberately retains synchronous
software fallback when no consumer is attached. Identifying thread 0x444800
as the loading-screen drawing thread is supported by its lifetime, disassembly,
live identity and the cooperative drawing call chain; the profiler does not
directly label individual guest thread IDs.
The next generic fix to investigate is GPU routing for cooperative secondary
threads, with the calling instance's exports/state and shared surface
ownership preserved. Merely enabling the flag or reusing an executor whose
getExports is bound to the main instance is insufficient. Validation should
cover a secondary-thread Direct3D draw, cross-thread clear/draw/lock ordering,
software fallback, and an original-game before/after profile. No such fix or
speedup has been tested here. Profiling overhead is unknown; these instrumented
times are not a speed comparison against the earlier uninstrumented route.
All 56 transferred original artifacts passed SHA-256 checks. Chrome and driver exited normally; the supervisor recorded all 16 discovered child PIDs and both owned groups absent at 23:32:57.113 UTC. Independent checks also found the supervisor and all recorded PIDs/groups absent. No further run was needed for packaging or analysis.