Phase 1: asset pipeline (Python script + 5 processed .bin/.attr files)
Adds tools/convert_screens.py which converts each source PNG to a
160x200 multicolor bitmap (8000 B .bin) plus a 1000 B .attr screen-
memory table, following c64-wiki Multicolor_Bitmap_Mode:
- 2-bit pixel: 00=$D021, 01=attr high nibble, 10=attr low nibble,
11=color RAM nibble.
- Per 4x8 cell: $D021 is forced into the 4-color set (it's global);
the other 3 are the 3 most common non-d021 colors in the cell.
- Pixels snap to the nearest of those 4, then packed 4-per-byte
MSB-first into the bitmap; attr byte = (cell_color_2 << 4) |
cell_color_1 (low nibble = '10' color, high = '01' color).
Tried two downscale strategies on all 5 source images; both produce
visually equivalent output at the ~9x source-to-target scale. Picked
Strategy A (direct 160x200 LANCZOS) as default because it's simpler
and slightly faster; Strategy B (LANCZOS 320x200 then 2x BOX 160x200)
remains available via --strategy. See the script docstring for the
detailed rationale.
$D021 is auto-picked as the most common C64 palette color in the
downscaled image (turns out to be black for 4 of 5 screens, dark grey
for waiting2). The .d021 sidecar files document this value so Phase 2
can program the VIC without re-deriving it.
The '11' color (color RAM nibble per cell) is not stored in .attr (no
room in 1000 B). Default is to leave color RAM at the C64 boot value
(black), which gives effectively 3 unique colors per cell + 1 global.
Phase 2 can either accept this or extend the format with a per-cell
color RAM table.
Generated files are checked in so the build doesn't depend on Python.
Output sizes verified: each .bin is exactly 8000 B, each .attr is
exactly 1000 B, total 9000 B per screen. The .bin files are not all
zero (sanity check passed).
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
# Project context for Whack Hare! subagent
|
||||
|
||||
You are implementing one phase of a C64 game called "Whack Hare!"
|
||||
(quick-draw duel between a rabbit and a kid on a scooter, first to 5
|
||||
wins). You will be given a specific phase task. This document is the
|
||||
context you need to do the work.
|
||||
|
||||
## Project layout (read-only)
|
||||
|
||||
```
|
||||
/home/ballz/work/teletype/nyuller/
|
||||
├── oscar64/ # Oscar64 C cross-compiler (git submodule)
|
||||
│ ├── bin/oscar64 # The compiler binary (built by make)
|
||||
│ └── include/ # Runtime headers (c64/, c128/, gfx/, stdio.h, etc.)
|
||||
├── docs/c64/ # Downloaded C64 reference material
|
||||
│ ├── vic/ # VIC-II register map, graphics modes, cebix article
|
||||
│ ├── cia/ # CIA 1 + CIA 2 register maps
|
||||
│ ├── sid/ # SID register map, ADSR, filter
|
||||
│ ├── kernal/ # KERNAL jump table, full PRG text
|
||||
│ ├── memory/ # Memory map, zeropage, hardware internals
|
||||
│ ├── interrupts/ # Raster IRQ, IRQ/NMI flow, joystick
|
||||
│ ├── sprites/ # Sprite (MOB) programming
|
||||
│ └── cpu/ # 6510 overview
|
||||
├── OSCAR64.md # How the Oscar64 compiler works + C64 best practices
|
||||
├── PROG_C64.md # C64 hardware reference (synthesized)
|
||||
├── GAME.md # The game design spec
|
||||
├── tasks.md # The phased implementation plan (your roadmap)
|
||||
├── source_images/ # The 5 source PNGs for the game screens
|
||||
└── src/ # YOUR CODE GOES HERE
|
||||
├── helloworld.c # Existing minimal working program
|
||||
├── build.sh # Build script (handles compiler build, runs tests)
|
||||
└── build/ # Output directory (gitignored)
|
||||
```
|
||||
|
||||
## Build and test (this is the ONLY way to verify your work)
|
||||
|
||||
```sh
|
||||
cd /home/ballz/work/teletype/nyuller/src
|
||||
./build.sh # compile helloworld.c → build/helloworld.prg
|
||||
./build.sh -e # run in oscar64's built-in emulator (HEADLESS, fast)
|
||||
./build.sh -v # (DO NOT USE) VICE x64 — requires a real display
|
||||
./build.sh -V # (DO NOT USE) VICE x64sc — same problem
|
||||
```
|
||||
|
||||
**The oscar64 built-in emulator (`-e`) is the only test tool.** VICE
|
||||
does not work in this headless environment (no display, blank
|
||||
screenshots, manual ROM fetching). Do not waste time on VICE.
|
||||
The `-e` flag runs the same `.prg` file the C64 will run, with no
|
||||
setup, no ROMs, and at high speed.
|
||||
|
||||
`build.sh` will auto-build the oscar64 compiler if it's missing.
|
||||
It calls `oscar64 -i=/home/ballz/work/teletype/nyuller/oscar64/include -o=build/helloworld.prg helloworld.c`
|
||||
to compile, and the same command with `-e` to run.
|
||||
|
||||
The build artifacts in `build/` are: `helloworld.prg` (the C64 program),
|
||||
`helloworld.asm` (6502 listing), `helloworld.map` (region/section/object
|
||||
placement), `helloworld.lbl` (VICE monitor labels — still useful even
|
||||
though we don't use VICE), `helloworld.int` (intermediate code).
|
||||
|
||||
## CRITICAL: the 4-color-per-4×8-cell multicolor constraint
|
||||
|
||||
The 5 source PNGs have well more than 2 colors per 8×8 cell. They
|
||||
**must** be displayed using **multicolor bitmap mode** (VIC-II
|
||||
`$D011` BMM=1, `$D016` MCM=1): 160×200 with 2 bits per pixel, 4 colors
|
||||
per 4×8 cell, same 8000-byte bitmap size. Per-cell only 4 colors are
|
||||
available; the asset pipeline must quantize each 4×8 cell independently
|
||||
to 4 colors from the 16-color C64 palette (black, white, red, cyan,
|
||||
purple, green, blue, yellow, orange, brown, light red, dark grey,
|
||||
medium grey, light green, light blue, light grey).
|
||||
|
||||
The bitmap goes to 8 KB at $E000-$FFFF (or wherever the linker
|
||||
places it after `mmap_set(MMAP_RAM)` + `mmap_set(MMAP_NO_ROM)`).
|
||||
The 1000-byte attribute table (one entry per 4×8 cell, low nibble =
|
||||
foreground color, high nibble = the 2-bit background register selector
|
||||
$21-$24) goes to $D800-$DBE7.
|
||||
|
||||
## C64 hardware facts you'll need
|
||||
|
||||
- **CPU**: MOS 6510 (6502 + 6-bit I/O port at $00/$01). 1 MHz PAL.
|
||||
Banks BASIC/KERNAL/CHAREN via writes to $01.
|
||||
- **VIC-II** at $D000-$D3FF. 47 registers, mirrored every 64 bytes.
|
||||
The 16-color palette is fixed (see docs/c64/vic/vic_registers.md).
|
||||
The raster counter is 9 bits: $D012 is the low 8, bit 7 of $D011
|
||||
is bit 8.
|
||||
- **SID** (6581) at $D400-$D41C. 3 voices, ADSR envelopes, filter.
|
||||
Volatile freq registers, no NaN, master volume at $D418.
|
||||
- **CIA 1** at $DC00: keyboard, joystick, paddles, datasette,
|
||||
IRQ timer. Joystick bytes are active-low: bit 4 = fire.
|
||||
- Port 1 (right joystick, Hare) = $DC01
|
||||
- Port 2 (left joystick, Scoot) = $DC00
|
||||
- **CIA 2** at $DD00: serial bus, RS-232, NMI, **VIC bank bits**
|
||||
(low 2 bits of PRA select one of 4 VIC banks).
|
||||
- **KERNAL** at $E000-$FFFF. Jump table at $FF81-$FFF3.
|
||||
IRQ vector at $0314 (default → $EA31, the standard KERNAL ISR).
|
||||
NMI vector at $0318 (default → $FE47, the soft-reset / RESTORE handler).
|
||||
- **Color RAM** at $D800-$DBE7: 4-bit wide (low nibble only).
|
||||
- **Character ROM** at $D000-$DFFF in banks 0 and 2; can be relocated
|
||||
to RAM by changing $D018.
|
||||
- **PAL timing**: 312 lines, 50 Hz. The "stable raster" line for IRQs
|
||||
is line 311 (right after vertical blank, before any badlines).
|
||||
- **Badlines** cost 40 cycles every 8 text lines. Cycle-accurate
|
||||
code has to know this.
|
||||
|
||||
## Oscar64 patterns to use
|
||||
|
||||
- `mmap_trampoline()` once at startup, then `mmap_set(MMAP_RAM)` to
|
||||
bank out BASIC + KERNAL (gives you ~38 KB contiguous RAM at
|
||||
$0900-$A000). `mmap_set(MMAP_NO_ROM)` also banks out CHAR ROM
|
||||
($D000 becomes I/O, not char data — needed for the bitmap at $E000).
|
||||
- `#pragma region(name, start, end, , , {sections})` to remap
|
||||
memory (you probably won't need this for the first pass — the
|
||||
default layout works).
|
||||
- `#pragma stacksize(N)` and `#pragma heapsize(N)` to size those.
|
||||
- `#pragma compile("foo.c")` is what `#include <stdio.h>` etc. use
|
||||
to drag in implementation files. You never list multiple `.c`
|
||||
files on the command line; the headers do it.
|
||||
- `__assume(x < 8)` etc. to give the optimizer value-range hints.
|
||||
Helps the 6502 backend use 8-bit ops instead of 16-bit.
|
||||
- `__striped` qualifier for arrays where the 6502's lack of
|
||||
indirect-with-offset hurts.
|
||||
- `p""` prefix for PETSCII string literals (not ASCII). The C64's
|
||||
char ROM is not ASCII.
|
||||
- `__hwinterrupt` for ISRs (saves A/X/Y, exits with RTI).
|
||||
- `__native` for a function you want to force to native 6502 code.
|
||||
- `__zeropage` for global variables you want placed in ZP
|
||||
(or use `-Oz` to auto-place).
|
||||
- The runtime headers in `./oscar64/include/` are the right way to
|
||||
touch hardware: `#include <c64/vic.h>` (VIC-II struct at $D000),
|
||||
`#include <c64/cia.h>` (joystick reading helpers),
|
||||
`#include <c64/memmap.h>` (mmap_set, mmap_trampoline),
|
||||
`#include <c64/rasterirq.h>` (raster IRQ API — use this, don't
|
||||
write your own IRQ handler from scratch),
|
||||
`#include <c64/sprites.h>` (sprite setup, alignment),
|
||||
`#include <gfx/bitmap.h>` (hires drawing helpers),
|
||||
`#include <stdio.h>` / `<conio.h>` for text I/O.
|
||||
|
||||
## Game-specific design (full version in GAME.md)
|
||||
|
||||
| Element | Value |
|
||||
|---------|-------|
|
||||
| Title screen | `source_images/screen_title.png` — full-screen image with "WHACKED" logo |
|
||||
| Waiting 1 | `source_images/screen_waiting1.png` — 1.2 sec, "ping" jingle |
|
||||
| Waiting 2 | `source_images/screen_waiting_2.png` — random 2-5 sec, suspense music |
|
||||
| DRAW | white screen, big counter incrementing each frame, sharp stab |
|
||||
| Win Hare | `source_images/screen_win_hare.png` — Hare won (player 1) |
|
||||
| Win Scoot | `source_images/screen_win_scoot.png` — Scoot won (player 2) |
|
||||
| Score bar | top 8 pixels of the screen, "HARE 0..0 SCOOT" (5 pips per side) |
|
||||
| Match | first to 5 wins, then reset to title |
|
||||
| Player 1 (Hare) | left side, joystick port 1, fire = `(PEEK(0xDC01) & 0x10) == 0` |
|
||||
| Player 2 (Scoot) | right side, joystick port 2, fire = `(PEEK(0xDC00) & 0x10) == 0` |
|
||||
| State machine | TITLE → READY → WAIT → DRAW → WIN_P1/P2 → (back to READY or GAMEOVER) |
|
||||
| Game timer | 50 Hz PAL, line 311 for the raster IRQ |
|
||||
| Random | use `PEEK(0xD41B)` (SID oscillator 3, effectively random) |
|
||||
|
||||
## Commit conventions
|
||||
|
||||
- One commit per phase, with a clear `Phase N: <summary>` prefix.
|
||||
- Don't commit generated build artifacts in `src/build/` (gitignored).
|
||||
- Generated data files (e.g. `src/data/processed/*.bin`) are checked
|
||||
in so the build doesn't depend on Python being installed.
|
||||
|
||||
## What to do if you get stuck
|
||||
|
||||
1. Re-read the relevant section of `OSCAR64.md`, `PROG_C64.md`,
|
||||
`GAME.md`, or `tasks.md` (your phase section).
|
||||
2. Look at the Oscar64 samples: `../oscar64/samples/`. They're
|
||||
small, focused, and have working patterns. Good ones to look at:
|
||||
- `../oscar64/samples/memmap/allmem.c` — mmap_set usage
|
||||
- `../oscar64/samples/rasterirq/colorbars.c` — raster IRQ
|
||||
- `../oscar64/samples/memmap/easyflash.c` — memory layout
|
||||
- `../oscar64/samples/hires/lines.c` — hires drawing
|
||||
- `../oscar64/samples/sprites/` — sprite setup
|
||||
3. Look at the runtime headers you need to use:
|
||||
`ls /home/ballz/work/teletype/nyuller/oscar64/include/c64/` etc.
|
||||
4. Read the relevant docs/c64/ file for the chip you're working on.
|
||||
5. Do NOT spend time on VICE, xvfb, environment setup, or anything
|
||||
not in this project. The build environment is set up.
|
||||
|
||||
## Output for this phase
|
||||
|
||||
When you're done, report back:
|
||||
1. What you built (1-2 sentence summary)
|
||||
2. The output of `./build.sh -e` (proves it compiled and runs)
|
||||
3. The git commit hash and one-line summary
|
||||
4. Any concerns or follow-up work for the next phase
|
||||
Reference in New Issue
Block a user