1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-09 09:18:01 +00:00
nescript/README.md
Claude 169a481099
feat(platformer): add stomp-or-die enemy collisions, live HUD, GameOver state
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
2026-04-13 20:23:07 +00:00

134 lines
6.5 KiB
Markdown

# NEScript
A statically-typed, compiled programming language for NES game development.
NEScript compiles `.ne` source files directly into playable iNES ROM files, with no external assembler or linker dependencies. The compiler handles everything from source text to a ROM you can run in any NES emulator.
![Platformer demo](docs/platformer.gif)
_Source: [`examples/platformer.ne`](examples/platformer.ne)_
## Quick Start
```bash
# Build the compiler
cargo build --release
# Compile an example
cargo run -- build examples/hello_sprite.ne
# Run the output ROM in an emulator
# (produces examples/hello_sprite.nes)
```
## Hello World
```
game "Hello" {
mapper: NROM
}
var px: u8 = 128
var py: u8 = 120
on frame {
if button.right { px += 2 }
if button.left { px -= 2 }
if button.down { py += 2 }
if button.up { py -= 2 }
draw Smiley at: (px, py)
}
start Main
```
## Features
- **Game-aware syntax** -- states, sprites, palettes, backgrounds, and input are first-class constructs
- **Full type system** -- `u8`, `i8`, `u16`, `bool`, fixed-size arrays (`u8[N]`), `enum`, `struct`
- **Rich control flow** -- `if`/`else`, `while`, `for i in 0..N`, `loop`, `match`
- **Functions** -- with parameters, return types, `inline` hint, recursion detection
- **State machines** -- `state` with `on enter`, `on exit`, `on frame`, `on scanline(N)` handlers
- **Compile-time safety** -- call depth limits, recursion detection, type checking, unused-var warnings
- **IR-based optimizer** -- constant folding, dead code elimination, strength reduction (incl. div/mod by power-of-two), copy propagation, peephole passes including INC/DEC fold and live-range slot recycling
- **Full 16-bit arithmetic** -- u16 add/sub/compare lower to carry-propagating paired operations
- **Multiple mappers** -- NROM, MMC1, UxROM, MMC3 (including multi-scanline IRQ dispatch per state)
- **Audio subsystem** -- frame-walking pulse driver with user-declared `sfx`/`music` blocks, builtin effects and tracks, period table, and zero-cost elision when unused
- **Palette & background pipeline** -- `palette` and `background` blocks, initial values loaded at reset, vblank-safe `set_palette` / `load_background` runtime swaps
- **Asset pipeline** -- PNG-to-CHR conversion, inline tile data, sfx envelopes, music note streams
- **Inline assembly** -- `asm { ... }` with `{var}` substitution, plus `raw asm { ... }` for verbatim blocks
- **Hardware intrinsics** -- `poke(addr, value)` / `peek(addr)` for direct register access
- **Debug support** -- `--debug` flag enables `debug.log` / `debug.assert` writes to the emulator debug port
- **Compile-time diagnostics** -- `--dump-ir`, `--memory-map`, `--call-graph` flags
- **Single binary** -- no dependencies on ca65, Python, or any external tools
## Documentation
- **[Language Guide](docs/language-guide.md)** -- complete reference for every language feature
- **[Architecture](docs/architecture.md)** -- compiler internals and module overview
- **[NES Reference](docs/nes-reference.md)** -- hardware quick reference for contributors
- **[Examples README](examples/README.md)** -- how to build and run examples
## Examples
| Example | Features demonstrated |
|---------|---------------------|
| [`hello_sprite.ne`](examples/hello_sprite.ne) | D-pad input, sprite drawing |
| [`bouncing_ball.ne`](examples/bouncing_ball.ne) | Automatic movement, edge detection |
| [`coin_cavern.ne`](examples/coin_cavern.ne) | Multi-state game, functions, constants, gravity |
| [`arrays_and_functions.ne`](examples/arrays_and_functions.ne) | Arrays, functions, while loops, inline functions |
| [`state_machine.ne`](examples/state_machine.ne) | State transitions, on enter/exit, timers |
| [`sprites_and_palettes.ne`](examples/sprites_and_palettes.ne) | Inline CHR data, scroll, type casting |
| [`mmc1_banked.ne`](examples/mmc1_banked.ne) | MMC1 mapper, bank declarations, multiply |
| [`palette_and_background.ne`](examples/palette_and_background.ne) | Palette and background declarations, reset-time load, vblank-safe `set_palette` / `load_background` swaps |
| [`friendly_assets.ne`](examples/friendly_assets.ne) | **Pleasant asset syntax** — named NES colours, grouped `bg0..sp3` palettes with `universal:`, ASCII pixel-art sprites, `legend { } + map:` tilemaps, `palette_map:` attribute grids, scalar sfx `pitch:`, note-name music with `tempo:` |
| [`structs_enums_for.ne`](examples/structs_enums_for.ne) | Structs, enums, `for` loops, struct literals |
| [`inline_asm_demo.ne`](examples/inline_asm_demo.ne) | Inline asm with `{var}` substitution, `poke`/`peek` |
| [`audio_demo.ne`](examples/audio_demo.ne) | Audio subsystem: user `sfx`/`music` blocks, builtin effects, `play`/`start_music`/`stop_music` |
| [`platformer.ne`](examples/platformer.ne) | **End-to-end side-scroller** — custom CHR tileset, full background nametable, metasprite player with gravity/jump physics, wrap-around scrolling, stomp-or-die enemy collisions, live stomp-count HUD, pickup coins, user-declared SFX + music, and a Title → Playing → GameOver state machine with a proximity-based autopilot so the headless harness demonstrates the full gameplay loop (stomp, stomp, die, retry) inside six seconds |
## Compiler Commands
```bash
# Compile to ROM
nescript build game.ne
# Compile with custom output path
nescript build game.ne --output my_game.nes
# Type-check only (no ROM output)
nescript check game.ne
# View generated 6502 assembly
nescript build game.ne --asm-dump
# Enable debug mode
nescript build game.ne --debug
```
## Emulator Compatibility
Output ROMs are standard iNES format and work with any NES emulator:
- **[Mesen](https://www.mesen.ca/)** -- recommended, best debugging support
- **[FCEUX](https://fceux.com/)**
- **[Nestopia](http://nestopia.sourceforge.net/)**
## Project Status
NEScript implements all five planned milestones:
| Milestone | Status | Key Features |
|-----------|--------|-------------|
| M1: Hello Sprite | Done | Full compiler pipeline, assembler, ROM builder |
| M2: Game Loop | Done | Functions, arrays, IR, optimizer, call graph analysis |
| M3: Asset Pipeline | Done | PNG-to-CHR, sprites, `debug.log` / `debug.assert` |
| M4: Optimization | Done | Strength reduction, ZP promotion, type casting, asm-dump |
| M5: Bank Switching | Done | MMC1/UxROM/MMC3, bank declarations, software mul/div |
497 tests across the lexer, parser, analyzer, IR, optimizer, codegen, assembler, linker, runtime, ROM, and asset modules, with CI running fmt, clippy, test, and example compilation on every push.
## License
[MIT](LICENSE)