1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-16 12:12:04 +00:00
Commit graph

4 commits

Author SHA1 Message Date
Claude
20a244b9e7
examples: regenerate ROMs, gifs, and goldens after codegen local fix
Commit 76d0fd0 moved function-locals from a codegen-minted
`$0300+` absolute range into the analyzer's zero-page
allocations so inline-asm `{param}` substitutions resolve
correctly (compiler-bugs.md #1). Observable semantics are
preserved — the analyzer + codegen now agree, and every
primitive that used to work still does — but the emitted ROM
bytes change whenever a function reads or writes a local,
because zero-page addressing uses a 2-byte instruction and
absolute addressing uses 3.

Consequences that need regenerated artifacts:

- **Twelve committed `.nes` files are stale.** Same source, new
  compiler, different bytes. The `Build Examples` CI job
  rebuilds each example into a tmp path and diffs against the
  committed ROM, so any drift is a hard failure. Rebuilt all
  twelve (arrays_and_functions, bitwise_ops, coin_cavern,
  function_chain, loop_break_continue, mmc1_banked, platformer,
  pong, sprites_and_palettes, state_machine, structs_enums_for,
  war).

- **Three goldens drift by one animation frame.** Zero-page
  addressing shaves a cycle per local access, which over a full
  frame handler shifts timing-sensitive sequences by a cycle or
  two. war's dealing animation and platformer + pong's audio
  tick stream catch the shift at frame 180 — war's card under
  player A's deck is now one frame earlier in its slide, and all
  three programs' captured audio buffers start from slightly
  different envelope positions. The new goldens (`war.png` + the
  three `.audio.hash` files) reflect the same code compiled with
  the cycle-count-corrected primitives.

- **`platformer.gif` and `war.gif` rebuild.** Same one-frame
  timing drift, integrated across 360 frames of captured
  gameplay — the emulator job's gif-reproducibility check
  wouldn't pass without the refresh. `pong.gif` happened to
  byte-match the old capture after rebuild.

All verified:
  - `cargo clippy --all-targets -- -D warnings` clean on both
    rustc 1.94.1 and 1.95.0.
  - `cargo test --all-targets` — 616 + 3 + 75 tests pass.
  - Full emulator harness — 34/34 ROMs match goldens.
  - Committed-ROM reproducibility diff clean — every
    `examples/*.ne` compiles byte-identical to its committed
    `.nes`.
  - `docs/{platformer,war,pong}.gif` byte-match fresh captures.
  - SHA-256 of "NES" still computes to `AE9145DB…4E0D`.

https://claude.ai/code/session_01FRmSBruVWCufm3LsUVMs8v
2026-04-16 16:12:46 +00:00
Claude
e10d09db76
examples/war: redesign card art — opaque bodies, 16x16 pips, checkerboard back
The first-pass card tiles were riddled with palette-0 transparent
pixels that let the felt background bleed through every rank
glyph, small suit, and big pip. At arm's length the cards looked
like they had green specks eating them. This commit rewrites all
of the card art from scratch:

- **Opaque card bodies.** Every pixel inside a card tile is now
  either white (palette 2), red (1), or black (3). No `.` values
  anywhere on a face or back tile. The green felt only shows
  outside the card rectangle, where it should.

- **Readable rank glyphs.** The 13 rank tiles (A, 2-9, 10, J, Q, K)
  are now drawn as bold black strokes on a solid white body. The
  "holes" of the letters (e.g. the triangle inside an "A") are
  white, not transparent.

- **16x16 big pips.** The big centre pip is now a 4-tile (16x16)
  shape split into TL/TR/BL/BR quadrants per suit. Previously it
  was a 2-tile (16x8) half-height strip that looked cramped. The
  TL/TR quadrants kept their existing tile indices (28-35) so the
  shift is local; the new BL/BR quadrants are appended after the
  BIG WAR letters at tiles 88-95 to avoid renumbering the entire
  alphabet / digits / UI tile range.

- **Distinct suit shapes.** Spade is a smooth teardrop with a
  short stem and base; heart is two symmetric lobes with a V
  bottom; diamond is a clean rhombus; club is three circles
  joined over a stem. Side-by-side they are unmistakable.

- **Clean checkerboard card back.** The old card back was a
  diamond lattice that had the same transparent-bleed problem
  and looked noisy anyway. Replaced with a crisp 2-pixel black-
  and-white checkerboard that tiles seamlessly across the card
  back's 16x24 footprint.

- **draw_card_face now emits 6 sprites in a rank/suit + 4-tile
  big-pip layout.** The previous 6-sprite layout was
  `[rank][ssuit] / [pipL][pipR] / [blankL][blankR]`; the new
  one is `[rank][ssuit] / [pipTL][pipTR] / [pipBL][pipBR]` with
  the bottom row carrying the bottom half of the big pip
  instead of being wasted blank tiles.

Also:
- New constants TILE_PIP_TL_BASE / TR / BL / BR replace the old
  TILE_PIP_L_BASE / TILE_PIP_R_BASE in constants.ne.
- Refreshed war.nes and the goldens. Every emulator harness
  test still passes (31/31).

https://claude.ai/code/session_0143dTgh3UeRrtfHgQwzcv5z
2026-04-15 18:31:15 +00:00
Claude
4e8e349d7c
ir: clear wide_hi between functions to fix 16-bit op aliasing
The IrLowerer's wide_hi map records "this u8 temp's high byte
lives at this other temp" pairs whenever a 16-bit value is
produced. Both lower_function and lower_handler reset next_temp
to 0 at the start of each function, but neither cleared wide_hi
— so stale (low_id -> high_id) entries from earlier functions
leaked into subsequent ones.

When a fresh function reused those temp IDs for unrelated u8
expressions, is_wide() returned spurious true and widen() handed
back stale (lo, hi) pairs whose hi happened to coincide with the
*next* temp ID fresh_temp() was about to allocate. The result
was 16-bit IR ops (CmpEq16 in particular) where the destination
temp aliased one of the source operand high bytes — for War this
made `match phase` arms past P_WIN_B impossible to enter and the
game would freeze with both face-up cards on the table forever.

Fix: clear wide_hi alongside the next_temp reset in both
lower_function and lower_handler. Adds a regression test
(ir::tests::wide_hi_does_not_leak_between_functions) that
constructs a function whose body has no u16 ops but follows a
function that does, and asserts no CmpEq16 op aliases its dest
with an operand high byte.

Also:
- Convert the war Playing state's phase machine from an
  if-chain to a `match`, which is what tripped this bug to the
  surface (it was lurking in earlier ROMs too but their layouts
  never produced the dest/source collision shape).
- Refactor begin_draw_a/b to set fly_card / fly_face_up via
  globals before calling arm_fly, since arm_fly only takes 4
  params (the v0.1 ABI limit, now diagnosed by E0506).
- Hoist the P_RESOLVE comparison result to the global pf_result
  to dodge the param-clobbering issue documented in
  examples/war/COMPILER_BUGS.md §2.
- Document the bug as item #6 in COMPILER_BUGS.md with a
  minimal repro and reproducer-test pointer.
- Refresh the war golden + audio hash to match the new ROM.

https://claude.ai/code/session_0143dTgh3UeRrtfHgQwzcv5z
2026-04-15 15:57:26 +00:00
Claude
8ababdcec4
examples/war: working end-to-end War card game
A complete, playable port of the card game War: title screen with
0/1/2 player menu, animated deal, sliding cards, deck-count HUD, a
"WAR!" tie-break with buried cards, and a victory screen with a
fanfare. Source split across examples/war/*.ne (constants, assets,
audio, deck/queue logic, RNG, render helpers, and one state file
per game state) and pulled in via examples/war.ne.

Drives nearly every NEScript subsystem at once: custom 88-tile
sprite sheet (card frames, ranks, suits, font, BIG WAR letters);
felt background nametable; pulse-1 / pulse-2 / noise sfx; looping
march on pulse 2; an 8-bit Galois LFSR PRNG; queue-based decks
that conserve cards across rounds; a phase machine inside the
Playing state that handles draw/reveal/win/war/check; and an
autopilot that boots straight into 0-PLAYERS mode so the headless
jsnes harness captures real gameplay at frame 180.

While building this I uncovered five compiler bugs / limitations
in the v0.1 implementation; each is documented with a minimal
reproduction, root cause, current workaround, and proposed fix in
examples/war/COMPILER_BUGS.md. The most painful was the
parameter-VarId aliasing one (#1b) — two functions sharing a
parameter NAME end up sharing a single zero-page slot mapping
across the whole program. Once those compiler bugs are fixed, the
workarounds in war/*.ne should be reverted in the same PR.

https://claude.ai/code/session_0143dTgh3UeRrtfHgQwzcv5z
2026-04-15 15:22:20 +00:00