diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5128abe..72eec96 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,12 +58,26 @@ jobs: - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: cargo build --release - - name: Compile all .ne examples + - name: Verify committed ROMs match their .ne sources + # Every example has a committed .nes file under examples/. The + # compiler is deterministic (verified: same input → byte-identical + # output), so rebuilding each example into a tmp directory and + # diffing against the committed ROM catches "someone edited a .ne + # file but forgot to regenerate the ROM" at PR time. run: | + mkdir -p /tmp/reprotest + fail=0 for f in examples/*.ne; do - echo "==> Compiling $f" - cargo run --release -- build "$f" + name=$(basename "$f" .ne) + ./target/release/nescript build "$f" --output "/tmp/reprotest/$name.nes" + if ! cmp -s "examples/$name.nes" "/tmp/reprotest/$name.nes"; then + echo "::error file=examples/$name.ne::committed examples/$name.nes is stale; rerun \`cargo run --release -- build examples/$name.ne\` and commit the new ROM" + fail=1 + fi done + if [ $fail -ne 0 ]; then + exit 1 + fi - name: Verify ROMs are valid iNES run: | for f in examples/*.nes; do @@ -83,8 +97,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - uses: actions/setup-node@v4 with: node-version: '22' @@ -110,14 +122,9 @@ jobs: libxkbcommon0 \ libxrandr2 \ libasound2t64 - - name: Build compiler - run: cargo build --release - - name: Compile all .ne examples - run: | - for f in examples/*.ne; do - echo "==> Compiling $f" - ./target/release/nescript build "$f" - done + # No compiler build needed — the `.nes` files are committed + # alongside their `.ne` sources, and the `examples` CI job + # fails on any stale ROM, so the emulator job can trust them. - name: Install emulator harness dependencies working-directory: tests/emulator run: npm ci diff --git a/.gitignore b/.gitignore index 68204c8..3475059 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /target -*.nes *.dbg *.map diff --git a/CLAUDE.md b/CLAUDE.md index aa7bb6a..3be1741 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,6 +15,13 @@ re-derive the project conventions from scratch. pipeline. - Examples live in `examples/*.ne`. Every example is expected to compile cleanly and has a pinned emulator golden — see below. +- **`examples/*.nes` is committed.** The compiler is deterministic + (same source → byte-identical ROM), so the ROMs travel with the + repo. If you edit any `.ne` file you **must** rebuild its `.nes` + in the same commit — CI's `examples` job rebuilds each ROM into a + tmp path and fails if the committed version differs, pointing at + the exact `cargo run -- build examples/.ne` to run. The + pre-commit hook under `scripts/pre-commit` catches this locally. - `docs/future-work.md` lists the remaining gaps. If you implement something from that file, update the doc in the same PR. @@ -63,22 +70,30 @@ tests/emulator/ 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). +# 1. 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. +# 2. Verify every committed ROM still matches its golden. node run_examples.mjs -# → "21/21 ROMs match their goldens" on success +# → "22/22 ROMs match their goldens" on success # → FAIL / MISS lines + `actual/.png`, `actual/.diff.png`, # `actual/.wav` written for any ROM that mismatched ``` +The harness reads the committed `examples/*.nes` files directly — +since those are rebuilt and diffed by the `examples` CI job, the +emulator job doesn't need a compiler toolchain at all. If you're +iterating on the compiler locally, rebuild the example ROM(s) you +care about before re-running the harness: + +```bash +cargo build --release +./target/release/nescript build examples/hello_sprite.ne +# then: +(cd tests/emulator && node run_examples.mjs) +``` + ### Updating goldens If a change is supposed to flip goldens (you added a new example, changed diff --git a/examples/arrays_and_functions.nes b/examples/arrays_and_functions.nes new file mode 100644 index 0000000..71509e6 Binary files /dev/null and b/examples/arrays_and_functions.nes differ diff --git a/examples/audio_demo.nes b/examples/audio_demo.nes new file mode 100644 index 0000000..b72ed10 Binary files /dev/null and b/examples/audio_demo.nes differ diff --git a/examples/bitwise_ops.nes b/examples/bitwise_ops.nes new file mode 100644 index 0000000..94e82bb Binary files /dev/null and b/examples/bitwise_ops.nes differ diff --git a/examples/bouncing_ball.nes b/examples/bouncing_ball.nes new file mode 100644 index 0000000..0c92f57 Binary files /dev/null and b/examples/bouncing_ball.nes differ diff --git a/examples/coin_cavern.nes b/examples/coin_cavern.nes new file mode 100644 index 0000000..3e72b7f Binary files /dev/null and b/examples/coin_cavern.nes differ diff --git a/examples/comparisons.nes b/examples/comparisons.nes new file mode 100644 index 0000000..c09cb40 Binary files /dev/null and b/examples/comparisons.nes differ diff --git a/examples/function_chain.nes b/examples/function_chain.nes new file mode 100644 index 0000000..6471e48 Binary files /dev/null and b/examples/function_chain.nes differ diff --git a/examples/hello_sprite.nes b/examples/hello_sprite.nes new file mode 100644 index 0000000..97eaccf Binary files /dev/null and b/examples/hello_sprite.nes differ diff --git a/examples/inline_asm_demo.nes b/examples/inline_asm_demo.nes new file mode 100644 index 0000000..caee039 Binary files /dev/null and b/examples/inline_asm_demo.nes differ diff --git a/examples/logic_ops.nes b/examples/logic_ops.nes new file mode 100644 index 0000000..511a9ef Binary files /dev/null and b/examples/logic_ops.nes differ diff --git a/examples/loop_break_continue.nes b/examples/loop_break_continue.nes new file mode 100644 index 0000000..a3792b9 Binary files /dev/null and b/examples/loop_break_continue.nes differ diff --git a/examples/match_demo.nes b/examples/match_demo.nes new file mode 100644 index 0000000..772ecae Binary files /dev/null and b/examples/match_demo.nes differ diff --git a/examples/mmc1_banked.nes b/examples/mmc1_banked.nes new file mode 100644 index 0000000..3d28138 Binary files /dev/null and b/examples/mmc1_banked.nes differ diff --git a/examples/mmc3_per_state_split.nes b/examples/mmc3_per_state_split.nes new file mode 100644 index 0000000..f80e039 Binary files /dev/null and b/examples/mmc3_per_state_split.nes differ diff --git a/examples/palette_and_background.nes b/examples/palette_and_background.nes new file mode 100644 index 0000000..d65391d Binary files /dev/null and b/examples/palette_and_background.nes differ diff --git a/examples/platformer.nes b/examples/platformer.nes new file mode 100644 index 0000000..0ed5185 Binary files /dev/null and b/examples/platformer.nes differ diff --git a/examples/scanline_split.nes b/examples/scanline_split.nes new file mode 100644 index 0000000..5f95708 Binary files /dev/null and b/examples/scanline_split.nes differ diff --git a/examples/sprites_and_palettes.nes b/examples/sprites_and_palettes.nes new file mode 100644 index 0000000..e307e0a Binary files /dev/null and b/examples/sprites_and_palettes.nes differ diff --git a/examples/state_machine.nes b/examples/state_machine.nes new file mode 100644 index 0000000..9b438f4 Binary files /dev/null and b/examples/state_machine.nes differ diff --git a/examples/structs_enums_for.nes b/examples/structs_enums_for.nes new file mode 100644 index 0000000..553f6d0 Binary files /dev/null and b/examples/structs_enums_for.nes differ diff --git a/examples/two_player.nes b/examples/two_player.nes new file mode 100644 index 0000000..2611272 Binary files /dev/null and b/examples/two_player.nes differ diff --git a/examples/uxrom_banked.nes b/examples/uxrom_banked.nes new file mode 100644 index 0000000..dbff6c6 Binary files /dev/null and b/examples/uxrom_banked.nes differ diff --git a/scripts/pre-commit b/scripts/pre-commit index af6ada2..e9539d1 100755 --- a/scripts/pre-commit +++ b/scripts/pre-commit @@ -15,4 +15,28 @@ cargo clippy --all-targets -- -D warnings echo " Running tests..." cargo test --all-targets +# Committed ROMs must match their .ne sources — the compiler is +# deterministic, so any example whose on-disk .nes differs from a +# fresh build means someone edited the .ne file but forgot to +# rebuild. Rebuild each to a tmp path and fail if any differ, then +# point the user at the exact `cargo run` command to fix it. +echo " Checking committed ROMs match .ne sources..." +cargo build --release --quiet +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT +stale=0 +for f in examples/*.ne; do + name=$(basename "$f" .ne) + ./target/release/nescript build "$f" --output "$tmp/$name.nes" >/dev/null + if ! cmp -s "examples/$name.nes" "$tmp/$name.nes"; then + echo " STALE: examples/$name.nes" + echo " rerun: cargo run --release -- build $f" + stale=1 + fi +done +if [ $stale -ne 0 ]; then + echo "one or more committed ROMs are out of date" + exit 1 +fi + echo "All pre-commit checks passed."