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

4 commits

Author SHA1 Message Date
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
9137b1f713
examples/war: polish pass + README entry + plan close-out
End-of-implementation polish for the War example after the
compiler bugs were fixed:

- Title state now calls draw_big_war_banner instead of inlining
  12 draws — same pixel output, fewer lines.
- P_WAR_BURY redraws the previous round's face-up cards while
  the noise thumps fire so the table doesn't look empty for
  24 frames between the WAR banner and the new face-ups.
- Drop draw_word_war from render.ne (orphaned by the BIG WAR
  metasprite).
- Refresh comments in background.ne (now references the real
  felt tile) and deal_state.ne (drop the stale FRAMES_DEAL_STEP
  reference now that the deal pace is hard-coded at 2 frames).
- README.md and examples/README.md gain a war row.
- PLAN.md marks every implementation step complete and records
  the design revisions made along the way.
- Refresh the war audio hash to match the new ROM (the title
  screen helper change shifts one frame of pulse-2 timing
  enough to flip the FNV-1a). The frame-180 PNG is unchanged.

https://claude.ai/code/session_0143dTgh3UeRrtfHgQwzcv5z
2026-04-15 16:08:03 +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