From 57faf9e36aae731e4b01aec9cc1fb42c7e96e1d3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Apr 2026 15:12:18 +0000 Subject: [PATCH 1/2] commit built ROMs alongside .ne sources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compiler is deterministic: rebuilding any example produces a byte-identical ROM, verified across all 22 examples and all four mappers (NROM, MMC1, UxROM, MMC3). That means the .nes files are reproducible artefacts and can live next to their sources without drift. Benefits: - Users can clone the repo and open any example in an emulator without installing a Rust toolchain or running the compiler. - The emulator harness can trust examples/*.nes directly, so its CI job no longer needs a compiler build or a "compile all examples" loop — it just boots jsnes against the committed ROMs and diffs each against its golden. - ROM diffs in PRs are now meaningful: "this compiler change flipped 17 bytes in hello_sprite.nes" is visible review signal, not hidden behind the emulator golden. Guard rails so the ROMs don't drift from their sources: - .gitignore no longer excludes *.nes. - The `examples` CI job rebuilds every .ne into /tmp and fails loudly (with a GitHub error annotation pointing at the exact cargo command to rerun) if any committed ROM differs. - scripts/pre-commit does the same check locally. - CLAUDE.md now states that editing a .ne file requires rebuilding its .nes in the same commit, so future agents won't miss the invariant. Total footprint: 22 ROMs, 624 KB (avg 28 KB each — most are NROM 24 KB; two banked examples are larger). https://claude.ai/code/session_01BcCcHi6FUmTh8jC7UgkA3A --- .github/workflows/ci.yml | 33 +++++++++++++++++----------- .gitignore | 1 - CLAUDE.md | 31 +++++++++++++++++++------- examples/arrays_and_functions.nes | Bin 0 -> 24592 bytes examples/audio_demo.nes | Bin 0 -> 24592 bytes examples/bitwise_ops.nes | Bin 0 -> 24592 bytes examples/bouncing_ball.nes | Bin 0 -> 24592 bytes examples/coin_cavern.nes | Bin 0 -> 24592 bytes examples/comparisons.nes | Bin 0 -> 24592 bytes examples/function_chain.nes | Bin 0 -> 24592 bytes examples/hello_sprite.nes | Bin 0 -> 24592 bytes examples/inline_asm_demo.nes | Bin 0 -> 24592 bytes examples/logic_ops.nes | Bin 0 -> 24592 bytes examples/loop_break_continue.nes | Bin 0 -> 24592 bytes examples/match_demo.nes | Bin 0 -> 24592 bytes examples/mmc1_banked.nes | Bin 0 -> 57360 bytes examples/mmc3_per_state_split.nes | Bin 0 -> 24592 bytes examples/palette_and_background.nes | Bin 0 -> 24592 bytes examples/platformer.nes | Bin 0 -> 24592 bytes examples/scanline_split.nes | Bin 0 -> 24592 bytes examples/sprites_and_palettes.nes | Bin 0 -> 24592 bytes examples/state_machine.nes | Bin 0 -> 24592 bytes examples/structs_enums_for.nes | Bin 0 -> 24592 bytes examples/two_player.nes | Bin 0 -> 24592 bytes examples/uxrom_banked.nes | Bin 0 -> 90128 bytes scripts/pre-commit | 24 ++++++++++++++++++++ 26 files changed, 67 insertions(+), 22 deletions(-) create mode 100644 examples/arrays_and_functions.nes create mode 100644 examples/audio_demo.nes create mode 100644 examples/bitwise_ops.nes create mode 100644 examples/bouncing_ball.nes create mode 100644 examples/coin_cavern.nes create mode 100644 examples/comparisons.nes create mode 100644 examples/function_chain.nes create mode 100644 examples/hello_sprite.nes create mode 100644 examples/inline_asm_demo.nes create mode 100644 examples/logic_ops.nes create mode 100644 examples/loop_break_continue.nes create mode 100644 examples/match_demo.nes create mode 100644 examples/mmc1_banked.nes create mode 100644 examples/mmc3_per_state_split.nes create mode 100644 examples/palette_and_background.nes create mode 100644 examples/platformer.nes create mode 100644 examples/scanline_split.nes create mode 100644 examples/sprites_and_palettes.nes create mode 100644 examples/state_machine.nes create mode 100644 examples/structs_enums_for.nes create mode 100644 examples/two_player.nes create mode 100644 examples/uxrom_banked.nes 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 0000000000000000000000000000000000000000..71509e643f604e2eaf35d9bc62d920d7b819514d GIT binary patch literal 24592 zcmeI)KWGzC90&0C=k9V>2QOOF+Cff{(u#?-Q3(=65X3Qy7#CeTmDu(z;*$;z zM#vI;;41{gT`(z1(+dz~QBZIezL)U3-}}8^-sOE>=G?k|XM%H< zUvppD>GQF)z5B{C%@WNMeIm8eg^QY{+DmPyd8+-?vhyN|e^}OLEk zXF2WbbB{_Z?>#yGkgG+Po5hyr`dqQUvA^BB)SX4HO23W$X(m-;zfUc>=hEYR(@=p~ zQibGMS09mmp1B>q_{XOgUPor3>3(-jE}BA^MpKHdX!x>xXGB&fdZtKS#lv0kp)NWd z+AEz~JG>#53*$!K_mm90^+9H2?O-bKCZ&-xn_`(Jw`g*Qj<52x^0Hz?#mEX7$;1jh znp&<)DGf_$JM5Iwo0GgVMloC;e6w7!x{`Gz>Tl{R^)%hZ1px>^00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafK;Tb-3#;tIqt)`Pd19KgZzjomGx?vm)m7>!b|F$lrH4`i9SW7Oj3Nr2JnTi9qoB||lyw!J zU|~u@P$L92_+(#~J*-t!wC*-ris_-`px{kt@iG)Ro7a5T_QId%a+sni9 zO~ZPu%jn~nzq*VY%G@sVx-8RW*)Gdn8D6?QZ<*Sy@z5MhvLW@RP)c$(qfoXXv|%!q zwb_gfbJHBjh2fNdZVet=VjdRD@R(5o(4i+bqZrDkW+gsGNw7jE{s>O}|-A<`$y7GzC zm5zPl9fx-~*X54ONAmVcFv_EbZcjYTPv-8`?#rHXv)+Yas@;WuN?C8t)$abw{3I1e zRQyeQ3))Zb|A?*Uhxy4HM@{_r<37E6qx_JM3OYxuN6!btS7}mlC?;1M|Jomn)5Gy* z{}k0Xl}E8TKcDgB1m!`+Ny|OH{};z!S?5VM`krDg8WpKy$@U?$NgnUsqC=fD&>29=DFS-fwB%(rzn!vrj)R zH#9t{N5)BJ=;ff$6KgZk{G7A4i=wzN+@?2(wUygeyuH#s)sCVu76?E90uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<;J+5w*kYUfT=S5ALw61}n@je3mYUN)20#J<2tWV=5P$## nAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX?}|3=_%tyDb@ literal 0 HcmV?d00001 diff --git a/examples/bitwise_ops.nes b/examples/bitwise_ops.nes new file mode 100644 index 0000000000000000000000000000000000000000..94e82bb72437bec77fbe3f9d5d28290d40b74967 GIT binary patch literal 24592 zcmeI)&1(}u7zXh9NYZRUvXPp89Aqt0jCfG-AQJ3QsKgq)c=A?+Li`VMD%;itDXi9; z5poF*Izm9a2_9?%(iW7QM7;H2>8Y$I4;Je?X?9sb!K1>nEiaRKpP9`3*mG{q-kRW? zB~Rm1TRj+6?ZYRAh0F`N7oJc?+HyW*D$FR854j4n%CK|7bH5wbW-Uhl-27-UVQ3^< zEYo7y7R$BR&|YunDCg659pRbjFz3bb6LxggmMh8JaXXr{<)SSv-QGM5T*HE5AT^5)XvDPKVv(#( zthZVJ#(VVizEQ`~Md~;&vxe1ftQ#JQcSX(1el*f=*y%eo?XY0Ec6VJbN5k5^u4wy0 zU~?*!Ta9y%;+&V{+*IyWoO>PT+$86va-ZYePMq@xIk#L0nWV4hskpPzKc-)EH?$v( zc9`5Mtl9HVQX`W4OqzWE%p$eu>d~U*+Uy41ZQL%3HE#EpeHbir%h53=1GnsNv7X(v zVcRE7fXI>u%i93g_U5*Y|;fcQy0`rsXYzf7qWJt zOD|TCp|?r4^Vh`M(3qABnLy?;+r3E3`M8zKOi3*-e-Ufc+@ONu_0R6E}(sktfM8Ks)v?tL{pVD&)O>738&FX~Di;(-7JAOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1pW*}PTmCTH%vY844679tiR0ha?MNYLW`fiyz?gsToMwoz8DG;-Sn z2OJ?_VP$2mILK8gEwwOQC2M7&c+MndD;rDUy9vKMJTuR3_Oq$;;O_3Wl%hQLK8};; zo5}d>r4^ZoGZ|;+6RUk(&qR_1iLGZc$tsBz*Hj$-uwpE7;oj2z%7wD7(zyt7QOQL$ z7q!#*=heQ)?sO9s+dEk`cIS3+F01*&+N0+ zY-FO@r{(P7dbZVnKRx#S)$F_VY|BKi8YUX~M=+9Vq?8FqS}hu@m;0uvzqB6L@2P{@ zmeG%bR9AzOd13Tmy;Kc4+UQ$f)PY-ExW%s~81$a@Ui6IW8QpVFOUG7a8;O-!+x-Mw zjO&wja=#-dn=b1o^KTAI3{yQ!)$sN3%`i#MI1oSp0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z{z0I1BtGmOb$84YGur8P_xLY!SXl{&l*!bwLX zWC?xX6ABJ>H!X& z*A-)3k?V^5(ct~bxD8rvM+NSBlnZEW3TLA{XB2!60f)4fidTiK>} zhwVC2b(EqDt%k*umz8sVGETSF!FHckrE_FY>0VARNt0w!xBk>UW&CUx?|2uUON>wb zDmiccSf$(h${pj@U-(GFTg5%vm2W5Lp?L4LJ?fmcrK#1jDGkfyOx+mfIcarRjfRy*4r%) zwtC@%+M>=Ob$+6cxcRX8xM@|>>Lz`^b>LJ~37w2;^rNBBE9}(5n=^7}l49Xt@Rq;-?ZMVO09E~>ex z9Zo+T_xkR*k&Nx#tQxzsU7X5lesU4{h@;%^oQGN z%p%)+)3CjvCm05DD5Z&pO3oU`_oP3sSh4eX-v;xGHgeTyscPe;s);83sV@)Kl=Vt^ z;L9J@lshZsd2Lbux~AS=tvBBL(=Uuqe(qN*^+h>ZQ{G!DJHs?|aH^=^mxJ0lqn?DR zs)h&Cxlx_JTh*|ojJo|r4&3I3>uP<(g#ZEwAbdq!e*opn>Hz=% literal 0 HcmV?d00001 diff --git a/examples/function_chain.nes b/examples/function_chain.nes new file mode 100644 index 0000000000000000000000000000000000000000..6471e4862ef9cbed09611aa8bc86d9faa03872a6 GIT binary patch literal 24592 zcmeI)F^dyH6bJA(Gn-^%<0@zFylNE*cnFGEs017jlpsM6zd$U6Sb3$@aTZ%BjHEON z+bj+^LcovUUUj)DrLBb_mBh+I@qF*1tB7Af_-~d!Z{FJ-c7N&j?ydXlN>Oz@d0*HU ztF}1Y4df_wDs`u9FcOy>Sr^$5J7rzX2D+frXtfmB}f}sg_ecr^bBx zVR_vSZ#ULyxZ&z4@6B=CHKgK5BvOf^UR|n|M7!!Wr8+HfzUnEdPDq@odjHyQMW*K@ zu2k2pOSK_!z3QEm>a4_NiG<%iP?luI)8ZRfBvlyq4e)bzsCEJ8I#GPbfe_?Wd8HEEg8pQ&V!pWSmrTaK9HqTX6+F_gWR}N z_6s@!p7SyN!Vl97YL@WmS?RP1Td$g7Yb5?7rAJziwHX;bYc78w=5hSTm>0jN{4!a@ zJDMWCt%r>@ZyrXOsYeIXg*W|Qqk7af-dz2thdi9|@Dsmn_j&ha*Xyn~UH&Ic8nj!n zLAjRYWy==vxMlCO)p(UNKA3(EDx*PW2ALkb9=sV?dx!%95P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1pX4ZxJU0E?{zl)Q$O15bhc%@-KjndfC2&#fB*y_009U<00Izz i00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOL~?M&Jiiye}01 literal 0 HcmV?d00001 diff --git a/examples/hello_sprite.nes b/examples/hello_sprite.nes new file mode 100644 index 0000000000000000000000000000000000000000..97eaccfae1fd468a68bcab0f28c6df9e5cb1e140 GIT binary patch literal 24592 zcmeI)KWh|07{~GXv$;Q5xGV7zgT*Qmh*$)%2y);MP!16r-#{ASd8<0hTA7$CTdS;AOSS6Ms(Ui~ zw770R9XB;)yyH7%Iy=RA-<2vwE>TLH?RBIYNUXPeeW@-%Znt|Eq`E3`QzB1KANQ*~?p8%ql?`hCGkvOH->bQOaQU!NXU}|+*6e$l z^n>2e^KQsjpO`97jge;S%c99rQ^=%9lb$p(DMIn8ABu_4O|+R96Y_~Q$Nj}C-KEOp z=1uROIq0qi`#7s?Cp((WgWdTv>SXIS*sagzAPtYx@N39+M$bksM!}4N9VKO3M&p-V z)P9hDx)e=sI&k;b^>ig=?`ZZd>Pn3(JFd+5_4v)$xluB-i z^xTUuZl-d7&o=WgJH$ELidBd#Mjqp&*Njy^#-Q$XVs$abwYrzYsvF~S-TT|TjktR? z#_jstR;;eaxLfynvAPmtJ4P0s-b<1Br$sk>_X_P~^($23^e0*M9$lKd)QWcNYiq^& zO!T;t6NIC%7C*wl&axLCu#tz$^@=pVIWIETecN%}X{4GaYO0jWrdlmKr=PPsgxN(J zCg`gDK<%|IIQ=XwbTd6%tehU7Z8g(@cKXg&wHG#*Ve^|y$D>!HHzTJ;PLIOPb?%kj zFg~$;6Mpth)t>du!$C6J4B0+heD_HqlR{4lH945Pn;3Jp5K_Mp(f>19d7S@Bf z(xVY_i5+kX1^)z-LLoiHn^zBpUX&FC4@&EMn|4W{{R0Z$O?W$b^JeDlr^&Ty2Y-zW~(BH>52Z=A^Tqbc8rE7HsVHKHThg5H+dZwqN? zq}sx6i>bEAv_*Dr@NHDrr0uq(XKqCqkM^c;Cd%@NLY{-iA<1QURN*k2=5jnb$6+qb zSv)FmIG^T@+t20MiyW?}bu&Dg=WsL46?t@l!vcpqJ-xBQyb)o+JPwOyB`nd{4a;FA zR9eLU=#$W@FJcwUcRL~x*rRLkhh}oRLNmEfQ$_?2yT*#;PDNXD`PfY+IQ@*$IT5Y} zFS>dyF6ly?rGm<*>d-2f) z>WPp3aa8l0YdkuK8XOksE~!zzJ9ID6-KKnLSf=Jwu+Y=_!ws(XlHQxthYB9`bh%$m z9(9TGahWd%6C){;sZNU8_3%K$K@>n zD)g(P&~dgZ{+(HCYnr07JNQL^VbKUxBb1Ggjm?JdAK-!j1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00d66z^iTXd1t$}s9);VVy(8y&$3!e|1kgt5P$##AOHafKmY;|fB*y_ g009U<00Izz00bZa0SG_<0uX=z1Rwwb2%H*$|4aa4IsgCw literal 0 HcmV?d00001 diff --git a/examples/loop_break_continue.nes b/examples/loop_break_continue.nes new file mode 100644 index 0000000000000000000000000000000000000000..a3792b989d686af3636b6cc19d2d625d18d38a5d GIT binary patch literal 24592 zcmeI)&ubGw6bJB`d7E^jQIobtEeh)oiWLtc9;yUf1SLq(gBOoED8zq2dXlEH7jdLV zBLpQl;1mKLyjE=effWxui6=p%rCubSJSggWyENqBkDDNTx8d!~n|<>ppX|BkPG1<) zTJ^8#x1IRbj=1yVj#pMiR!4Sw>`B5At703(KD0Wvx!9}2CMtdPs-xP9KiYq9E8}@Y zZ56atuC4NIHMG9+K6`G4x9gh7pS1ai_vWz84#|i^NkSwc&E;iOkucuR6=bwa!bCr( zWmJ~1x1amfextH_K*F*9b7L|(B;iCqH!P!l5{^nJ@wfNdDzcjRoQ8Np?ZP$>}K-QNhv9Nl?*2%2|3mK#$)AZS@oi+i;FIJp6eAJ z=DeDkUi7+`cfsUpuI_S^t3kb^Qp|O}R=#jq!RZOzGixQjo1S?g_kibRzK1+d+t?y? zi?kf1d%l)EY3lqEr{))wbd^>gBprJFsYp|g7t^(Xk{MS@0ymT7qAQDbi|jJL5K_`~ zWllG<^j7L_q2=P7>@u;$yX@|6-IyPjdC}`+m`jrp?z+*ztF*aJo7d&B!;<2jvvQXv z_Y1Dsk{73?P0JWJ+aj}8$o{+@D8HMPbBfEGvc%=HTs}Kk-W)791Lg0sa!#e)`Tp1L znOt`GJx|lab1Lng3H`CVEA!dPgJ&xZoXrg{XB%IYjrY6qua@EZ7;uPh8U^^|M6)~Rasn)JR_?f+u{3?Kjj2tWV=5P$##AOHaf iKmY;|fB*y_009U<00Izz00bZa0SG_<0ucCb1bzaJdL}CX literal 0 HcmV?d00001 diff --git a/examples/match_demo.nes b/examples/match_demo.nes new file mode 100644 index 0000000000000000000000000000000000000000..772ecae55dae569bbaaf2ab0cab3a6e3699d344f GIT binary patch literal 24592 zcmeI)ziSjh6bJCxna%yE+}k@ZF*a)mfvAXxMUVrBfO>>QBKZeW7`PNd%2aX&wh>m+ z=?Q0XWWW&u))r#!P%zb%wkZs;k+rf=jPLE5=}AC2qY;ZrMO5yVaqpVn4!xEu=|yV{mwN5^B@iiWnxuE z6&gq$%zerRE)9IH_*5zJfGfW7x5~yJX~tBJHxrCBGu{8y%!Pj1jLe#$4fV*Xg>2PE zc&BeRsAVY#M{hrJDN()BO}SZ|Flxn(RKeXHCPpnEl?v{RGHULl+@#_@ z6+atyx%sg9q-kW+s3!dlsKCm&rmYj#H4W>!SK6-Yg&AjioMLHr_=R4(Xho_O$=37M zi?-K@Fwo&{Z%?B_i&fBo4O-S2~(X zmf#D%Lcu@5v`f<|qoWRn>QK(fL9xDX(guG!3*R@%!*lQR-h0n|?#Rt+t8>aLh?8-| zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z0D+_R*WGAiHtHU28Ig!E@xtVEWRwdR6A>kTWO9iYC7H;G3o;Bo8_^XVVZWaF+7Z&& zOm&3c5t)w2cEr@d;O%%_%Wh|EXt|AB#``h2xosJmt4TJr3v4)*Ou5X4z=hC-(OlML zWf!WGxxCBHxUevp^ITSN;p}AYcl(uG^_&ZrC+p^1cHV_6lewbH=3ThtLSSu76bEK6 zE*KFP;~C?{C7Y@6X-|vvi%o@AeUYyAy@6V}y|Z$or~SIl#IhM4?lkmF-%B$_tgQEp zmxjZlE>zZvaiPEW(#>wT+3DlmXX0XGaxr_EMpulu59}CxvRBG$Z^28;M#jD_#hLI< zFA+)yl?Ot_fvw53qJzFn^P{~#xV@)zu=rk%s;zX^b|XgJWOr;WXYXakWa46Yzh}=I z9-lYwYSXr+!ds);DLl*TLfbv|wkO+C>Y%OUFhBm-`!&$Rl@)knd$QawX{;QEVNW`1vpMH(lcUyFG*X8C^MXNPGR#|_4kZLt4W&NtsYVn=i zw8fz<9%{c)e_Vf3*Rrlv-M*qKG%_hg_LVM2_OKjvbKB);x$14t+L+rPe6(Vs8LOs! z^3R(uno-0*4FU)tfB*srAbk>aEbQWPi=sFR_QHe6H`DmOF*B|?=84J|l<|J6{By2QIMKf2@?&SaO| zF6Fx9bt%6We;6Ov=F1sRQJXI;+9q!^VKbrVg`(XJCotEP7jT=y36-}Q2j7OkhP39{ zY}$s|tX8zyX&dIVny^{PhO=4ixa(DH_q+|4v*TuLcEN_LS*>idb2ePE!Dksmksp8<3!T6MBk5a5X-r{|(6-QGef2*W@ zm9uxvVQ#%><~r81Bb<)o@CWIPisP4A;qJ=O?$VL2F7tO5-{cH6-VKaMoUO7J^L|oE z8@Bw3%SDRXjbxzP$)piHzn_kGCMxrkx^y^4^DhtlczEor%!Z?ALC}^^69cyxZ$kkPigtUjil%9#EF(!%X4R?mRCMHtK1xM^RsrF z!S`W0vv%rQ+R0^L990SVcQMTqap)B`ro&saVx!8juoHjb1*29ZTah!KJ3PP!0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009X6R{{?=sXrLj7WE_DS*+ET?aeLM4*wSb?+}0h u1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SNp%0zUzmBXP_C literal 0 HcmV?d00001 diff --git a/examples/palette_and_background.nes b/examples/palette_and_background.nes new file mode 100644 index 0000000000000000000000000000000000000000..d65391d3ce7f538350e4e46a94f5b5990c5bbb57 GIT binary patch literal 24592 zcmeI(ziSjh6bJD6vAG{8tQYUcu8OQd5`!sH2(dVH2`GmI8?mvFG{RkFC8P>xF^#a2 zCg2GNVR66_0{R~$7Qvj&ijA$HM=DthW1$$|?3p7G0yZ|uckdW>-n=)n_qi)xb2qQu zI>k8~UJD=M@Nq4Scb*y+u^{3>R0)l=!*s;L$O%m$;$bur8a5?@6F&@#S(nk9n_pcf z4Bg2tbGmG#%W_?o-%36X^4hfBstC;0C>PM)G~8CgyjF6By%JGAc(WEH+cqH>CM*vU zVrQ6=CfizgtyBpuTTIDb@+sS9W6w5WvTe@NgBzwiZQgk~V%VIsZg}e3(!Q_#O((TM-v%8D`jp4B&=iFdIrnXyl2{hGVw1+` zX-4Tooz$fwS0%Ho$~3D`v}!g~tteE93Ia2m!w+G?=Iz> zajE6y&ti!d`?UB)JM%6topZ(Ln0x%plzZ&-#cW=kn0CwOFJ<#wb==L@CftH|!7cie zuAT2$AO1RV0|5v?00Izzz>yJXJZd~?XwlHJL7k%vjEKsiE~R%TsaM8T1-`_lceHmwq`p>M%^2as?_Gv5EoXf;f-;=TBEVh zOTHSeSX0TS63yq$7tJu-!36;bKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG|gzX>e&*xJn7`mBDaJG1rr zqCIG_eq{b*h7Sq>2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uVSv G0>1&`5Xc4q literal 0 HcmV?d00001 diff --git a/examples/platformer.nes b/examples/platformer.nes new file mode 100644 index 0000000000000000000000000000000000000000..0ed5185196d0cb27f2c24d212db299957ea49af3 GIT binary patch literal 24592 zcmeI4ZEO@p7{_OK?qzrH6)ElZeRDxd4aQ=KHs(TC5=?{VOEefi5IqwKtuYY?8iMyY8`ib{+_GH0tJIJMFgSVwT257@$Ka=?+ zqx;B*a>{EQ8#c1%g@aLY7%w;sxJoW|_PizQoi>v#+DRCl%Kpk<1+S?x88dsHH^9XC zKBjYtRdd2>SS_n#^{jz4GHNuBTExB#&BUS^lBKbgqZtWazl_Fm!&Y@hvfZ4-f+JkA z4en<(;91RTVg0gLJT>_S=7)&~c%Er-t0qHi^#c)Miim}lo}rHUh%hxvZ#_eq9(#HO zWMDp%HL<=COV`)dGTo}pxOzq8R^ck=Y9V%?6Z?i}@RzNiHqNE}s-^bjnkU*x)coh(?2Z z_N%;0jO!_l+f}}b`b>qRkrwV63`dQxoqdypV^)vdBsSjmIz2muXC-_KirMz^m26A9i+b-D^uR9Y z9WCmOU9I=S&Gh7=-sOTG*abcR`0V`VkHh@#hfFPI`6B4CTSS_KGlS0BT186(-&dM( z;>wJ<$NB!$8hc0hV9GRX<)(e-m0oPNlt$dMT-3C(6}D*7eTGIzV0KV8L#RQ_F2YUNIMB03A>s zkbAwlMc29*^|+?8wngK`E{|&wwP@=pxfeQf(0SH!#VyUQg_F#m^*d)Nh-k4_Cg4=f z`Ll8^V0j_qwoLl68^I}N83Cr@h3{-WboI} zlny=={5{Yghy_UCuz!<(nSa{%h3|FW6TU^hi`qBZ0d2dsM!Qd|(JrY!tK;f>>RakI zb)EW{+NR#A)~SB=59K%Ir1HIzQHGR53Rm7y_9(lRok~L4qO4br9{KFkPd@&Le17!s zA(BjN+uF5x(~BF(?OL<8L|Y1*35!Q_s?j)NVhna6?zkN^@u z0!RP}AOR$R1dsp{Kmter2_OL^fCP{L5s3s z$A^ar2{pDsxjYlxF&z_sfn{G79!dCsae{<(l1#4i&J=7fQFJmhlRFVjY|i&3hWZH6 zTN5IT=nEI>@Qfy)MD_d;2*?i!jpr^Dsu%J@PEA->N>5KuJ{%5{y5-4a*V(QS`Cs)S4P+U8(v) zrzI}56zkunMUIQI6lI zU3W&dWMZ%FQo_HS3V%L_ZtkFxJX%!;%N%n@!yjIy)J#n?)ze)jr#K)00SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009V`8G$zg>Yp3bHr%djZ`5i#vhkhT?0*694gm;200Izz p00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fWX-i_zSQL?hOC{ literal 0 HcmV?d00001 diff --git a/examples/sprites_and_palettes.nes b/examples/sprites_and_palettes.nes new file mode 100644 index 0000000000000000000000000000000000000000..e307e0a56615cdd2ffd246575da18cd4ccdf0916 GIT binary patch literal 24592 zcmeI)zl#$=6bJA(Gn-#F?uIiLk3&|Gz=?&3#UTNQ10@_{<3DhX690p%j+0{=VdP39 zY_mAv2>BoMI;K)+aVLy|TV$Bi+_{`a4od1@TwKc|kgIWeQ3Psuff(sBt#= zy4-ij$BlI!ANYFCXLDTk4XHR%iCp5mSC^_I(W`nbsjfOm9lu ztM+Y3bxY!Y)oV+2L*lMP%5NVJT^jdY7Rj}p2{ik|6$SH|X6Es=PbHnd^%-B2Up#3A zwWVf_kak~am-0f(V*C4Yk?b2p@n2^0|g>1y6YNXh8 zq;*I~Mo(MIkNh}wN4)7sHt7pD@gur~pUafZNBX$Y4dz+mOg%ZB%!1iljp|9y1hf53 zAM4(COj( z;fJBM3tSL@00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_@Gkd?+e*w5mclL9A9zS?8t+gty zl~0{`VEs$(V!d$VYCA%o$ zO0jm{{l;bYnuI$=-?U^mB-}05E=hJ(!fgp5zr9mQm{3&hrYirzg-WTX@`;}A)53nY zXZX*?_QF(`-$PTQqXr0veD%L^cW|W1Ep7#Fu_ZJuZCb{-r52fk;N+8O>dkD9IJL0a zbvmDM>)gHmukKnNyqErW($5J$H`Q}tUUr%X-R<%)GS8az$?e*JxT8#sv+@5Nkq_%U z$b6byHhhLlGbpQHPD`K5fTRp$*Bg}59MDmjvNH8PoQ`yVn@75shLg+XBy@(yc_B7@ zDB|%H*HxK~@3tkk9C}NB{1A%Fuk6@)o@3;eSZHbJtXmm6nvGF!<#e3Bj5G9Hp5u3> z#FMc){zJ1lI+@Z_FQJmR*UwXGoNbl7SxTw;*=+LW0dF3qUZei9{;HmudP;Sklp<&B zc+l=F5To8Z&1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00jOjaD7L8e6~}YPoJl)`C4sN8dqz@KL$Vo0SG_<0uX=z1Rwwb k2tWV=5P$##AOHafKmY;|fB*y_009U<00Izzz<(p~3;j2fe*gdg literal 0 HcmV?d00001 diff --git a/examples/structs_enums_for.nes b/examples/structs_enums_for.nes new file mode 100644 index 0000000000000000000000000000000000000000..553f6d05bccad2de26f885d8e6c66ebe081b2eb8 GIT binary patch literal 24592 zcmeI)&ubGw6bJB`d7EU{9E?&^?P(PWR0Kslhy+{;N|5*i{0Dka$kAg@B_8%7OwG{< z3$k>;DeRvhnBH1Yauhsyu=EtxlLy86UP6~1{0GGECVcZ|-ez{@L(X}4_sNRZs=78l z=gHP`k{`YC%BtAv*q%!~NnE!ou|eWPs}oyGyt-=Q)=#f;l_`E}|2tF0b48g7GF8h| zJyVUt>DR@5Ltd^hvA<*MF|W;W+BT%(Xi3Bp^SQcIZHZ1b7fE$NVzrvnQk{{wSk3)y zy(Q^hmbh8nw<6UQiQCoOS*b2b+>mJT>HUUP`zNa>&LBn&>=^+zL^m> z54bsV!Fumy?{&|ao}(V`h+;nGQo<)_^UsGP4~uqkzoUy~&TxPF-Ro2hQW~UY@NTd( zNRlI55P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1pYgLN4x67tKIIJd*Q}w-R`CU zY<8M$d(%+ z&EN&EkWXNo`?x%=N2eR9*gr^$F`b*jrKH3ag$jq5!(y$-Rg=R;zEnO>z}+xSFs1 z?R<6a-r#UAKW?3?TO97^YgMkUak$H&LQn6Ft@vj)@+Y=Rx#oqf+lJLnWZ$V4PM?ZQ zcW#bTv3UM-+Gby=Q+9GwW$Jx}vdq++b4dFU<$(4%_h|CPm%O=Q;EIyD=3!-*dgie4{hv`G$*Lmt8dApJX7_Kq*%lXf-b{KdwgI z+G@AD+O40;tl&kbHt{mM?xL+vp*^69gVl*Y&e!-gv9p}0@kE~{`l}PG*B~<+(2eRe ze5j5}mz{nZSX~UJv&`w;->qV>p`G43Q%BUCQ}eS6cH4*Tb+K{6)is5wjg;Yh?>aJDYH{G{gV=k~k00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|_=g4DM4X&WTHCJg2HUOH9^dL-EB|8v6cB&_1Rwwb2tWV=5P$##AOHaf eKmY;|fB*y_009U<00Izz00bZa0SNpz0zU!rqvaa_ literal 0 HcmV?d00001 diff --git a/examples/uxrom_banked.nes b/examples/uxrom_banked.nes new file mode 100644 index 0000000000000000000000000000000000000000..dbff6c6b715ec46fa3284497ed33b1efc1101f10 GIT binary patch literal 90128 zcmeIyX>=1+7{>8COVY%RNhmE;#R0KeL`6ic5M`)>5i5$guXRD=j*42h3d*XGV`0TNTl4 zv2k^>5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILkOGZM^Um~2C*L_I5T39-&GvdYfuUlcCmhdof|#c{Uc?E+0Nu9M1R^hT zLVmSiT~6qMtc;v6b0U%x(VQsjC@kxmn<$TK(d}TE7q#WtGP-$Xs*+);uvKi>i>j(Z zMRjQ}uBu)tYD#;WsuC*tl=lAZymHm;r($qvu1Zz?RSYTZB~{f|#UK@yy!)WhwSw_( zA`ouUHGC1Sm6h;~HW3>2y<78Q!(DgCRahlE@nX_XwhayA3nGE2AJZCWp{aiAYc*P^ z7cF_!!|uM-ru{QAL5=83RKt`533-jlfkbIwR@XqnPt1+`iL4UNYI;`JeJg9|#dz08 z{uS5q!p;Vv>4q7Jmc`cIMgMjw30m_x@NcK8uT z9(D9F#~ye52`4t3bn+>ujyvu2%o%5%b@n;uo;Uvd3oe{+(Z!cEUV7Q(S6tb2)x=3x zUo+Xg_PXnDm@;*mH~q%u88dIXdDbnr&d$!c?e^Rqciwe({+_x1z4y%v=HLH7%YzRs zc=(Y=AA9_XC!c!ynP;DS{)HD8wk}%y(#uO;d9|(mwb$Qx^R2?$@4Wlo`yYI`^rMeI z`Si2TzxZ<5S6_d#y!dU$ci;c;<4-Go4p;v2>uj_qtA3aU1#ANWHnN zr*DiiT_b9?7ee1?*w~7i)rN1>F4vo7vnZP@e6t}vB|SUs>uKLe%SY0%1Klfk0?n(C z&$*0ztfj&kRjsvD%8a!aRtB0YGOm$v^~}7?{EXx9_CNpu1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009L4GlA8M#KPLesXBj>pRG%!n$*EfDZxSj0R#|0009ILKmY**5I_I{1Q0*~0R#|0 T009ILKmY**5I_Kd|DC{k_Il-0 literal 0 HcmV?d00001 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." From 12b54a715e2ab7e417d27c2cb14a753ab8e36335 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Apr 2026 15:21:38 +0000 Subject: [PATCH 2/2] ci: emulator job must still rebuild ROMs from source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit wired the emulator job to read the committed examples/*.nes directly, with the rationale that the `examples` job's reproducibility diff would catch "stale committed ROMs" at PR time. That reasoning is wrong and defeats the point of the emulator harness. Failure mode: a compiler change lands with correctly-rebuilt ROMs (passing the examples job) but introduces a rendering regression that flips a golden. The emulator job would catch this today because it runs the harness against freshly-compiled ROMs. With the previous commit's shortcut, the harness would boot the committed ROMs — which the PR author just rebuilt to match — so the mismatch wouldn't show up until a second commit touched anything else. That's exactly the kind of silent-failure hole the golden harness exists to plug. Fix: emulator job rebuilds the compiler (toolchain + cache + `cargo build --release`) and compiles every .ne into the workspace before running the harness, same as the pre-ROMs- committed era. The committed ROMs keep their review/demo role (clone-and-play, diff visibility in PRs) but the test job always validates the working compiler, not a frozen snapshot. CLAUDE.md updated to match: the harness runs against whatever sits in examples/*.nes, so iterating locally still means rebuilding the ROM(s) you care about first. https://claude.ai/code/session_01BcCcHi6FUmTh8jC7UgkA3A --- .github/workflows/ci.yml | 23 +++++++++++++++++++---- CLAUDE.md | 30 ++++++++++++++++-------------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72eec96..11a14be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,6 +97,8 @@ 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' @@ -122,13 +124,26 @@ jobs: libxkbcommon0 \ libxrandr2 \ libasound2t64 - # 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: Build compiler + run: cargo build --release + - name: Compile all .ne examples + # Overwrites the committed examples/*.nes in the CI workspace + # (the workspace is ephemeral; the committed ROMs aren't + # touched). This is load-bearing: the goldens must be diffed + # against what *this* compiler produces, not against the ROMs + # an earlier commit happened to ship, otherwise a broken + # compiler change can still pass the emulator job as long as + # the contributor forgot to rebuild the ROMs. The `examples` + # job catches that second case via its reproducibility diff. + run: | + for f in examples/*.ne; do + echo "==> Compiling $f" + ./target/release/nescript build "$f" + done - name: Install emulator harness dependencies working-directory: tests/emulator run: npm ci - - name: Diff each ROM's framebuffer against its committed golden + - name: Diff each ROM's framebuffer against its golden working-directory: tests/emulator run: node run_examples.mjs - name: Upload actual + diff PNGs on failure diff --git a/CLAUDE.md b/CLAUDE.md index 3be1741..4df3038 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,29 +70,31 @@ tests/emulator/ The harness is **separate** from `cargo test`. You have to run it by hand: ```bash -# 1. Install node deps (once per worktree; node_modules/ is gitignored). +# 1. Rebuild every example with the current compiler. The harness +# reads whatever sits under examples/*.nes — if you want to test +# your working copy you have to rebuild them first. +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 -# 2. Verify every committed ROM still matches its golden. +# 3. Verify every ROM still matches its golden. node run_examples.mjs # → "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) -``` +The harness always runs against whatever sits in `examples/*.nes`, +so iterating on the compiler means rebuilding the example first. +CI's `emulator` job does this too — it builds the compiler, compiles +every `.ne` into the workspace (overwriting the committed ROMs, +which are ephemeral in the CI checkout), and then runs the harness. +The committed ROMs are a PR-review convenience and a "did this +change affect codegen" tripwire via the `examples` job's +reproducibility diff; they are **not** what the emulator job tests. ### Updating goldens