Skip to content

GameQ launcher room

build/roms/gameq-hub.gb runs the pinned pret/pokered engine, tileset and sprites. Walking, collision, NPCs and text boxes use Pokémon Red’s code. It boots directly into the library. GameQ displays the room across the Q’s whole 320×240 RGB565 screen; ordinary cartridges use the separate aspect-preserving display mode.

Since 0.1.5GQ, the complete 1 MiB ROM is compressed losslessly into signed application flash. The room loads without SD, including after returning from a game. Game ROMs and saves remain on the card. A legacy SD copy of the hub is ignored; room updates arrive with firmware.

  • Eight numbered bookshelves, left to right across the top: walk up and press Z (A). Each selects that position in the shell’s sorted snapshot of up to eight .gb files in /GameQ/roms, excluding the hub, directories and dotfiles such as macOS ._pokemon-red.gb metadata. The shell shows the filename before launch and identifies empty shelves. Shelf assignments stay stable until a rescan.
  • Clerk on the right: GameQ settings. Enter (START) opens settings too. Closing settings returns to the same room.
  • DOOM cabinet beside the shelves: describes the planned game. DOOM is not playable in this firmware.
  • Cancel while playing a cartridge returns to the room through the host. Pokémon’s save remains separate from the hub.
  • SHIFT/CAPS toggles fast-forward. The NFC activity LED lights while it is enabled and turns off on return or error.

Physical 0.1.6GQ recorded two shelf-to-game launches and clean saved returns; 0.1.7GQ has operator-confirmed walking, CAPS fast-forward and save/reload. The desk is GameQ’s native settings screen. Complete shelf/clerk coverage and no-SD startup remain device checks. See the hardware record and the earlier room-routing investigation for the evidence and historical failures.

The room is 6×5 map blocks, or 12×10 walking cells, with a reading table, chairs and solid outer boundary. Each walking cell is 16×16 Game Boy pixels. There are no exits, staircase, warps, map connections or stock party menu. Default spawn is (5,4), facing up; the clerk occupies (9,4).

Terminal window
make hub
python3 -m unittest tests.test_gameq_hub -v

scripts/build-hub.sh copies the pinned tree into build/hub/source, removes copied helper binaries, and applies scripts/patch-hub.py only to that copy. RGBDS 1.0.3 builds the modified Red source. The title becomes NUL-padded GAMEQHUB, and rgbfix refreshes its checksums. Outputs are:

  • build/roms/gameq-hub.gb — 1 MiB, MBC3+RAM+BATTERY header.
  • build/roms/gameq-hub.sym and .map — linked symbols and memory layout.
  • build/roms/gameq-hub.build.json — SHA-256 fingerprints of the hub source, pokered assembly/graphics/build inputs, ROM, symbols and map. Generated upstream objects, ROMs and host tool binaries are excluded. ROM tests reject stale or mismatched artifacts before attempting a replay.

The script checks that an existing canonical pokemon-red.gb retains SHA-1 ea9bcae617fdf159b045185467ae58b2e4a48b9a. The patcher refuses to write into third_party/pokered; exact source anchors fail if an upstream change invalidates a patch. Reapplying to a copied tree is idempotent.

Overlays under hub/overlay provide the initializer, request loop and text handlers. The patcher generates map blocks, objects, command constants and a collision whitelist from the real Red blockset. Its seven source patches change startup, START dispatch, room size, initial warp, bookshelf interception, the extra ROM section and WRAM reservation.

Firmware builds assemble the hub inside the same container source snapshot before native compilation and tests. They do not trust imported prebuilt hub files: a fixed-size ROM exported with a normalized timestamp can otherwise be mistaken for an unchanged file by an incremental context transfer.

scripts/embed-hub.py verifies that provenance, compresses all 64 ROM banks, and generates the C flash asset plus build/firmware/builtin-hub.json. It checks that the generated asset decodes to exactly the original bytes. No graphics, maps or other banks are discarded to make the room fit.

At runtime the pinned inflater streams 16 KiB banks through the same two-pass PSRAM write/readback verification used for SD games. It checks the compressed SHA-256 before decoding, each bank’s embedded fingerprint, the complete ROM SHA-256 and a clean end of stream. The roughly 34 KiB decoder workspace is released before gameplay. The hub never reads or writes a persistent save.

