1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00
nescript/examples/README.md
Claude 688d9afcec
platformer: end-to-end side-scroller demo + three runtime bug fixes
Adds `examples/platformer.ne`, a full side-scrolling game that
exercises nearly every subsystem of the compiler in one program:
custom CHR tileset, 32×30 background nametable with per-region
attribute palettes, 2×2 metasprite hero with gravity/jump physics,
wrap-around horizontal scrolling, moving enemies, coin pickups,
user-declared SFX + music, and a Title → Playing state machine
with autopilot so the headless jsnes harness captures real
gameplay at frame 180. Tile art + nametable are generated by
`scripts/gen_platformer_tiles.rs` (`cargo run --bin gen_platformer_tiles`).

Building this out uncovered three independent runtime bugs that
together made the example render as black-on-black smileys. All
three are fixed in this commit:

1. **`gen_init` enabled sprite rendering before the linker's
   initial palette/background load runs.** The PPU's v-register
   auto-increments on every `$2007` write *during active
   rendering*, so the palette load (32 B) and nametable load
   (1024 B) were scrambled past the first ~72 bytes — every
   existing program with a `background Level { ... }` block was
   silently rendering zero-filled VRAM. Fix: leave `PPU_MASK = 0`
   at the end of `gen_init` and emit a new `gen_enable_rendering`
   call *after* all initial VRAM writes complete.

2. **Audio tick corrupted `ZP_CURRENT_STATE`.** The audio
   driver's period-table lookup reused `$02/$03` as a temporary
   indirect pointer with a comment claiming the slots were free
   because the tick doesn't call mul/div. But `$03` is also
   `ZP_CURRENT_STATE` used by the state dispatch loop, so every
   music note silently overwrote the state index with the high
   byte of `__period_table` (`0xC5` in the platformer ROM),
   wedging the state machine forever. Fix: `gen_nmi` now PHAs
   `$02/$03` on entry and PLA-restores them on exit, and the
   audio tick JSR moves inside that save/restore window (it used
   to be spliced by the linker *before* the register saves, so
   even A/X/Y were technically being trashed pre-save). Only
   `audio_demo`'s audio hash shifts (its note timings move a few
   cycles); every other golden is unchanged.

3. **Sub-palette mirroring footgun.** Writing a 32-byte palette
   blob sequentially causes the sprite sub-palettes' "index 0"
   slots at `$3F10/$3F14/$3F18/$3F1C` to clobber the background
   universal colour at `$3F00/$3F04/$3F08/$3F0C` via NES hardware
   mirroring. The example's palette sets all eight first bytes
   to `$22` (sky blue) for this reason; `docs/future-work.md`
   picks up a TODO to warn on inconsistent first-byte values in
   the analyzer.

Also:

- `docs/platformer.gif` — 6-second recording of the example
  running in jsnes, generated by the new
  `tests/emulator/record_gif.mjs` puppeteer helper (encodes via
  `gifenc`, committed as a dev-dependency under
  `tests/emulator/package.json`).
- README / examples/README tables and the 497-test count are
  updated to cover the new example.

https://claude.ai/code/session_01BcCcHi6FUmTh8jC7UgkA3A
2026-04-13 13:04:26 +00:00

74 lines
2.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# NEScript Examples
## Quick Start
```bash
# 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](https://www.mesen.ca/), [FCEUX](https://fceux.com/), 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 |
| `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, enemies, coin pickups, user-declared SFX + music, and a Title → Playing state machine with an autopilot so the headless harness still hits interesting gameplay at frame 180. 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
```bash
# 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
```