mirror of
https://github.com/imjasonh/nescript
synced 2026-07-13 11:07:31 +00:00
Sprite resolution, asset wiring, shift-assign, unreachable state warning
Sprite/asset pipeline: - Linker::link_with_assets() places sprite CHR data in ROM at correct tile - assets::resolve_sprites() walks Program for inline sprite bytes - CodeGen::with_sprites() maps sprite names to tile indices - gen_draw() uses correct tile index from sprite declarations - main.rs wires the full resolution pipeline Shift-assign operators (<<= and >>=): - AssignOp::ShiftLeftAssign and ShiftRightAssign variants - Parser handles in both statement and array index contexts - Codegen emits ASL A / LSR A - IR lowering maps to ShiftLeft/ShiftRight ops Unreachable state warning (W0104): - BFS from start state finds reachable states via transitions - States not reached produce W0104 warning Error polish helpers: - suggest_var_name() for "did you mean" suggestions - emit_undefined_var() for E0502 with typo hints - Used by analyzer for better diagnostics 242 tests pass, clippy clean. https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
parent
5d2d242520
commit
6430c3a935
13 changed files with 737 additions and 17 deletions
|
|
@ -13,6 +13,15 @@ pub struct Linker {
|
|||
mapper: Mapper,
|
||||
}
|
||||
|
||||
/// CHR data for a sprite, placed at a specific tile index in CHR ROM.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpriteData {
|
||||
pub name: String,
|
||||
pub tile_index: u8,
|
||||
/// Raw CHR bytes (16 bytes per 8x8 tile).
|
||||
pub chr_bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
/// A smiley face CHR tile for the default sprite (M1).
|
||||
const DEFAULT_SPRITE_CHR: [u8; 16] = [
|
||||
// Plane 0 (low bits)
|
||||
|
|
@ -62,7 +71,17 @@ impl Linker {
|
|||
}
|
||||
|
||||
/// Link all code sections into a .nes ROM.
|
||||
///
|
||||
/// This is a thin wrapper around [`Linker::link_with_assets`] that passes
|
||||
/// an empty sprite list, so the CHR ROM only contains the default smiley
|
||||
/// tile at index 0.
|
||||
pub fn link(&self, user_code: &[Instruction]) -> Vec<u8> {
|
||||
self.link_with_assets(user_code, &[])
|
||||
}
|
||||
|
||||
/// Link all code sections into a .nes ROM, placing sprite CHR data at
|
||||
/// specific tile indices.
|
||||
pub fn link_with_assets(&self, user_code: &[Instruction], sprites: &[SpriteData]) -> Vec<u8> {
|
||||
// For NROM: everything fits in one 16 KB PRG bank ($C000-$FFFF)
|
||||
// Layout:
|
||||
// $C000: RESET handler (init + palette load + user code)
|
||||
|
|
@ -125,9 +144,17 @@ impl Linker {
|
|||
builder.set_mapper(crate::rom::mapper_number(self.mapper));
|
||||
builder.set_prg(prg);
|
||||
|
||||
// CHR ROM with default sprite tile
|
||||
// CHR ROM: tile 0 is reserved for the default smiley, followed by
|
||||
// any user-declared sprites placed at their assigned tile indices.
|
||||
let mut chr = vec![0u8; 8192];
|
||||
chr[..16].copy_from_slice(&DEFAULT_SPRITE_CHR);
|
||||
for sprite in sprites {
|
||||
let offset = sprite.tile_index as usize * 16;
|
||||
let end = offset + sprite.chr_bytes.len();
|
||||
if end <= chr.len() {
|
||||
chr[offset..end].copy_from_slice(&sprite.chr_bytes);
|
||||
}
|
||||
}
|
||||
builder.set_chr(chr);
|
||||
|
||||
builder.build()
|
||||
|
|
|
|||
|
|
@ -74,6 +74,62 @@ fn link_rom_size_correct() {
|
|||
assert_eq!(rom_data.len(), 16 + 16384 + 8192);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_with_sprites_places_chr_data() {
|
||||
let linker = Linker::new(Mirroring::Horizontal);
|
||||
let user_code = vec![Instruction::implied(NOP)];
|
||||
|
||||
let sprite_bytes: Vec<u8> = (0x20..0x30).collect(); // 16 bytes, one tile
|
||||
let sprites = vec![SpriteData {
|
||||
name: "Player".into(),
|
||||
tile_index: 1,
|
||||
chr_bytes: sprite_bytes.clone(),
|
||||
}];
|
||||
|
||||
let rom_data = linker.link_with_assets(&user_code, &sprites);
|
||||
|
||||
// CHR starts right after the 16-byte iNES header and 16 KB PRG bank.
|
||||
let chr_start = 16 + 16384;
|
||||
|
||||
// Tile 0 should still contain the built-in smiley (first 16 bytes, not
|
||||
// all zero).
|
||||
let tile0 = &rom_data[chr_start..chr_start + 16];
|
||||
assert_ne!(
|
||||
tile0, &[0u8; 16],
|
||||
"default smiley should occupy tile index 0",
|
||||
);
|
||||
|
||||
// Tile 1 (CHR offset 16) should contain the sprite's CHR bytes exactly.
|
||||
let tile1 = &rom_data[chr_start + 16..chr_start + 32];
|
||||
assert_eq!(tile1, sprite_bytes.as_slice());
|
||||
|
||||
// Tile 2 and beyond should be untouched (all zeros).
|
||||
let tile2 = &rom_data[chr_start + 32..chr_start + 48];
|
||||
assert_eq!(tile2, &[0u8; 16]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_with_sprites_spanning_multiple_tiles() {
|
||||
let linker = Linker::new(Mirroring::Horizontal);
|
||||
let user_code = vec![Instruction::implied(NOP)];
|
||||
|
||||
// 32 bytes = 2 tiles. The linker should place them consecutively
|
||||
// starting at the requested tile index.
|
||||
let sprite_bytes: Vec<u8> = (0..32).collect();
|
||||
let sprites = vec![SpriteData {
|
||||
name: "Big".into(),
|
||||
tile_index: 4,
|
||||
chr_bytes: sprite_bytes.clone(),
|
||||
}];
|
||||
|
||||
let rom_data = linker.link_with_assets(&user_code, &sprites);
|
||||
let chr_start = 16 + 16384;
|
||||
|
||||
// Tile 4 starts at CHR offset 64.
|
||||
let placed = &rom_data[chr_start + 64..chr_start + 64 + 32];
|
||||
assert_eq!(placed, sprite_bytes.as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn palette_load_writes_to_ppu() {
|
||||
let linker = Linker::new(Mirroring::Horizontal);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue