Phase 8: polish and end-to-end test

Banner text rendering (PRESS FIRE / HARE WINS! / SCOOT WINS!):
- src/banner.c, src/banner.h: new module; renders NUL-terminated
  ASCII text in any character row of the multicolor bitmap, using
  the same 4x8 custom font as the score bar.  Truncates to 40 chars,
  centers in the row, clears the row first.
- src/score.c, src/score.h: font extended from 8 to 16 entries —
  added P, F, W, I, N, !, space (and one reserved).  font_lookup()
  maps ASCII to font index.
- src/game.c: TITLE renders 'PRESS FIRE' in row 1 below the score
  bar; GAMEOVER renders 'HARE WINS!' or 'SCOOT WINS!' based on
  last_winner (set by game_enter_win_p1/p2).  Other states clear
  row 1 on entry.

State transition stinger:
- src/audio.c, src/audio.h: audio_play_stinger(voice, duration)
  starts a low square-wave burst that auto-cleans up via
  audio_advance_stinger (called from audio_state_step).  A 5-frame
  stinger is fired on every state transition; the per-state voice
  is chosen to avoid colliding with the new state's audio
  (TITLE/READY: voice 0/1 free, WAIT: voice 2 free, DRAW: voice 0
  free, WIN/GAMEOVER: voice 2 free).  Stinger is silenced by the
  next audio_state_enter() via the existing audio_stop() call.

TITLE border flash changed from 12.5 Hz to 1 Hz (50 on, 50 off).

Build system:
- src/build.sh: added -O0/-O1/-O2/-O3/-Os/-g flag handling.  Both
  default (-O1) and -O3 builds produce a 43913-byte .prg
  (well under the 51308-byte LOAD"*",8,1 limit).
- src/main.c: pinned #pragma stacksize(0x400) — the oscar64 default
  is the same, but pinning makes the layout predictable across
  optimization levels.  -O3 needs this exact size; larger values
  cause 'Cannot place stack section' link errors because the
  optimizer's larger code section leaves less room in the
  stack/heap gap.
- src/tick.c: marked frame_tick_handler __noinline so -O3 doesn't
  inline the entire state machine (6000+ bytes) into the IRQ
  handler.  With __noinline, the handler is 136 bytes — small
  enough for the raster line budget.
- src/game.h: marked game_step __noinline for the same reason.

The .prg is 173 blocks (out of 202 max), well within the BASIC
load area.  Both default and -O3 builds run cleanly in the oscar64
built-in emulator.
This commit is contained in:
Whack Hare Agent
2026-07-17 02:55:16 +02:00
parent a8ce56464d
commit b7bd76dee3
11 changed files with 464 additions and 31 deletions
+11
View File
@@ -29,7 +29,18 @@
// We don't malloc, so the heap is unused. Setting it to 0 frees the
// space for the screen data in the main region.
//
// The stack is set to 0x400 (1 KB) — this is the oscar64 default,
// pinned explicitly so the layout is predictable across `-O1` /
// `-O3` builds. With `-O3` the code section is larger, so the
// data section's spillover into the default stack/heap gap region
// leaves only ~0x400 for both. Anything larger (0x600+) makes
// `-O3` fail to link with "Cannot place stack section". 1 KB is
// plenty because the oscar64 software stack lives in zero-page
// (0xF7-0xFF, 9 bytes per the -O3 default); the spillover area
// only needs to hold the few locals/params that don't fit in ZP.
#pragma heapsize(0)
#pragma stacksize(0x400)
int main(void)
{