From 57faf9e36aae731e4b01aec9cc1fb42c7e96e1d3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Apr 2026 15:12:18 +0000 Subject: [PATCH] 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."