mirror of
https://github.com/imjasonh/nescript
synced 2026-07-10 01:37:45 +00:00
metatiles + collision: metatileset, room, paint_room, collides_at
Closes §H. 2×2 metatiles and a parallel collision map are now a
first-class construct. `metatileset Name { metatiles: [{ id, tiles,
collide }, ...] }` declares a library of 2×2 tile bundles. `room Name
{ metatileset: M, layout: [...] }` lays them out on a 16×15 grid. The
compiler expands each room at compile time into:
- a 960-byte nametable (`__room_tiles_<name>`)
- a 64-byte attribute table (`__room_attrs_<name>`)
- a 240-byte collision bitmap (`__room_col_<name>`)
`paint_room Name` reuses the vblank-safe `load_background` update
machinery for the nametable blit and installs the collision bitmap
pointer into `ZP_ROOM_COL_LO`/`ZP_ROOM_COL_HI` (ZP $18/$19).
`collides_at(x, y)` JSRs into a small runtime helper that reads
`(room_col),Y` with `Y = (y & 0xF0) | (x >> 4)` and returns 0/1.
The helper links in only when the `__collides_at_used` marker is
emitted, so programs that declare a room but never query it pay
zero bytes for the subroutine.
`parse_byte_array` grows a `[value; count]` shortcut — 240-entry
`layout` arrays are unwieldy to spell out a byte at a time.
See `examples/metatiles_demo.ne` for the end-to-end flow: a probe
sprite bounces off walls via `collides_at` and lands on the left
side of the playfield at frame 180 — direct evidence that the
collision query works.
Also defers the register-allocator work from §"Code quality /
tooling" and documents the audio-goldens constraint in future-work
so the next agent sees it.
This commit is contained in:
parent
9719dc4111
commit
82b3d0d20a
23 changed files with 1344 additions and 60 deletions
|
|
@ -79,6 +79,14 @@ pub const ZP_PENDING_BG_TILES_LO: u8 = 0x14;
|
|||
pub const ZP_PENDING_BG_TILES_HI: u8 = 0x15;
|
||||
pub const ZP_PENDING_BG_ATTRS_LO: u8 = 0x16;
|
||||
pub const ZP_PENDING_BG_ATTRS_HI: u8 = 0x17;
|
||||
/// Pointer to the currently-painted room's collision bitmap. Set by
|
||||
/// `paint_room Name` (via the codegen); read by the `__collides_at`
|
||||
/// runtime helper to resolve each collision query against the
|
||||
/// active room. Only reserved when the program declares a `room`;
|
||||
/// otherwise the analyzer's user-ZP bump pointer starts at `$18` as
|
||||
/// before.
|
||||
pub const ZP_ROOM_COL_LO: u8 = 0x18;
|
||||
pub const ZP_ROOM_COL_HI: u8 = 0x19;
|
||||
|
||||
// ── Debug instrumentation ──
|
||||
//
|
||||
|
|
@ -2065,6 +2073,67 @@ pub fn gen_vram_buf_drain() -> Vec<Instruction> {
|
|||
out
|
||||
}
|
||||
|
||||
/// Emit the `__collides_at` runtime helper: a small subroutine
|
||||
/// that answers "does the pixel at `(x, y)` fall inside a solid
|
||||
/// metatile of the currently-painted room?". Spliced in whenever
|
||||
/// the codegen emits the `__collides_at_used` marker.
|
||||
///
|
||||
/// **Contract**
|
||||
/// - Input: `A = x` (screen pixel 0..255), `X = y` (screen pixel
|
||||
/// 0..239). `(x >> 4, y >> 4)` addresses the 16×15 metatile
|
||||
/// grid; rooms span pixels `(0..255, 0..239)` exactly.
|
||||
/// - Output: `A = 1` if the covering metatile is marked
|
||||
/// `collide: true`, `A = 0` otherwise. Flags reflect the final
|
||||
/// LDA so callers can BEQ / BNE directly.
|
||||
/// - Clobbers: `A, X, Y`. Uses ZP `$04-$05` as a scratch pointer
|
||||
/// (the same byte pair runtime helpers like `gen_audio_tick`
|
||||
/// reuse as a temporary workspace), plus the room-pointer ZP
|
||||
/// slots populated by `paint_room` at `$18-$19`.
|
||||
/// - Out-of-range inputs (`y >= 240`) clamp to the last row
|
||||
/// because the division only keeps bits 4-7, giving row 14.
|
||||
/// Users can avoid the edge case by gating the query on their
|
||||
/// own screen bounds.
|
||||
///
|
||||
/// **Lookup**
|
||||
/// - Row = `y >> 4` (0..14, so fits in a single LSR-by-4).
|
||||
/// - Col = `x >> 4` (0..15).
|
||||
/// - Index = row * 16 + col = `(y & 0xF0) | (x >> 4)`. The `* 16`
|
||||
/// is exactly what `y >> 4 << 4` = `y & 0xF0` computes, so no
|
||||
/// multiply needed — that's why we laid out the bitmap at 16
|
||||
/// columns wide in the first place.
|
||||
/// - Load byte at `room_col_ptr + index`. The byte is 0 (no
|
||||
/// collide) or 1 (collide); we hand it back unchanged.
|
||||
pub fn gen_collides_at() -> Vec<Instruction> {
|
||||
let mut out = Vec::new();
|
||||
out.push(Instruction::new(NOP, AM::Label("__collides_at".into())));
|
||||
// Save x on the stack — we need A for the y-based offset.
|
||||
out.push(Instruction::implied(PHA));
|
||||
// Compute `y & 0xF0`. Y is already in X from the caller's
|
||||
// contract; transfer to A so we can mask it.
|
||||
out.push(Instruction::implied(TXA));
|
||||
out.push(Instruction::new(AND, AM::Immediate(0xF0)));
|
||||
// Stash the masked y (= row * 16) in Y.
|
||||
out.push(Instruction::implied(TAY));
|
||||
// Pull x back, shift right 4 to get the column (0..15).
|
||||
out.push(Instruction::implied(PLA));
|
||||
out.push(Instruction::new(LSR, AM::Accumulator));
|
||||
out.push(Instruction::new(LSR, AM::Accumulator));
|
||||
out.push(Instruction::new(LSR, AM::Accumulator));
|
||||
out.push(Instruction::new(LSR, AM::Accumulator));
|
||||
// Combine col (A) with row*16 (Y), producing the linear
|
||||
// metatile index in A.
|
||||
out.push(Instruction::new(STA, AM::ZeroPage(0x04)));
|
||||
out.push(Instruction::implied(TYA));
|
||||
out.push(Instruction::new(ORA, AM::ZeroPage(0x04)));
|
||||
// Use Y as the index into the room-pointer-relative bitmap.
|
||||
out.push(Instruction::implied(TAY));
|
||||
// Read `(ZP_ROOM_COL_LO),Y` — 0 or 1. The LDA sets Z and N;
|
||||
// the caller's BNE / BEQ / STA picks up whichever it needs.
|
||||
out.push(Instruction::new(LDA, AM::IndirectY(ZP_ROOM_COL_LO)));
|
||||
out.push(Instruction::implied(RTS));
|
||||
out
|
||||
}
|
||||
|
||||
/// Emit the reset-time PRNG state seed. Spliced into the reset
|
||||
/// path whenever `gen_prng` is linked in, so the first `rand8()`
|
||||
/// call returns a useful value even without an explicit
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue