diff --git a/examples/arrays_and_functions.nes b/examples/arrays_and_functions.nes index 481c3da..ab94d15 100644 Binary files a/examples/arrays_and_functions.nes and b/examples/arrays_and_functions.nes differ diff --git a/examples/audio_demo.nes b/examples/audio_demo.nes index cff3bea..cecd4e0 100644 Binary files a/examples/audio_demo.nes and b/examples/audio_demo.nes differ diff --git a/examples/bitwise_ops.nes b/examples/bitwise_ops.nes index 50571da..9f57276 100644 Binary files a/examples/bitwise_ops.nes and b/examples/bitwise_ops.nes differ diff --git a/examples/bouncing_ball.nes b/examples/bouncing_ball.nes index 74adae0..6970f4a 100644 Binary files a/examples/bouncing_ball.nes and b/examples/bouncing_ball.nes differ diff --git a/examples/coin_cavern.nes b/examples/coin_cavern.nes index 230f497..6ae24af 100644 Binary files a/examples/coin_cavern.nes and b/examples/coin_cavern.nes differ diff --git a/examples/comparisons.nes b/examples/comparisons.nes index 99c527b..f98c42d 100644 Binary files a/examples/comparisons.nes and b/examples/comparisons.nes differ diff --git a/examples/function_chain.nes b/examples/function_chain.nes index b68a2ac..84a89d7 100644 Binary files a/examples/function_chain.nes and b/examples/function_chain.nes differ diff --git a/examples/hello_sprite.nes b/examples/hello_sprite.nes index 09872a2..03e695c 100644 Binary files a/examples/hello_sprite.nes and b/examples/hello_sprite.nes differ diff --git a/examples/inline_asm_demo.nes b/examples/inline_asm_demo.nes index 2c19839..fd89a0e 100644 Binary files a/examples/inline_asm_demo.nes and b/examples/inline_asm_demo.nes differ diff --git a/examples/logic_ops.nes b/examples/logic_ops.nes index 460671b..d2f40bd 100644 Binary files a/examples/logic_ops.nes and b/examples/logic_ops.nes differ diff --git a/examples/loop_break_continue.nes b/examples/loop_break_continue.nes index 0d66288..1f28ccc 100644 Binary files a/examples/loop_break_continue.nes and b/examples/loop_break_continue.nes differ diff --git a/examples/match_demo.nes b/examples/match_demo.nes index aa3dd91..7bdc158 100644 Binary files a/examples/match_demo.nes and b/examples/match_demo.nes differ diff --git a/examples/metasprite_demo.nes b/examples/metasprite_demo.nes index a42abea..6fcad6f 100644 Binary files a/examples/metasprite_demo.nes and b/examples/metasprite_demo.nes differ diff --git a/examples/mmc1_banked.nes b/examples/mmc1_banked.nes index c647061..4c90e57 100644 Binary files a/examples/mmc1_banked.nes and b/examples/mmc1_banked.nes differ diff --git a/examples/mmc3_per_state_split.nes b/examples/mmc3_per_state_split.nes index 43a0a22..88a2425 100644 Binary files a/examples/mmc3_per_state_split.nes and b/examples/mmc3_per_state_split.nes differ diff --git a/examples/nested_structs.nes b/examples/nested_structs.nes index 0ae7f4e..5fa3196 100644 Binary files a/examples/nested_structs.nes and b/examples/nested_structs.nes differ diff --git a/examples/noise_triangle_sfx.nes b/examples/noise_triangle_sfx.nes index 619fda3..e4fdf68 100644 Binary files a/examples/noise_triangle_sfx.nes and b/examples/noise_triangle_sfx.nes differ diff --git a/examples/scanline_split.nes b/examples/scanline_split.nes index 24096ea..d39912d 100644 Binary files a/examples/scanline_split.nes and b/examples/scanline_split.nes differ diff --git a/examples/sfx_pitch_envelope.nes b/examples/sfx_pitch_envelope.nes index 7075ebd..8989f4d 100644 Binary files a/examples/sfx_pitch_envelope.nes and b/examples/sfx_pitch_envelope.nes differ diff --git a/examples/sprite_flicker_demo.nes b/examples/sprite_flicker_demo.nes index 6b0f146..2481636 100644 Binary files a/examples/sprite_flicker_demo.nes and b/examples/sprite_flicker_demo.nes differ diff --git a/examples/sprites_and_palettes.nes b/examples/sprites_and_palettes.nes index 3f0fe1d..59e1e2f 100644 Binary files a/examples/sprites_and_palettes.nes and b/examples/sprites_and_palettes.nes differ diff --git a/examples/state_machine.nes b/examples/state_machine.nes index c02ee94..948a84a 100644 Binary files a/examples/state_machine.nes and b/examples/state_machine.nes differ diff --git a/examples/structs_enums_for.nes b/examples/structs_enums_for.nes index a336c34..e9df64d 100644 Binary files a/examples/structs_enums_for.nes and b/examples/structs_enums_for.nes differ diff --git a/examples/two_player.nes b/examples/two_player.nes index c5a75b3..db4c774 100644 Binary files a/examples/two_player.nes and b/examples/two_player.nes differ diff --git a/examples/uxrom_banked.nes b/examples/uxrom_banked.nes index a0b7aa8..15844ed 100644 Binary files a/examples/uxrom_banked.nes and b/examples/uxrom_banked.nes differ diff --git a/examples/uxrom_banked_to_banked.nes b/examples/uxrom_banked_to_banked.nes index f675664..fb01da7 100644 Binary files a/examples/uxrom_banked_to_banked.nes and b/examples/uxrom_banked_to_banked.nes differ diff --git a/examples/uxrom_user_banked.nes b/examples/uxrom_user_banked.nes index b9fb319..8c98c50 100644 Binary files a/examples/uxrom_user_banked.nes and b/examples/uxrom_user_banked.nes differ diff --git a/src/linker/mod.rs b/src/linker/mod.rs index 3f9927a..2a482c6 100644 --- a/src/linker/mod.rs +++ b/src/linker/mod.rs @@ -157,7 +157,13 @@ const DEFAULT_SPRITE_CHR: [u8; 16] = [ 0b0011_1100, ]; -/// Default palette data for M1 (writes to PPU $3F00). +/// Default palette data for M1 (writes to PPU $3F00). Spliced into +/// PRG under [`DEFAULT_PALETTE_LABEL`] when the program has no +/// user-declared palette, and loaded by +/// [`runtime::gen_initial_palette_load`] via the same indirect-loop +/// path that user palettes use — keeps the reset-time palette +/// loader small (one code path, ~20 bytes) instead of the old +/// 170-byte per-entry unrolled store sequence. const DEFAULT_PALETTE: [u8; 32] = [ // Background palettes 0x0F, 0x00, 0x10, 0x20, // palette 0 (black, dark gray, light gray, white) @@ -171,6 +177,12 @@ const DEFAULT_PALETTE: [u8; 32] = [ 0x0F, 0x12, 0x22, 0x32, // sprite palette 3 ]; +/// Label under which [`DEFAULT_PALETTE`] is spliced into PRG when +/// emitted. Prefixed with `__` so it can never collide with a +/// user-declared palette's label, which the asset pipeline prefixes +/// with `__palette_`. +const DEFAULT_PALETTE_LABEL: &str = "__default_palette"; + impl Linker { pub fn new(mirroring: Mirroring) -> Self { Self { @@ -414,7 +426,16 @@ impl Linker { if let Some(first_palette) = palettes.first() { all_instructions.extend(runtime::gen_initial_palette_load(&first_palette.label())); } else { - all_instructions.extend(self.gen_palette_load()); + // No user palette: fall back to a sensible built-in + // palette so sprites show up in a reasonable colour + // scheme without any user setup. Uses the same indirect + // loop loader as the user-palette path (reads a 32-byte + // blob through a ZP pointer) — ~20 bytes of code plus a + // 32-byte data block that gets spliced in below, versus + // the ~170 bytes the old inline-stores path cost. The + // data block lives alongside the user palette blobs so + // the label resolves in the normal assembly pass. + all_instructions.extend(runtime::gen_initial_palette_load(DEFAULT_PALETTE_LABEL)); } // Load the initial background if the program declared any. @@ -550,6 +571,18 @@ impl Linker { for pal in palettes { all_instructions.extend(runtime::gen_data_block(&pal.label(), pal.colors.to_vec())); } + // When the program has no user palette, splice the built-in + // default palette blob under `__default_palette` so the + // reset-time loop loader above can resolve it. Programs + // that declare a palette fall through the user path and + // skip this entirely — saves 32 bytes of ROM data when the + // default is unused. + if palettes.is_empty() { + all_instructions.extend(runtime::gen_data_block( + DEFAULT_PALETTE_LABEL, + DEFAULT_PALETTE.to_vec(), + )); + } for bg in backgrounds { all_instructions.extend(runtime::gen_data_block( &bg.tiles_label(), @@ -753,24 +786,4 @@ impl Linker { fixed_bank_file_offset, } } - - /// Generate instructions to load the default palette into the PPU. - fn gen_palette_load(&self) -> Vec { - let mut out = Vec::new(); - - // Set PPU address to $3F00 (palette start) - out.push(Instruction::new(LDA, AM::Absolute(0x2002))); // read PPU status to reset latch - out.push(Instruction::new(LDA, AM::Immediate(0x3F))); - out.push(Instruction::new(STA, AM::Absolute(0x2006))); // PPU addr high byte - out.push(Instruction::new(LDA, AM::Immediate(0x00))); - out.push(Instruction::new(STA, AM::Absolute(0x2006))); // PPU addr low byte - - // Write all 32 palette bytes - for &color in &DEFAULT_PALETTE { - out.push(Instruction::new(LDA, AM::Immediate(color))); - out.push(Instruction::new(STA, AM::Absolute(0x2007))); // PPU data - } - - out - } } diff --git a/src/linker/tests.rs b/src/linker/tests.rs index 6b28c10..61f7524 100644 --- a/src/linker/tests.rs +++ b/src/linker/tests.rs @@ -633,30 +633,45 @@ fn link_banked_chr_rom_survives_with_switchable_banks() { } #[test] -fn palette_load_writes_to_ppu() { +fn default_palette_blob_present_when_no_user_palette() { + // With no user palette, the linker emits the shared reset-time + // loop loader (which writes twice to `$2006` and loops writing + // through `$2007`) and splices a 32-byte `__default_palette` + // data block into PRG. The end-to-end ROM should contain the + // default palette bytes verbatim at some offset in the fixed + // bank. let linker = Linker::new(Mirroring::Horizontal); - let palette_insts = linker.gen_palette_load(); + let user_code = vec![Instruction::new(NOP, AM::Label("__ir_main_loop".into()))]; + let rom = linker.link(&user_code); - // Should write to PPU address register ($2006) twice - let ppu_addr_writes: Vec<_> = palette_insts - .iter() - .filter(|i| i.opcode == STA && i.mode == AM::Absolute(0x2006)) - .collect(); - assert_eq!( - ppu_addr_writes.len(), - 2, - "should set PPU address (high and low bytes)" - ); + // The first four bytes of DEFAULT_PALETTE are {0x0F, 0x00, 0x10, + // 0x20}; they should appear verbatim in the PRG portion of the + // iNES file (bytes 16..16+16_384). We look for that 4-byte + // sequence rather than matching the full 32 bytes so this stays + // robust against minor palette tweaks. + let prg = &rom[16..16 + 16_384]; + let found = prg.windows(4).any(|w| w == [0x0F, 0x00, 0x10, 0x20]); + assert!(found, "default palette bytes should appear in PRG"); +} - // Should write 32 palette bytes to $2007 - let ppu_data_writes: Vec<_> = palette_insts - .iter() - .filter(|i| i.opcode == STA && i.mode == AM::Absolute(0x2007)) - .collect(); - assert_eq!( - ppu_data_writes.len(), - 32, - "should write all 32 palette bytes" +#[test] +fn no_default_palette_blob_when_user_palette_present() { + // A program that declares its own palette should suppress the + // built-in fallback entirely — the `__default_palette` label + // never gets emitted, and the assembler's label table doesn't + // contain it. + use crate::assets::PaletteData; + let linker = Linker::new(Mirroring::Horizontal); + let user_code = vec![Instruction::new(NOP, AM::Label("__ir_main_loop".into()))]; + let user_pal = PaletteData { + name: "Menu".into(), + colors: [0x0F; 32], + }; + let result = + linker.link_banked_with_ppu_detailed(&user_code, &[], &[], &[], &[user_pal], &[], &[]); + assert!( + !result.labels.contains_key("__default_palette"), + "default palette must be suppressed when user palette is present" ); } diff --git a/tests/emulator/goldens/audio_demo.audio.hash b/tests/emulator/goldens/audio_demo.audio.hash index aaa85d4..7fbc913 100644 --- a/tests/emulator/goldens/audio_demo.audio.hash +++ b/tests/emulator/goldens/audio_demo.audio.hash @@ -1 +1 @@ -49c411ce 132084 +e064fdf0 132084 diff --git a/tests/emulator/goldens/noise_triangle_sfx.audio.hash b/tests/emulator/goldens/noise_triangle_sfx.audio.hash index e0af6eb..ee351f8 100644 --- a/tests/emulator/goldens/noise_triangle_sfx.audio.hash +++ b/tests/emulator/goldens/noise_triangle_sfx.audio.hash @@ -1 +1 @@ -5146a336 132084 +7b0caf36 132084 diff --git a/tests/emulator/goldens/sfx_pitch_envelope.audio.hash b/tests/emulator/goldens/sfx_pitch_envelope.audio.hash index f4d7072..a4eb4f4 100644 --- a/tests/emulator/goldens/sfx_pitch_envelope.audio.hash +++ b/tests/emulator/goldens/sfx_pitch_envelope.audio.hash @@ -1 +1 @@ -eec31111 132084 +c1ba0463 132084