1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-09 09:18:01 +00:00
nescript/docs/future-work.md
Claude d98c7f3d82
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
2026-04-13 11:11:33 +00:00

178 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Future Work
This document tracks the gaps between what NEScript currently compiles and
what the spec describes. Items are grouped by area. Anything implemented and
tested is omitted — `git log` is the authoritative record of what shipped.
---
## PNG-sourced palette and nametable assets
**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`.
**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.
---
## User code distribution across switchable banks
**Status.** `mapper: MMC1 / UxROM / MMC3` plus top-level `bank Name { prg }`
declarations are honored by the iNES header and by the linker, which reserves
each declared bank as a 16 KB switchable slot. However, the IR codegen puts
every user function and state handler into the fixed bank at `$C000-$FFFF`
the declared banks exist only as empty space. Programs outgrowing the fixed
16 KB have nowhere to put their code.
**What's needed.**
- A bank-assignment step (analyzer or a new pass) that maps each user function
/ state handler to a target bank, either via explicit `bank Foo { fun bar()
... }` nesting or by greedy size-packing.
- Codegen support for emitting into non-fixed banks and for generating
cross-bank trampolines (the runtime helper scaffold already exists in
`runtime/mod.rs::gen_bank_trampoline`; it just isn't invoked).
- Linker changes so that functions in a switchable bank are found by the
JSR fix-up logic when the call crosses bank boundaries.
---
## Language feature gaps (post-v0.1)
From the spec's "Reserved for Future Versions" section:
| Feature | Description |
|--------------------|-----------------------------------------------------------------------|
| **Fixed-point** | `fixed8.8` type for sub-pixel movement with operator support. |
| **Text / HUD** | Font sheet declarations + layout system for scores, health, menus. |
| **Metasprites** | Multi-tile sprite groups with relative positioning. |
| **Tilemaps** | Declarative level data with built-in collision queries. |
| **SRAM / saves** | Persistent storage declarations for battery-backed save data. |
| **NES 2.0 header** | Extended iNES header format for additional metadata. |
### Struct / array field widths
Struct and array element types are currently restricted to the single-byte
primitives (`u8`, `i8`, `bool`). `u16`, nested struct fields, and array fields
are rejected with `E0201`. The analyzer's field-layout machinery needs to
grow multi-byte offsets, and IR lowering needs to treat wide fields as the
existing wide-var path already does for `u16` globals.
---
## Audio pipeline
**What ships today.** Frame-walking pulse driver with `sfx Name { duty, pitch,
volume }` and `music Name { duty, volume, repeat, notes }` blocks; builtin
effects and tracks; a 60-entry period table; `__audio_used` marker that
elides the whole subsystem when no program statement references it.
**Still TODO for richer audio.**
- Triangle / noise / DMC channels (today the driver only uses pulse 1 and
pulse 2).
- Multi-channel tracker playback (one `notes` list per channel).
- `@sfx("file.nsf")` / `@music("file.ftm")` asset directives — neither the
NSF nor the FamiTracker format is parsed yet.
- Per-note pitch changes within a sfx (currently `pitch` latches once at
trigger time).
---
## Debug instrumentation
**What ships today.** `debug.log(...)` and `debug.assert(...)` lower to $4800
writes when `--debug` is passed, and are stripped entirely in release builds.
**Not yet implemented.**
- Mesen-compatible symbol export (`.mlb` / `.sym` files) — the CLI does not
emit them, and the previous `DebugSymbols` helper was removed as dead code
during cleanup.
- Source maps relating ROM addresses to source lines — the `SourceLoc`
IR op exists but is not consumed by the linker or CLI.
- Array bounds checking in debug mode.
- Frame overrun detection (cycles-per-frame counting).
- `debug.overlay(x, y, text)` — needs the text/HUD subsystem above.
---
## Code quality / tooling
### Register allocator
All IR temps currently spill to a recycled zero-page slot (`$80-$FF`). The
peephole pass mops up the most obvious waste, but a real CFG-aware allocator
that holds short-lived temps in `A`/`X`/`Y` would cut a noticeable number of
LDA/STA pairs.
### Cross-block temp live-range analysis
The slot recycler is function-local per-block. Temps that flow across block
boundaries get a dedicated slot for the entire function, even if a later
block could reuse the slot.
### `--no-opt` flag
There is no way to disable the optimizer from the CLI. Adding one would make
optimizer-introduced bugs easier to bisect.
### Compilation benchmarks
Compilation is fast (<100 ms for every example today) but has no `cargo
bench` harness, so regressions would slip through.
### WASM build target
To build a browser IDE we would need to route file I/O through a trait so the
core pipeline works on `&str → Vec<u8>` without touching `std::fs`. Today the
parser's preprocess pass and the asset resolver read files directly.
---
## Error message polish
### Unused error codes
`ErrorCode` only defines codes that are actually emitted. Previously there
were placeholder variants (`E0202` invalid cast, `E0403` unreachable state)
marked `#[allow(dead_code)]`; those were removed during cleanup. If those
semantics come back, add the codes at that point.
### Missing diagnostics
- No warning for implicit-drop of a function return value (`my_fun()` at
statement position when `my_fun` returns non-void).
- `W0102` ("loop without yield") is only emitted for bare `loop`, not for
`while true` or `loop { if cond { continue } }`.
- No warning for `fast` variables that never justify the zero-page slot
(could cross-reference access counts).
---
## Open design questions
1. **Inline asm label syntax.** `.label:` (ca65 style) vs `label:` (generic)?
Today the inline-asm parser accepts `label:` but not `.label`; migrating
would be cheap but would invalidate any copy-pasted ca65 fragments.
2. **Debug port address.** $4800 is conventional but not universal. Should
we support multiple debug output methods?
3. **OAM allocation strategy.** Sequential allocation vs priority-based with
automatic sprite cycling for the 8-per-scanline limit?
4. **Error recovery granularity.** How aggressively should the parser
recover? More recovery means more errors per compile but also risks
cascading false errors.