mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
palette/background: first-class declarations with reset-time load and runtime swaps
Re-adds `palette Name { colors: [...] }` and
`background Name { tiles: [...], attributes: [...] }` as first-class
declarations, plus `set_palette Name` and `load_background Name`
statements for runtime swaps. Unlike the previous iteration that
quietly no-op'd, this one is fully wired through the pipeline and
its behavior is pinned by both unit tests and an emulator golden.
Pipeline:
- Lexer: re-adds `palette`, `background`, `set_palette`,
`load_background` keywords and tokenizes them.
- AST: `PaletteDecl` (name + 1..=32 colour bytes) and `BackgroundDecl`
(name + 0..=960 tile bytes + 0..=64 attribute bytes) live in
`Program`. `Statement::SetPalette` and `Statement::LoadBackground`
name-reference these declarations.
- Parser: `palette Name { colors: [...] }` / `background Name
{ tiles: [...], attributes: [...] }` blocks and their statement
forms parse via the existing byte-array helper.
- Analyzer: validates colour indices ($00-$3F), palette length
(<=32), nametable length (<=960), attribute length (<=64), and
duplicate decl names. `set_palette` / `load_background` targets
must reference a declared name (E0502 otherwise). When a program
declares palette or background, the analyzer bumps the user
zero-page allocator's starting address from `$10` to `$18` to
reserve `$11-$17` for the runtime update handshake — programs
that don't use the feature keep the old layout so their emulator
goldens stay byte-exact.
- Assets: `PaletteData` and `BackgroundData` resolve declarations
into zero-padded fixed-size blobs (32 / 960 / 64 bytes) and
expose `label()` / `tiles_label()` / `attrs_label()` for codegen
to reference.
- IR: new `IrOp::SetPalette(String)` and
`IrOp::LoadBackground(String)`; lowering forwards the names
verbatim.
- Codegen: `gen_set_palette` writes the palette label pointer into
ZP `$12/$13` and ORs bit 0 into the update flags at `$11`;
`gen_load_background` does the same for tile/attribute pointers
at `$14/$15/$16/$17` with bit 1. Both emit a `__ppu_update_used`
marker so the linker splices in the NMI apply helper only when
the feature is actually used.
- Runtime: `gen_initial_palette_load` and
`gen_initial_background_load` write the first declared
palette/background at reset time (before rendering is enabled,
where PPU writes are safe). `gen_nmi(has_ppu_updates)` takes a
new flag; when true it splices `gen_ppu_update_apply` at the top
of the NMI body, which checks the `$11` flags byte and copies
pending palette / nametable data to `$3F00` / `$2000` inside
vblank. All helpers use only ZP $02/$03 as scratch at reset time
and never clobber ZP slots live across NMI.
- Linker: new `link_banked_with_ppu` takes slice of `PaletteData` /
`BackgroundData`; splices each blob as a labelled data block in
PRG ROM, picks the first-declared as the reset-time load target,
enables background rendering automatically when a background is
declared, and threads `has_ppu_updates` into `gen_nmi`. Old
`link_banked` remains as a thin wrapper for callers without
palette/background data so existing tests don't shift.
Tests:
- Lexer: tokenization of the 4 new keywords (single added test case).
- Parser: 5 new tests for `palette` / `background` decls with and
without attributes, plus `set_palette` / `load_background`
statements.
- Analyzer: 9 new tests covering acceptance of declared
palettes/backgrounds, E0502 for unknown names, E0201 for
out-of-range NES colors and oversized blobs, E0501 for duplicate
names, and the zero-page-layout guard (palette/bg decls bump ZP
start; no decls keeps it at $10).
- Resolver: 3 new tests for zero-padding, truncation of oversized
decls, and label derivation.
- IR: 2 new lowering tests for `set_palette` and `load_background`.
- Integration: 5 new tests — blob contents spliced verbatim into
PRG, `STA $12` / `STA $14` emitted by set_palette /
load_background codegen, and a regression guard that programs
without palette/background still land user vars at $10.
- Emulator: new `examples/palette_and_background.ne` driven by a
frame counter that toggles between `CoolBlues` / `WarmReds` and
`TitleScreen` / `StageOne` every 90 frames. Golden PNG and audio
hash checked in under `tests/emulator/goldens/` and verified via
`node run_examples.mjs` — rendered image shows the blue
`CoolBlues` palette with the nametable populated from
`TitleScreen`.
Docs:
- `README.md` adds the feature to the headline list and the example
table.
- `docs/language-guide.md` restores the palette/background sections
with the full 32-byte layout table and `set_palette` /
`load_background` statement references.
- `docs/future-work.md` replaces the "removed as dead code" entry
with the remaining gaps (PNG-sourced palette and nametable
assets, cross-vblank large background updates, memory-map
reporting).
- `spec.md` restores the grammar productions and usage examples.
- `examples/README.md` lists the new demo.
All 497 unit + integration tests pass. Clippy clean. All 21
emulator goldens match after the update pass.
https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
This commit is contained in:
parent
fdb1ec7c91
commit
d98c7f3d82
29 changed files with 1757 additions and 46 deletions
|
|
@ -6,31 +6,29 @@ tested is omitted — `git log` is the authoritative record of what shipped.
|
|||
|
||||
---
|
||||
|
||||
## Runtime palette / nametable updates
|
||||
## PNG-sourced palette and nametable assets
|
||||
|
||||
**Status.** Parsed away. Previously the compiler accepted `palette Name
|
||||
{ colors: [...] }`, `background Name { chr: @chr(...) }`, `load_background
|
||||
Name`, and `set_palette Name` statements, but the lowering silently dropped
|
||||
them: the declarations were never resolved into CHR or palette blobs and the
|
||||
statements emitted zero instructions. They were removed during cleanup to
|
||||
avoid quietly misleading users.
|
||||
**What ships today.** `palette Name { colors: [...] }` and
|
||||
`background Name { tiles: [...], attributes: [...] }` declarations with
|
||||
inline byte arrays. The first declared palette / background is loaded
|
||||
at reset time (before rendering enables), and both blocks get named
|
||||
data blobs in PRG ROM so `set_palette Name` / `load_background Name`
|
||||
can queue a vblank-safe swap via the NMI handler. See
|
||||
`examples/palette_and_background.ne`.
|
||||
|
||||
**What a proper implementation needs.**
|
||||
- New AST declarations for palette and background/nametable data, with
|
||||
analyzer validation for color indices and nametable dimensions.
|
||||
- An asset pipeline pass that compiles PNG or inline byte data into a
|
||||
ROM-resident blob with a known label.
|
||||
- Runtime helpers that write to PPU `$2006/$2007` during vblank from a
|
||||
source pointer.
|
||||
- IR ops `LoadBackground(BlobId)` and `SetPalette(BlobId)` that the
|
||||
codegen emits inside an NMI-safe window.
|
||||
- A `--memory-map` breakdown that shows palette and nametable budgets,
|
||||
since they eat into the PRG-ROM cost model that `memory_map` reports
|
||||
for CHR bytes.
|
||||
|
||||
Until this exists, programs should use `poke(0x2006, ...)` / `poke(0x2007, ...)`
|
||||
directly inside an `on frame` handler (runs immediately after vblank) to push
|
||||
palette or nametable updates.
|
||||
**Still TODO.**
|
||||
- `@palette("file.png")` — analyze image colours and map to nearest
|
||||
NES master-palette indices. `nearest_nes_color()` already lives in
|
||||
`src/assets/palette.rs` but is not wired through the resolver.
|
||||
- `@nametable("file.png")` — convert a 256×240 image into a 960-byte
|
||||
nametable plus 64-byte attribute table with automatic tile
|
||||
deduplication (max 256 unique tiles per pattern table).
|
||||
- Per-state background rendering control — programs currently get a
|
||||
single fixed nametable at reset; per-state swaps work but are
|
||||
limited by the NMI-time write budget (~2273 cycles, enough for a
|
||||
palette but not a full 1024-byte nametable).
|
||||
- `--memory-map` should report palette and background PRG ROM usage
|
||||
alongside the variable layout.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -641,6 +641,27 @@ Set the PPU scroll position:
|
|||
scroll(scroll_x, scroll_y)
|
||||
```
|
||||
|
||||
### Set Palette
|
||||
|
||||
```
|
||||
set_palette NightPalette
|
||||
```
|
||||
|
||||
Queues the named palette for a vblank-safe copy into PPU palette
|
||||
RAM (`$3F00-$3F1F`). The write is applied by the NMI handler on the
|
||||
next vblank. See `palette` declarations below.
|
||||
|
||||
### Load Background
|
||||
|
||||
```
|
||||
load_background Level1
|
||||
```
|
||||
|
||||
Queues the named background (a full-screen 32×30 nametable + 64-byte
|
||||
attribute table) for a vblank-safe copy into nametable 0
|
||||
(`$2000-$23FF`). Applied by the NMI handler at the next vblank. See
|
||||
`background` declarations below.
|
||||
|
||||
### Function Calls as Statements
|
||||
|
||||
```
|
||||
|
|
@ -664,6 +685,72 @@ sprite Coin {
|
|||
}
|
||||
```
|
||||
|
||||
### Palette Declarations
|
||||
|
||||
```
|
||||
palette MainPalette {
|
||||
colors: [0x0F, 0x01, 0x11, 0x21,
|
||||
0x0F, 0x06, 0x16, 0x26,
|
||||
0x0F, 0x09, 0x19, 0x29,
|
||||
0x0F, 0x0B, 0x1B, 0x2B,
|
||||
0x0F, 0x01, 0x11, 0x21,
|
||||
0x0F, 0x16, 0x27, 0x30,
|
||||
0x0F, 0x14, 0x24, 0x34,
|
||||
0x0F, 0x0B, 0x1B, 0x2B]
|
||||
}
|
||||
```
|
||||
|
||||
Each byte is an NES master-palette index (`$00-$3F`). The 32 bytes
|
||||
map directly to PPU palette RAM `$3F00-$3F1F` in the canonical NES
|
||||
layout:
|
||||
|
||||
| Offset | Contents |
|
||||
|-----------------|-------------------------------------|
|
||||
| `$00`-`$03` | Background sub-palette 0 (4 colours) |
|
||||
| `$04`-`$07` | Background sub-palette 1 |
|
||||
| `$08`-`$0B` | Background sub-palette 2 |
|
||||
| `$0C`-`$0F` | Background sub-palette 3 |
|
||||
| `$10`-`$13` | Sprite sub-palette 0 |
|
||||
| `$14`-`$17` | Sprite sub-palette 1 |
|
||||
| `$18`-`$1B` | Sprite sub-palette 2 |
|
||||
| `$1C`-`$1F` | Sprite sub-palette 3 |
|
||||
|
||||
The first byte of each sub-palette is typically `$0F` (black); it
|
||||
serves as the shared background colour. Lists shorter than 32 bytes
|
||||
are zero-padded; lists longer than 32 are a compile error.
|
||||
|
||||
The *first* `palette` declared in a program is loaded into VRAM at
|
||||
reset time, before rendering is enabled, so the title screen boots
|
||||
with the right colours on frame 0. Additional declarations sit in
|
||||
PRG ROM as named data blobs and become active via `set_palette Name`,
|
||||
which queues the write for the next vblank.
|
||||
|
||||
### Background Declarations
|
||||
|
||||
```
|
||||
background TitleScreen {
|
||||
tiles: [0x00, 0x01, 0x01, 0x00, /* ... up to 960 bytes ... */]
|
||||
attributes: [0xFF, 0x55, /* ... up to 64 bytes ... */]
|
||||
}
|
||||
```
|
||||
|
||||
A background is a 32×30 nametable. `tiles` is the nametable itself
|
||||
— 960 bytes of CHR tile indices, one per 8×8 cell, in row-major
|
||||
order (left-to-right, top-to-bottom). `attributes` is the 8×8
|
||||
attribute table (64 bytes) that controls which sub-palette each
|
||||
16×16 metatile uses.
|
||||
|
||||
Both lists are zero-padded up to their fixed sizes; `attributes`
|
||||
may be omitted entirely, in which case every cell uses background
|
||||
sub-palette 0.
|
||||
|
||||
The *first* `background` declared is loaded into nametable 0 at
|
||||
reset time and background rendering is enabled automatically.
|
||||
Additional backgrounds can be swapped in via `load_background Name`,
|
||||
which queues the update for the next vblank. Full-nametable updates
|
||||
do not fit inside a single vblank, so large background swaps may
|
||||
require the program to disable rendering temporarily.
|
||||
|
||||
### Asset Sources
|
||||
|
||||
Three ways to provide asset data:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue