1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00
nescript/examples/vram_buffer_demo.ne
Claude 807c9c7318
compiler: VRAM update buffer (nt_set / nt_attr / nt_fill_h)
Closes the highest-priority remaining catalogue item (§G). User
code queues PPU writes during `on frame` via three new intrinsics;
the NMI drains the 256-byte ring at `$0400-$04FF` to `$2007`
during vblank. Programs that never touch the buffer pay zero
bytes and zero cycles for the feature — verified by the existing
46 ROMs all matching their goldens with no drift.

Also fixes the failing CI Format check from 7b4570e by running
cargo fmt across the working tree.

**Runtime:**
- New `runtime::gen_vram_buf_drain` emits the drain routine
  (`__vram_buf_drain`). Walks entries `[len][addr_hi][addr_lo]
  [byte_0]...[byte_(len-1)]` and stops at `len == 0`. Uses
  `LDA $0400,X` indexed-absolute so no ZP scratch is needed.
  Drain costs ~12 setup cycles + 8 cycles per data byte; the
  256-byte buffer can hold ~50 single-tile writes that drain
  in roughly 1000 cycles, well inside the ~2273-cycle vblank.
- `NmiOptions` gains `has_vram_buf`. The NMI JSRs the drain
  after the existing palette/background handshake (compiler-
  queued PPU writes win priority for vblank cycles).

**IR + codegen:**
- Three new ops `IrOp::NtSet`, `IrOp::NtAttr`, `IrOp::NtFillH`.
- The codegen helpers compute the PPU address inline:
  `$2000 + y*32 + x` for nametable, `$23C0 + (y/4)*8 + (x/4)`
  for attribute. Each append lays down a fresh `0` sentinel so
  the NMI sees a well-formed buffer regardless of whether more
  entries get appended later in the frame.
- `__vram_buf_used` marker drops on first use; gates the
  runtime splice + NMI JSR.

**Analyzer:**
- AST-walking helper `program_uses_vram_buf` detects intrinsic
  use at analyze-init time so the user-RAM bump pointer can
  start at `$0500` (past the buffer) rather than the legacy
  `$0300`. Programs that don't use the buffer keep the legacy
  start.
- Three intrinsic names registered in `is_intrinsic` /
  `is_void_intrinsic` with arity checks.

**Tests + example:**
- `examples/vram_buffer_demo.ne` exercises all three intrinsics
  on a backgrounded program — three single-tile score writes,
  a 16-tile horizontal fill, and an attribute write that flips
  the top-left metatile group's palette to red. Committed
  golden + audio hash.
- Four new integration tests: byte-level JSR-to-drain
  assertion, drain-omitted-when-unused, RAM-bump assertion for
  programs that DO use the buffer, and arity enforcement for
  `nt_set`.

**CI fix:**
- `cargo fmt` ran across the tree. Picks up a one-line fmt
  diff in `tests/integration_test.rs` that the prior commit
  shipped without running fmt, causing the Format CI job to
  fail on `7b4570e`.

All 758 tests pass. Clippy clean. 47/47 emulator goldens match.
2026-04-18 21:14:31 +00:00

57 lines
1.9 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// VRAM update buffer demo — `nt_set`, `nt_attr`, and `nt_fill_h`
// append entries to a 256-byte ring at `$0400-$04FF` during
// `on frame`; the NMI handler drains them to PPU `$2007` during
// vblank. This is the idiom every nesdoug HUD / dialog box / score
// counter is built on — the user code never touches `$2006` or
// `$2007` directly, just appends record after record.
//
// This demo paints a "scoreboard" of three tiles in the top row
// each frame, then fills a 16-tile horizontal stripe a few rows
// down with a single tile pattern. Frame 180 captures the scene
// after the buffer has drained the same set of writes for ~3
// seconds — the visible output is stable.
game "VRAM Buffer Demo" {
mapper: NROM
mirroring: horizontal
}
palette Default {
universal: black
bg0: [dk_blue, blue, sky_blue]
bg1: [dk_red, red, lt_red]
bg2: [dk_green, green, lt_green]
bg3: [black, lt_gray, white]
sp0: [dk_blue, blue, sky_blue]
sp1: [red, orange, white]
sp2: [dk_teal, teal, lt_teal]
sp3: [dk_olive, olive, yellow]
}
background Empty {
legend { ".": 0 }
map: [
"................................"
]
}
on frame {
// Three single-tile writes for a tiny "score" digit row.
// Each call appends a 4-byte buffer entry: [len=1][hi][lo][tile].
nt_set(2, 1, 1)
nt_set(3, 1, 2)
nt_set(4, 1, 3)
// A horizontal fill: 16 copies of the smiley starting at (8, 4).
// Buffer entry is [len=16][hi][lo][tile × 16] = 19 bytes.
nt_fill_h(8, 4, 16, 0)
// Update the attribute byte for the metatile that contains the
// score row so it picks up sub-palette 1 (red gradient) for
// visual contrast against the rest of the screen. (x, y) here
// are nametable cell coordinates; the codegen translates to
// the attribute table address $23C0+.
nt_attr(0, 0, 0b01010101)
}
start Main