mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
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 from7b4570eby 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.
This commit is contained in:
parent
7b4570eee5
commit
807c9c7318
15 changed files with 699 additions and 27 deletions
|
|
@ -336,27 +336,31 @@ documented as a **design choice** anywhere. Add a paragraph to the
|
|||
language guide explaining why, plus a pointer to the hand-rolled
|
||||
explicit-stack pattern (small `u8[N]` stack + `u8` top).
|
||||
|
||||
### G. VRAM update buffer primitive
|
||||
### G. VRAM update buffer follow-ups
|
||||
|
||||
The highest-leverage missing runtime feature. Today
|
||||
`load_background` / `set_palette` queue PPU writes under the hood,
|
||||
but there is no user-visible "write these N bytes into nametable
|
||||
slot `(x,y)` next vblank" primitive. That's the idiom behind every
|
||||
scoreboard, dialog box, destroyed-metatile animation, and streaming
|
||||
scroll in the nesdoug chapters. Concrete API sketch:
|
||||
`nt_set(x, y, tile)`, `nt_attr(x, y, value)`, and
|
||||
`nt_fill_h(x, y, len, tile)` ship today — see
|
||||
`examples/vram_buffer_demo.ne`. The runtime ring lives at
|
||||
`$0400-$04FF` (gated on the `__vram_buf_used` marker; the analyzer
|
||||
bumps the user-RAM bump pointer to `$0500` when the buffer is in
|
||||
use). Each append lays down `[len][addr_hi][addr_lo][data…]` and
|
||||
writes a fresh `0` sentinel; the NMI drains the buffer at vblank
|
||||
via `LDA $0400,X / STA $2007` indexed-absolute (4 cycles per data
|
||||
byte, no ZP cost).
|
||||
|
||||
```
|
||||
buffer.nametable_write(x, y, [0x20, 0x21, 0x22]) // horizontal
|
||||
buffer.nametable_write_v(x, y, [0x20, 0x21, 0x22]) // vertical
|
||||
buffer.attribute_write(x, y, 0b00011011) // one byte
|
||||
buffer.flush() // force an eof
|
||||
```
|
||||
Still TODO:
|
||||
|
||||
Runtime shape: a fixed ring buffer at a known RAM address
|
||||
(`$0400`?). Each entry is `[header, addr_hi, addr_lo, len, data…]`
|
||||
where `header` carries the `NT_UPD_HORZ` / `NT_UPD_VERT` /
|
||||
`NT_UPD_EOF` bits the neslib engine already uses. The NMI handler
|
||||
drains the buffer every frame and writes `$FF` as the sentinel.
|
||||
- **Vertical (column) writes** — `nt_write_v(x, y, ...)` would set
|
||||
`$2000` bit 2 (auto-increment 32) before the data writes and
|
||||
clear it after. Useful for tilemap-driven scrolling.
|
||||
- **Variable-length writes from a u8 array global** — today
|
||||
`nt_fill_h` repeats one tile; a `nt_copy_h(x, y, src_var, len)`
|
||||
variant that copies from a declared `u8[N]` global removes the
|
||||
fill-only restriction.
|
||||
- **Buffer-overflow detection** — the runtime drain assumes the
|
||||
256-byte buffer never overflows. A debug-mode check that traps
|
||||
when `head` would advance past `$04FF` would catch the worst
|
||||
failure mode (writes wrapping into adjacent RAM).
|
||||
|
||||
### H. Metatiles + collision as a first-class construct
|
||||
|
||||
|
|
@ -559,12 +563,13 @@ to a specific bank to avoid bank-switch cost on a hot path.
|
|||
|
||||
Remaining gap items in order of user value:
|
||||
|
||||
1. VRAM update buffer (§G) — unblocks HUDs, dialog, streaming.
|
||||
2. Register allocator (existing section) — compounding size win.
|
||||
3. Signedness on Cmp16/Cmp ops (§A follow-up) — closes the i16
|
||||
1. Register allocator (existing section) — compounding size win.
|
||||
2. Signedness on Cmp16/Cmp ops (§A follow-up) — closes the i16
|
||||
correctness gap.
|
||||
4. Metatiles + collision (§H) — closes several items at once.
|
||||
5. Inline-asm format specifiers + directive list (§D follow-ups).
|
||||
3. Metatiles + collision (§H) — closes several items at once.
|
||||
4. Inline-asm format specifiers + directive list (§D follow-ups).
|
||||
5. VRAM buffer follow-ups (§G) — vertical writes, array copy,
|
||||
overflow detection.
|
||||
6. Arrays-of-structs + bitfields (§C) + fn pointers (§B) —
|
||||
turns NEScript into a general-purpose NES language.
|
||||
7. UNROM-512 + MMC5 (§V) — ecosystem fit.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue