mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 00:45: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
44
spec.md
44
spec.md
|
|
@ -63,8 +63,9 @@ if else while for in match
|
|||
break continue return
|
||||
true false not and or
|
||||
fast slow include start transition
|
||||
sprite sfx music
|
||||
sprite background palette sfx music
|
||||
draw play stop_music start_music
|
||||
load_background set_palette
|
||||
asm raw bank
|
||||
loop wait_frame
|
||||
u8 i8 u16 bool
|
||||
|
|
@ -498,7 +499,24 @@ The compiler auto-generates the controller read routine and previous-frame track
|
|||
|
||||
### 10.1 Palettes
|
||||
|
||||
The NES has 4 background palettes and 4 sprite palettes, each containing 4 colors selected from the NES's 64-color master palette. First-class `palette` declarations and `set_palette` / `load_background` statements are not yet in the language — see `docs/future-work.md`. Until then, push palette and nametable bytes directly via `poke(0x2006, ...)` / `poke(0x2007, ...)` inside an `on frame` handler (immediately after vblank).
|
||||
The NES has 4 background palettes and 4 sprite palettes, each containing 4 colors selected from the NES's 64-color master palette. A `palette` declaration provides all 32 bytes at once:
|
||||
|
||||
```
|
||||
palette my_palette {
|
||||
colors: [0x0F, 0x00, 0x10, 0x30, // bg sub-palette 0
|
||||
0x0F, 0x07, 0x17, 0x27, // bg sub-palette 1
|
||||
0x0F, 0x09, 0x19, 0x29, // bg sub-palette 2
|
||||
0x0F, 0x01, 0x11, 0x21, // bg sub-palette 3
|
||||
0x0F, 0x00, 0x10, 0x30, // sprite sub-palette 0
|
||||
0x0F, 0x14, 0x24, 0x34, // sprite sub-palette 1
|
||||
0x0F, 0x1A, 0x2A, 0x3A, // sprite sub-palette 2
|
||||
0x0F, 0x12, 0x22, 0x32] // sprite sub-palette 3
|
||||
}
|
||||
|
||||
set_palette my_palette // queued for next vblank
|
||||
```
|
||||
|
||||
The first declared palette is loaded at reset time, before rendering is enabled. Subsequent declarations live in PRG ROM as named blobs and become active via `set_palette`, which queues the write for the next vblank via the NMI handler.
|
||||
|
||||
### 10.2 Sprites
|
||||
|
||||
|
|
@ -525,7 +543,16 @@ The `draw` statement writes to the OAM shadow buffer. The compiler manages OAM s
|
|||
|
||||
### 10.3 Backgrounds
|
||||
|
||||
First-class `background` declarations and `load_background` are not yet in the language. See `docs/future-work.md` for the roadmap; for now, use `poke` inside a frame handler to push nametable bytes directly to PPU `$2006/$2007`.
|
||||
```
|
||||
background TitleScreen {
|
||||
tiles: [0x00, 0x01, 0x02, /* ... up to 960 bytes ... */]
|
||||
attributes: [0xFF, 0x55, /* ... up to 64 bytes ... */]
|
||||
}
|
||||
|
||||
load_background TitleScreen // queued for next vblank
|
||||
```
|
||||
|
||||
`tiles` is a 32×30 nametable (up to 960 bytes, in row-major order); `attributes` is the 8×8 attribute table (up to 64 bytes). Shorter lists are zero-padded; omitted `attributes` default to all-zero. The first declared background is loaded at reset time and background rendering is enabled automatically.
|
||||
|
||||
### 10.4 Scrolling
|
||||
|
||||
|
|
@ -847,7 +874,8 @@ game_prop = IDENT ":" ( IDENT | INTEGER ) ;
|
|||
include = "include" STRING ;
|
||||
|
||||
top_level_decl = var_decl | const_decl | fun_decl | state_decl
|
||||
| sprite_decl | sfx_decl | music_decl | bank_decl ;
|
||||
| sprite_decl | palette_decl | background_decl
|
||||
| sfx_decl | music_decl | bank_decl ;
|
||||
|
||||
var_decl = ["fast" | "slow"] "var" IDENT ":" type ["=" expr] ;
|
||||
const_decl = "const" IDENT ":" type "=" expr ;
|
||||
|
|
@ -866,6 +894,10 @@ event_kind = "enter" | "exit" | "frame" | "scanline" "(" INTEGER ")" ;
|
|||
sprite_decl = "sprite" IDENT "{" { sprite_prop } "}" ;
|
||||
sprite_prop = IDENT ":" expr ;
|
||||
|
||||
palette_decl = "palette" IDENT "{" "colors" ":" "[" int_list "]" "}" ;
|
||||
background_decl = "background" IDENT "{" bg_prop { bg_prop } "}" ;
|
||||
bg_prop = ("tiles" | "attributes") ":" "[" int_list "]" ;
|
||||
|
||||
sfx_decl = "sfx" IDENT "{" sfx_prop { sfx_prop } "}" ;
|
||||
sfx_prop = ("duty" ":" INTEGER | "pitch" ":" "[" int_list "]" | "volume" ":" "[" int_list "]") ;
|
||||
music_decl = "music" IDENT "{" music_prop { music_prop } "}" ;
|
||||
|
|
@ -881,7 +913,7 @@ statement = var_decl | const_decl | assign_stmt | if_stmt | while_stmt
|
|||
| for_stmt | loop_stmt | return_stmt | break_stmt | continue_stmt
|
||||
| draw_stmt | play_stmt | transition_stmt | fun_call
|
||||
| asm_block | debug_stmt | music_stmt | scroll_stmt
|
||||
| wait_frame_stmt ;
|
||||
| load_bg_stmt | set_palette_stmt | wait_frame_stmt ;
|
||||
|
||||
if_stmt = "if" expr block { "else" "if" expr block } [ "else" block ] ;
|
||||
while_stmt = "while" expr block ;
|
||||
|
|
@ -898,6 +930,8 @@ play_stmt = "play" IDENT ;
|
|||
music_stmt = ("start_music" | "stop_music") [IDENT] ;
|
||||
transition_stmt = "transition" IDENT ;
|
||||
scroll_stmt = "scroll" "(" expr "," expr ")" ;
|
||||
load_bg_stmt = "load_background" IDENT ;
|
||||
set_palette_stmt= "set_palette" IDENT ;
|
||||
wait_frame_stmt = "wait_frame" "(" ")" ;
|
||||
|
||||
debug_stmt = "debug" "." ( "log" | "assert" ) "(" expr { "," expr } ")" ;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue