All references updated:
- src/build.sh: PRG=nyuller.prg, D64=nyuller.d64, disk name 'ny',
all grep/pgrep patterns updated, all comments updated
- All 10 header files: include guards WHACK_HARE_* → NYULLER_*
- C source comments: 'Whack Hare!' → 'Nyuller'
- README.md: updated build command output path
- GAME.md: title updated
- tasks.md: all whack_hare.prg → nyuller.prg
- src/AGENT_CONTEXT.md: project name updated
- tools/convert_screens.py: project name updated
Build artifacts renamed: whack_hare.* → nyuller.*, whack.d64 → nyuller.d64
Note: title screen image (source_images/screen_title.png) still shows
'WHACKED' logo — that's a visual asset, not a code reference.
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.
Draws a score bar across the top 8 rows of the multicolor bitmap at
$E000-$E13F (320 bytes), with HARE on the far left, SCOOT on the far
right, and 5 pips for each player between them.
A pip is 8x8 pixels (2 cells wide x 1 cell tall = 16 bitmap bytes):
filled = 1-pixel white border around a 6x6 black block; empty = an 8x8
white square (the border alone). Pips are packed 2 cells each with
no gap, so adjacent pip borders merge into a 2-px white vertical
divider.
Labels use a hand-built 4x8 font (1 byte per row, top 4 bits = 4
pixels) for the 8 chars H/A/R/E/S/C/O/T (64 bytes total). Each font
row is expanded 1bpp -> 2bpp via a 16-byte lookup table (expand4):
each '1' becomes '11' (white, from color RAM), each '0' becomes '00'
(background black, from $D021).
score_render() also rewrites the top 40 cells' attributes
(screen memory = 0, color RAM = 1) so the score bar's palette is
exactly 2 colors. Scores are clamped to 0..5 on render to defend
against double-tap bugs in Phase 4.