mirror of
https://github.com/imjasonh/nescript
synced 2026-07-11 10:10:49 +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:
parent
cc3f7eec7e
commit
6501f105bd
1 changed files with 65 additions and 32 deletions
|
|
@ -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
|
(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
|
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
|
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
|
byte nametable/attribute blobs. The nametable path now **also auto-generates
|
||||||
addresses and a running total alongside the variable layout.
|
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.**
|
**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
|
- **Per-state background rendering control** — programs currently load a
|
||||||
single nametable at reset. Per-state swaps work but are limited by the
|
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
|
NMI-time write budget (~2273 cycles, enough for a palette but not a
|
||||||
full 1024-byte nametable).
|
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
|
functions into a specific switchable bank. The codegen emits per-bank
|
||||||
instruction streams; the linker runs a two-pass assembly (discover labels
|
instruction streams; the linker runs a two-pass assembly (discover labels
|
||||||
per-bank, then resolve with the merged label table) so banked code can
|
per-bank, then resolve with the merged label table) so banked code can
|
||||||
still reference fixed-bank symbols. Fixed → banked calls are rewritten to
|
still reference fixed-bank symbols. Cross-bank calls — both fixed → banked
|
||||||
`JSR __tramp_<name>`, where each trampoline is a per-function stub in the
|
*and* banked → banked — are rewritten to `JSR __tramp_<name>`, where each
|
||||||
fixed bank that saves the current bank, switches, calls the target,
|
trampoline is a per-function stub in the fixed bank that reads the
|
||||||
restores, and returns. `runtime/mod.rs::gen_bank_trampoline` is the
|
caller's current bank from `ZP_BANK_CURRENT`, pushes it on the hardware
|
||||||
per-mapper emitter. See `examples/uxrom_user_banked.ne`.
|
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.**
|
**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
|
- **Greedy size-packing.** Placement is explicit-only today — there is no
|
||||||
pass that takes a program with too much fixed-bank code and
|
pass that takes a program with too much fixed-bank code and
|
||||||
automatically spills the biggest leaf functions to declared empty banks.
|
automatically spills the biggest leaf functions to declared empty banks.
|
||||||
- **MMC3 per-state-handler split** — the `mmc3_per_state_split.ne`
|
- **MMC3 per-state-handler split** — the `mmc3_per_state_split.ne`
|
||||||
example still uses the legacy fixed-bank placement for its handlers.
|
example still uses the legacy fixed-bank placement for its handlers.
|
||||||
Extending the banked-fun syntax to state handlers (plus trampoline
|
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. |
|
| **Fixed-point** | `fixed8.8` type for sub-pixel movement with operator support. |
|
||||||
| **Text / HUD** | Font sheet declarations + layout system for scores, health, menus. |
|
| **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. |
|
| **Tilemaps** | Declarative level data with built-in collision queries. |
|
||||||
| **SRAM / saves** | Persistent storage declarations for battery-backed save data. |
|
| **SRAM / saves** | Persistent storage declarations for battery-backed save data. |
|
||||||
|
|
||||||
NES 2.0 headers are now supported via `game Foo { header: nes2 }` — see
|
NES 2.0 headers are now supported via `game Foo { header: nes2 }` — see
|
||||||
`src/rom/mod.rs`.
|
`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
|
### Struct / array field widths
|
||||||
|
|
||||||
`u16` struct fields now compile. Nested struct fields and array fields are
|
Nested struct fields (`hero.pos.x`) and array struct fields
|
||||||
still rejected with `E0201`; the field-layout accumulator handles variable
|
(`hero.inv[i]`) now compile end-to-end. The analyzer recursively
|
||||||
sizes correctly, but the IR-lowering side needs extending to recurse for
|
flattens the struct layout into per-leaf synthetic variables (with
|
||||||
nested structs and to multiply by element size for array fields.
|
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**
|
elides the whole subsystem when no program statement references it. **Plus**
|
||||||
`channel: triangle` and `channel: noise` on `sfx` blocks, which splice in
|
`channel: triangle` and `channel: noise` on `sfx` blocks, which splice in
|
||||||
per-channel slots that write to $4008-$400B (triangle) or $400C-$400F
|
per-channel slots that write to $4008-$400B (triangle) or $400C-$400F
|
||||||
(noise) when a program declares them. Pulse-only programs still produce
|
(noise) when a program declares them. **Plus per-frame pitch envelopes
|
||||||
byte-identical driver code. See `examples/noise_triangle_sfx.ne`.
|
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.**
|
**Still TODO for richer audio.**
|
||||||
- **DMC channel** — delta-modulation sample playback is not wired yet.
|
- **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).
|
tracker).
|
||||||
- **`@sfx("file.nsf")` / `@music("file.ftm")`** — neither the NSF nor the
|
- **`@sfx("file.nsf")` / `@music("file.ftm")`** — neither the NSF nor the
|
||||||
FamiTracker format is parsed yet.
|
FamiTracker format is parsed yet.
|
||||||
- **Per-note pitch changes within a sfx** — `pitch` latches once at
|
- **Per-frame pitch envelopes on triangle / noise sfx** — the data
|
||||||
trigger time.
|
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
|
entries for every lowered statement. Debug builds emit array bounds checks
|
||||||
(CMP against size, BCC past a `JMP __debug_halt` wedge) and bump an
|
(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
|
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.**
|
**Still TODO.**
|
||||||
- **`debug.overlay(x, y, text)`** — needs the text/HUD subsystem (see
|
- **`debug.overlay(x, y, text)`** — needs the text/HUD subsystem (see
|
||||||
Language feature gaps).
|
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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue