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

docs/future-work: prune items shipped on this branch

- PNG-sourced assets: drop the "automatic CHR generation" TODO
  now that `png_to_nametable_with_chr` ships with the resolver.
- User code distribution: drop the banked → banked TODO; only
  greedy size-packing and the MMC3 per-state-handler split remain.
- Language feature gaps: drop the metasprite row from the post-v0.1
  table and add a paragraph describing the new `metasprite` syntax.
  Drop the "nested struct / array struct field" gap; replace it
  with a note about the still-rejected array-of-structs case.
- Audio pipeline: note the new pulse pitch envelope path; replace
  the "pitch latches once" TODO with the triangle/noise extension.
- Debug instrumentation: note `debug.frame_overrun_count()` and
  `debug.frame_overran()`; drop the matching "richer telemetry" TODO.

Items kept (and unchanged) include the WASM build target, register
allocator, fixed-point arithmetic, text/HUD, tilemaps, SRAM, DMC,
multi-channel tracker, NSF/FTM imports, debug.overlay, per-state
background swaps, and the four open design questions.

https://claude.ai/code/session_01KEczoNUX3WmcFLfq6iAQxB
This commit is contained in:
Claude 2026-04-15 03:31:58 +00:00
parent cc3f7eec7e
commit 6501f105bd
No known key found for this signature in database

View file

