Skip to content

DOOM feasibility experiment

Measured on 2026-09-15. Real Freedoom E1M1 completed a scripted 700-tic host run with a 2 MiB DOOM zone. A Q port still needs a qualified mutable memory strategy and a combined firmware size measurement. DOOM is not included in the accepted 0.1.7GQ firmware.

The experiment used the native doomgeneric engine at dcb7a8dbc7a16ce3dda29382ac9aae9d77d21284 and Freedoom 0.13.0, the release linked by the official download page. All source, assets, executables and captures remain in the ignored local build/reviews/doom-feasibility/ directory. Nothing was installed on the Q or written to its SD card by this experiment.

The macOS ARM64 host adapter rendered 320×200 indexed frames, disabled sound and multiplayer, streamed the WAD through seek/read calls, and supplied a fixed sequence of movement, turning, firing and use events. It ran E1M1 at skill 2 through 700 game tics: 20 seconds of game time at DOOM’s 35 Hz rate. Enemies remained enabled. The player moved from the starting position, fired ammunition and finished with 85 health.

Zone capacity Result Peak allocated zone bytes Peak non-purgeable zone bytes WAD bytes read
1 MiB Startup failed before the first frame; 54,912-byte allocation failed 1,047,832 934,736 14,025,538
2 MiB Completed 2,094,192 1,531,320 15,875,290
3 MiB Completed 3,138,032 1,531,464 15,373,215
4 MiB Completed 4,189,688 1,531,120 15,159,068

DOOM uses spare zone capacity for purgeable caches, so a nearly full zone does not mean all those bytes must remain resident. The experiment establishes that 2 MiB worked and 1 MiB failed for this sequence; it does not identify the exact minimum between them.

At 2 MiB, the largest zone request was 115,192 bytes. Engine calls to the host allocator peaked at 2,287,849 bytes, including the zone: 190,697 bytes were outside it. This excludes libc’s own internal allocations and the measurement wrappers’ headers. The host has eight-byte pointers; these are host allocation measurements, not ARM allocation sizes.

All successful runs, including a second independent 2 MiB run, produced identical player position, health, and captured RGB frames at tic 10 and the end. The final 320×200 RGB frame has SHA-256 c574f154a99b5783bd72fa55b96377334e5c5ef4d35c6430675d3a83c4eec6ff. The adapter avoids comparing initial wipe frames, which can contain the uninitialized previous display. Host execution time is not a Q performance measurement.

The only functional source change was replacing doomgeneric’s output-buffer allocation of four bytes per pixel with sizeof(pixel_t). CMAP256 then uses a 64,000-byte output buffer. The separate 64,000-byte I_VideoBuffer inside the zone remains; buffer sharing is unimplemented. Other scratch changes measured zone occupancy, engine allocations and WAD reads.

The independent ARM build used GCC 12.2.1, Cortex-M4 hard float, -Os, function/data sections and linker garbage collection. A minimal headless adapter retained the actual doomgeneric_Create() / doomgeneric_Tick() entry points. It linked newlib, math and nosys with mkdir/_fini stubs. The ARM binary was linked, not executed.

Standalone ARM component Bytes
Text and read-only data 267,239
Initialized writable data 62,028
Zero-initialized data 239,828
Flash load: text + initialized data 329,267
Static RAM: initialized + zero-initialized data 301,856

The accepted 0.1.7GQ image uses 1,220,608 of 1,441,792 firmware bytes, leaving 221,184 bytes. The standalone engine’s flash load exceeds that headroom by 108,083 bytes. This is not the exact cost of integration: the Q already contains some libc/math routines, and a combined link may share or discard code differently. Static ARM RAM also excludes the dynamic zone, heap-allocated output buffer, filesystem state and live MicroPython objects.

  1. Mutable PSRAM access. GameQ owns the lower 4 MiB of PSRAM and currently uses explicitly aligned word accesses. DOOM’s ordinary C zone contains byte and halfword stores and cannot be placed there without qualification. The host run uses ordinary host memory and provides no evidence that these writes work on the Q. Its internal heap span is only 389,688 bytes before live Python allocations. A safe external-memory strategy or a substantial allocation redesign is required.
  2. Combined flash and SRAM layout. Measure the real Q link, buffer sharing, and memory reuse after stopping the Game Boy runtime. Preserve the working firmware updater and resident hub while choosing that layout.
  3. Q adapter and hardware acceptance. Filesystem, display conversion, bounded engine ticks, input, power handling, failure containment and return to the hub remain unimplemented. No Q FPS, DOOM save/restore, other level, or full-campaign compatibility claim follows from this experiment.

With the retained scratch sources and existing toolchain, the host commands are:

Terminal window
python3 build/reviews/doom-feasibility/host/prepare.py
python3 build/reviews/doom-feasibility/host/run.py

host/run.py records each exact invocation. Its engine arguments are -iwad freedoom1.wad -mb N -warp 1 1 -skill 2 -nosound -nomusic. host/doomgeneric_probe.c records the input schedule. See host/host-results.json, host/run-*/output.txt, host/run-*/result.json and host/run-*/first-frame.png / last-frame.png for host evidence. These ignored experiment helpers are local artifacts, not repository build dependencies.

ARM commands and limits are preserved in arm/build.sh, arm/Dockerfile, arm/platform.c and arm/measurement.json; output is arm/results/out/doom-arm.elf. ELF SHA-256: 4df5ef464089deb2d9194ae0a4b00f32ac236a848ebf60c95e1ffad34135a2f4.

Input Bytes SHA-256
doomgeneric source archive at the pin above 3,090,406 1bd3f7f26220494159a38d71f2847ec81b58d6bbd7c7c8d81b08993018001148
Official Freedoom 0.13.0 ZIP 24,143,781 3f9b264f3e3ce503b4fb7f6bdcb1f419d93c7b546f4df3e874dd878db9688f59
Extracted freedoom1.wad 28,795,076 7323bcc168c5a45ff10749b339960e98314740a734c30d4b9f3337001f9e703d

The ZIP hash matches the upstream release’s freedoom-0.13.0-CHECKSUM file. Archive notices were retained. See the runtime roadmap for candidate selection and the broader compatibility and dependency work.