# GAME.md — *Whack Hare!*

A two-player reaction-time duel for the Commodore 64. Two players stand
back-to-back, take ten paces, and the first one to hit fire when the
signal appears wins the round. First to **5 wins** wins the match.

The two players are **Hare** (player 1, on the left — a white rabbit
with claws and a bad attitude) and **Scoot** (player 2, on the right —
a kid on a kick scooter). The two facing-off screens, the win
screens, and the title screen all come from the artwork in
`./source_images/`.

## 1. The cast

| Player | Side | C64 control port | Artwork on the title |
|--------|------|------------------|----------------------|
| Hare (the rabbit) | Left  | Joystick port 1 (`$DC01`) | Lower-left of `screen_title.png` |
| Scoot (the kid on a scooter) | Right | Joystick port 2 (`$DC00`) | Lower-right of `screen_title.png` |

Fire is the only button used. Both ports are read every frame; the
joystick value is the standard `J = PEEK(0xDC00/0xDC01)` byte with bit
4 (mask $10) as the fire button (active low — pressed when bit 4 is
clear).

## 2. Artwork

All artwork lives in `./source_images/` as 1390×1130 PNGs (8-bit RGB,
dithered down to the C64's 16-color palette by whoever made them — the
colors in the files are the colors the C64 will display):

| File | Resolution | Used for |
|------|------------|----------|
| `source_images/screen_title.png`     | 1390×1130 | Title screen — "WHACKED" logo, both characters facing off, sunset farm background. |
| `source_images/screen_waiting1.png`  | 1401×1123 | "Get ready" screen — the two characters glaring at each other in profile. |
| `source_images/screen_waiting_2.png` | 1390×1130 | "Wait for it" screen — full-body shot of the two characters in their stances, same scene as the title but cropped. |
| `source_images/screen_win_hare.png`  | 1391×1131 | Hare won — Hare punching Scoot in the face mid-scooter. |
| `source_images/screen_win_scoot.png` | 1389×1132 | Scoot won — Scoot has ridden over Hare, who is sprawled on the ground. |

A practical note for converting to C64: the originals are 1390×1130, but
the C64 hires screen is 320×200. We'll have to either:

- Pre-crop / pre-scale the assets in a tool (recommended) to 320×200
  before embedding them, or
- Pick a 320×200 region of the original (the center 320×200 crop is
  usually the most interesting part) and embed that.

We'll do the cropping in an image tool (ImageMagick, GIMP, or a small
Python script) and let the C64 just blit the resulting 320×200 hires
bitmap. The Oscar64 `#embed lzo` directive is what pulls the cropped
`.bin` into the binary.

## 3. The screens (in play order)

The game is a simple state machine. The screens, in the order a player
sees them during one round:

1. **Title** (`TITLE` state) — `screen_title.png`. Static background.
   A short, flashing *"PRESS FIRE ON BOTH PORTS"* banner across the
   bottom half. The score is shown at the top: a row of "0"s on the
   left (Hare) and right (Scoot), reading `0    0`. No music, or a
   very low-key idle loop on voice 1.

2. **"Get ready"** (`READY` state) — `screen_waiting1.png`. Up for
   **exactly ~1.2 seconds**. A short two-note "ping" jingle (e.g. SID
   voice 1 plays A4 then A5 with a quick attack/decay envelope). The
   score at the top is visible. The text "READY…" or just the screen
   alone is fine.

3. **"Wait…"** (`WAIT` state) — `screen_waiting_2.png`. The suspense
   state. Stays up for a **random duration between 2.0 and 5.0
   seconds**, uniform. A looping suspenseful bassline plays on SID
   voice 1 (a slow two-note pulse, say C2–G1 alternating every ~16
   jiffies), with voice 2 doing a quiet pulse-width "heartbeat"
   arpeggio. No indication of how long is left — that's the point.

4. **"DRAW!"** (`DRAW` state) — white screen, large black digits
   (described below), a sharp percussive stab on the SID (voice 3,
   noise waveform, attack=0, decay=1, sustain=0, release=2, gated off
   after 4 frames). The counter starts at `001` and increments **once
   per frame** so a fast eye + finger can win in single-digit
   milliseconds.

5. **Win screen** (`WIN_P1` or `WIN_P2` state) — `screen_win_hare.png`
   or `screen_win_scoot.png`. Up for **exactly 2.0 seconds**. A short
   3-4 note "winner" jingle on the SID (e.g. C–E–G–C, major triad
   arpeggio, fast attack, long release, voices 1 and 2 stacked). The
   score at the top updates immediately (you see the new digit the
   moment the win screen appears).

