Xenon 2

How it works · write-up

Handoff: capture the zoom-text interstitial glyphs + perspective starfield

xenondoc/handoff-zoomtext-starfield-capture.md · 5 KB · updated 2026-09-17

Goal

Extend the DrawCommandStream / sprite-atlas capture pipeline (src/drawCommandStream.c, src/xenonRender.c) to reproduce the "zoom-text interstitial" screens — "GET READY PLAYER n", "HIGH SCORE / LOADING LEVEL n", and other screens sharing the same machinery. Currently these screens are drawn straight to the ST framebuffer with no hook into the capture pipeline, so none of it reaches the GPU reconstruction.

Full technical writeup already exists: xenondoc/XENON2.MD, §3.6 ("Zoom-text interstitial screens — scaled 16×22 font + perspective starfield flythrough") and the corresponding rows in the §7 quick address/symbol reference. All addresses below are also annotated directly in the Ghidra project (/mydumpat0) with plate comments on the renamed functions. Read §3.6 in full before starting — this note is just a pointer + task list, not a restatement.

Two independent capture gaps

1. New font — 16×22 scaled glyphs, not yet in the atlas

  • Font glyph table: 0x9DC0, 4-plane unmasked (same format family as wall tiles, SPRITE_FORMAT_4PLANES_TILE), 176 bytes/glyph, ~42 glyphs (estimate, not yet confirmed against a live capture).
  • Charset: "!ABCDEFGHIJKLMNOPQRSTUVWXYZ.:><0123456789+" at 0x8719; glyph index comes from a linear-scan lookup (0x7fd8) against a table at 0x871A.
  • g_spriteMemoryRegion[] in src/xenonRender.c has no entry anywhere near this address — nothing has scanned it. Next step: add a region entry (opaque/unmasked, 16×22, .maxSpriteCount ≈ 42, .stAddr = 0x9DC0), verify the glyph count and exact byte span against a live memory dump before trusting the 42/0x1CE0 estimate.
  • The actual scaled draw routine is blitter_drawScaledText_FUN_0000803a (0x803a) — reads the 176-byte source glyph and stretches it per-row using a 16-entry bit-doubling mask table at 0x876c, indexed by the live scale value DAT_00007f3c (0-15). Capture should probably just grab the source glyph (fixed 16×22) into the atlas and let the GPU do the scaling, rather than trying to reproduce the CPU's row-doubling algorithm.

2. Perspective starfield flythrough — different code path than gameplay stars

  • Starfield_DrawPerspectiveFlythrough_FUN_000085b4 (0x85b4) reuses the same 48-record Starfield_48StarRecords array (0x3d1dc) as gameplay, but reinterprets each 6-byte record as 3D (x, y, z) and projects it: screenX = (x<<4)/z + 160, screenY = (y<<4)/z + 100. z shrinks every frame; stars respawn at the vanishing point when they go off-screen or behind the camera.
  • Per-pixel color comes from an 8-entry depth→bitplane table at 0x867c (indexed by z>>10 & 7), giving palette indices {7,7,8,8,6,5,4,9} — wider than gameplay's fixed 4-7.
  • There's no capture hook analogous to DrawCommandStream_OnStarfieldDrawInstructionFetch for this routine yet. Existing star capture assumes the gameplay (screenOffset, phase, xMask) record layout — this would need its own hook reading the reinterpreted (x, y, z) layout instead.

Also needed: live palette-register observation

ZoomTextInterstitial_MainLoop_FUN_00008530 (0x8530) writes an 8-word table (0x86fa) directly into the ST palette hardware registers $FFFF8240-$FFFF825C once per frame (confirmed VBL-synced, not HBL/per-scanline — verified via find-cross-references on 0xffff8240, all 16 static write sites are ordinary subroutines, none interrupt-driven). Both the text and the flythrough stars cycle color via this mechanism. The capture pipeline currently assumes a static per-level palette everywhere else — this screen would need the palette itself captured/sampled per frame, not just sprite positions, for the color-cycling to reproduce correctly. Worth deciding whether that's in scope for a first pass or deferred (static/fixed color per glyph would still look reasonable).

Suggested order of work

  1. Confirm the exact glyph count/byte span of the 0x9DC0 font via a live memory dump (open the game on one of these screens, dump ST RAM, verify glyph boundaries) before writing scan code.
  2. Add the new g_spriteMemoryRegion[] entry and scan/decode function (likely reuse or lightly adapt the existing tile decoder given the matching 4-plane unmasked format).
  3. Add a capture hook for blitter_drawScaledText_FUN_0000803a / blitting_DrawFixedLengthText16x22 call sites, pushing masked-sprite entries the same shape as tiles/objects.
  4. Add a second starfield-style hook for Starfield_DrawPerspectiveFlythrough_FUN_000085b4, reinterpreting the record layout as documented above.
  5. Decide on palette handling (static assumed color vs. live per-frame palette capture) before wiring up the GPU side.

Relevant addresses (see XENON2.MD §7 for the complete table)

Address Symbol
0x76b4 OnLifeLost_SwapTurnAndRespawn_FUN_000076b4 (entry point / trigger)
0x84c4 ZoomTextInterstitial_ShowScreen_FUN_000084c4 (driver)
0x8530 ZoomTextInterstitial_MainLoop_FUN_00008530 (per-frame loop)
0x85b4 Starfield_DrawPerspectiveFlythrough_FUN_000085b4
0x803a blitter_drawScaledText_FUN_0000803a
0x8004 blitting_DrawFixedLengthText16x22
0x9dc0 16×22 font glyph table
0x8719 charset string (42 chars)
0x867c depth→bitplane-pattern table (perspective stars)
0x86fa per-frame palette table