The compile bench had a hand-maintained parallel copy of
`src/main.rs::compile`, and that copy went out of sync after
bank switching landed — the bench kept handing the linker
`PrgBank::empty(...)` slots even though the CLI started
populating per-bank instruction streams + trampoline requests.
The assembler then panicked with `unresolved label:
'__tramp_step_animation'` on `uxrom_user_banked.ne` under
`cargo test --all-targets`, which is what CI runs. A plain
`cargo test --release` (what CLAUDE.md used to document) never
builds the bench so the bug slipped through local validation.
Fix:
- New `nescript::pipeline` module with `compile_source(source,
source_dir, &CompileOptions)` that owns the full
`parse → analyze → lower → optimize → codegen → peephole →
link` pipeline including the per-bank stream + trampoline
reconstruction. Returns a `CompileOutput` carrying the ROM,
the linker result, analysis, IR, assets, instructions, and
source-loc markers so downstream tools have one place to
pull metadata from.
- `src/main.rs::compile` reduces to file I/O + preprocessing +
a single `compile_source` call + CLI-only side effects
(`--dump-ir`, `--call-graph`, `--asm-dump`, `--memory-map`,
`--symbols`, `--source-map`).
- `benches/compile.rs::compile_pipeline` becomes a one-line
`compile_source` call. It is now structurally impossible for
the bench to drift from the CLI path.
- `tests/integration_test.rs::compile_with_debug_artifacts`
likewise delegates to `compile_source`. This also fixes a
latent bug in the helper where it used `Linker::with_mapper`
without `.with_header(...)` — programs opting into
`header: nes2` would have quietly got an iNES 1.0 header
through this path.
- `CLAUDE.md`: updated the "Running the basics" section to
specify `cargo test --all-targets` (plain `cargo test` skips
benches) and to point at `scripts/pre-commit` with the exact
install command. Also installed the hook in this worktree.
All 24 existing `examples/*.nes` rebuild byte-identical through
the new pipeline. 624 tests + all 25 emulator goldens pass.
https://claude.ai/code/session_01MaNVcDmK9gsspRkdxowQAM
The compile benchmark was building each example via an in-memory
pipeline that mirrored the CLI except for one thing — it always
handed the linker empty `PrgBank::empty(...)` slots. That stayed
silently fine until `uxrom_user_banked.ne` started nesting a
function inside a `bank` block: the IR codegen emits
`JSR __tramp_step_animation` at the call site, and with no
trampoline request on the `Extras` bank the assembler's fixup
pass panics with "unresolved label". Local `cargo test` missed
it because the bench is only compiled under `--all-targets`,
which is what CI runs.
Fix: reconstruct the same `banked_streams` + `bank_trampolines`
dance `src/main.rs` already does for the real build path, and
thread the header format through `with_header` for parity.
https://claude.ai/code/session_01MaNVcDmK9gsspRkdxowQAM
Implements three items from docs/future-work.md's
"PNG-sourced palette and nametable assets" section:
- `palette Name @palette("file.png")` — the parser accepts a PNG
shortcut form; the asset resolver decodes the image via the
new `png_to_palette` helper, mapping each pixel's RGB to the
nearest NES master-palette index and building a 32-byte blob
that enforces the universal-first-byte convention (same as
the grouped-form parser). Errors cleanly on missing files or
more than 16 unique colours.
- `background Name @nametable("file.png")` — the parser accepts
a PNG shortcut form; the resolver decodes a 256×240 image into
a 960-byte tile-index table (deduplicating up to 256 unique
8×8 tiles) plus a 64-byte attribute table (bucketed by
average quadrant brightness). CHR data is not yet generated
automatically — callers still need to provide matching CHR
via the existing sprite / `@chr(...)` pipeline; the
limitation is documented on the `png_to_nametable` helper
and can be lifted in a follow-up.
- `--memory-map` now prints a "PRG ROM data blobs" section
listing each palette (32 B) and background (960 + 64 B)
under its linker-assigned label, plus a grand total. The
memory-map code is factored into `write_memory_map` which
takes a writer so unit tests can drive it against a
`Vec<u8>`. Memory-map printing moved to after the link step
so palette/background CPU addresses are available.
Call-site changes: `resolve_palettes` and `resolve_backgrounds`
now take a `source_dir` path and return `Result<_, String>`
because PNG decoding can fail. Updated the CLI driver,
benches/compile.rs, and every integration-test compile helper.
All 23 committed examples rebuild byte-identical; 525 lib
tests + 72 integration tests + 3 bin tests pass; clippy clean.
Adds two items from the "Code quality / tooling" section of
docs/future-work.md. Both make it easier to chase regressions
without touching codegen.
- `nescript build --no-opt` skips the IR optimizer pass so
optimizer-introduced miscompiles can be bisected against the
unoptimized output. Threaded through CompileOptions and gated
at the single optimizer call site in src/main.rs. Covered by a
new integration test that compiles the same program twice
(opt on / opt off) and asserts both outputs are valid iNES
ROMs with matching headers and reset vectors.
- A criterion-based `benches/compile.rs` harness that times the
full parse -> analyze -> lower -> optimize -> codegen -> link
pipeline on every examples/*.ne file. Sources are pre-read
into memory so file I/O stays off the hot loop, and each
example gets its own Criterion group for easy regression
spotting.
Committed ROM bytes under examples/*.nes are unchanged; the
emulator goldens under tests/emulator/goldens/ are untouched.