6. **Loop or game-over.** If the winner's score is now < 5, jump back
   to step 2 (READY) for the next round. If the winner's score is 5,
   go to step 7.

7. **Game over** (`GAMEOVER` state) — same as the title screen, but
   with a "GAME OVER — HARE WINS!" / "GAME OVER — SCOOT WINS!"
   banner instead of "PRESS FIRE", and a 5–6 note "match over"
   fanfare. Stays up for 6 seconds (long enough to be appreciated),
   then resets to step 1 (TITLE) with scores at 0/0.

## 4. The "DRAW!" screen — what to draw

The C64 hires screen is 320×200 with a 16-color palette. The DRAW
screen is:

- Background: solid white (`$D021` = white, bitmap all 0s).
- A huge number in black, centered, drawn with the standard 8×8 ROM
  character set scaled up. The counter is 3 digits (001..999) so it
  stays in the visible screen at any size.
- Implementation: easiest is to use the 8×8 ROM font, scaled 4× (so
  each character is 32×32 pixels), centered. That gives 3 chars ×
  32px = 96px wide on a 320px screen, so each digit is huge but fits.
  Redrawing the screen is just "redraw the 3 screen memory cells with
  the new counter value". The VIC's redraw of a 96×32 region in
  character mode takes one frame's worth of work in the worst case;
  the counter is updated once per frame, in sync with the raster
  IRQ, so there's no flicker.

  A nicer version would use a custom multicolor font (the C64 has 4
  colors per 8×8 cell in multicolor character mode), but hires
  character mode is simpler and the white-with-black-text look is
  the classic quick-draw visual.

- Each frame, in the raster IRQ handler, the game logic does:
  ```c
  counter++;
  if (counter > 999) counter = 999;   // cap it
  screen[40 * 11 + 13] = '0' + (counter / 100);
  screen[40 * 11 + 14] = '0' + ((counter / 10) % 10);
  screen[40 * 11 + 15] = '0' + (counter % 10);
  ```
  (and similarly for the colors at $D800+, but they're all black, so
  set once at the start of the DRAW state).

- Optionally: each digit can have a small horizontal "shake" applied
  per frame to give a stressed-out look. Or a small black bar across
  the screen to make it look like a flash.

## 5. The score bar (top of every game screen)

- 1 character row, top of the screen. In hires + char mode, that means
  using the top 8 pixels of the bitmap as character cells, and the
  rest of the screen (200-8 = 192 pixels) as a hires image below.
  The easiest implementation is to set the VIC's video matrix
  pointer to start at a screen memory address that *visually* aligns
  with the top of the screen but in practice is wherever we want.
  Concretely: screen memory at $0400, but the screen "rows" we use for
  the score are just the very top 8 pixels of the bitmap (we draw the
  characters there by hand in the bitmap, OR we use a hybrid mode
  where the top 8 rows are character cells and the rest is hires — but
  C64 character rows are always 8 pixels high, so we'd have an
  8-pixel score bar and a 192-pixel art area. Good enough.)
- Layout, top row, left to right:
  - Cols 0-15: "HARE  " (label) + 5 small icons or just "HARE: " +
    the digit, centered on the left half.
  - Cols 24-39: "SCOOT: " + the digit, centered on the right half.
  - Total format: `   HARE  *    SCOOT  *  ` where the * is the
    number of filled-in score pip (0..5). Pips are simpler than
    digits: 5 small squares per side, filled or empty.
- Score pip rendering: a custom character for "filled pip" (a solid
  block in the foreground color) and "empty pip" (an outline or a
  blank). Each side takes 5 character cells of width.

The cleanest implementation: have two pre-loaded custom character sets
for the pip characters (filled and empty), and the rest of the score
bar is the standard ROM character set. Switch between them with
$D018. But that adds complexity — for the first pass, drawing the
pips as solid filled blocks in the bitmap is fine (the score bar is
only 8 pixels high and the pips are 6×6 blocks).

## 6. Audio (SID 6581) — the five cues

The SID has 3 voices. The game uses voice 1 for music, voice 2 for
harmony, voice 3 for sound effects. The filter is bypassed (volume
$0F, no filter routing) for the first pass — we can add filter
sweeps later for polish.

