mirror of
https://github.com/imjasonh/nescript
synced 2026-07-18 06:36:57 +00:00
parser/lowering: declarative metasprites for multi-tile sprite groups
Multi-tile sprites used to require one hand-written `draw` per tile,
e.g. the four-call sequence in `examples/platformer.ne`'s
`draw_player()`. The new `metasprite Name { ... }` declaration
collects parallel `dx`/`dy`/`frame` arrays plus a reference to the
underlying sprite, and `draw Name at: (x, y)` expands to one OAM
slot per tile in the IR lowering — the codegen sees N regular
DrawSprite ops, so the runtime OAM cursor allocator picks them up
without any metasprite-specific awareness.
The metasprite's `frame:` array is interpreted *relative to the
underlying sprite's base tile*: index 0 means "the first tile this
sprite owns", which is the natural reading for a 16×16 hero whose
pixel art the asset resolver split into four consecutive tiles.
The lowering walks `program.sprites` to compute base tile indices
the same way `assets::resolve_sprites` would, then folds the base
into each frame entry before storing the metasprite info. Sprites
sourced from external `@chr(...)` / `@binary(...)` files whose
bytes aren't available at parse time fall back to a one-tile
assumption — those programs are rare and can declare metasprites
against pixel-art sprites instead.
The new `examples/metasprite_demo.ne` declares a 16×16 hero sprite
and arranges its four tiles into a metasprite, then sweeps the
hero across the screen so the harness captures it mid-motion.
The new keyword is added to the lexer/token list, and the parser
accepts `sprite:` (the otherwise-keyword) as a property name in
metasprite bodies so the natural spelling parses.
https://claude.ai/code/session_01KEczoNUX3WmcFLfq6iAQxB
This commit is contained in:
parent
9878b7d87d
commit
6b080316a4
17 changed files with 619 additions and 0 deletions
|
|
@ -350,6 +350,69 @@ fn lower_debug_frame_overrun_count_emits_peek() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_metasprite_draw_expands_to_one_op_per_tile() {
|
||||
// `draw Hero at: (10, 20)` where Hero is a 4-tile metasprite
|
||||
// should lower to four `DrawSprite` ops, each with the
|
||||
// metasprite's underlying sprite name and one tile from the
|
||||
// declaration's `frame:` array (offset by the sprite's base
|
||||
// tile index — the runtime smiley occupies tile 0, so a
|
||||
// single-sprite program starts user tiles at 1).
|
||||
let ir = lower_ok(
|
||||
r#"
|
||||
game "T" { mapper: NROM }
|
||||
sprite Tile {
|
||||
pixels: [
|
||||
"@@@@@@@@",
|
||||
"@@@@@@@@",
|
||||
"@@@@@@@@",
|
||||
"@@@@@@@@",
|
||||
"@@@@@@@@",
|
||||
"@@@@@@@@",
|
||||
"@@@@@@@@",
|
||||
"@@@@@@@@"
|
||||
]
|
||||
}
|
||||
metasprite Hero {
|
||||
sprite: Tile
|
||||
dx: [0, 8, 0, 8]
|
||||
dy: [0, 0, 8, 8]
|
||||
frame: [0, 0, 0, 0]
|
||||
}
|
||||
on frame {
|
||||
draw Hero at: (10, 20)
|
||||
wait_frame
|
||||
}
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
let frame_fn = ir
|
||||
.functions
|
||||
.iter()
|
||||
.find(|f| f.name.contains("frame"))
|
||||
.unwrap();
|
||||
let draws: Vec<_> = frame_fn
|
||||
.blocks
|
||||
.iter()
|
||||
.flat_map(|b| &b.ops)
|
||||
.filter_map(|op| match op {
|
||||
IrOp::DrawSprite { sprite_name, .. } => Some(sprite_name.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
draws.len(),
|
||||
4,
|
||||
"metasprite with 4 tiles should expand to 4 DrawSprite ops"
|
||||
);
|
||||
for name in &draws {
|
||||
assert_eq!(
|
||||
name, "Tile",
|
||||
"expanded ops should target the underlying sprite"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_nested_struct_literal_init_expands_to_leaves() {
|
||||
// A `Hero { pos: Vec2 { x: 1, y: 2 }, hp: 100, inv: [3,4,5,6] }`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue