How it works · write-up
Handoff: capture the zoom-text interstitial glyphs + perspective starfield
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+"at0x8719; glyph index comes from a linear-scan lookup (0x7fd8) against a table at0x871A. g_spriteMemoryRegion[]insrc/xenonRender.chas 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 at0x876c, indexed by the live scale valueDAT_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-recordStarfield_48StarRecordsarray (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.zshrinks 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 byz>>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_OnStarfieldDrawInstructionFetchfor 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
- Confirm the exact glyph count/byte span of the
0x9DC0font via a live memory dump (open the game on one of these screens, dump ST RAM, verify glyph boundaries) before writing scan code. - 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). - Add a capture hook for
blitter_drawScaledText_FUN_0000803a/blitting_DrawFixedLengthText16x22call sites, pushing masked-sprite entries the same shape as tiles/objects. - Add a second starfield-style hook for
Starfield_DrawPerspectiveFlythrough_FUN_000085b4, reinterpreting the record layout as documented above. - 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 |