1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 00:45:38 +00:00
nescript/examples/hud_demo.ne

334 lines
8.6 KiB
Text
Raw Normal View History

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.
2026-04-18 21:34:44 +00:00
// HUD demo — the VRAM update buffer driving a classic status-bar
// layout above a scrolling playfield.
//
// The playfield shows a ball bouncing back and forth; every wall
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
// hit bumps a score counter that the HUD renders at the right of
// the status row. A "lives" indicator on the left ticks down
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.
2026-04-18 21:34:44 +00:00
// periodically and resets, demonstrating both `nt_set` (for
// single-cell updates when state changes) and `nt_fill_h` (for
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
// repeatedly painting a multi-cell indicator). `nt_attr` calls at
// startup paint the top row in a distinct palette so the HUD
// reads as "UI chrome" instead of "gameplay background".
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.
2026-04-18 21:34:44 +00:00
//
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
// User code never touches `$2006` / `$2007` directly — it just
// appends records to the 256-byte ring at `$0400-$04FF` and the
// NMI handler drains them during vblank. Only cells whose backing
// state changed get a buffer entry: the demo tracks `last_score`
// and `last_lives` so the common "state didn't change this frame"
// path appends nothing.
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.
2026-04-18 21:34:44 +00:00
//
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
// The visible NES output at native 256×240 is intentionally
// readable rather than flashy — see the table-of-tiles layout
// rather than a polished AAA HUD. Run the ROM in any emulator to
// watch the lives countdown and score tick over time.
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.
2026-04-18 21:34:44 +00:00
game "HUD Demo" {
mapper: NROM
mirroring: horizontal
}
palette GameColors {
universal: black
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
bg0: [dk_blue, blue, sky_blue] // playfield tiles
bg1: [red, white, yellow] // HUD palette
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.
2026-04-18 21:34:44 +00:00
bg2: [dk_green, green, lt_green]
bg3: [dk_gray, lt_gray, white]
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
sp0: [black, yellow, white] // ball sprite (yellow)
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.
2026-04-18 21:34:44 +00:00
sp1: [red, orange, white]
sp2: [dk_teal, teal, lt_teal]
sp3: [dk_olive, olive, yellow]
}
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
// ── HUD tile art. The compiler allocates sprite tiles in
// declaration order starting at tile 1 (tile 0 is the built-in
// smiley used by the playfield). The constants below match
// that order so the call sites can use names instead of magic
// numbers.
//
// `#` = palette index 1; with bg1 = [red, white, yellow] that's
// red. `%` = index 2 = white. `@` = index 3 = yellow.
// Solid fill (everything index 1 = red) — the "HUD chrome"
// background. Pre-painting row 1 with this tile gives the HUD
// strip a uniform red look so individual cell writes (hearts,
// digits) show up as obviously different content.
sprite Bar {
pixels: [
"########",
"########",
"########",
"########",
"########",
"########",
"########",
"########"
]
}
// White heart-on-red — uses index 2 (white) for the heart shape,
// index 1 (red) for the surrounding fill. Stands out crisply
// against the all-red Bar tiles.
sprite Heart {
pixels: [
"########",
"#%%##%%#",
"%%%%%%%%",
"%%%%%%%%",
"#%%%%%%#",
"##%%%%##",
"###%%###",
"########"
]
}
// Yellow digit glyphs — all use index 3 (yellow) for the strokes
// and index 1 (red) for the surrounding fill. Big enough that the
// digit is readable even at NES resolution.
sprite Digit0 {
pixels: [
"########",
"##@@@@##",
"#@####@#",
"#@####@#",
"#@####@#",
"#@####@#",
"##@@@@##",
"########"
]
}
sprite Digit1 {
pixels: [
"########",
"###@@###",
"##@@@###",
"###@@###",
"###@@###",
"###@@###",
"##@@@@##",
"########"
]
}
sprite Digit2 {
pixels: [
"########",
"##@@@@##",
"#@####@#",
"####@@##",
"##@@####",
"#@######",
"#@@@@@@#",
"########"
]
}
sprite Digit3 {
pixels: [
"########",
"##@@@@##",
"#@####@#",
"####@@##",
"######@#",
"#@####@#",
"##@@@@##",
"########"
]
}
sprite Digit4 {
pixels: [
"########",
"####@@##",
"###@@@##",
"##@##@##",
"#@@@@@@#",
"####@@##",
"####@@##",
"########"
]
}
sprite Digit5 {
pixels: [
"########",
"#@@@@@@#",
"#@######",
"#@@@@@##",
"######@#",
"#@####@#",
"##@@@@##",
"########"
]
}
sprite Digit6 {
pixels: [
"########",
"##@@@@##",
"#@####@#",
"#@@@@@##",
"#@####@#",
"#@####@#",
"##@@@@##",
"########"
]
}
sprite Digit7 {
pixels: [
"########",
"#@@@@@@#",
"######@#",
"#####@##",
"####@###",
"###@####",
"###@####",
"########"
]
}
sprite Digit8 {
pixels: [
"########",
"##@@@@##",
"#@####@#",
"##@@@@##",
"#@####@#",
"#@####@#",
"##@@@@##",
"########"
]
}
sprite Digit9 {
pixels: [
"########",
"##@@@@##",
"#@####@#",
"##@@@@@#",
"######@#",
"#@####@#",
"##@@@@##",
"########"
]
}
// Yellow ball for the playfield, uses sp0 sub-palette
// (index 1 = yellow against universal black).
sprite Ball {
pixels: [
"..####..",
".######.",
"########",
"########",
"########",
"########",
".######.",
"..####.."
]
}
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.
2026-04-18 21:34:44 +00:00
background Playfield {
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
// Pre-paint row 1 (where the HUD lives) with the solid Bar
// tile so single-cell writes (hearts and digits) stand out
// crisply against the uniform red background. Row 0 keeps the
// default smiley so the attribute-painted strip has visible
// chrome above the HUD content. Rows 2+ are zero-padded by
// the parser to fill the rest of the nametable with smileys
// — that's the playfield.
legend {
".": 0 // smiley (default)
"B": 1 // Bar — declared first sprite, lands at tile 1
}
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.
2026-04-18 21:34:44 +00:00
map: [
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
"................................", // row 0: smiley chrome
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" // row 1: red Bar canvas for HUD
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.
2026-04-18 21:34:44 +00:00
]
}
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
// Tile-index constants matching the sprite-declaration order.
const BAR_TILE: u8 = 1
const HEART_TILE: u8 = 2
const DIGIT_BASE: u8 = 3 // Digit N is at tile DIGIT_BASE + N
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.
2026-04-18 21:34:44 +00:00
// Ball position + velocity.
var bx: u8 = 64
var by: u8 = 100
var dx: u8 = 1 // 1 = moving right, 0 = moving left
var dy: u8 = 1 // 1 = moving down, 0 = moving up
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
// HUD state.
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.
2026-04-18 21:34:44 +00:00
var score: u8 = 0
var lives: u8 = 5
var last_score: u8 = 255 // 255 forces an initial paint on frame 0
var last_lives: u8 = 255
var life_tick: u8 = 0
var attr_set: u8 = 0 // 1 once the HUD attribute has been painted
on frame {
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
// ── One-shot: paint all 8 top-row attribute bytes so the
// entire top four rows pick up sub-palette 1 (red HUD
// palette). `0b01010101` means all four 16×16 sub-
// quadrants of each metatile use sub-palette 1.
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.
2026-04-18 21:34:44 +00:00
if attr_set == 0 {
attr_set = 1
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
nt_attr(0, 0, 0b01010101)
nt_attr(4, 0, 0b01010101)
nt_attr(8, 0, 0b01010101)
nt_attr(12, 0, 0b01010101)
nt_attr(16, 0, 0b01010101)
nt_attr(20, 0, 0b01010101)
nt_attr(24, 0, 0b01010101)
nt_attr(28, 0, 0b01010101)
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.
2026-04-18 21:34:44 +00:00
}
// ── Playfield: bounce the ball, count bounces as score. ──
if dx == 1 {
bx += 1
if bx >= 240 {
dx = 0
score += 1
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
if score >= 10 { score = 0 }
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.
2026-04-18 21:34:44 +00:00
}
} else {
bx -= 1
if bx == 16 {
dx = 1
score += 1
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
if score >= 10 { score = 0 }
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.
2026-04-18 21:34:44 +00:00
}
}
if dy == 1 {
by += 1
if by >= 200 { dy = 0 }
} else {
by -= 1
if by == 32 { dy = 1 }
}
draw Ball at: (bx, by)
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
// ── HUD: tick lives once per second; reset to 5 at zero. ──
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.
2026-04-18 21:34:44 +00:00
life_tick += 1
if life_tick >= 60 {
life_tick = 0
if lives == 0 {
lives = 5
} else {
lives -= 1
}
}
// ── HUD: rewrite only the cells whose backing state changed.
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
// `nt_set` for the score digit, `nt_fill_h` for the
// lives bar (plus a second fill to erase the stale
// tail with the Bar tile so the display "shrinks"
// cleanly). The shadow-compare skips the write on the
// ~58 of 60 frames where nothing changed.
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.
2026-04-18 21:34:44 +00:00
if score != last_score {
last_score = score
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
nt_set(28, 1, DIGIT_BASE + score)
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.
2026-04-18 21:34:44 +00:00
}
if lives != last_lives {
last_lives = lives
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
nt_fill_h(2, 1, lives, HEART_TILE)
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.
2026-04-18 21:34:44 +00:00
if lives < 5 {
var blanks: u8 = 5 - lives
hud_demo: declare proper digit + heart CHR so the HUD is readable The previous version of hud_demo passed `score & 0x0F` and tile index `1` (= Heart) to nt_set / nt_fill_h, but the demo had no Heart sprite declared and tile 1 in CHR was uninitialized garbage. The result was a screen of blue smileys with a tiny red strip in the corner — the buffer mechanism worked, but the visual gave no sense that anything HUD-shaped was happening. This commit makes the HUD actually look like a HUD: - 12 sprite declarations (Bar, Heart, Digit0..9, Ball) that the compiler lays into CHR at known tile indices in declaration order. Tile-index constants (`BAR_TILE`, `HEART_TILE`, `DIGIT_BASE`) match that order so the call sites can use names instead of magic numbers. - bg1 palette restructured to `[red, white, yellow]` so pixel-art characters resolve to visible colours: `#` = red (background fill), `%` = white (heart shape), `@` = yellow (digit strokes). - Background pre-paints row 1 with the solid `Bar` (red) tile via a `legend { "B": 1 }` entry, giving the HUD a uniform red canvas for individual cell writes to land on. - Eight `nt_attr` calls at startup paint the entire top metatile row (4 rows × 32 cols) with sub-palette 1 so the HUD chrome reads as visually distinct from the playfield. The result at frame 180 is unmistakably HUD-shaped: a yellow-on- red status bar at the top of the screen above blue playfield with a yellow ball bouncing around. Per-frame cost still scales with what changed — `last_score` / `last_lives` shadow-compares mean the buffer stays empty on the ~58 of 60 frames where nothing ticks. Tests: 758 pass. Clippy clean. 48/48 emulator goldens match.
2026-04-18 22:12:43 +00:00
nt_fill_h(2 + lives, 1, blanks, BAR_TILE)
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.
2026-04-18 21:34:44 +00:00
}
}
}
start Main