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.
Replace busy-wait frame counter with a 50 Hz raster IRQ at line 311
(PAL stable line). The IRQ handler increments a 16-bit frame_count
and calls game_step() once per frame. Per-state timing now uses
enter_frame timestamps + frame_count comparisons.
- New tick.h/tick.c: install one RIRQ via Oscar64's rirq library,
call a __interrupt handler that bumps frame_count and runs the
state machine. Mask CIA 1 + CIA 2 IRQs and set RST8 (the high
bit of the 9-bit raster register) so the IRQ fires at line 311
not line 55.
- game.h: expose volatile frame_count, replace per-state 'frame'
counter with enter_frame timestamps.
- game.c: use frame_count - enter_frame everywhere; sample SID
$D41B at READY enter for a random 100..250 frame WAIT duration;
trigger a low-square-wave stinger on SID voice 1 when DRAW
faults out (no fire for 500 frames) and gate it off ~0.2 sec
later via a counter decremented every frame.
- main.c: replace the busy-wait loop with rasterirq_setup() and
an empty for(;;); idle.
(Filename is tick.c/.h not rasterirq.c/.h because the oscar64
library's own rasterirq.c does '#include "rasterirq.h"' to pull
in its own header, and that include would otherwise pick up
ours and lose NUM_IRQS.)