From e10d09db767231f9853b0a6d2462520fe91926c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Apr 2026 18:31:15 +0000 Subject: [PATCH] =?UTF-8?q?examples/war:=20redesign=20card=20art=20?= =?UTF-8?q?=E2=80=94=20opaque=20bodies,=2016x16=20pips,=20checkerboard=20b?= =?UTF-8?q?ack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- examples/war.nes | Bin 24592 -> 24592 bytes examples/war/assets.ne | 727 ++++++++++++++++++-------- examples/war/constants.ne | 13 +- examples/war/render.ne | 37 +- tests/emulator/goldens/war.audio.hash | 2 +- tests/emulator/goldens/war.png | Bin 10139 -> 10327 bytes 6 files changed, 532 insertions(+), 247 deletions(-) diff --git a/examples/war.nes b/examples/war.nes index 954fc65a7b29a1cc9617feb22fb3598f6c38eb8e..daeda7e0cc086ff42959ec4c60c4c6fc96990994 100644 GIT binary patch literal 24592 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 UL%>78L%>78L%>7e{|5s93jiPi?EnA( delta 4111 zcma)84Qx}_6@Jgpzt4#iLkNzWkVi;J0to~L6Qw3`Ta{7dv9#GrrD<(ewF9;en5Icc zV|}Fb30U_u(3RFQv4R9o>IHeDN?E&7Cvg%xHX%O{lE7F5D1r{5EommzpoDnm+-JWy zq^yHT_nq&2=ey_LbMC!9+{PbJ|Ou_TQQQkzkqb5^u7L!_l*n$*lA#O|ldTKQQ>(_@gYGzEN zRnROrwy`l+4U;pLwFRx2X;24fwJ~uAX|*m`K}7=)4e{9_HeiHVhz&y;sNDeUUmpVb zsO^x1+97Dm;@r4}a)CFZlpoIxwhM|m*dZtuX=fyGFq*Q-DeIbNIt9(DMucEQh>K@2 zqzgmbV6VF?f`jt^?i2!liv~Ld#VQ?&vPFYhL^m60Df!4+D#P4Sn1tp4X(AeoveGcb zIS1k9Af7phc@E-c2rjYD2$AeL$UH+OmXU6ZsW3#JCYXVwQprGc zkxWiqTDLs4@ylYci=tNM*n+Dz>5A7rRYKP31&7*m%?fsRw%vP-Y6Bfs~1w=darg1 zdhcFjW1iZBW8RKJwU+@EY9AYG)PCY~tSz`WtC_bzBX+aS+=87+jyo)F-Tt1@m>0=8 zhs5`gA&2x_@2uz-jp!E*cF!zEgTZJp7`@~vQ6e7EMRthu$$HV{e92(FL~_K1&aH;M zm8=r;NspMte?tB)7O#0FMk`d4pW0QyPZuaz^qP`QIrS@>ny}HNB2pf3a#2GwCf^(rf4;N9SSNz8+;L?je6Coi8U|Xo-*}wM0cl5y>scFl!@na zB1#XYOsj4lo{5#w{eH^M(%mOgrrz`PWiYoZa4UfKP!uPIggjjg-vc;r;4rP>IS3w1 zRgZuS(4#FE;h&<=?VdLj^^!n0nW18rNKtm_tHnLAW-nXjr$CN&QA`s)*~Al3wmo<^ zZ2}^^1owM1zS2Jaq_*7Hw%1U?6kSe5r~%X>v*hGN_Ts|hjBw|1gax*JP#-?<5Aq;e zswn9V6o$2`cr`nFmR`{IfNF8@m{6I7q8|#w(gmr6JndR5e;?i70(XtJGQQ?8=D7_a z>CVc`EaojGrmT`#z(7f9$&-vk}#&$uPzNN`}rdsFw~ zZJ^&F>3aespd>RF1q3nM$l2Qmy5-cbUJ5Rd>R~(UL3=7e z(y!TBf^TbYgAgA$)sM+T#%bUj0=NVO1Zna@;8MS_go`N5K$eTZa@lwb(QmVJu@A2H zA}H9}&d01M`x0<%)SR&*GgDbgo_AL*ZqVJ947Y{3-vkFNco+GDdr9qk$SSAx#&zjU zb!{13m-Jft!n86Ktu(IhS&tr7L&EN2*_QU{RTnOU;x?@yRq}agx~2Eq;ry(k0yNFG zK|E^s&|#CT*8M+ihaSrC(|txp8&er=os|)^j9y3)8on zwzwC|dPkl>^7Cvf^f&z@1O%xsmhtjLbnJSJ)~H$dX1V-oj-TeJ3;cAu3Uggkzl2vy zov=X?dV%jhs_VEiTUW+Zi*)e+6V~^q9z!w{{_!KisQasL)|F>67TNB6h%uXuruYQS z|Irk~y8qFpfS))@5&X7M7Qt3{SUvbbP?~)RV8&M3U2PZq%K#JCIX~ zltt+}>zCYWdKw=4s^)ftgt5QAOkKyy!I}Ggtjg0Sb;6H-~g0Ch#z3{3-H#N-! zI))(dsf-5kB2cJ#PKP)_9Tb=VTm->Eyt>zR2A&;CLmTji{aZsBjr0RO)_Xd%Mo@=l zVuw<(ZyB-DPI5B;LiG#7sr%_2?H#z#IXIj3-FP-v$3JG9`{poYlX8$hE^IJO43n{i zb;Y$;BRYJ}#%C~fnaBaIuj{g^OoxE){+|iY&ljVCO!->Sy^k~=q zu2C2#htCg>B@$g7;n8h!T_Q0&a!-blxu>TRW8j{gp5i>dQeSB)m%JX*LYkK3d}fa% z`M6|Axqk9wh}mW{+L7xK1?;+)zI}Uegz>9FJS{!G|0#*m8>N~GqC;3g2Jrat}W7_uLpA8W3E#xFFxHzo^+w6W>G7@(+1 diff --git a/examples/war/assets.ne b/examples/war/assets.ne index b0c2cb0..dfb54ea 100644 --- a/examples/war/assets.ne +++ b/examples/war/assets.ne @@ -84,284 +84,431 @@ sprite Tileset { "22222223", "22222223", "22222223", - // ── 07: card back top-left ─────────────────────── - // The back is a black-and-white diamond lattice, - // painted as a 2×2 repeating block. Every card back - // uses all four corners tiled into a 16×16 / 16×24 - // block. - "..333333", - ".3333333", - ".3322333", - "333.2.33", - "33.222.3", - "33.2.233", - "3322.333", - "3333.333", - // ── 08: card back top-right ────────────────────── - "333333..", - "3333333.", - "33322.33", - "33.2.333", - "3.222.33", - "33.2.333", - "333.2233", - "3333.333", - // ── 09: card back bottom-left ──────────────────── - "3333.333", - "3322.333", - "33.2.233", - "33.222.3", - "333.2.33", - ".3322333", - ".3333333", - "..333333", - // ── 10: card back bottom-right ─────────────────── - "3333.333", - "333.2233", - "33.2.333", - "3.222.33", - "33.2.333", - "33322.33", - "3333333.", - "333333..", + // ── 07-10: card back ───────────────────────────── + // + // A simple 2×2 checkerboard of 2-pixel squares in black + // (palette 3) and white (palette 2). Each 8×8 tile + // carries one quadrant of a 16×16 repeating block; the + // pattern is self-similar, so every corner tile is + // actually the same art — they just get named TL/TR/BL/ + // BR so draw_card_back reads naturally. + // + // draw_card_back draws a 16×24 card as: + // [TL][TR] row 0 (y=0-7) + // [BL][BR] row 1 (y=8-15) + // [TL][TR] row 2 (y=16-23) — pattern repeats + // + // The checkerboard tiles below therefore tile seamlessly + // both horizontally (TL vs TR) and vertically (row 0 vs + // row 2), which the old diamond-lattice design did not. + + // ── 07: card back — TL quadrant ────────────────── + // 2-pixel square checkerboard, starting black at (0,0). + // ##..##.. + // ##..##.. + // ..##..## + // ..##..## + // ##..##.. + // ##..##.. + // ..##..## + // ..##..## + "33223322", + "33223322", + "22332233", + "22332233", + "33223322", + "33223322", + "22332233", + "22332233", + // ── 08: card back — TR quadrant ────────────────── + // Mirrored half-phase from TL so the horizontal join + // between TL and TR preserves the 2-pixel square grid. + "33223322", + "33223322", + "22332233", + "22332233", + "33223322", + "33223322", + "22332233", + "22332233", + // ── 09: card back — BL quadrant ────────────────── + // Half-phase shift in Y so the vertical join preserves + // the grid too. + "33223322", + "33223322", + "22332233", + "22332233", + "33223322", + "33223322", + "22332233", + "22332233", + // ── 10: card back — BR quadrant ────────────────── + "33223322", + "33223322", + "22332233", + "22332233", + "33223322", + "33223322", + "22332233", + "22332233", // ── 11: rank A (Ace) ───────────────────────────── - // Bold black glyph on a white card body. The rank is - // drawn 6×6 px and centred horizontally in the 8×8 tile. - // The card body fills index 2 (white) so the glyph reads - // as black-on-white when the card frame's outline tiles - // wrap it. + // Bold 5×7 black glyph on a fully-opaque white card body. + // Every pixel inside the tile is either 2 (white — the + // card body) or 3 (black — the glyph stroke). Nothing + // is transparent: a `.` anywhere here would let the + // felt green bleed through the card. "22222222", - "22.33.22", - "2.3333.2", - "2.3..3.2", - "2.3333.2", - "2.3..3.2", - "2.3..3.2", + "22233222", + "22233222", + "23222322", + "23333322", + "23222322", + "23222322", "22222222", // ── 12: rank 2 ─────────────────────────────────── "22222222", - "2.3333.2", - "2....3.2", - "22.33.22", - "2.3...22", - "2.3...22", - "2.3333.2", + "23333222", + "22222322", + "22223222", + "22232222", + "22322222", + "23333322", "22222222", // ── 13: rank 3 ─────────────────────────────────── "22222222", - "2.3333.2", - "22...3.2", - "22.33.22", - "22...3.2", - "2....3.2", - "2.3333.2", + "23333222", + "22222322", + "22333222", + "22222322", + "22222322", + "23333222", "22222222", // ── 14: rank 4 ─────────────────────────────────── "22222222", - "2.3..3.2", - "2.3..3.2", - "2.3..3.2", - "2.3333.2", - "22...3.2", - "22...3.2", + "23222322", + "23222322", + "23222322", + "23333322", + "22222322", + "22222322", "22222222", // ── 15: rank 5 ─────────────────────────────────── "22222222", - "2.3333.2", - "2.3...22", - "2.3333.2", - "22...3.2", - "22...3.2", - "2.3333.2", + "23333322", + "23222222", + "23333222", + "22222322", + "22222322", + "23333222", "22222222", // ── 16: rank 6 ─────────────────────────────────── "22222222", - "2.3333.2", - "2.3...22", - "2.3333.2", - "2.3..3.2", - "2.3..3.2", - "2.3333.2", + "22333322", + "23222222", + "23333222", + "23222322", + "23222322", + "22333222", "22222222", // ── 17: rank 7 ─────────────────────────────────── "22222222", - "2.3333.2", - "22...3.2", - "22..3.22", - "22.3..22", - "22.3..22", - "22.3..22", + "23333322", + "22222322", + "22223222", + "22232222", + "22322222", + "23222222", "22222222", // ── 18: rank 8 ─────────────────────────────────── "22222222", - "2.3333.2", - "2.3..3.2", - "2.3333.2", - "2.3..3.2", - "2.3..3.2", - "2.3333.2", + "22333222", + "23222322", + "22333222", + "23222322", + "23222322", + "22333222", "22222222", // ── 19: rank 9 ─────────────────────────────────── "22222222", - "2.3333.2", - "2.3..3.2", - "2.3..3.2", - "2.3333.2", - "22...3.2", - "2.3333.2", + "22333222", + "23222322", + "23222322", + "22333322", + "22222322", + "22333222", "22222222", // ── 20: rank 10 (condensed "1" + "0") ───────────── - // "10" squeezed into an 8x8 tile. The "1" takes columns - // 0-1, a gap, the "0" takes columns 4-7. + // "10" squeezed into an 8×8 tile: a thin "1" in cols 0-2 + // and a narrow "0" in cols 4-7 with a 1-col gap. "22222222", - "2.3.33.2", - "233.3..3", - "2.3.3..3", - "2.3.3..3", - "2.3.3..3", - "2.3.33.2", + "22323322", + "23323322", + "22323322", + "22323322", + "22323322", + "23323322", "22222222", // ── 21: rank J ─────────────────────────────────── "22222222", - "222.33.2", - "222..3.2", - "222..3.2", - "222..3.2", - "22.3.3.2", - "22.333.2", + "22333322", + "22222322", + "22222322", + "22222322", + "23222322", + "22333222", "22222222", // ── 22: rank Q ─────────────────────────────────── "22222222", - "2.3333.2", - "2.3..3.2", - "2.3..3.2", - "2.3.33.2", - "2.3333.2", - "22.33.32", + "22333222", + "23222322", + "23222322", + "23223322", + "23233222", + "22332322", "22222222", // ── 23: rank K ─────────────────────────────────── "22222222", - "2.3..3.2", - "2.3.3.22", - "2.33..22", - "2.3.3.22", - "2.3..3.2", - "2.3..3.2", + "23222322", + "23223222", + "23322222", + "23322222", + "23223222", + "23222322", "22222222", // ── 24: small spade ♠ ──────────────────────────── - // 6x6 centred pip on a white 8x8 tile. + // 6×6 black pip centred on a fully-opaque white 8×8 tile. + // Used as the top-right corner indicator on spade cards. + // ...##... + // ..####.. + // .######. + // ..####.. + // ..####.. + // ..#..#.. + "22222222", + "22233222", + "22333322", + "23333332", + "22333322", + "22233222", + "22233222", "22222222", - "2222.222", - "222.3.22", - "22.333.2", - "2.33333.", - "2.33333.", - "222.3.22", - "222333.2", // ── 25: small heart ♥ ──────────────────────────── - // Red pip on white. + // Red pip on white (palette index 1 = red). + // .##..##. + // ######## + // ######## + // ######## + // .######. + // ..####.. + // ...##... "22222222", - "22.11.22", - "2.1111.2", - "2.1111.2", - "2.1111.2", - "22.11.22", - "222.1.22", + "21122112", + "21111112", + "21111112", + "22111122", + "22211222", + "22221222", "22222222", // ── 26: small diamond ♦ ────────────────────────── + // Red pip on white (compact rhombus). + // ...##... + // ..####.. + // .######. + // ######## + // .######. + // ..####.. + // ...##... "22222222", - "22221222", - "2221.122", - "221...12", - "221...12", - "2221.122", + "22211222", + "22111122", + "21111112", + "22111122", + "22211222", "22221222", "22222222", // ── 27: small club ♣ ───────────────────────────── + // Black pip on white. Three rounded leaves over a short + // stem — simplified to fit an 8×8 tile. + // ...##... + // ..####.. + // ...##... + // .######. + // ######## + // .######. + // ...##... "22222222", - "2222.222", - "2223332.", - "2223332.", - "22.333.2", - "2.33333.", - "22.3.3.2", - "222333.2", - // ── 28: big spade — left half ──────────────────── - // The big centre-pip is drawn 16×8: two tiles side by - // side. Each suit's (left half, right half) pair is - // authored explicitly so the centre art can be bolder - // and more symmetric than the corner pips. - "2222222.", - "222222.3", - "22222.33", - "2222.333", - "222.3333", - "22.33333", - "2.333333", - ".3333333", - // ── 29: big heart — left half ──────────────────── - "2222.111", - "222.1111", - "22.11111", - "22.11111", - "22.11111", - "222.1111", - "2222.111", - "22222.11", - // ── 30: big diamond — left half ────────────────── + "22233222", + "22333322", + "22233222", + "23333332", + "23333332", + "22233222", "22222222", - "22222.12", - "2222.112", - "222.1112", - "22.11112", - "2.111112", - "22.11112", - "222.1112", - // ── 31: big club — left half ───────────────────── - "2222.333", - "2222.333", - "222.3333", - "22.33333", - "2.333333", - "2.333333", - "22.333.3", - "2223.333", - // ── 32: big spade — right half ─────────────────── - ".3333333", - "2.333333", - "22.33333", - "222.3333", - "2222.333", - "22222.33", - "222222.3", - "2222222.", - // ── 33: big heart — right half ─────────────────── - "111.2222", - "1111.222", - "11111.22", - "11111.22", - "11111.22", - "1111.222", - "111.2222", - "11.22222", - // ── 34: big diamond — right half ───────────────── + // ── 28-43: big centre pips ─────────────────────── + // + // Every face-up card shows a big 16×16 pip spanning the + // bottom two rows of the card. Each suit's pip is + // authored below as four 8×8 quadrants (top-left, + // top-right, bottom-left, bottom-right) so the full + // 16×16 shape is the concatenation: + // + // [ TL ][ TR ] + // [ BL ][ BR ] + // + // The card body around the pip is solid white (palette + // 2); the pip itself is palette 1 (red — hearts and + // diamonds) or palette 3 (black — spades and clubs). + // Crucially, no tile pixel is `.` (palette 0 — + // transparent): the card must be opaque so the green + // felt does not bleed through. + // + // Order: spade, heart, diamond, club. + // TL tiles 28-31, TR tiles 32-35, BL tiles 88-91 + // (declared at the end of the Tileset), BR tiles 92-95. + + // ── 28: big spade — TL quadrant ───────────────── + // Full 16×16 spade shape (TL + TR side by side, + // BL + BR underneath at tiles 88-95): + // ................ + // .......##....... + // ......####...... + // .....######..... + // ....########.... + // ...##########... + // ..############.. + // .##############. + // ################ + // ################ + // .##############. + // ...##########... + // .......##....... + // .....######..... + // ....########.... + // ................ "22222222", - "21.22222", - "211.2222", - "2111.222", - "21111.22", - "211111.2", - "21111.22", - "2111.222", - // ── 35: big club — right half ──────────────────── - "333.2222", - "333.2222", - "3333.222", - "33333.22", - "333333.2", - "333333.2", - "3.333.22", - "333.3222", + "22222223", + "22222233", + "22222333", + "22223333", + "22233333", + "22333333", + "23333333", + // ── 29: big heart — TL quadrant ────────────────── + // Full 16×16 heart shape: + // ................ + // ..####....####.. + // .##############. + // ################ + // ################ + // ################ + // ################ + // ################ + // .##############. + // .##############. + // ..############.. + // ...##########... + // ....########.... + // .....######..... + // .......##....... + // ................ + "22222222", + "22111122", + "21111111", + "11111111", + "11111111", + "11111111", + "11111111", + "11111111", + // ── 30: big diamond — TL quadrant ──────────────── + // Full 16×16 diamond shape (symmetric rhombus): + // ................ + // .......##....... + // ......####...... + // .....######..... + // ....########.... + // ...##########... + // ..############.. + // .##############. + // ################ + // .##############. + // ..############.. + // ...##########... + // ....########.... + // .....######..... + // ......####...... + // .......##....... + "22222222", + "22222221", + "22222211", + "22222111", + "22221111", + "22211111", + "22111111", + "21111111", + // ── 31: big club — TL quadrant ─────────────────── + // Full 16×16 club shape — three leaves in a triangle + // (one on top, two on sides) joined over a short stem + // and a wider base: + // ................ + // .....######..... + // ....########.... + // ....########.... + // .....######..... + // ..###..######... + // .#####.########. + // .############### + // ################ + // .##############. + // ..############.. + // .......##....... + // .....######..... + // ....########.... + // ................ + // ................ + "22222222", + "22222333", + "22223333", + "22223333", + "22222333", + "22333222", + "23333322", + "23333333", + // ── 32: big spade — TR quadrant ────────────────── + "22222222", + "32222222", + "33222222", + "33322222", + "33332222", + "33333222", + "33333322", + "33333332", + // ── 33: big heart — TR quadrant ────────────────── + "22222222", + "22111122", + "11111112", + "11111111", + "11111111", + "11111111", + "11111111", + "11111111", + // ── 34: big diamond — TR quadrant ──────────────── + "22222222", + "12222222", + "11222222", + "11122222", + "11112222", + "11111222", + "11111122", + "11111112", + // ── 35: big club — TR quadrant ─────────────────── + "22222222", + "33322222", + "33332222", + "33332222", + "33322222", + "33333322", + "33333332", + "33333332", // ── 36-61: alphabet A..Z (8x8, white on transparent) ─ // Bold sans-serif letters drawn with 2-pixel strokes so // they still read over the felt at NES resolution. Every @@ -851,6 +998,132 @@ sprite Tileset { "222222..", "2222222.", "22222222", - ".2222222" + ".2222222", + // ── 88-95: big centre pip — BL and BR quadrants ── + // Continuation of the 16×16 big-pip shapes declared at + // tiles 28-35. Placed here at the end of the Tileset so + // adding them didn't require renumbering every letter, + // digit, and UI tile in the middle of the sheet. The + // full 16×16 layout is: + // + // [TL=28..31 ][TR=32..35 ] (top half, cols 0-15) + // [BL=88..91 ][BR=92..95 ] (bottom half) + // + // Order inside each group is spade / heart / diamond / + // club — identical to TILE_PIP_*_BASE indexing so a + // single `TILE_PIP_*_BASE + suit` lookup picks the right + // tile for each suit. + + // ── 88: big spade — BL quadrant ────────────────── + // Full spade bottom half: single-row peak at row 8, + // then a smooth teardrop curving down through a narrow + // stem and a short base. Matches the TL/TR triangle + // top to form a classic playing-card spade. + // ################ row 8 — widest + // .##############. row 9 + // ..############.. row 10 + // ...##########... row 11 + // .....######..... row 12 + // .......##....... row 13 — stem + // .....######..... row 14 — base + // ................ row 15 + "33333333", + "23333333", + "22333333", + "22233333", + "22222333", + "22222223", + "22222333", + "22222222", + // ── 89: big heart — BL quadrant ────────────────── + // Full heart bottom half: + // .##############. + // .##############. + // ..############.. + // ...##########... + // ....########.... + // .....######..... + // .......##....... + // ................ + "21111111", + "21111111", + "22111111", + "22211111", + "22221111", + "22222111", + "22222221", + "22222222", + // ── 90: big diamond — BL quadrant ──────────────── + // Full diamond bottom half (mirror of the top): + // ################ + // .##############. + // ..############.. + // ...##########... + // ....########.... + // .....######..... + // ......####...... + // .......##....... + "11111111", + "21111111", + "22111111", + "22211111", + "22221111", + "22211111", + "22222111", + "22222221", + // ── 91: big club — BL quadrant ─────────────────── + // Full club bottom half: + // ################ + // .##############. + // ..############.. + // .......##....... + // .....######..... + // ....########.... + // ................ + // ................ + "33333333", + "23333333", + "22333333", + "22222223", + "22222333", + "22223333", + "22222222", + "22222222", + // ── 92: big spade — BR quadrant ────────────────── + "33333333", + "33333332", + "33333322", + "33333222", + "33322222", + "32222222", + "33322222", + "22222222", + // ── 93: big heart — BR quadrant ────────────────── + "11111112", + "11111112", + "11111122", + "11111222", + "11112222", + "11122222", + "12222222", + "22222222", + // ── 94: big diamond — BR quadrant ──────────────── + "11111111", + "11111112", + "11111122", + "11111222", + "11112222", + "11111222", + "11112222", + "12222222", + // ── 95: big club — BR quadrant ─────────────────── + "33333333", + "33333332", + "33333322", + "32222222", + "33322222", + "33332222", + "22222222", + "22222222" ] } diff --git a/examples/war/constants.ne b/examples/war/constants.ne index 722d096..1a3411e 100644 --- a/examples/war/constants.ne +++ b/examples/war/constants.ne @@ -59,9 +59,16 @@ const TILE_RANK_BASE: u8 = 11 // 13 tiles: A, 2..10, J, Q, K // Small suit glyphs for the card corner, one per suit. const TILE_SUIT_SMALL_BASE: u8 = 24 // 4 tiles: ♠ ♥ ♦ ♣ -// Big centre-pip, authored as a 16×8 pair per suit (left half + right half). -const TILE_PIP_L_BASE: u8 = 28 // 4 tiles -const TILE_PIP_R_BASE: u8 = 32 // 4 tiles +// Big centre-pip, authored as a 16×16 shape split into four 8×8 +// quadrants per suit. Each `_BASE + suit` picks the tile for that +// suit's quadrant. The TL/TR quadrants are contiguous at 28-35; +// the BL/BR quadrants live at 88-95 because they were added +// after the alphabet / digit / BIG WAR tiles and putting them in +// the middle would have shifted every later tile's index. +const TILE_PIP_TL_BASE: u8 = 28 // spade/heart/diamond/club TL +const TILE_PIP_TR_BASE: u8 = 32 // spade/heart/diamond/club TR +const TILE_PIP_BL_BASE: u8 = 88 // spade/heart/diamond/club BL +const TILE_PIP_BR_BASE: u8 = 92 // spade/heart/diamond/club BR // Alphanumerics (8×8) used for all on-screen text that lives on the // sprite layer. Letters A-Z then digits 0-9, contiguous. diff --git a/examples/war/render.ne b/examples/war/render.ne index aeb44fb..26c3633 100644 --- a/examples/war/render.ne +++ b/examples/war/render.ne @@ -13,16 +13,19 @@ // ── Card face ────────────────────────────────────────────── // -// Draws the 6-sprite card face at (x, y) for the packed card -// byte. Layout (each cell is 8×8): +// Draws the 6-sprite face of a 16×24 card at (x, y) for the +// packed `rank<<4 | suit` byte. Layout (each cell is 8×8): // -// [rank ][small_suit] row 0 -// [pipL ][pipR ] row 1 -// [blankL][blankR ] row 2 +// [ rank ][ssuit] row 0: rank letter + small suit pip +// [pipTL ][pipTR] row 1: top half of the big 16×16 pip +// [pipBL ][pipBR] row 2: bottom half of the big pip // -// Rank is the condensed glyph at TILE_RANK_BASE + (rank - 1); -// small suit + big-pip halves are indexed directly off their -// base constants. +// Every tile in the card has a fully-opaque white background +// (palette index 2) so the felt green behind the card does not +// bleed through — see `assets.ne` for the art itself. The big +// centre pip is a single 16×16 shape split across the bottom +// two rows (4 tiles total); `rank` sits in the top-left corner +// and `small_suit` in the top-right. fun draw_card_face(dcf_in_x: u8, dcf_in_y: u8, dcf_in_card: u8) { // Snapshot every parameter into a fresh local *before* any // nested function call. NEScript v0.1 passes parameters via @@ -38,20 +41,22 @@ fun draw_card_face(dcf_in_x: u8, dcf_in_y: u8, dcf_in_card: u8) { var dcf_suit: u8 = card_suit(dcf_card) var dcf_rank_tile: u8 = TILE_RANK_BASE + dcf_rank - 1 var dcf_small_tile: u8 = TILE_SUIT_SMALL_BASE + dcf_suit - var dcf_pipl_tile: u8 = TILE_PIP_L_BASE + dcf_suit - var dcf_pipr_tile: u8 = TILE_PIP_R_BASE + dcf_suit + var dcf_pip_tl: u8 = TILE_PIP_TL_BASE + dcf_suit + var dcf_pip_tr: u8 = TILE_PIP_TR_BASE + dcf_suit + var dcf_pip_bl: u8 = TILE_PIP_BL_BASE + dcf_suit + var dcf_pip_br: u8 = TILE_PIP_BR_BASE + dcf_suit var dcf_x1: u8 = dcf_x + 8 var dcf_y1: u8 = dcf_y + 8 var dcf_y2: u8 = dcf_y + 16 // Row 0 — rank corner + small suit draw Tileset at: (dcf_x, dcf_y) frame: dcf_rank_tile draw Tileset at: (dcf_x1, dcf_y) frame: dcf_small_tile - // Row 1 — big centre pip (left + right halves) - draw Tileset at: (dcf_x, dcf_y1) frame: dcf_pipl_tile - draw Tileset at: (dcf_x1, dcf_y1) frame: dcf_pipr_tile - // Row 2 — blank bottom so the card body is symmetric - draw Tileset at: (dcf_x, dcf_y2) frame: TILE_FRAME_BLANK_L - draw Tileset at: (dcf_x1, dcf_y2) frame: TILE_FRAME_BLANK_R + // Row 1 — top half of the 16×16 big pip + draw Tileset at: (dcf_x, dcf_y1) frame: dcf_pip_tl + draw Tileset at: (dcf_x1, dcf_y1) frame: dcf_pip_tr + // Row 2 — bottom half of the 16×16 big pip + draw Tileset at: (dcf_x, dcf_y2) frame: dcf_pip_bl + draw Tileset at: (dcf_x1, dcf_y2) frame: dcf_pip_br } // Draw the card-back lattice at (x, y). 6 sprites again. Rows diff --git a/tests/emulator/goldens/war.audio.hash b/tests/emulator/goldens/war.audio.hash index 040c3bc..e8f8ea6 100644 --- a/tests/emulator/goldens/war.audio.hash +++ b/tests/emulator/goldens/war.audio.hash @@ -1 +1 @@ -4747e070 132084 +21f4a315 132084 diff --git a/tests/emulator/goldens/war.png b/tests/emulator/goldens/war.png index 0784e9d534e4ea6898fa55e7b0c6e866f68eeadb..398430ede53012e839ba03201a66c12145d55621 100644 GIT binary patch literal 10327 zcmeHNdrVVT7%y@2HGCv<(`tn{lcCcUwHnJrs~d{U2#eFWC|Dn}m1u<4M-jVTqmyka z5QbY*L^08+(P?$+5UbQjuwbihl8LYGTH0zH@~Tr>x%BS4_xAR2DR9gFFxi@%+{?W^ z_dDPBJHPXNzjMNOpCu>u>pQeBhr{WYG&^A)hZ6yRa+-SgglnVvyph8h6_u1QV}4e| zv&%Eq^7{OmaIf{-sDw4S#RC@|u_O zaJ|Lm`U-K19xlm9<*T(%vv?C@vG+#i7F?UX;^6q(L(+FmoHdl~qicANu@&_%uG!_T zqCCTl=T|Z&glK^tPCWk7Jb#yzTf5~@`Yz=i&K%k1i?XuR@F)eOr`eAC`=_U^Eyc~VJfjm!C@-B7AhfEo?ENLX0$=sf@L`r4H*i)8t-{4KL*)= z=$o9>CBu)3}OCbXxnW?N!Q=@hEruUho0Se4nvebFh&)JKQG zn%^V5r0QCwcF5AvYqgWz)t((;pUPix(ndZt)rMbmreHVh2R2I4aiF7+ z2^{nRr2SVQQ%cHp%)C;e;YxCX-lEAl?Mh`MXM@4(!MurFqLtD}QpX{;fi!-6prIxu zp2!a51ynbx1EGFUmn-s<|CMNm{z4FJx!n;f2)Dyj&MgR2xgA5oR1Rn5pvn1vot3Gb zepO{_sX}$FgDWA`VnDqW@4A3&ejkkuJ$8(QvzV-m@ik{T%UQ?aOSd{vHUOTc${z zQ6adhy_;69BlZ&INBy=8#MTR+ATUKDlLXjNjYUdNNmt_BCg}Oa4iH7S1SBi2)>stJ z_^{1V8y9XMcXeL2xcv5U=ZNi(sYxfvX=FW)f)Z_FLh0vAa?n!&Y@JJ=SK1cV^GnX^ zSRcna#!&uiiOq^sk|RD9$j!HX4%%Q(;BDHCYO`HRUVJ?|=dA6tXDDJlz);l<4uKz3 z)5whmMH_c46#OQ$X`BG-%bHNTsM#I^3!$SmkXxW%2MZ5&fm#IL2|R1xkhWGa+J+5y^vSs;gAFq5Q0rHp@52fTYdt@)zuCm` zTLxIN(|CsFJq;~$H#zc)2gOt*$>xQ0Lz+3$zOLIK;~6r}qm%7bgG2h-GaQ=DiN5mU zyfDu?+1$9tVVgq)$vzUQX9gpj#O@ejh9i=P1_v1sR?)*b@I+08jHiF9LU+fAGrM6# zSr?(~T~k*p%tP1fGp-lL#*tGW8Y<I)J4m;TFv7t`hQ4$vs+cSA&F&Z}jk@$r=0$#<)j7AN z^-#R)`_Pw8IH3gNQ8=NrM(7IkVbo)-PL>+d}-`VA9OwgcBbx-TDufYE$?C literal 10139 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 xD9V+}2Yoga=0~-mgLPBe)rCY{{tb#tu+7u