1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-09 01:16:12 +00:00
nescript/CLAUDE.md
Claude 25946f74ba
docs: add CLAUDE.md documenting the jsnes harness and repo conventions
New top-level `CLAUDE.md` so future AI agents (and humans) don't have
to rederive the project conventions from the existing docs. Covers:

- The phase layout under `src/` and the expectation that every
  module has a co-located `tests.rs`.
- The core `cargo` commands and the fact that `fmt` and `clippy`
  are mandatory before committing.
- The jsnes emulator harness end-to-end:
  - File layout (`harness.html`, `run_examples.mjs`, `goldens/`,
    `actual/`).
  - How to run it locally (build release compiler, compile every
    example, `npm install`, `node run_examples.mjs`).
  - How to update goldens with `UPDATE_GOLDENS=1` and the rules for
    when that's allowed.
  - The procedure for adding a new example (build, golden, verify,
    README).
  - What the harness catches (codegen / runtime / PPU / APU / asset
    regressions) vs. what it doesn't (input, beyond frame 180).
  - How the CI job in `.github/workflows/ci.yml` wires everything up
    and uploads `actual/` on failure.
- Zero-page allocation rules (runtime reserves `$00-$0F`,
  palette/bg reserves `$11-$17` conditionally, user vars `$10+` or
  `$18+`, IR temps `$80+`) so the next agent doesn't accidentally
  collide with the runtime.
- "Things to avoid" list: no backwards-compat shims, no silencing
  goldens without understanding the diff, no committing the
  gitignored harness directories.

https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 11:38:54 +00:00

166 lines
7 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.