| State | Voice 1 | Voice 2 | Voice 3 | Notes |
|-------|---------|---------|---------|-------|
| TITLE | (silent, or a low idle pulse) | — | — |  |
| READY | triangle A4 then A5, attack 0, decay 9, release 0, no sustain, gated off after 8 frames each | — | — | Two notes, ~0.6s total |
| WAIT | square wave C2, pulse width 50%, slow envelope (attack 0, decay 15, sustain 8, release 15) | triangle 1 octave up, same envelope, slightly delayed | — | Looping, every ~16 jiffies re-trigger |
| DRAW  | — | — | noise, attack 0, decay 1, sustain 0, release 2, gated on for 4 frames | Sharp stab |
| WIN   | triangle C5–E5–G5–C6 arpeggio, fast attack, slow release, ~0.8s | triangle a third below, same notes, very quiet (volume 4) | — | Major chord jingle |
| GAMEOVER | triangle, fanfare notes (G4–C5–E5–G5–C6), long notes, ~3s | triangle, sustained fifth below | — | End-of-match feel |

(The actual note frequencies in Hz are listed in the SID 6581
datasheet — `freq = 16.777216 * note_hz / 16777216` in the 16-bit
frequency register. We'll define them in a `notes.h` lookup table in
the source.)

Voice 1 waveform is set per state; voice 2 is on only for WAIT and WIN.
The voice 3 noise in DRAW is the only sound effect that's not a
musical note.

The music is driven by a raster IRQ that updates the SID registers
on a per-note schedule. Since each state has a small, fixed set of
notes, the IRQ can be very simple — a "next note time" jiffy counter
and a pointer into a per-state note table.

## 7. Inputs

| Action | Player 1 (Hare) | Player 2 (Scoot) |
|--------|------------------|------------------|
| Fire | `PEEK(0xDC01) & 0x10 == 0` | `PEEK(0xDC00) & 0x10 == 0` |
| (other directions unused) | (ignore) | (ignore) |

`PEEK(0xDC00)` and `PEEK(0xDC01)` are the active-low joystick bytes on
CIA 1 ports A and B. Bit 4 is the fire button; the lower 4 bits are
the directions (up/down/left/right). In the title screen the game
also has to read both ports every frame to see if either player has
pressed fire.

Note: the C64's keyboard is wired in parallel with the joystick
matrix, so `PEEK(0xDC00/0xDC01)` will also return "pressed" if you
hold a key in the right column. For our purposes this is fine (most
players use joysticks), but if we want to be strict we can disable
the keyboard with `POKE 0xDC02, 0xE0` (set keyboard columns as inputs)
during gameplay and re-enable with `POKE 0xDC02, 0xFF` when returning
to BASIC.

## 8. Timing (PAL C64, 50 Hz / 312 lines)

The game runs at the PAL frame rate: 50 fields per second, 312 lines
per field. We use a single raster IRQ set to fire on a stable line
(line 311, the one right after vertical blank and before any badlines)
on every frame, so we have a 50 Hz tick to update game state, read
joysticks, update the SID, and redraw the screen.

- TITLE state: update at 25 Hz (every other frame) to flash the
  prompt. Read joysticks every frame so the input is responsive.
- READY state: fixed-duration, ~1.2 sec = 60 frames. Use a frame
  counter, not a real-time timer.
- WAIT state: random 100-250 frames. The random number is from the
  SID's $D41B oscillator register (read-only noise that's
  effectively random).
- DRAW state: indefinite. Counter increments every frame. The first
  fire press wins. If for some reason neither player presses for
  10 seconds (500 frames), declare it a "fault" and go to TITLE
  with no point awarded.
- WIN state: fixed 2.0 sec = 100 frames.
- GAMEOVER state: fixed 6.0 sec = 300 frames.

## 9. The state machine

```
                  both fire
        +-------------------------+
        |                         v
      TITLE  ----- fire1 only ---->  TITLE  (wait for player 2)
        |                         ^
        |  both still holding? no
        v
      READY   (fixed 60 frames, "ping" jingle)
        |
        v
      WAIT    (random 100..250 frames, suspense music)
        |
        v
      DRAW    (counter++, play stab, wait for fire)
        |    \
        |     \-- both never fire in 500 frames --> fault back to TITLE
        |
        v  fire1 first
      WIN_P1  (100 frames, jingle, +1 to player 1)
        |  \
        |   \-- player 1 score == 5 --> GAMEOVER
        v
      WIN_P2  (100 frames, jingle, +1 to player 2)
        |  \
        |   \-- player 2 score == 5 --> GAMEOVER
        v
      (back to READY for next round)
```