@ -16,19 +16,25 @@ The palette path maps each pixel to its nearest NES master-palette index
(via `nearest_nes_color()` in `src/assets/palette.rs`), deduplicates, and
emits the 32-byte blob; the nametable path slices a 256×240 PNG into the
32×30 tile grid, deduplicates (max 256 unique tiles), and emits the 960+64
byte nametable/attribute blobs. `--memory-map` reports per-blob PRG ROM
addresses and a running total alongside the variable layout.
byte nametable/attribute blobs. The nametable path now **also auto-generates
the per-tile CHR data** via `png_to_nametable_with_chr` and slots it into
CHR ROM after the user's sprite tile range — see
`examples/auto_chr_background.ne` for the end-to-end flow.
`--memory-map` reports per-blob PRG ROM addresses and a running total
alongside the variable layout.
**Still TODO.**
- **Automatic CHR generation from `@nametable` PNGs** — the nametable
resolver currently produces tile indices 0..N but does not write matching
CHR data, so users still need to supply CHR via `@chr(...)` with the same
tile ordering. Closing this gap requires coordinating the PNG tile
dedupe with the CHR allocator so both pipelines agree on indices.
- **Per-state background rendering control** — programs currently load a
single 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).
- **Per-quadrant palette selection from PNG sources** — the
`png_to_nametable_with_chr` attribute path picks sub-palettes based on
brightness buckets, which is fine for grayscale demos but doesn't let
the user say "this 32×32 tile uses sub-palette 2". A separate
`palette_map:` shortcut exists for inline backgrounds; the PNG path
could grow a sibling `@palette_map("hint.png")` that overrides the
brightness buckets.
---
@ -38,24 +44,29 @@ addresses and a running total alongside the variable layout.
functions into a specific switchable bank. The codegen emits per-bank
instruction streams; the linker runs a two-pass assembly (discover labels
per-bank, then resolve with the merged label table) so banked code can
still reference fixed-bank symbols. Fixed → banked calls are rewritten to
`JSR __tramp_<name>`, where each trampoline is a per-function stub in the
fixed bank that saves the current bank, switches, calls the target,
restores, and returns. `runtime/mod.rs::gen_bank_trampoline` is the
per-mapper emitter. See `examples/uxrom_user_banked.ne`.
still reference fixed-bank symbols. Cross-bank calls — both fixed → banked
*and* banked → banked — are rewritten to `JSR __tramp_<name>`, where each
trampoline is a per-function stub in the fixed bank that reads the
caller's current bank from `ZP_BANK_CURRENT`, pushes it on the hardware
stack, switches to the target, JSRs the entry, then pulls and restores
the caller's bank. `gen_mapper_init` seeds `ZP_BANK_CURRENT` with the
fixed bank index at reset so the first cross-bank call from the fixed
bank still leaves the fixed bank mapped at $8000. See
`examples/uxrom_user_banked.ne` (fixed → banked) and
`examples/uxrom_banked_to_banked.ne` (banked → banked).
**Still TODO.**
- **Banked → banked cross-bank calls.** The codegen panics if a function
in bank A tries to call a function in bank B. The fix is to generalize
the trampoline registry so the caller's bank restore logic works for
arbitrary target banks, not just calls originating in the fixed bank.
- **Greedy size-packing.** Placement is explicit-only today — there is no
pass that takes a program with too much fixed-bank code and
automatically spills the biggest leaf functions to declared empty banks.
- **MMC3 per-state-handler split** — the `mmc3_per_state_split.ne`
example still uses the legacy fixed-bank placement for its handlers.
Extending the banked-fun syntax to state handlers (plus trampoline
emission on handler dispatch) would unify the two paths.
emission on handler dispatch) would unify the two paths. The blocker
isn't the trampoline — those work for any caller now — but the
state-handler dispatcher in the IR codegen needs to learn that
state handlers can live in a switchable bank, and to JSR through a
trampoline whose entry is the handler label.
---
@ -67,19 +78,33 @@ From the spec's "Reserved for Future Versions" section:
|--------------------|-----------------------------------------------------------------------|
| **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 headers are now supported via `game Foo { header: nes2 }` — see
`src/rom/mod.rs`.
**Metasprites** are now supported via `metasprite Name { sprite: ...,
dx: [...], dy: [...], frame: [...] }` — see `examples/metasprite_demo.ne`.
The IR lowering expands `draw Hero at: (x, y)` into one `DrawSprite` op
per tile, with each tile's frame index offset by the underlying sprite's
base tile so the codegen sees a stream of regular draws and the OAM
cursor allocator picks them up unchanged. Negative offsets and
runtime-varying tile selection are still TODO — the current form takes
literal `u8` offsets.
### Struct / array field widths
`u16` struct fields now compile. Nested struct fields and array fields are
still rejected with `E0201`; the field-layout accumulator handles variable
sizes correctly, but the IR-lowering side needs extending to recurse for
nested structs and to multiply by element size for array fields.
Nested struct fields (`hero.pos.x`) and array struct fields
(`hero.inv[i]`) now compile end-to-end. The analyzer recursively
flattens the struct layout into per-leaf synthetic variables (with
intermediate `Struct(...)` symbols for the dotted prefixes), and the
parser loops the dotted chain in `parse_primary` and
`parse_assign_or_call` so the existing `format!("{name}.{field}")`
synthetic-name model still works without IR changes. Array-of-structs
is still rejected with E0201 — the synthetic-variable model can't
index per-element struct layouts without further codegen work, see
`src/analyzer/mod.rs::register_struct`.
---
@ -91,8 +116,13 @@ effects and tracks; a 60-entry period table; `__audio_used` marker that
elides the whole subsystem when no program statement references it. **Plus**
`channel: triangle` and `channel: noise` on `sfx` blocks, which splice in
per-channel slots that write to $4008-$400B (triangle) or $400C-$400F
(noise) when a program declares them. Pulse-only programs still produce
byte-identical driver code. See `examples/noise_triangle_sfx.ne`.
(noise) when a program declares them. **Plus per-frame pitch envelopes
on Pulse-1 sfx** — a `pitch:` array with more than one distinct value
opts into a separate `__sfx_pitch_<name>` blob that the audio tick walks
in lockstep with the volume envelope, writing `$4002` on every NMI for a
real frequency-sweeping pulse channel. Pulse-only programs without
varying-pitch sfx still produce byte-identical driver code. See
`examples/noise_triangle_sfx.ne` and `examples/sfx_pitch_envelope.ne`.
**Still TODO for richer audio.**
- **DMC channel** — delta-modulation sample playback is not wired yet.
@ -101,8 +131,12 @@ byte-identical driver code. See `examples/noise_triangle_sfx.ne`.
tracker).
- **`@sfx("file.nsf")` / `@music("file.ftm")`** — neither the NSF nor the
FamiTracker format is parsed yet.
- **Per-note pitch changes within a sfx**`pitch` latches once at
trigger time.
- **Per-frame pitch envelopes on triangle / noise sfx** — the data
shape (a parallel pitch array on the `sfx` block) is the same as for
Pulse-1, but the runtime triangle/noise tick blocks currently only
write their volume registers (`$4008` / `$400C`). Extending them to
also walk a per-channel pitch envelope and write `$400A` / `$400E`
is the natural next step now that the pulse path is proven.
---
@ -117,16 +151,15 @@ op and writes a plain-text map of `<rom_offset> <file_id> <line> <col>`
entries for every lowered statement. Debug builds emit array bounds checks
(CMP against size, BCC past a `JMP __debug_halt` wedge) and bump an
overrun counter at `$07FF` in the NMI handler when the main loop didn't
reach `wait_frame` before the next vblank.
reach `wait_frame` before the next vblank. **Plus** two new query
expressions: `debug.frame_overrun_count()` returns the cumulative counter
and `debug.frame_overran()` returns a per-frame sticky bit (cleared by
the next `wait_frame`) so user code can write
`debug.assert(not debug.frame_overran())` guards.
**Still TODO.**
- **`debug.overlay(x, y, text)`** — needs the text/HUD subsystem (see
Language feature gaps).
- **Richer frame overrun telemetry** — today a single counter is bumped.
A `debug.frame_overrun_count()` builtin that exposes the counter to user
code, plus a per-frame "did this frame overrun" bit for
`debug.assert!(no_overrun)`-style guards, would make the data more
actionable.
---