# CLAUDE.md
Guidance for Claude Code (and any other AI agents) working in this repo.
Keep it short and practical — it's here so the next agent doesn't have to
re-derive the project conventions from scratch.
---
## Project shape
- **NEScript** is a Rust-based compiler that turns `.ne` source files into
iNES ROMs. Single binary, no external assemblers, no external linkers.
- `src/` is a flat module layout: each compiler phase is its own directory
with `mod.rs` + `tests.rs`. See `docs/architecture.md` for the phase
pipeline.
- Examples live in `examples/*.ne`. Every example is expected to compile
cleanly and has a pinned emulator golden — see below.
- `docs/future-work.md` lists the remaining gaps. If you implement
something from that file, update the doc in the same PR.
## Running the basics
```bash
cargo build --release # build the compiler
cargo test # all Rust tests (lib + integration)
cargo fmt # mandatory before committing
cargo clippy --all-targets # mandatory before committing; fix or #[allow]
./target/release/nescript build examples/hello_sprite.ne # build one ROM
```
Compile every example at once:
```bash
for f in examples/*.ne; do cargo run --release -- build "$f"; done
```
## The jsnes emulator harness
This is the most important piece of project-specific tooling. Every `.ne`
example has a **pixel-exact PNG golden** and a **sample-exact audio hash**
committed under `tests/emulator/goldens/`. Any compiler change that alters
observable behaviour — codegen, optimizer, runtime, linker, asset pipeline
— will flip at least one golden, and CI will fail loudly with a visible
diff. Do not skip or weaken this check.
### Layout
```
tests/emulator/
harness.html # thin wrapper around jsnes; exposes window.nesHarness
# with loadRomBase64, runFrames, rawPixelsBase64,
# audioHash, audioWavBase64
run_examples.mjs # puppeteer-driven runner (headless Chrome)
package.json # depends on jsnes, pngjs, puppeteer
goldens/
<name>.png # 256×240 RGBA framebuffer at frame 180 (~3s at 60fps)
<name>.audio.hash # one line: "<fnv1a-hex> <sample-count>"
actual/ # gitignored; written on every run for diff artifacts
```
### Running it locally
The harness is **separate** from `cargo test`. You have to run it by hand:
```bash
# 1. Build every example first. The harness reads pre-built .nes files
# from examples/ — it will not invoke cargo.
cargo build --release
for f in examples/*.ne; do ./target/release/nescript build "$f"; done
# 2. Install node deps (once per worktree; node_modules/ is gitignored).
cd tests/emulator
npm install # or `npm ci` in CI
# 3. Verify every example still matches its golden.
node run_examples.mjs
# → "21/21 ROMs match their goldens" on success
# → FAIL / MISS lines + `actual/<name>.png`, `actual/<name>.diff.png`,
# `actual/<name>.wav` written for any ROM that mismatched
```
### Updating goldens
If a change is supposed to flip goldens (you added a new example, changed
a rendering path, fixed a bug that was baked into the old output), update
them with:
```bash
cd tests/emulator
UPDATE_GOLDENS=1 node run_examples.mjs # rewrites every mismatched golden
# or
node run_examples.mjs --update-goldens
```
Then `git diff tests/emulator/goldens/` the result, eyeball each change,
and include the updated PNG+hash files in the same commit as the code
change. Goldens are the contract; the commit message should explain why
each diff is legitimate. **Never** `UPDATE_GOLDENS=1` just to silence a
failing CI — that defeats the entire purpose of the harness.
### Adding a new example
1. Write `examples/<name>.ne`.
2. Build it with the release compiler so a `.nes` file lands next to it.
3. Run `UPDATE_GOLDENS=1 node run_examples.mjs` to generate
`goldens/<name>.png` and `goldens/<name>.audio.hash`. Both files must
be committed — the runner treats missing goldens as a hard failure.
4. Verify visually that the generated PNG is what you actually intended
(open it; you can use Read on the PNG file to have Claude display it).
5. Add the example to the tables in `README.md` and `examples/README.md`.
### What the harness tests (and doesn't)
- **Tests**: final rendered framebuffer at frame 180, full audio sample
stream over the same window. Catches codegen miscompiles, runtime
bugs, linker layout changes, PPU timing regressions, APU regressions,
asset pipeline bugs — essentially anything that affects the observable
behaviour of a whole program.
- **Does not test**: input handling (no buttons pressed during the run),
anything past frame 180 (~3 seconds), state transitions that require
user input. Examples that need input to look non-trivial should
structure themselves so a good demo happens on autopilot — e.g. a
frame counter that drives the interesting state (`examples/palette_and_background.ne`
is a working pattern).
### CI integration
The `emulator` job in `.github/workflows/ci.yml` installs Chrome deps,
builds all examples, then runs the harness. On failure it uploads the
`actual/` directory and `report.json` as an artifact named `emulator-diff`
so reviewers can download and inspect the pixel diffs without cloning the
repo. The CI job does **not** pass `UPDATE_GOLDENS`; if it flips, the
change needs a manual update + review.
## Conventions worth knowing
- Every `src/**/mod.rs` has a co-located `tests.rs`. Add unit tests
there, not in a separate file.
- Big cross-phase tests go in `tests/integration_test.rs`. Use the
`compile` / `compile_banked` helpers at the top of that file instead
of re-building the pipeline by hand.
- Error codes live in `src/errors/diagnostic.rs`. Don't add a new code
without emitting it from somewhere — clippy will catch unused variants,
but past agents have also let them sit as dead code.
- Zero page is tight. `$00-$0F` is reserved for the runtime (frame
flag, input, OAM cursor, sfx/music pointers). `$11-$17` is reserved
for PPU palette/background updates **when the program declares them**
(the analyzer bumps the user ZP start from `$10` to `$18` in that
case — programs without palette/bg keep the old `$10` layout to
preserve their goldens). User vars go at `$10+` or `$18+`; IR temps
land at `$80+`.
- `docs/future-work.md` is the authoritative roadmap. If you finish an
item, delete its section; if you add a new gap, write one.
## Things to avoid
- **Don't add backwards-compat shims.** The repo is pre-1.0; breaking
changes are fine if they improve the code. Delete dead code outright
rather than `#[allow(dead_code)]`-ing it.
- **Don't skip `cargo fmt` / `cargo clippy`.** CI runs both and they
are cheap.
- **Don't `UPDATE_GOLDENS=1` without reading the diff.** If you can't
explain why a golden flipped, the change is probably wrong.
- **Don't commit `tests/emulator/actual/` or `tests/emulator/node_modules/`.**
Both are gitignored, but it's worth double-checking before a commit
that touches the emulator directory.