2026-04-11 22:17:18 +00:00
# NEScript Examples
## Quick Start
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
```bash
# Build the compiler
2026-04-11 22:17:18 +00:00
cargo build --release
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
# Compile all examples
for f in examples/*.ne; do cargo run -- build "$f"; done
2026-04-11 22:17:18 +00:00
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
# Or compile one
2026-04-11 22:17:18 +00:00
cargo run -- build examples/hello_sprite.ne
```
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
Open any `.nes` file in an NES emulator ([Mesen ](https://www.mesen.ca/ ), [FCEUX ](https://fceux.com/ ), etc.)
2026-04-11 22:17:18 +00:00
## Examples
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
| 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 |
cleanup: fix silent miscompiles and delete dead code exposed by code review
Two correctness bugs were silently producing wrong ROMs:
- `x << n` / `x >> n` always shifted by 1, regardless of `n`, because
the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the
count. Now eval_const the RHS into a compile-time count; fall back
to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when
the amount isn't constant. Strength reduction folds the variable
form back to a fixed count once the optimizer knows the value.
- `x / n` / `x % n` always returned 0, because the lowering emitted
`LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the
runtime call was "TODO for now". Added real `IrOp::Div` and
`IrOp::Mod`, wired them through use-counting and DCE, gave codegen
`__divide`-based implementations, and taught strength reduction to
rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into
AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts
too, which were previously left for the codegen to emit inefficient
software calls.
Dead code removed (no backward-compat shims kept):
- `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the
Mesen/.sym emitters had no callers outside their own tests;
`main.rs` never wrote a symbol file. Documented the intent in
`docs/future-work.md` so it comes back intentionally if needed.
- `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state):
defined, formatted, and marked `#[allow(dead_code)]` but never
emitted. W0104 now carries the unreachable-state semantics too.
- `Level::Info`: never constructed.
- `load_background` / `set_palette` statements and their
`BackgroundDecl` / `PaletteDecl` parser support: parsed and
silently dropped by IR lowering (`// TODO: implement in asset
pipeline`). Removed keywords, AST variants, parser paths, analyzer
arms, and tests. `docs/future-work.md` documents the runtime
palette/nametable design for when it comes back.
Doc cleanup:
- `docs/architecture.md` was describing files that don't exist
(`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`,
`rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the
real flat `mod.rs` + `tests.rs` layout and the real pipeline order.
- `docs/future-work.md` was a hybrid of open work and "recently
completed" entries that duplicated the active stubs at the top of
the file. Collapsed to just the gaps that are actually still open.
- `README.md` claimed Mesen symbol export and 210 tests; updated both.
- `docs/language-guide.md` and `spec.md` described `palette` decls,
`set_palette` / `load_background`, `debug.overlay`, and error codes
that were never emitted. Trimmed.
- Stale comments on `Statement::Play`/`StartMusic`/`StopMusic`
claimed the audio subsystem was "a no-op at codegen time".
Tests:
- Regression tests for every fix above (`lower_shift_left_with_literal
_count_uses_that_count`, `lower_shift_right_with_variable_count
_uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm
_zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`,
`strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by
_power_of_two`, `strength_reduce_shift_var_with_constant_amount`).
- Renamed the `program_with_sprites_and_palette` integration test
(which was exercising the now-removed `load_background`/`set_palette`)
to `program_with_inline_sprite_chr`.
`examples/sprites_and_palettes.ne` lost its `palette`/`set_palette`
usage. Nothing in the emulator test presses A, so the headless
jsnes render shouldn't move, but the golden may need regeneration
via `UPDATE_GOLDENS=1` if it does.
https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
| `sprites_and_palettes.ne` | sprites, scroll, cast | Inline CHR data, PPU scroll writes, type casting |
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
| `mmc1_banked.ne` | MMC1, banks, multiply | Banked mapper with software multiply |
codegen: user code in switchable banks via cross-bank trampolines
Adds a `bank Foo { fun bar() { ... } }` parser form so user functions
can opt into living in a switchable PRG bank instead of the fixed
bank, plus the IR codegen, runtime, and linker work to make calls
across the bank boundary actually run. Programs that don't use the
new syntax produce byte-identical ROMs to before — verified by
rebuilding every existing example and diffing.
Pipeline shape:
* Parser accepts both `bank Foo: prg` (legacy reserved slot) and
`bank Foo { fun ... }` (functions land in the named bank). Nested
functions get tagged `bank: Some("Foo")` on the FunDecl + IrFunction.
* Analyzer bumps the user zero-page start past `$10` whenever the
program declares any banked function, so `__bank_select`'s STA into
ZP_BANK_CURRENT can't clobber a user variable. Programs without
banked functions keep the legacy `$10` start.
* IrCodeGen emits each banked function into its own per-bank
instruction stream (`banked_streams: HashMap<String, Vec<Instruction>>`)
while the fixed-bank stream gets the dispatcher loop + state
handlers + top-level functions, exactly like before. Cross-bank
calls from the fixed bank rewrite `JSR __ir_fn_<name>` to
`JSR __tramp_<name>`; in-bank calls stay direct. Banked → fixed
calls are direct (the fixed bank is always mapped at $C000-$FFFF).
Banked → other-banked calls aren't supported in this pass and
panic loudly during codegen.
* Runtime's `gen_bank_trampoline` takes the trampoline label and
entry label as parameters now (one trampoline per banked function,
not one per bank) so the linker can request any number of stubs.
* Linker assembles banked banks twice: a discovery pass to learn
each bank's labels, then a final pass that seeds the merged label
table so banked code can JSR into the fixed bank's runtime helpers
(math, audio, etc.). The fixed-bank assembler is also seeded with
the cross-bank labels so the trampolines' `JSR __ir_fn_<name>`
resolves into the bank's $8000 window. New `asm::assemble_with_labels`
/ `asm::assemble_discover_labels` helpers wire this up.
* PrgBank carries `Vec<Instruction>` + a list of `BankTrampoline`
requests now, replacing the old `data: Vec<u8>` + single
`entry_label: Option<String>` shape. The compiler populates both
from the codegen output; the linker's two-pass assembly handles
the rest.
New example: `examples/uxrom_user_banked.ne` puts a sprite-stepping
helper inside `bank Extras { fun step_animation() { ... } }`. The
fixed-bank state handler calls it via the generated trampoline, and
the harness golden locks in pixel + audio output at frame 180.
UxROM is the only mapper exercised by the new example. MMC1 and
MMC3 also work through the same path (the linker emits the right
mapper-specific bank-select code), but no example uses them yet —
the existing `mmc1_banked.ne` / `mmc3_per_state_split.ne` keep
their fixed-bank-only layout.
Limitations carried forward:
* No banked → banked cross-bank calls (panics in codegen).
* No greedy size-packing; placement is explicit-only.
* MMC3 state handlers don't get banked (the per-state split path
is untouched).
2026-04-14 11:41:20 +00:00
| `uxrom_user_banked.ne` | UxROM, `bank Foo { fun ... }` , cross-bank trampoline | First example to put real user code inside a switchable bank. The animation step lives in `bank Extras` and is invoked from the fixed-bank state handler via a generated `__tramp_step_animation` stub that selects bank 0, JSRs the body, then restores the fixed bank before returning. |
2026-04-15 02:37:19 +00:00
| `uxrom_banked_to_banked.ne` | UxROM, banked → banked cross-bank call | Two `bank Foo { fun ... }` blocks: `step` lives in bank Logic and calls `clamp` in bank Helpers. The trampoline uses `ZP_BANK_CURRENT + PHA/PLA` to save and restore the caller's bank, so the same per-callee stub works whether the caller is in the fixed bank or another switchable bank. |
palette/background: first-class declarations with reset-time load and runtime swaps
Re-adds `palette Name { colors: [...] }` and
`background Name { tiles: [...], attributes: [...] }` as first-class
declarations, plus `set_palette Name` and `load_background Name`
statements for runtime swaps. Unlike the previous iteration that
quietly no-op'd, this one is fully wired through the pipeline and
its behavior is pinned by both unit tests and an emulator golden.
Pipeline:
- Lexer: re-adds `palette`, `background`, `set_palette`,
`load_background` keywords and tokenizes them.
- AST: `PaletteDecl` (name + 1..=32 colour bytes) and `BackgroundDecl`
(name + 0..=960 tile bytes + 0..=64 attribute bytes) live in
`Program`. `Statement::SetPalette` and `Statement::LoadBackground`
name-reference these declarations.
- Parser: `palette Name { colors: [...] }` / `background Name
{ tiles: [...], attributes: [...] }` blocks and their statement
forms parse via the existing byte-array helper.
- Analyzer: validates colour indices ($00-$3F), palette length
(<=32), nametable length (<=960), attribute length (<=64), and
duplicate decl names. `set_palette` / `load_background` targets
must reference a declared name (E0502 otherwise). When a program
declares palette or background, the analyzer bumps the user
zero-page allocator's starting address from `$10` to `$18` to
reserve `$11-$17` for the runtime update handshake — programs
that don't use the feature keep the old layout so their emulator
goldens stay byte-exact.
- Assets: `PaletteData` and `BackgroundData` resolve declarations
into zero-padded fixed-size blobs (32 / 960 / 64 bytes) and
expose `label()` / `tiles_label()` / `attrs_label()` for codegen
to reference.
- IR: new `IrOp::SetPalette(String)` and
`IrOp::LoadBackground(String)`; lowering forwards the names
verbatim.
- Codegen: `gen_set_palette` writes the palette label pointer into
ZP `$12/$13` and ORs bit 0 into the update flags at `$11`;
`gen_load_background` does the same for tile/attribute pointers
at `$14/$15/$16/$17` with bit 1. Both emit a `__ppu_update_used`
marker so the linker splices in the NMI apply helper only when
the feature is actually used.
- Runtime: `gen_initial_palette_load` and
`gen_initial_background_load` write the first declared
palette/background at reset time (before rendering is enabled,
where PPU writes are safe). `gen_nmi(has_ppu_updates)` takes a
new flag; when true it splices `gen_ppu_update_apply` at the top
of the NMI body, which checks the `$11` flags byte and copies
pending palette / nametable data to `$3F00` / `$2000` inside
vblank. All helpers use only ZP $02/$03 as scratch at reset time
and never clobber ZP slots live across NMI.
- Linker: new `link_banked_with_ppu` takes slice of `PaletteData` /
`BackgroundData`; splices each blob as a labelled data block in
PRG ROM, picks the first-declared as the reset-time load target,
enables background rendering automatically when a background is
declared, and threads `has_ppu_updates` into `gen_nmi`. Old
`link_banked` remains as a thin wrapper for callers without
palette/background data so existing tests don't shift.
Tests:
- Lexer: tokenization of the 4 new keywords (single added test case).
- Parser: 5 new tests for `palette` / `background` decls with and
without attributes, plus `set_palette` / `load_background`
statements.
- Analyzer: 9 new tests covering acceptance of declared
palettes/backgrounds, E0502 for unknown names, E0201 for
out-of-range NES colors and oversized blobs, E0501 for duplicate
names, and the zero-page-layout guard (palette/bg decls bump ZP
start; no decls keeps it at $10).
- Resolver: 3 new tests for zero-padding, truncation of oversized
decls, and label derivation.
- IR: 2 new lowering tests for `set_palette` and `load_background`.
- Integration: 5 new tests — blob contents spliced verbatim into
PRG, `STA $12` / `STA $14` emitted by set_palette /
load_background codegen, and a regression guard that programs
without palette/background still land user vars at $10.
- Emulator: new `examples/palette_and_background.ne` driven by a
frame counter that toggles between `CoolBlues` / `WarmReds` and
`TitleScreen` / `StageOne` every 90 frames. Golden PNG and audio
hash checked in under `tests/emulator/goldens/` and verified via
`node run_examples.mjs` — rendered image shows the blue
`CoolBlues` palette with the nametable populated from
`TitleScreen`.
Docs:
- `README.md` adds the feature to the headline list and the example
table.
- `docs/language-guide.md` restores the palette/background sections
with the full 32-byte layout table and `set_palette` /
`load_background` statement references.
- `docs/future-work.md` replaces the "removed as dead code" entry
with the remaining gaps (PNG-sourced palette and nametable
assets, cross-vblank large background updates, memory-map
reporting).
- `spec.md` restores the grammar productions and usage examples.
- `examples/README.md` lists the new demo.
All 497 unit + integration tests pass. Clippy clean. All 21
emulator goldens match after the update pass.
https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 11:11:33 +00:00
| `palette_and_background.ne` | palette, background, set_palette, load_background | Reset-time initial load plus vblank-safe runtime swaps |
language: pleasant asset syntax for palettes, CHR, bg, sfx, music
Adds six NES-friendly authoring shortcuts so programs don't have to
hand-pack hex bytes for every kind of art asset. Every new syntax is
strictly additive — existing examples keep their byte-identical ROMs
and goldens.
* palette: ~50 named NES colours (`black`, `sky_blue`, `dk_red`, …)
usable anywhere a colour byte is expected, plus a grouped-form
`bg0..sp3` + `universal:` shape that auto-fills every sub-
palette's first byte (fixing the `$3F10` mirror trap).
* sprite: `pixels:` ASCII-art alternative to 16-byte CHR, supporting
multi-tile sprites split in row-major reading order.
* sfx: scalar `pitch:` matching the v1 driver's latch-once behaviour,
plus `envelope:` as a friendlier alias for `volume:`.
* music: `tempo:` default duration + note-name notes (`C4, Eb4,
rest 10`) alongside the existing `pitch, duration` pair form.
* background: `legend { '.': 0, '#': 1 }` + `map:` string rows,
plus `palette_map:` grids that auto-pack the 64-byte attribute
table from 16×15 sub-palette digits.
A new `examples/friendly_assets.ne` exercises every shortcut at once
with a matching pixel + audio golden; the other 22 golden tests still
match byte-for-byte.
https://claude.ai/code/session_01PzaSFj3VahDzxEYTKCESkz
2026-04-13 16:09:53 +00:00
| `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. |
2026-04-14 10:42:53 +00:00
| `noise_triangle_sfx.ne` | `channel: noise` , `channel: triangle` on `sfx` blocks | Demonstrates the noise and triangle sfx channels. Declares one noise burst and one triangle bass note, plays each on a timer so the emulator harness captures both the pixel output and the APU state. |
2026-04-15 02:54:56 +00:00
| `sfx_pitch_envelope.ne` | varying-pitch pulse SFX | A 16-frame frequency sweep written as a per-frame `pitch:` array on a Pulse-1 sfx. The compiler emits a separate `__sfx_pitch_<name>` blob and gates the audio tick's pitch update path on the `__sfx_pitch_used` marker, so programs that stick to the scalar `pitch:` form still get byte-identical ROM output. |
2026-04-15 03:13:30 +00:00
| `metasprite_demo.ne` | declarative multi-tile sprites | A 16× 16 hero sprite split into a `metasprite Hero { sprite: Hero16, dx: [...], dy: [...], frame: [...] }` declaration. `draw Hero at: (px, py)` then expands to one `DrawSprite` op per tile in the IR lowering, each with its dx/dy added to the user's anchor point and the frame offset by the underlying sprite's base tile. The codegen needs no metasprite-specific support — it sees N regular draws and the OAM cursor allocator handles the slots. |
analyzer/lowering: support nested struct fields and array struct fields
Struct field types beyond the v1 scalar set (`u8`, `i8`, `u16`,
`bool`) used to error out with `E0201: struct fields must be
u8/i8/u16/bool`. The size accumulator already handled them
correctly — what was missing was: (1) the analyzer side that
synthesizes per-leaf symbols and allocations for nested structs
plus a single array-typed symbol for array fields, (2) the
parser's chained-field-access path, and (3) the IR-lowering
recursion through nested struct literal initializers and array
literal field values.
The synthetic-variable model carries through unchanged: a
`var p: Player` where `Player { pos: Vec2, hp: u8, inv: u8[4] }`
and `Vec2 { x: u8, y: u8 }` produces flat allocations for
`p.pos.x`, `p.pos.y`, `p.hp`, and `p.inv`, plus an intermediate
`p.pos` Struct symbol so dotted-name lookups still resolve. Array
fields get a single allocation with the array type so the
existing `Expr::ArrayIndex` lowering path handles `p.inv[i]`
without changes. Array-of-structs is still rejected with E0201
because the synthetic model can't index per-element layouts
without further codegen work.
The parser change is the only structural move: `parse_primary`
and `parse_assign_or_call` now loop the dot chain into a single
joined identifier so `p.pos.x` becomes `FieldAccess("p.pos", "x")`
and `p.inv[0]` becomes `ArrayIndex("p.inv", 0)`. The downstream
analyzer and IR lowering use the same `format!("{name}.{field}")`
join they already used for one-level access — no plumbing
changes required.
Includes a new `examples/nested_structs.ne` that exercises both
features end-to-end with two `Hero` instances carrying nested
positions and inventory arrays. The reproducibility tripwire
ROM is committed alongside it and the emulator harness has a
matching pair of golden files.
https://claude.ai/code/session_01KEczoNUX3WmcFLfq6iAQxB
2026-04-15 02:19:49 +00:00
| `nested_structs.ne` | nested struct fields, array struct fields, chained literals | Two `Hero` instances each carry a `Vec2` position and a `u8[4]` inventory. Exercises `hero.pos.x` chained access, `hero.inv[i]` array-field access, and chained struct-literal initializers (`Hero { pos: Vec2 { x: ..., y: ... }, inv: [...] }` ). |
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:19:28 +00:00
| `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` . |
2026-04-11 22:17:18 +00:00
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
## Emulator Controls
2026-04-11 22:17:18 +00:00
| NES Button | Typical Key |
|------------|-------------|
| D-pad | Arrow keys |
| A | Z |
| B | X |
| Start | Enter |
| Select | Right Shift |
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
## About Sprites
2026-04-11 22:17:18 +00:00
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
Sprite names in `draw Player at: (x, y)` are parsed and recorded in the AST.
You can define sprites with inline CHR tile data:
2026-04-11 22:17:18 +00:00
```
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
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
2026-04-11 22:17:18 +00:00
# Compile to ROM
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
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
2026-04-11 22:17:18 +00:00
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
# View generated 6502 assembly
cargo run -- build game.ne --asm-dump
2026-04-11 22:17:18 +00:00
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
- arrays_and_functions: arrays, while loops, inline/regular functions
- state_machine: multi-state flow with on enter/exit handlers
- sprites_and_palettes: inline CHR data, palette switching, scroll, cast
- mmc1_banked: MMC1 mapper, bank declarations, software multiply
Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
# Debug mode
cargo run -- build game.ne --debug
2026-04-11 22:17:18 +00:00
```