1
0
Fork 0
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:
Claude 2026-04-12 10:01:44 +00:00
parent 5d2d242520
commit 6430c3a935
No known key found for this signature in database
13 changed files with 737 additions and 17 deletions

View file

@ -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()

View file

@ -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);