States are stored in a single `byte` or `enum` variable. Each frame,
the IRQ handler runs the state machine, updates the SID, and updates
the score / counter display.

## 10. Asset preparation pipeline

Before the C64 ever sees these PNGs, we have to:

1. **Crop** each PNG to 320×200 (or pick a 320×200 region that looks
   good). The center 320×200 of each is usually what we want; the
   aspect ratio is close to 16:10 which is what the C64 has, so a
   direct crop with no scaling works.
2. **Reduce colors** to the C64's 16-color palette. The originals
   look like they were already done with a C64 palette in mind
   (the orange/purple/yellow/brown/green range is classic C64), but
   we should still quantize to a 16-color palette that matches the
   VIC's actual 16 fixed colors. Tools: ImageMagick
   (`-colors 16 +dither` or similar), GIMP (Image → Mode →
   Indexed), or a small Python/PIL script.
3. **Convert to 320×200 hires bitmap format** — a single .bin file
   of 8000 bytes (320×200 / 8 bits per byte, row-major, MSB first).
4. **Compress with LZO** at build time via Oscar64's
   `#embed lzo "file.bin"` directive. The decompressed bitmap is
   stored in a RAM region at startup.
5. **Copy to $E000-$FF40 (or wherever the hires area is)** at the
   start of each state transition. Since all 4 screens are different
   bitmaps, we either keep them all in RAM (4 × 8000 = 32 KB, way
   too much) or we stream one in at a time from the .prg file at
   each state transition.

The practical solution: embed all 4 screens with `#embed lzo`, store
them in a reserved region of the .prg file (after the program code
and before the runtime data), and at each state transition, decompress
the right one into the hires area using `oscar_expand_lzo`. This is
the same pattern Oscar64 samples use for the charsets and sprites.

## 11. Memory budget (rough)

| Item | Size | Where |
|------|------|-------|
| Compiled C code (logic, IRQ, audio) | ~4-6 KB | `$0900-$1F00` |
| Stack (C locals, IRQ save area) | 1 KB | BSS |
| Heap (no malloc) | 0 KB | n/a |
| One decompressed hires bitmap | 8000 bytes | `$6000-$7F40` (or similar 8 KB-aligned window) |
| 4 compressed bitmaps in .prg | ~10-15 KB total | After code, in ROM/loaded region |
| Character set (1 KB) + Color RAM (1 KB) | 2 KB | RAM |
| Custom font (pips, counter digits) | 256 bytes | RAM |
| SID note tables | ~200 bytes | ROM |
| **Total loaded** | ~25-30 KB | Fits comfortably in `$0900-$A000` (38 KB) or in a no-ROM build |

The game is comfortably within C64 limits. We can even use the default
$0900-$A000 region with all ROMs banked in (so the KERNAL ISR
trampoline still works), leaving the full 38 KB available for code,
data, and one decompressed bitmap at a time.

## 12. What's *not* in scope (yet)

- Sound test menu (no — the audio is fixed per state).
- Player name entry (no — pure 2-player hot-seat).
- Difficulty levels (no — the WAIT duration is fixed; we can vary it
  per round if we want a "tiebreaker" round).
- AIs (no — strictly 2-player).
- Save/load high scores (no — sessions are short).
- NTSC (50 Hz vs 60 Hz) — PAL is the target. NTSC is a small port:
  different raster line counts, different stable-raster line numbers.
  The raster IRQ line (311) and frame rate (50 Hz) are the only
  numbers that need to change.
- The score bar's *exact* pixel layout — we'll iterate on the
  visual design after we have the gameplay working.

## 13. Source layout (proposed)

```
src/
├── main.c              # entry point, main loop
├── game.c / game.h     # state machine
├── screens.c / .h      # 4 compressed bitmaps + show_screen(N) helper
├── audio.c / .h        # SID note tables + play_state(N) helper
├── input.c / .h        # joystick read helpers
├── score.c / .h        # score bar renderer
├── draw.c / .h         # the white-screen + counter rendering
├── rasterirq.c / .h    # 50 Hz frame tick
├── notes.h             # SID frequency table
├── build.sh            # (already exists)
└── build/              # (gitignored) output
```

The plan is to keep the code as flat as possible — no `region` /
`section` pragmas in the first pass, just the default Oscar64 layout,
and we reach for `#pragma region(…)` if the linker complains.

---

The art is striking, the gameplay is dead simple, and the SID has
plenty of room for the audio we want. This should be a fun first C64
project.
