1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-09 09:18:01 +00:00

linker: gate default smiley CHR tile on __default_sprite_used marker

Drop the built-in smiley from CHR tile 0 unless something in the
program actually references it. The marker fires when either:

  1. `IrOp::DrawSprite` lowering falls back to tile 0 because the
     sprite name doesn't resolve to a user declaration, or
  2. The same lowering sees a runtime `frame:` override (which
     could index any tile, including 0).

A third source of dependency — a background nametable entry of 0 —
is detected in the linker by scanning `bg.tiles` for zeros. This
preserves the smiley for programs like `examples/friendly_assets`
that use tile 0 as a background placeholder, even though their
draws resolve to user-declared sprites.

Programs whose draws all resolve to explicitly-declared sprites
with static frames AND whose backgrounds reference tiles 1+ now
leave CHR tile 0 as an all-zero blank, freeing 16 CHR bytes that
the user can treat as an always-transparent background tile.
Verified against the current example set: `sprites_and_palettes`
and `auto_chr_background` reclaim tile 0; every other example
keeps it (either they fall back to tile 0 via an undeclared draw
name or their background tilemap references tile 0).

All 33 emulator goldens still pass — removing an unreferenced CHR
tile can't change observable output.

https://claude.ai/code/session_016kM6P7PukktBDqTZexrrAN
This commit is contained in:
Claude 2026-04-16 13:32:52 +00:00
parent 7533ac281e
commit 6561daff35
No known key found for this signature in database
7 changed files with 127 additions and 17 deletions

View file

@ -1226,11 +1226,15 @@ fn sprite_resolution_uses_tile_index() {
"Player sprite CHR bytes should be placed at tile index 1",
);
// The default smiley tile at index 0 should still be non-zero (untouched).
// The default smiley tile at index 0 should be blank ($00) —
// the program's one `draw Player at: (...)` resolves to the
// declared Player sprite at tile 1, so the `__default_sprite_used`
// marker never fires and the linker doesn't copy the smiley
// into tile 0. That frees the 16 bytes for user graphics.
let tile0 = &rom_data[chr_start..chr_start + 16];
assert_ne!(
assert_eq!(
tile0, &[0u8; 16],
"tile 0 should still contain the default smiley",
"tile 0 should be blank when no draw falls back to the smiley",
);
// In PRG ROM, look for `LDA #$01 ; STA $0201,Y` which writes
@ -1882,12 +1886,16 @@ fn e2e_bank_declarations_dont_affect_nrom_prg_size() {
#[test]
fn e2e_banked_chr_rom_is_preserved() {
// CHR ROM should still contain the default smiley sprite at
// tile 0 regardless of how many PRG banks the ROM has.
// tile 0 regardless of how many PRG banks the ROM has — but
// only when the program actually exercises the fallback. A
// `draw` of an undeclared sprite name drops the marker; we
// rely on that here rather than declaring a sprite so we keep
// testing the "banked ROM still emits the smiley" path.
let source = r#"
game "CHRCheck" { mapper: MMC1 }
bank One: prg
bank Two: prg
on frame { wait_frame }
on frame { draw Unknown at: (0, 0) }
start Main
"#;
let rom = compile_banked(source);