The mailbox is 16 bytes at $DEF0–$DEFF, offset 0x1EF0 in the 8 KiB WRAM image. It contains literal ASCII GQH1, not Red’s text encoding. Linker assertions require wBoxDataEnd <= $DEF0 and mailbox end <= $DF00. The pinned box data ends at $DEE2; the stack begins at $DF00.

Offset Writer Meaning
0–3 ROM ASCII GQH1
4 ROM sets, host clears 0 idle; 1 launch shelf slot; 2 settings; 3 DOOM information; $80 startup ready
5 ROM Zero-based shelf slot 0–7 for command 1; otherwise 0
6–8 ROM Player X, Y and facing at latest startup or interaction
9 Host sets, ROM clears Startup response: 1 restore; 2 default spawn
10–12 Host Requested X, Y and facing for response 1
13 ROM 1 after host startup response; 0 when standalone
14–15 ROM Reserved, zero

Facing values are 0 down, 4 up, 8 left and 12 right. The ROM publishes command after position and argument, so each record is complete when the host polls between frames. Position is a checkpoint, not a continuous feed while walking.

The ROM initializes the room, publishes $80, then waits up to 120 frames. gameqgb.hub_reply(b'\0') writes response 2; three bytes (x,y,facing) write a restore request followed by response 1. The ROM clears command and response after accepting it. A missing response times out into the default room. Any non-zero response attaches the host, and only 1 reads restore bytes.

Restore requires X and Y inside the 12×10 room, a passable unoccupied tile and one of the four facing values. Invalid requests use the default spawn. Valid requests recompute the view pointer as wOverworldMap + 13 + 12*(y>>1) + (x>>1), set X/Y and their sub-block bits, then apply facing after resetting the sprite. This keeps camera, collision and player position synchronized.

The shell keeps three checkpoint bytes when a shelf launches a cartridge. On return it starts a fresh hub and supplies those bytes. This preserves the launch tile and facing. It is not a full emulator snapshot or persistent hub save; a device reboot starts at the default location. The hub never loads or writes a Pokémon save.

Shelf handlers store their slot before the far call because Red’s bank-switch helper overwrites A. The publisher captures position then writes command 1. The shell replaces that emulator with the selected cartridge and later reconstructs the room from its checkpoint.

Settings and cabinet commands clear the argument to 0. The host acknowledges them with gameqgb.hub_reply(b''), clearing byte 4. Shelf and clerk text close automatically afterwards. The cabinet uses a normal A-button confirmation for its planned-game text.

Request loops use DelayFrame with interrupts enabled. With an attached host, a request waits until the host clears it or replaces the emulator. Standalone requests self-clear after 16 frames, keeping the room usable in an emulator without a GameQ host. Byte 9 is startup-only.

After the ROM is built, hub/harness.c executes it on gameq_core.c with the RST 38 trap armed. Tests verify direct boot, handshake, all eight shelf arguments, exact position/facing on return, clerk and START settings, cabinet information, resumed movement, and restore acceptance/rejection across every room cell. Source tests verify copy isolation, patch idempotence and fixed mailbox placement. Captures come from the actual ROM.

Twelve Cortex-M4 mailbox cases pass in the ARM emulator, and probes on the pinned MicroPython runtime preserve the mailbox return value. The physical 0.1.5GQ trace also contains 12 captured mailboxes equal to their async returns. Its two shelf-zero requests reached cartridge loading, which failed PSRAM verification at 0x0a1029 and 0x0a58a9. Earlier settings requests have no captured initiating input. See the physical test record for those earlier failures and the successful 0.1.6GQ load/save/reload results.

GameQ/system/hub-trace.txt records the native command, mailbox, frame input and raw keys at room boundaries. This separates a ROM request from a Cancel exit. last-error.txt retains the latest exception across successful room returns.

Physical 0.1.6GQ passed shelf-zero cartridge launch/return and save loading after a full reboot. Resident startup without SD and all room controls still need explicit acceptance. A reported first-boot “Yikes” could not be reproduced; its exact error is unknown. The room uses a fixed eight-port layout with generic shelf numbers; filenames appear in the host. Audio is disabled. DOOM and Game Boy Color emulation are separate future work.