mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
docs + example: HUD demo and language-guide VRAM buffer section
Follow-up to 807c9c7 (the VRAM update buffer core). Adds the
realistic-HUD example the core was missing, plus a language-guide
section that explains when and how to use the three buffer
intrinsics.
**examples/hud_demo.ne**
A bouncing-ball playfield with a classic status bar across the
top:
- 5-cell lives indicator that ticks down once per second and
resets at zero, drawn via `nt_fill_h` (plus a second
`nt_fill_h` to erase the stale tail).
- Score counter at the right edge that bumps on every wall
bounce, drawn via `nt_set`.
- One-shot `nt_attr` call on the first frame flipping the
top-left metatile group to sub-palette 1 (the red HUD
palette) so the UI chrome reads as distinct from the
playfield.
The demo's point is the `last_score != score` / `last_lives !=
lives` shadow-compare pattern: on the ~58-of-60 frames where
nothing changed, the buffer stays empty and drain work is zero.
That's the whole reason the VRAM buffer exists — per-frame cost
scales with what moved, not with HUD complexity. Committed
`.nes` + pixel/audio goldens.
**docs/language-guide.md**
New "VRAM Update Buffer" section between "Hardware Intrinsics"
and "Inline Assembly". Covers:
- Why user code can't just poke `$2006` / `$2007` directly.
- The three intrinsics + their coordinate systems (cell, not
pixel).
- The HUD pattern with a ready-to-paste code snippet and a
pointer at `examples/hud_demo.ne`.
- A per-entry budget table + worked 1000-cycle drain example
against the ~2273-cycle vblank budget.
- Known limits: horizontal-only, no overflow check,
no coalescing — all already tracked under `future-work.md` §G.
**examples/README.md**
`vram_buffer_demo.ne` reframed as the minimal test-case exercise
it actually is, with a pointer at `hud_demo.ne` for the realistic
pattern. New table row for `hud_demo.ne`.
All 758 tests pass. Clippy clean. 48/48 emulator goldens match.
This commit is contained in:
parent
807c9c7318
commit
854b61ea1e
6 changed files with 242 additions and 1 deletions
|
|
@ -1325,6 +1325,93 @@ The address argument to both is a compile-time constant. Zero-page
|
|||
addresses compile to `STA $XX` / `LDA $XX`; anything larger compiles
|
||||
to absolute addressing.
|
||||
|
||||
## VRAM Update Buffer
|
||||
|
||||
The PPU's `$2006` / `$2007` registers can only be written safely
|
||||
during vblank — writing during active rendering corrupts the
|
||||
internal address register and garbles every subsequent tile.
|
||||
`on frame` handlers run partly during vblank and partly during
|
||||
rendering, so user code can't directly `poke` the PPU.
|
||||
|
||||
The VRAM update buffer solves this: user intrinsics **queue** PPU
|
||||
writes to a 256-byte ring at `$0400-$04FF` during `on frame`, and
|
||||
the NMI handler **drains** the ring to `$2007` during vblank. User
|
||||
code never touches `$2006` or `$2007` directly.
|
||||
|
||||
Three intrinsics cover the common patterns:
|
||||
|
||||
```
|
||||
nt_set(x, y, tile) // one tile at nametable cell (x, y)
|
||||
nt_attr(x, y, value) // one attribute byte covering the
|
||||
// 4×4-cell metatile at (x, y)
|
||||
nt_fill_h(x, y, len, tile) // horizontal run of `len` copies
|
||||
// of `tile` starting at (x, y)
|
||||
```
|
||||
|
||||
`x` and `y` are nametable cell coordinates (`0..32`, `0..30`) — not
|
||||
pixel coordinates. The compiler computes the PPU address
|
||||
(`$2000 + y*32 + x` for nametable, `$23C0 + (y/4)*8 + (x/4)` for
|
||||
attribute) and emits the buffer-append inline at each call site.
|
||||
|
||||
### HUD pattern
|
||||
|
||||
Queue an update only when the underlying state changes. That
|
||||
makes per-frame cost scale with what actually moved, not with HUD
|
||||
complexity:
|
||||
|
||||
```
|
||||
var score: u8 = 0
|
||||
var last_score: u8 = 255 // 255 forces the first-frame paint
|
||||
|
||||
on frame {
|
||||
// ... gameplay that may or may not bump `score` ...
|
||||
|
||||
if score != last_score {
|
||||
last_score = score
|
||||
var digit: u8 = score & 0x0F
|
||||
nt_set(28, 1, digit) // one 4-byte buffer entry
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A typical HUD touches two or three cells per change, so the 256-
|
||||
byte buffer is more than enough for any realistic frame. See
|
||||
`examples/hud_demo.ne` for a worked example with a bouncing-ball
|
||||
playfield, a score cell that updates on each wall hit, a 5-cell
|
||||
lives indicator drawn via `nt_fill_h`, and a one-shot `nt_attr`
|
||||
call at startup that paints the HUD row in a distinct palette.
|
||||
|
||||
### Budget
|
||||
|
||||
Per-entry buffer cost:
|
||||
|
||||
| Intrinsic | Buffer bytes | Drain cycles |
|
||||
|----------------|--------------------|----------------------|
|
||||
| `nt_set` | 4 | ~20 |
|
||||
| `nt_attr` | 4 | ~20 |
|
||||
| `nt_fill_h` | `3 + len` | `~12 + 8*len` |
|
||||
|
||||
The 256-byte buffer holds ~50 single-tile writes that drain in
|
||||
~1000 cycles, well inside vblank's ~2273-cycle budget. Programs
|
||||
that don't call any of the three intrinsics pay zero bytes and
|
||||
zero cycles — the drain routine isn't linked, the NMI doesn't
|
||||
JSR it, and the 256-byte buffer region stays available for user
|
||||
variables.
|
||||
|
||||
### Limits
|
||||
|
||||
- Only **horizontal** writes (PPU auto-increment 1). Vertical
|
||||
writes (column-fill) would need to toggle `$2000` bit 2; that's
|
||||
a known follow-up documented in `docs/future-work.md` §G.
|
||||
- `nt_fill_h` takes a runtime `len`. If `len` overflows the
|
||||
remaining space in the buffer (head + 3 + len > 256) the writer
|
||||
scribbles into neighbouring RAM. The runtime does not bounds-
|
||||
check; a debug-mode overflow trap is a known follow-up.
|
||||
- The buffer does not coalesce adjacent writes. Calling
|
||||
`nt_set(0, 0, 1)` then `nt_set(1, 0, 2)` emits two separate
|
||||
entries (8 buffer bytes) even though a single `len=2` entry
|
||||
would fit both.
|
||||
|
||||
## Inline Assembly
|
||||
|
||||
For more elaborate sequences, use `asm { ... }` blocks:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue