mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
The previous platformer example drew enemies but had almost no interaction with them: only enemy 1 had a stomp check, the stomp window was unreachable under the default +1-px-per-frame-plus-a- jump-every-40-frames autopilot, contact from any other angle was a silent no-op, and the header comment promised a "title → playing → game-over state machine" that didn't actually exist. The README demo gif and the committed golden both froze that state — a level the player could walk through indefinitely with no consequence. Flesh the enemy interaction model out into something real: - `resolve_enemy_hit(e_sx)`: one helper, called symmetrically for both enemies. Computes the player/enemy hitbox overlap (horizontal in `e_sx ∈ (72, 96)`, vertical in `player_y ∈ (152, 176)`) and branches three ways — falling onto the head is a stomp bounce (`rise_count = 6`, `fall_vy = 0`, `stomp_count += 1`, `play Boing`); overlap while `rise_count > 0` is a grace pass-through so the stomp bounce itself can't retrigger contact on the same enemy; anything else (walking into the side, standing on the ground against the enemy) is fatal — `alive = 0` and `play hit`. - New `GameOver` state: draws four enemy tiles across the middle of the screen plus a coin row sized to `stomp_count`, stops the music, lingers 60 frames then auto-retries, and also honours Start for an instant retry. - Proximity-based autopilot: pre-jump when an enemy is exactly 19 px ahead (`e1_sx == 99` or `e2_sx == 99`), capped at two jumps per life by `auto_jumps < AUTOPILOT_JUMPS`. Tuning: a JUMP_RISE=12, GRAVITY_CAP=4 jump lands the player's feet at enemy-head height exactly 21 frames after lift-off, by which point the autopilot camera has scrolled the enemy under the player. The first jump fires on Playing frame 1 and stomps enemy 1 on frame 22; the second fires on Playing frame 101 and stomps enemy 2 on frame 122. After that the autopilot is exhausted and the third enemy encounter (camera wraps back past enemy 1) is fatal — the golden harness now sees the full stomp, stomp, die, retry, stomp loop instead of a frozen walk. - Live HUD: up to four coin sprites in the top-left, one per stomp, rendered both during `Playing` and on the `GameOver` screen so the score is visible in the death frame. `Playing`'s player draw is now guarded by `if alive == 1` so the hero disappears on the fatal-contact frame and the enemy that killed them is visible underneath. Verified with a per-frame ZP trace through the patched puppeteer + jsnes harness: first stomp at emu frame 44 (camera_x=22), second at emu frame 144 (camera_x=122), death at emu frame 283 (camera_x=5 after a 256-px wrap), `Playing` restart at emu frame 343, third stomp at emu frame 365. All 22 emulator goldens still match after the update, and `docs/platformer.gif` regenerated from the new ROM now shows two clean stomps, a clean side-collision death, the GameOver screen, and the retry cycle all inside the 6-second demo window. Golden updates: - `tests/emulator/goldens/platformer.png` — the frame-180 capture now shows the hero walking forward with a two-coin HUD after both autopilot stomps (previously: a frozen bouncing hero). - `tests/emulator/goldens/platformer.audio.hash` — the track now includes two `Boing` stomp bounces, which shifts the hash. - `examples/platformer.nes` — rebuilt from the rewritten source. Also updates the platformer rows in `README.md` and `examples/README.md` to match the new gameplay. https://claude.ai/code/session_013Bi4H4YQ5or5HtMB4doUFi
3.2 KiB
3.2 KiB
NEScript Examples
Quick Start
# Build the compiler
cargo build --release
# Compile all examples
for f in examples/*.ne; do cargo run -- build "$f"; done
# Or compile one
cargo run -- build examples/hello_sprite.ne
Open any .nes file in an NES emulator (Mesen, FCEUX, etc.)
Examples
| File | Features | Description |
|---|---|---|
hello_sprite.ne |
input, draw | Move a sprite with the d-pad |
bouncing_ball.ne |
if/else, variables | Auto-bouncing sprite with edge detection |
coin_cavern.ne |
states, functions, constants | 3-state game with gravity and coin collection |
arrays_and_functions.ne |
arrays, functions, while | Enemy array with collision detection |
state_machine.ne |
on enter/exit, transitions | Multi-state flow with timers |
sprites_and_palettes.ne |
sprites, scroll, cast | Inline CHR data, PPU scroll writes, type casting |
mmc1_banked.ne |
MMC1, banks, multiply | Banked mapper with software multiply |
palette_and_background.ne |
palette, background, set_palette, load_background | Reset-time initial load plus vblank-safe runtime swaps |
friendly_assets.ne |
named colours, grouped palette, pixel art, tilemap+legend, palette_map, scalar sfx pitch, note-name music | Exercises every "friendlier" asset syntax at once — the palette uses bg0..sp3 + a shared universal:, the sprite is authored as ASCII pixel art, the background uses a legend { ... } + map: tilemap with a palette_map: for attributes, the sfx uses a scalar pitch: + envelope: alias, and the music uses note names (C4, E4 40, rest 10) with a tempo: default. |
platformer.ne |
every subsystem | End-to-end side-scrolling demo: custom CHR tileset, full 32×30 nametable with per-region attribute palettes, 2×2 metasprite hero with gravity/jump physics, wrap-around horizontal scrolling, stomp-or-die enemy collisions with a live stomp-count HUD, coin pickups, user-declared SFX + music, and a Title → Playing → GameOver state machine with a proximity-based autopilot so the headless harness cycles through stomp, stomp, die, and retry inside six seconds. Regenerate the tile art with cargo run --bin gen_platformer_tiles. |
Emulator Controls
| NES Button | Typical Key |
|---|---|
| D-pad | Arrow keys |
| A | Z |
| B | X |
| Start | Enter |
| Select | Right Shift |
About Sprites
Sprite names in draw Player at: (x, y) are parsed and recorded in the AST.
You can define sprites with inline CHR tile data:
sprite Player {
chr: [0x3C, 0x42, 0x81, 0x81, 0x81, 0x81, 0x42, 0x3C,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
}
If no matching sprite declaration exists, the draw uses the built-in default
tile (a smiley face). See sprites_and_palettes.ne for a full example.
Compiler Commands
# Compile to ROM
cargo run -- build game.ne
# Custom output path
cargo run -- build game.ne --output my_game.nes
# Type-check only
cargo run -- check game.ne
# View generated 6502 assembly
cargo run -- build game.ne --asm-dump
# Debug mode
cargo run -- build game.ne --debug