romdevtools 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/AGENTS.md +56 -44
  2. package/CHANGELOG.md +355 -0
  3. package/README.md +4 -4
  4. package/examples/README.md +7 -7
  5. package/examples/atari2600/templates/platformer.asm +1227 -325
  6. package/examples/atari2600/templates/puzzle.asm +1056 -0
  7. package/examples/atari2600/templates/racing.asm +909 -257
  8. package/examples/atari2600/templates/shmup.asm +1035 -218
  9. package/examples/atari2600/templates/sports.asm +1143 -229
  10. package/examples/atari7800/templates/hello_sprite.c +8 -4
  11. package/examples/atari7800/templates/platformer.c +991 -152
  12. package/examples/atari7800/templates/puzzle.c +1091 -145
  13. package/examples/atari7800/templates/racing.c +949 -118
  14. package/examples/atari7800/templates/shmup.c +812 -130
  15. package/examples/atari7800/templates/sports.c +820 -181
  16. package/examples/c64/templates/platformer.c +876 -157
  17. package/examples/c64/templates/puzzle.c +881 -143
  18. package/examples/c64/templates/racing.c +873 -88
  19. package/examples/c64/templates/shmup.c +762 -154
  20. package/examples/c64/templates/sports.c +755 -95
  21. package/examples/gb/templates/platformer.c +841 -175
  22. package/examples/gb/templates/puzzle.c +1094 -176
  23. package/examples/gb/templates/racing.c +761 -169
  24. package/examples/gb/templates/shmup.c +679 -169
  25. package/examples/gb/templates/sports.c +790 -153
  26. package/examples/gba/templates/platformer.c +624 -169
  27. package/examples/gba/templates/puzzle.c +535 -207
  28. package/examples/gba/templates/racing.c +513 -196
  29. package/examples/gba/templates/shmup.c +565 -168
  30. package/examples/gba/templates/sports.c +454 -162
  31. package/examples/gbc/templates/platformer.c +944 -176
  32. package/examples/gbc/templates/puzzle.c +1131 -177
  33. package/examples/gbc/templates/racing.c +891 -175
  34. package/examples/gbc/templates/shmup.c +827 -179
  35. package/examples/gbc/templates/sports.c +870 -156
  36. package/examples/genesis/templates/platformer.c +747 -129
  37. package/examples/genesis/templates/puzzle.c +702 -208
  38. package/examples/genesis/templates/racing.c +728 -193
  39. package/examples/genesis/templates/shmup.c +535 -142
  40. package/examples/genesis/templates/shmup_2p.c +13 -1
  41. package/examples/genesis/templates/sports.c +495 -158
  42. package/examples/gg/templates/platformer.c +883 -214
  43. package/examples/gg/templates/puzzle.c +906 -181
  44. package/examples/gg/templates/racing.c +919 -160
  45. package/examples/gg/templates/shmup.c +716 -177
  46. package/examples/gg/templates/sports.c +735 -128
  47. package/examples/lynx/templates/platformer.c +604 -50
  48. package/examples/lynx/templates/puzzle.c +533 -130
  49. package/examples/lynx/templates/racing.c +538 -102
  50. package/examples/lynx/templates/shmup.c +461 -122
  51. package/examples/lynx/templates/sports.c +496 -69
  52. package/examples/msx/platformer/main.c +648 -159
  53. package/examples/msx/puzzle/main.c +750 -185
  54. package/examples/msx/racing/main.c +669 -177
  55. package/examples/msx/shmup/main.c +460 -177
  56. package/examples/msx/sports/main.c +591 -124
  57. package/examples/nes/templates/platformer.c +586 -160
  58. package/examples/nes/templates/puzzle.c +603 -222
  59. package/examples/nes/templates/racing.c +505 -197
  60. package/examples/nes/templates/shmup.c +339 -144
  61. package/examples/nes/templates/sports.c +341 -182
  62. package/examples/pce/platformer/main.c +875 -204
  63. package/examples/pce/puzzle/main.c +797 -216
  64. package/examples/pce/racing/main.c +782 -206
  65. package/examples/pce/shmup/main.c +638 -211
  66. package/examples/pce/sports/main.c +585 -167
  67. package/examples/porting-across-platforms/README.md +1 -1
  68. package/examples/sms/templates/platformer.c +765 -176
  69. package/examples/sms/templates/puzzle.c +783 -177
  70. package/examples/sms/templates/racing.c +812 -133
  71. package/examples/sms/templates/shmup.c +601 -148
  72. package/examples/sms/templates/shmup_2p.c +17 -1
  73. package/examples/sms/templates/sports.c +633 -121
  74. package/examples/snes/templates/music_demo.c +7 -0
  75. package/examples/snes/templates/platformer-data.asm +123 -24
  76. package/examples/snes/templates/platformer-hdr.asm +57 -0
  77. package/examples/snes/templates/platformer.c +587 -149
  78. package/examples/snes/templates/puzzle-data.asm +116 -21
  79. package/examples/snes/templates/puzzle-hdr.asm +57 -0
  80. package/examples/snes/templates/puzzle.c +632 -185
  81. package/examples/snes/templates/racing-data.asm +390 -32
  82. package/examples/snes/templates/racing-hdr.asm +57 -0
  83. package/examples/snes/templates/racing.c +807 -177
  84. package/examples/snes/templates/shmup-data.asm +87 -29
  85. package/examples/snes/templates/shmup-hdr.asm +57 -0
  86. package/examples/snes/templates/shmup.c +459 -180
  87. package/examples/snes/templates/sports-data.asm +48 -2
  88. package/examples/snes/templates/sports-hdr.asm +57 -0
  89. package/examples/snes/templates/sports.c +414 -156
  90. package/package.json +12 -12
  91. package/src/cores/wasm/bluemsx_libretro.js +1 -1
  92. package/src/cores/wasm/bluemsx_libretro.wasm +0 -0
  93. package/src/cores/wasm/fceumm_libretro.js +1 -1
  94. package/src/cores/wasm/fceumm_libretro.wasm +0 -0
  95. package/src/cores/wasm/gambatte_libretro.js +1 -1
  96. package/src/cores/wasm/gambatte_libretro.wasm +0 -0
  97. package/src/cores/wasm/geargrafx_libretro.js +1 -1
  98. package/src/cores/wasm/geargrafx_libretro.wasm +0 -0
  99. package/src/cores/wasm/genesis_plus_gx_libretro.js +1 -1
  100. package/src/cores/wasm/genesis_plus_gx_libretro.wasm +0 -0
  101. package/src/cores/wasm/handy_libretro.js +1 -1
  102. package/src/cores/wasm/handy_libretro.wasm +0 -0
  103. package/src/cores/wasm/mgba_libretro.js +1 -1
  104. package/src/cores/wasm/mgba_libretro.wasm +0 -0
  105. package/src/cores/wasm/prosystem_libretro.js +1 -1
  106. package/src/cores/wasm/prosystem_libretro.wasm +0 -0
  107. package/src/cores/wasm/snes9x_libretro.js +1 -1
  108. package/src/cores/wasm/snes9x_libretro.wasm +0 -0
  109. package/src/cores/wasm/stella2014_libretro.js +1 -1
  110. package/src/cores/wasm/stella2014_libretro.wasm +0 -0
  111. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  112. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  113. package/src/host/LibretroHost.js +304 -11
  114. package/src/http/tool-registry.js +11 -11
  115. package/src/mcp/server.js +6 -0
  116. package/src/mcp/tools/cheats.js +2 -1
  117. package/src/mcp/tools/disasm-rebuild.js +315 -65
  118. package/src/mcp/tools/disasm.js +149 -28
  119. package/src/mcp/tools/find-references.js +216 -51
  120. package/src/mcp/tools/frame.js +14 -6
  121. package/src/mcp/tools/index.js +18 -4
  122. package/src/mcp/tools/input.js +31 -7
  123. package/src/mcp/tools/lifecycle.js +6 -4
  124. package/src/mcp/tools/memory.js +208 -39
  125. package/src/mcp/tools/platform-docs.js +1 -1
  126. package/src/mcp/tools/playtest.js +56 -4
  127. package/src/mcp/tools/preview-tile.js +6 -2
  128. package/src/mcp/tools/project.js +1114 -120
  129. package/src/mcp/tools/rom-id.js +5 -1
  130. package/src/mcp/tools/run-until.js +4 -2
  131. package/src/mcp/tools/snippets.js +6 -6
  132. package/src/mcp/tools/sprite-pipeline.js +14 -2
  133. package/src/mcp/tools/state.js +2 -1
  134. package/src/mcp/tools/tile-inspect.js +8 -1
  135. package/src/mcp/tools/toolchain.js +55 -11
  136. package/src/mcp/tools/watch-memory.js +145 -27
  137. package/src/observer/bus.js +73 -0
  138. package/src/observer/livestream.html +4 -2
  139. package/src/observer/tool-wrap.js +17 -14
  140. package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +64 -17
  141. package/src/platforms/atari2600/MENTAL_MODEL.md +5 -1
  142. package/src/platforms/atari2600/TROUBLESHOOTING.md +40 -0
  143. package/src/platforms/atari7800/MENTAL_MODEL.md +32 -11
  144. package/src/platforms/atari7800/TROUBLESHOOTING.md +5 -5
  145. package/src/platforms/c64/MENTAL_MODEL.md +11 -4
  146. package/src/platforms/c64/TROUBLESHOOTING.md +13 -0
  147. package/src/platforms/gb/MENTAL_MODEL.md +19 -4
  148. package/src/platforms/gb/TROUBLESHOOTING.md +101 -6
  149. package/src/platforms/gb/lib/c/README.md +10 -11
  150. package/src/platforms/gb/lib/c/gb_crt0.s +27 -3
  151. package/src/platforms/gb/lib/c/patch-header.js +19 -6
  152. package/src/platforms/gba/MENTAL_MODEL.md +4 -4
  153. package/src/platforms/gba/TROUBLESHOOTING.md +3 -3
  154. package/src/platforms/gba/lib/c/gba_sfx.c +40 -0
  155. package/src/platforms/gba/lib/c/gba_sfx.h +10 -0
  156. package/src/platforms/gbc/MENTAL_MODEL.md +16 -4
  157. package/src/platforms/gbc/TROUBLESHOOTING.md +24 -3
  158. package/src/platforms/gbc/UPSTREAM_SOURCES.md +1 -1
  159. package/src/platforms/gbc/lib/c/README.md +10 -11
  160. package/src/platforms/gbc/lib/c/font.h +43 -0
  161. package/src/platforms/gbc/lib/c/gb_crt0.s +26 -3
  162. package/src/platforms/gbc/lib/c/patch-header.js +19 -6
  163. package/src/platforms/genesis/MENTAL_MODEL.md +43 -9
  164. package/src/platforms/genesis/TROUBLESHOOTING.md +2 -2
  165. package/src/platforms/genesis/lib/c/genesis_sfx.c +37 -0
  166. package/src/platforms/genesis/lib/c/genesis_sfx.h +1 -0
  167. package/src/platforms/gg/MENTAL_MODEL.md +4 -4
  168. package/src/platforms/gg/TROUBLESHOOTING.md +14 -18
  169. package/src/platforms/gg/UPSTREAM_SOURCES.md +1 -1
  170. package/src/platforms/gg/lib/c/gg_crt0.s +14 -2
  171. package/src/platforms/gg/lib/c/joypad_read.c +29 -0
  172. package/src/platforms/lynx/MENTAL_MODEL.md +1 -1
  173. package/src/platforms/lynx/TROUBLESHOOTING.md +3 -3
  174. package/src/platforms/lynx/lib/c/lynx_sfx.c +38 -2
  175. package/src/platforms/lynx/lib/c/lynx_sfx.h +1 -0
  176. package/src/platforms/msx/MENTAL_MODEL.md +11 -5
  177. package/src/platforms/msx/TROUBLESHOOTING.md +21 -0
  178. package/src/platforms/msx/lib/c/msx_crt0.s +27 -0
  179. package/src/platforms/msx/lib/c/msx_hw.h +3 -0
  180. package/src/platforms/msx/lib/c/msx_vdp.c +70 -0
  181. package/src/platforms/nes/MENTAL_MODEL.md +12 -5
  182. package/src/platforms/nes/lib/c/nes_runtime.c +190 -34
  183. package/src/platforms/nes/lib/c/nes_runtime.h +35 -0
  184. package/src/platforms/pce/MENTAL_MODEL.md +14 -5
  185. package/src/platforms/pce/TROUBLESHOOTING.md +9 -0
  186. package/src/platforms/pce/lib/c/pce_hw.h +13 -1
  187. package/src/platforms/pce/lib/c/pce_sound.c +22 -0
  188. package/src/platforms/pce/lib/c/pce_video.c +32 -0
  189. package/src/platforms/sms/MENTAL_MODEL.md +11 -6
  190. package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
  191. package/src/platforms/sms/lib/c/sms_crt0.s +14 -2
  192. package/src/platforms/snes/MENTAL_MODEL.md +7 -2
  193. package/src/platforms/snes/TROUBLESHOOTING.md +40 -1
  194. package/src/playtest/playtest.js +73 -3
  195. package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +13 -8
  196. package/src/toolchains/cc65/presets/nes/chr-ram-runtime.crt0.s +58 -5
  197. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +52 -3
  198. package/src/toolchains/cc65/presets/pce/rom32k.cfg +52 -0
  199. package/src/toolchains/index.js +64 -19
@@ -1,263 +1,591 @@
1
- /* ── puzzle.c — Game Boy Advance Tonc match-3 falling-block scaffold
1
+ /* ── puzzle.c — Game Boy Advance falling-jewel match-3 (complete game) ────────
2
2
  *
3
- * 6-wide x 12-tall grid. A 1x3 active piece (3 colours, randomly
4
- * chosen from the cell palette) drops from the top; LEFT/RIGHT
5
- * shifts, A rotates the colour order, DOWN soft-drops, START
6
- * triggers a hard-drop. Horizontal triples clear and bump score.
3
+ * FACET FALL a COMPLETE, working game: press-start title, a falling-trio
4
+ * match-3 in a 6x12 well, cascade chains, levels that speed the fall, score +
5
+ * persistent hi-score (cartridge SRAM), and DMA/PSG music + SFX. The jewels
6
+ * are VIVID — the GBA's 15-bit palette gives 32768 colours, so each gem reads
7
+ * as a faceted stone (a bright glint, a mid body, a dark rim) rather than a
8
+ * flat block, and the well sits in a framed cabinet.
7
9
  *
8
- * The grid lives in plain RAM (u8 grid[12][6]) and is drawn into BG0's
9
- * 32x32 tile map. We redraw on landings + clears, not every frame.
10
+ * The game: a vertical trio of three jewels (each its own colour) falls into
11
+ * the well. LEFT/RIGHT move it, A/B cycle its three colours (the classic
12
+ * trio "rotate"), DOWN soft-drops, START hard-drops. When it lands, any
13
+ * straight run of 3+ same-coloured jewels (horizontal, vertical, OR diagonal)
14
+ * clears; survivors fall and cascades chain for multiplied score. Clear enough
15
+ * and the level ticks up and the fall quickens. Stack to the rim and it's over.
10
16
  *
11
- * Cell size: 8x8 (one BG tile per cell). Grid origin at tile (4, 2)
12
- * so the playfield sits at pixel (32, 16) with room for score above
13
- * and instructions below.
17
+ * THIS FILE IS MEANT TO BE FORKED AND MODIFIED into your own game — even a
18
+ * very different one. The markers tell you what's what:
19
+ * HARDWARE IDIOM (load-bearing) — dodges a documented GBA footgun; reshape
20
+ * your gameplay around it (see TROUBLESHOOTING before changing).
21
+ * GAME LOGIC (clay) — match rules, tuning, art, scoring: reshape freely.
14
22
  *
15
- * Cells:
16
- * 0 = empty 1 = red 2 = green 3 = blue
23
+ * What depends on what:
24
+ * gba_sfx.{h,c} PSG sound: sfx_tone/sfx_noise one-shots + the music loop
25
+ * (sfx_music_tick once per frame — forget it and the game is silent).
26
+ * libtonc (the build links it) — VBlankIntrWait/key_poll/TTE/tonccpy.
17
27
  *
18
- * Yours to extend: vertical-triple clear, T/L shape pieces, gravity
19
- * after clears (current code just deletes; no settle), game-over.
28
+ * HANDHELD, SO SINGLE-PLAYER ONLY (honest note): 2P versus on the GBA means a
29
+ * link cable between two units a second emulator instance this environment
30
+ * can't provide. So FACET FALL is a 1P MARATHON: clear, chain, level up, and
31
+ * push your hi-score. (Contrast the NES/Genesis puzzle templates, which ARE
32
+ * split-screen 2P versus — two controllers on one machine.)
33
+ *
34
+ * BANDWIDTH NOTE — and a TEACHING POINT vs the GB/NES version of this game
35
+ * (examples/nes/templates/puzzle.c): on the NES a full-board repaint must
36
+ * squeeze through a ~16-entry vblank tile queue, BUDGETED across 12 frames of
37
+ * dirty-row-bitmask tricks. The GBA has no such famine — its BG tilemap is
38
+ * plain VRAM you write whenever you like. So FACET FALL just REPAINTS THE
39
+ * WHOLE WELL (72 cells = 72 u16 SE writes) every time the board changes, in
40
+ * one go, no queue, no dirty-row gymnastics. Same genre, two bandwidth
41
+ * worlds — fork accordingly.
20
42
  */
21
43
 
22
44
  #include <tonc.h>
23
45
  #include "gba_sfx.h"
24
46
 
25
- /* draw a 5-digit score WITHOUT tte_printf (broken in this libtonc — GBA-1). */
26
- static void draw_score(int x, unsigned v) {
47
+ /* The title screen renders this examples({op:'fork'}) stamps your game's
48
+ * name here automatically. Keep it ≤16 chars of A-Z 0-9 space dash. */
49
+ #define GAME_TITLE "FACET FALL"
50
+
51
+ /* ── GAME LOGIC (clay — reshape freely) ──────────────────────────────────────
52
+ * Board geometry. The well is 6 wide x 12 tall, each cell ONE 8x8 BG tile.
53
+ * Origin in BG-tile coords: the playfield sits at pixel (well_tx*8, WELL_TY*8)
54
+ * with the HUD above and the controls hint below. */
55
+ #define GRID_W 6
56
+ #define GRID_H 12
57
+ #define WELL_TY 3 /* top tile row of the well interior */
58
+ #define WELL_TX 16 /* left tile col of the well interior (centered)*/
59
+
60
+ #define EMPTY 0 /* cell colours 1..3 = ruby / emerald / sapphire */
61
+
62
+ /* ── GAME LOGIC (clay) — BG tile art (regular Mode-0 4bpp BG tiles).
63
+ * Each 8x8 4bpp tile is 8 u32 rows; each nibble is a palette index within the
64
+ * BG palbank we use (bank 0). Index 0 = transparent → shows the backdrop
65
+ * colour. KEY TRICK: the three jewel tiles are the SAME faceted SHAPE drawn on
66
+ * three different palette indices (glint/body/rim per colour), so a cell
67
+ * changes colour by changing its TILE id — no palette rewrite, no attribute
68
+ * juggling. The faceted look (a 1-3-1 nibble gradient) is what makes the
69
+ * jewels pop on the GBA's wide palette. */
70
+ #define BG_BACK 1 /* cabinet backdrop dither */
71
+ #define BG_BAND 2 /* flat band behind the HUD text */
72
+ #define BG_FRAME 3 /* well frame / border */
73
+ #define BG_INNER 4 /* empty well cell (recessed, faint speck) */
74
+ #define BG_GEM1 5 /* jewel colour 1 (ruby) */
75
+ #define BG_GEM2 6 /* jewel colour 2 (emerald) */
76
+ #define BG_GEM3 7 /* jewel colour 3 (sapphire) */
77
+
78
+ /* cell colour (1..3) → BG tile id; empty shows the recessed inner cell. */
79
+ static u16 bg_tile_for(u8 col) {
80
+ return col ? (u16)(BG_GEM1 - 1 + col) : BG_INNER;
81
+ }
82
+
83
+ static const u32 bg_tile_back[8] = { /* steel dither cabinet */
84
+ 0x12121212, 0x21212121, 0x12121212, 0x21212121,
85
+ 0x12121212, 0x21212121, 0x12121212, 0x21212121,
86
+ };
87
+ static const u32 bg_tile_band[8] = { /* solid band behind the HUD */
88
+ 0x33333333, 0x33333333, 0x33333333, 0x33333333,
89
+ 0x33333333, 0x33333333, 0x33333333, 0x33333333,
90
+ };
91
+ static const u32 bg_tile_frame[8] = { /* bevelled steel border */
92
+ 0x44444444, 0x43333334, 0x43333334, 0x43333334,
93
+ 0x43333334, 0x43333334, 0x43333334, 0x44444444,
94
+ };
95
+ static const u32 bg_tile_inner[8] = { /* recessed empty cell + speck */
96
+ 0x00000000, 0x00000000, 0x00000000, 0x00011000,
97
+ 0x00011000, 0x00000000, 0x00000000, 0x00000000,
98
+ };
99
+ /* One faceted-jewel SHAPE per colour. The nibbles are a tiny gradient:
100
+ * 2 = glint (bright), 1 = body (mid), 3 = rim (dark). build_gem_tiles()
101
+ * remaps {1,2,3} into each colour's three palette indices so the single
102
+ * shape becomes three distinct, vivid stones. */
103
+ static const u32 gem_shape[8] = {
104
+ 0x00033300, 0x00321230, 0x03212210, 0x32122130,
105
+ 0x32112130, 0x03211230, 0x00321330, 0x00033300,
106
+ };
107
+ static u32 gem_ram[3][8]; /* colours 1..3, built at boot */
108
+
109
+ /* ── GAME LOGIC (clay) — remap the one jewel shape into three vivid colours.
110
+ * Shape nibble n in {1,2,3} → palette index (3*k + n) for colour k, so each
111
+ * jewel uses its own 3-index slice of the palbank (glint/body/rim). */
112
+ static void build_gem_tiles(void) {
113
+ int k, r, i;
114
+ for (k = 0; k < 3; k++)
115
+ for (r = 0; r < 8; r++) {
116
+ u32 v = gem_shape[r], out = 0;
117
+ for (i = 0; i < 8; i++) {
118
+ u32 nib = (v >> (i * 4)) & 0xF;
119
+ if (nib) nib = (u32)(3 * k + nib); /* into this colour's slice */
120
+ out |= nib << (i * 4);
121
+ }
122
+ gem_ram[k][r] = out;
123
+ }
124
+ }
125
+
126
+ /* ── GAME LOGIC (clay — reshape freely) — game state (plain BSS; the GBA has
127
+ * 256 KB of EWRAM + 32 KB of IWRAM, so none of the NES version's
128
+ * absolute-address scratch-page gymnastics).
129
+ * NOTE for headless verification: unlike the Genesis template (whose work-RAM
130
+ * globals are readable by symbol name), the GBA libretro core exposes NO
131
+ * IWRAM/EWRAM region, so a headless agent reads game state from what's ON
132
+ * HARDWARE — the BG0 tilemap (the locked well, in VRAM), OAM (the falling
133
+ * trio), and save_ram (the hi-score). The verify harness decodes those. */
134
+ #define ST_TITLE 0
135
+ #define ST_PLAY 1
136
+ #define ST_OVER 2
137
+ static u8 state;
138
+
139
+ static u8 grid[GRID_H][GRID_W]; /* the well: 0 = empty, 1..3 = colour */
140
+ static s16 piece_x; /* falling trio: column 0..5 */
141
+ static s16 piece_y; /* row of its TOP cell (<0 = above rim) */
142
+ static u8 piece_col[3]; /* trio colours, top to bottom */
143
+ static u16 score, hiscore;
144
+ static u16 cleared_total; /* gems cleared, drives the level */
145
+ static u8 level; /* 1..9, speeds up the fall */
146
+
147
+ static u8 matched[GRID_H][GRID_W];/* match scan scratch */
148
+ static u16 fall_t; /* frames until next gravity step */
149
+
150
+ /* ── GAME LOGIC (clay) — xorshift16 PRNG (a handful of ARM instructions) ── */
151
+ static u16 rng = 0xACE1;
152
+ static u8 random8(void) {
153
+ u16 r = rng;
154
+ r ^= r << 7;
155
+ r ^= r >> 9;
156
+ r ^= r << 8;
157
+ rng = r;
158
+ return (u8)r;
159
+ }
160
+
161
+ /* ── HARDWARE IDIOM (load-bearing — reshape gameplay around this; see TROUBLESHOOTING) ──
162
+ * PERSISTENT SRAM at 0x0E000000. Two footguns, both fatal-but-silent:
163
+ * 1. The SRAM bus is 8 BITS WIDE. Byte reads/writes only — a u16/u32
164
+ * access doesn't fault, it just reads the same byte mirrored (and a
165
+ * wide write stores one byte), so your data "almost" round-trips and
166
+ * then the checksum never matches. Every access below is via vu8.
167
+ * 2. Emulators and flashcarts detect the SAVE TYPE by scanning the ROM
168
+ * image for a marker string. Without "SRAM_V" in the ROM, mGBA gives
169
+ * the cart NO save memory at all and writes to 0x0E000000 vanish.
170
+ * The aligned, (used)-attributed const below plants that marker —
171
+ * delete it and persistence dies even though this code is untouched.
172
+ * Layout: 'V' 'X' score-lo score-hi checksum (xor ^ 0xA5) — magic+checksum
173
+ * so a fresh (0xFF-filled) cart reads as "no record" instead of garbage.
174
+ * requires: nothing else — self-contained; safe to transplant whole. */
175
+ #define SRAM_BYTE ((volatile u8 *)0x0E000000)
176
+ __attribute__((used, aligned(4))) static const char sram_type_marker[] = "SRAM_V113";
177
+
178
+ static u16 hiscore_load(void) {
179
+ u8 lo, hi;
180
+ if (SRAM_BYTE[0] != 'V' || SRAM_BYTE[1] != 'X') return 0;
181
+ lo = SRAM_BYTE[2];
182
+ hi = SRAM_BYTE[3];
183
+ if (SRAM_BYTE[4] != (u8)(lo ^ hi ^ 0xA5)) return 0;
184
+ return (u16)(lo | (hi << 8));
185
+ }
186
+
187
+ static void hiscore_save(u16 v) {
188
+ SRAM_BYTE[0] = 'V';
189
+ SRAM_BYTE[1] = 'X';
190
+ SRAM_BYTE[2] = (u8)v;
191
+ SRAM_BYTE[3] = (u8)(v >> 8);
192
+ SRAM_BYTE[4] = (u8)((u8)v ^ (u8)(v >> 8) ^ 0xA5);
193
+ }
194
+
195
+ /* ── GAME LOGIC (clay) — TTE text helpers ────────────────────────────────────
196
+ * Draw right-aligned decimal digits at pixel (x,y) WITHOUT tte_printf. The
197
+ * bundled libtonc's tte_printf with a %d conversion is broken (it routes
198
+ * through a vsnprintf path that isn't wired in this build — it garbles
199
+ * output AND wedges the loop when called per-frame, GBA-1). We build the
200
+ * string ourselves and use tte_write, which processes the #{P:x,y} position
201
+ * command but does NO format conversion → safe every frame. */
202
+ static void draw_num(int x, int y, unsigned v, int digits) {
27
203
  char buf[24];
28
204
  int i, n = 0;
29
- buf[n++]='#'; buf[n++]='{'; buf[n++]='P'; buf[n++]=':';
30
- if (x >= 100) buf[n++] = '0' + (x/100)%10;
31
- if (x >= 10) buf[n++] = '0' + (x/10)%10;
32
- buf[n++] = '0' + x%10;
33
- buf[n++]=','; buf[n++]='8'; buf[n++]='}';
34
- for (i = 4; i >= 0; i--) { buf[n+i] = '0' + (v % 10); v /= 10; }
35
- n += 5; buf[n] = 0;
205
+ buf[n++] = '#'; buf[n++] = '{'; buf[n++] = 'P'; buf[n++] = ':';
206
+ if (x >= 100) buf[n++] = (char)('0' + (x / 100) % 10);
207
+ if (x >= 10) buf[n++] = (char)('0' + (x / 10) % 10);
208
+ buf[n++] = (char)('0' + x % 10);
209
+ buf[n++] = ',';
210
+ if (y >= 100) buf[n++] = (char)('0' + (y / 100) % 10);
211
+ if (y >= 10) buf[n++] = (char)('0' + (y / 10) % 10);
212
+ buf[n++] = (char)('0' + y % 10);
213
+ buf[n++] = '}';
214
+ for (i = digits - 1; i >= 0; i--) { buf[n + i] = (char)('0' + (v % 10)); v /= 10; }
215
+ n += digits; buf[n] = 0;
36
216
  tte_write(buf);
37
217
  }
38
218
 
39
- #define COLS 6
40
- #define ROWS 12
219
+ /* ── HARDWARE IDIOM (load-bearing — reshape gameplay around this; see TROUBLESHOOTING) ──
220
+ * THE WELL IS BACKGROUND TILES on BG0 (Mode 0, a REGULAR text BG). A 32x32
221
+ * map (BG_REG_32x32) is one screenblock; each map entry is a u16: tile id
222
+ * (10 bits) + hflip/vflip + a 4-bit palbank. SE_BUILD(tile, palbank, hf, vf)
223
+ * packs it. Footguns this dodges:
224
+ * - VRAM IGNORES BYTE WRITES (a u8 store duplicates the byte into both
225
+ * halves of the 16-bit lane). We only ever write whole u16 SE entries
226
+ * (via this helper) and tonccpy() tile data — both VRAM-safe.
227
+ * - TTE owns BG1 (CBB 2 / SBB 30). Keep this map (SBB 28) and our tile
228
+ * graphics (CBB 0) clear of those blocks or text and well corrupt each
229
+ * other.
230
+ * Unlike the NES, there is NO vblank-queue famine here — set_cell can run
231
+ * any time, so paint_well() just rewrites all 72 cells when the board changes.
232
+ * requires: REG_BG0CNT → CBB 0 / SBB 28 (set in main), DCNT_BG0 enabled. */
233
+ static SCR_ENTRY *const well_map = se_mem[28];
234
+ static void set_cell(int tx, int ty, u16 tile) {
235
+ well_map[ty * 32 + tx] = SE_BUILD(tile, 0, 0, 0);
236
+ }
237
+
238
+ /* Repaint the whole well interior from the grid (cheap on GBA — see idiom). */
239
+ static void paint_well(void) {
240
+ int r, c;
241
+ for (r = 0; r < GRID_H; r++)
242
+ for (c = 0; c < GRID_W; c++)
243
+ set_cell(WELL_TX + c, WELL_TY + r, bg_tile_for(grid[r][c]));
244
+ }
41
245
 
42
- #define TILE_BLANK 0
43
- #define TILE_RED 1
44
- #define TILE_GREEN 2
45
- #define TILE_BLUE 3
246
+ /* Paint the static cabinet: backdrop dither, HUD band, well frame. Done once
247
+ * per state entry (the interior is then repainted by paint_well). */
248
+ static void paint_cabinet(void) {
249
+ int r, c, x0 = WELL_TX - 1;
250
+ for (r = 0; r < 32; r++)
251
+ for (c = 0; c < 32; c++)
252
+ set_cell(c, r, (r < 2) ? BG_BAND : BG_BACK); /* rows 0-1 = HUD band */
253
+ /* well frame: a border box around the interior */
254
+ for (c = 0; c <= GRID_W + 1; c++) {
255
+ set_cell(x0 + c, WELL_TY - 1, BG_FRAME);
256
+ set_cell(x0 + c, WELL_TY + GRID_H, BG_FRAME);
257
+ }
258
+ for (r = 0; r < GRID_H; r++) {
259
+ set_cell(x0, WELL_TY + r, BG_FRAME);
260
+ set_cell(x0 + GRID_W + 1, WELL_TY + r, BG_FRAME);
261
+ }
262
+ }
46
263
 
47
- /* Grid origin in BG-tile coords. */
48
- #define GRID_TX 4
49
- #define GRID_TY 2
264
+ /* ── GAME LOGIC (clay) HUD / screens (TTE on BG1, priority 0) ── */
265
+ static void draw_hud_numbers(void) {
266
+ tte_erase_rect(28, 4, 70, 12); draw_num(28, 4, score, 5);
267
+ tte_erase_rect(116, 4, 158, 12); draw_num(116, 4, hiscore, 5);
268
+ tte_erase_rect(210, 4, 220, 12); draw_num(210, 4, level, 1);
269
+ }
50
270
 
51
- static const u32 tile_red[8] = {
52
- 0x11111111, 0x11111111, 0x11111111, 0x11111111,
53
- 0x11111111, 0x11111111, 0x11111111, 0x11111111,
54
- };
55
- static const u32 tile_green[8] = {
56
- 0x22222222, 0x22222222, 0x22222222, 0x22222222,
57
- 0x22222222, 0x22222222, 0x22222222, 0x22222222,
58
- };
59
- static const u32 tile_blue[8] = {
60
- 0x33333333, 0x33333333, 0x33333333, 0x33333333,
61
- 0x33333333, 0x33333333, 0x33333333, 0x33333333,
62
- };
63
- /* Backdrop tile (colour index 4 = steel grey): a dither so the whole screen
64
- * reads as a "cabinet" behind the playfield instead of flat black — a lone
65
- * 6x12 grid floating on black looks blank to a human (frame verify <92%). */
66
- static const u32 tile_back[8] = {
67
- 0x40404040, 0x04040404, 0x40404040, 0x04040404,
68
- 0x40404040, 0x04040404, 0x40404040, 0x04040404,
69
- };
70
- #define TILE_BACK 4
71
-
72
- static u8 grid[ROWS][COLS];
73
-
74
- static u8 piece[3];
75
- static s16 piece_x;
76
- static s16 piece_y;
77
- static u16 fall_timer;
78
- static u16 score;
79
- static u32 rng_state = 1;
80
-
81
- static u32 xorshift(void) {
82
- rng_state ^= rng_state << 13;
83
- rng_state ^= rng_state >> 17;
84
- rng_state ^= rng_state << 5;
85
- return rng_state;
271
+ static void draw_hud_labels(void) {
272
+ tte_erase_screen();
273
+ tte_write("#{P:8,4}SC");
274
+ tte_write("#{P:96,4}HI");
275
+ tte_write("#{P:196,4}LV");
86
276
  }
87
277
 
88
- static u8 random_colour(void) { return 1 + (xorshift() % 3); }
278
+ static void enter_title(void) {
279
+ state = ST_TITLE;
280
+ paint_cabinet();
281
+ for (int r = 0; r < GRID_H; r++)
282
+ for (int c = 0; c < GRID_W; c++) { grid[r][c] = EMPTY; set_cell(WELL_TX + c, WELL_TY + r, BG_INNER); }
283
+ tte_erase_screen();
284
+ tte_write("#{P:64,40}" GAME_TITLE);
285
+ tte_write("#{P:76,72}PRESS START");
286
+ tte_write("#{P:88,92}HI");
287
+ draw_num(112, 92, hiscore, 5);
288
+ tte_write("#{P:32,116}DPAD MOVE - AB SPIN");
289
+ tte_write("#{P:48,128}START DROP - 1P MARATHON");
290
+ }
89
291
 
90
- static void new_piece(void) {
91
- piece[0] = random_colour();
92
- piece[1] = random_colour();
93
- piece[2] = random_colour();
94
- piece_x = COLS / 2 - 1;
95
- piece_y = -3;
292
+ static void spawn_piece(void);
293
+ static void enter_play(void) {
294
+ int r, c;
295
+ state = ST_PLAY;
296
+ for (r = 0; r < GRID_H; r++)
297
+ for (c = 0; c < GRID_W; c++) grid[r][c] = EMPTY;
298
+ score = 0; level = 1; cleared_total = 0; fall_t = 0;
299
+ /* Stir the PRNG with time-on-title so each run differs. */
300
+ rng ^= (u16)REG_VCOUNT ^ ((u16)REG_VCOUNT << 7);
301
+ if (rng == 0) rng = 0xACE1;
302
+ paint_cabinet();
303
+ paint_well();
304
+ draw_hud_labels();
305
+ draw_hud_numbers();
306
+ /* No need to swallow the START that began the run: key_hit only fires on a
307
+ * fresh press (curr & ~prev), so the held START doesn't also hard-drop. */
308
+ spawn_piece();
96
309
  }
97
310
 
98
- static int tile_for(u8 cell) {
99
- switch (cell) {
100
- case 1: return TILE_RED;
101
- case 2: return TILE_GREEN;
102
- case 3: return TILE_BLUE;
103
- default: return TILE_BACK; /* empty cell shows the backdrop, not black */
311
+ static void enter_over(void) {
312
+ state = ST_OVER;
313
+ if (score > hiscore) {
314
+ hiscore = score;
315
+ hiscore_save(hiscore); /* byte-wise SRAM write — see the SRAM idiom */
316
+ draw_hud_numbers();
104
317
  }
318
+ sfx_noise(20); /* game-over rumble */
319
+ tte_write("#{P:84,60}GAME OVER");
320
+ tte_write("#{P:76,80}PRESS START");
105
321
  }
106
322
 
107
- static void draw_cell(int col, int row) {
108
- if (row < 0 || row >= ROWS) return;
109
- u8 v = grid[row][col];
110
- SCR_ENTRY *map = se_mem[28];
111
- map[(GRID_TY + row) * 32 + (GRID_TX + col)] =
112
- SE_BUILD(tile_for(v), 0, 0, 0);
323
+ /* ── GAME LOGIC (clay reshape freely) ──────────────────────────────────────
324
+ * Match scan: mark every straight run of 3+ same-coloured jewels in all 4
325
+ * directions (a cell can belong to several runs — the mask de-dupes), and
326
+ * return how many cells matched. Runs flat-out on the ARM7 — no need to smear
327
+ * it across frames like the cc65 (NES) version. */
328
+ static const s8 DIRS4[4][2] = { {0,1}, {1,0}, {1,1}, {1,-1} };
329
+
330
+ static u8 mark_and_count(void) {
331
+ u8 r, c, d, len, k, cnt, col;
332
+ s8 dr, dc;
333
+ s16 sr, sc;
334
+ cnt = 0;
335
+ for (r = 0; r < GRID_H; r++)
336
+ for (c = 0; c < GRID_W; c++) matched[r][c] = 0;
337
+ for (r = 0; r < GRID_H; r++) {
338
+ for (c = 0; c < GRID_W; c++) {
339
+ col = grid[r][c];
340
+ if (col == EMPTY) continue;
341
+ for (d = 0; d < 4; d++) {
342
+ dr = DIRS4[d][0]; dc = DIRS4[d][1];
343
+ sr = (s16)r - dr; sc = (s16)c - dc;
344
+ if (sr >= 0 && sr < GRID_H && sc >= 0 && sc < GRID_W
345
+ && grid[sr][sc] == col) continue; /* not the run's start */
346
+ len = 1;
347
+ sr = (s16)r + dr; sc = (s16)c + dc;
348
+ while (sr >= 0 && sr < GRID_H && sc >= 0 && sc < GRID_W
349
+ && grid[sr][sc] == col) { len++; sr += dr; sc += dc; }
350
+ if (len >= 3) {
351
+ sr = r; sc = c;
352
+ for (k = 0; k < len; k++) {
353
+ if (!matched[sr][sc]) { matched[sr][sc] = 1; cnt++; }
354
+ sr += dr; sc += dc;
355
+ }
356
+ }
357
+ }
358
+ }
359
+ }
360
+ return cnt;
113
361
  }
114
362
 
115
- static void draw_grid(void) {
116
- for (int r = 0; r < ROWS; r++)
117
- for (int c = 0; c < COLS; c++)
118
- draw_cell(c, r);
363
+ /* Collapse each column so survivors rest on the floor (walk from the bottom,
364
+ * copying jewels down to a write cursor, then zero everything above it). */
365
+ static void apply_gravity(void) {
366
+ u8 c;
367
+ s16 r, w;
368
+ for (c = 0; c < GRID_W; c++) {
369
+ w = GRID_H - 1;
370
+ for (r = GRID_H - 1; r >= 0; r--) {
371
+ if (grid[r][c] != EMPTY) { grid[w][c] = grid[r][c]; w--; }
372
+ }
373
+ for (; w >= 0; w--) grid[w][c] = EMPTY;
374
+ }
119
375
  }
120
376
 
121
- static void draw_piece(int col, int row, int clear) {
122
- /* Transient overlay: when clearing, restore from grid (so we don't
123
- * blow away locked cells behind the piece). */
124
- SCR_ENTRY *map = se_mem[28];
125
- for (int i = 0; i < 3; i++) {
126
- int r = row + i;
127
- if (r < 0 || r >= ROWS) continue;
128
- u8 v = clear ? grid[r][col] : piece[i];
129
- map[(GRID_TY + r) * 32 + (GRID_TX + col)] =
130
- SE_BUILD(tile_for(v), 0, 0, 0);
377
+ /* ── GAME LOGIC (clay) — clear matches, drop survivors, chain cascades.
378
+ * Returns the chain depth (0 = the lock matched nothing). Score, level and the
379
+ * full-well repaint happen here. */
380
+ static u8 resolve_board(void) {
381
+ u8 n, r, c, chain;
382
+ u16 amt;
383
+ chain = 0;
384
+ for (;;) {
385
+ n = mark_and_count();
386
+ if (n == 0) break;
387
+ ++chain;
388
+ for (r = 0; r < GRID_H; r++)
389
+ for (c = 0; c < GRID_W; c++)
390
+ if (matched[r][c]) grid[r][c] = EMPTY;
391
+ amt = (u16)n * 10;
392
+ if (chain > 1) amt *= chain; /* cascades pay multiplied */
393
+ if (score < 65000u) score += amt;
394
+ /* clear chime — pitch rises with chain depth (bigger freq code = higher) */
395
+ sfx_tone(1, (u16)(1500 + ((u16)chain << 6)), 8);
396
+ apply_gravity();
397
+ cleared_total += n;
398
+ while (level < 9 && cleared_total >= (u16)level * 10) ++level;
131
399
  }
400
+ if (chain) { paint_well(); draw_hud_numbers(); }
401
+ return chain;
132
402
  }
133
403
 
134
- static int collides(int col, int row) {
135
- if (col < 0 || col >= COLS) return 1;
136
- for (int i = 0; i < 3; i++) {
137
- int r = row + i;
138
- if (r >= ROWS) return 1;
139
- if (r >= 0 && grid[r][col] != 0) return 1;
404
+ /* Can the trio occupy column x, rows y..y+2? Cells above the rim are fine
405
+ * (pieces enter from above); below the floor or on a jewel is not. */
406
+ static u8 can_place(s16 x, s16 y) {
407
+ s16 i, cy;
408
+ if (x < 0 || x >= GRID_W) return 0;
409
+ for (i = 0; i < 3; i++) {
410
+ cy = y + i;
411
+ if (cy < 0) continue;
412
+ if (cy >= GRID_H) return 0;
413
+ if (grid[cy][x] != EMPTY) return 0;
140
414
  }
141
- return 0;
415
+ return 1;
416
+ }
417
+
418
+ static void spawn_piece(void) {
419
+ piece_x = GRID_W / 2;
420
+ piece_y = -2;
421
+ piece_col[0] = (u8)(1 + random8() % 3);
422
+ piece_col[1] = (u8)(1 + random8() % 3);
423
+ piece_col[2] = (u8)(1 + random8() % 3);
424
+ if (!can_place(piece_x, piece_y)) enter_over(); /* well full → game over */
142
425
  }
143
426
 
427
+ /* ── GAME LOGIC (clay) — land the trio, resolve, respawn. ── */
144
428
  static void lock_piece(void) {
145
- for (int i = 0; i < 3; i++) {
146
- int r = piece_y + i;
147
- if (r >= 0 && r < ROWS) grid[r][piece_x] = piece[i];
429
+ s16 i, y;
430
+ for (i = 0; i < 3; i++) {
431
+ y = piece_y + i;
432
+ if (y >= 0) grid[y][piece_x] = piece_col[i];
148
433
  }
149
- /* Horizontal triples on the affected rows. */
150
- for (int i = 0; i < 3; i++) {
151
- int r = piece_y + i;
152
- if (r < 0 || r >= ROWS) continue;
153
- for (int c = 0; c <= COLS - 3; c++) {
154
- u8 a = grid[r][c], b = grid[r][c + 1], d = grid[r][c + 2];
155
- if (a != 0 && a == b && b == d) {
156
- grid[r][c] = 0;
157
- grid[r][c + 1] = 0;
158
- grid[r][c + 2] = 0;
159
- if (score < 65500u) score += 30;
160
- sfx_tone(1, 1700, 10); /* triple-clear chime */
161
- }
434
+ paint_well();
435
+ sfx_tone(2, 900, 4); /* lock thunk */
436
+ if (piece_y < 0) { enter_over(); return; } /* locked above the rim */
437
+ resolve_board();
438
+ if (state != ST_PLAY) return;
439
+ spawn_piece();
440
+ }
441
+
442
+ /* ── GAME LOGIC (clay) — input + gravity. Edge-triggered moves (key_hit = one
443
+ * cell per press), held DOWN soft-drops, A/B cycle the trio's colours, START
444
+ * hard-drops. libtonc's key_poll() (called once per frame in main) maintains
445
+ * the curr/prev key state that key_hit/key_held read — that's the idiomatic
446
+ * Tonc edge-trigger, no hand-rolled prev-mask needed. May end the game
447
+ * (lock → top-out). ── */
448
+ static void update_play(void) {
449
+ u8 t, fd;
450
+
451
+ if (key_hit(KEY_LEFT) && can_place(piece_x - 1, piece_y)) --piece_x;
452
+ if (key_hit(KEY_RIGHT) && can_place(piece_x + 1, piece_y)) ++piece_x;
453
+ if (key_hit(KEY_A)) { /* cycle colours downward */
454
+ t = piece_col[2]; piece_col[2] = piece_col[1];
455
+ piece_col[1] = piece_col[0]; piece_col[0] = t;
456
+ sfx_tone(2, 1300, 3);
457
+ }
458
+ if (key_hit(KEY_B)) { /* cycle colours upward */
459
+ t = piece_col[0]; piece_col[0] = piece_col[1];
460
+ piece_col[1] = piece_col[2]; piece_col[2] = t;
461
+ sfx_tone(2, 1400, 3);
462
+ }
463
+ if (key_hit(KEY_START)) { /* hard drop */
464
+ while (can_place(piece_x, piece_y + 1)) ++piece_y;
465
+ lock_piece();
466
+ return;
467
+ }
468
+
469
+ if (key_held(KEY_DOWN)) fall_t += 4; /* soft drop */
470
+ ++fall_t;
471
+ fd = (u8)(32 - ((level << 1) + level)); /* 29 (lv1) .. 5 (lv9) */
472
+ if (fall_t >= fd) {
473
+ fall_t = 0;
474
+ if (can_place(piece_x, piece_y + 1)) ++piece_y;
475
+ else lock_piece(); /* may end the game */
476
+ }
477
+ }
478
+
479
+ /* ── GAME LOGIC (clay) — stage the falling trio's sprites. The LOCKED well is
480
+ * BG tiles (only what moves every frame earns OAM slots): 3 sprites for the
481
+ * trio. Cells above the rim aren't drawn — they'd poke out over the HUD band.
482
+ * Off-screen / inactive slots park at y=200. ── */
483
+ static OBJ_ATTR obj_buffer[128];
484
+ #define TILE_TRIO 1 /* OBJ tile 1 = the faceted jewel sprite (4bpp 8x8) */
485
+
486
+ static void stage_sprites(void) {
487
+ int i;
488
+ int playing = (state == ST_PLAY);
489
+ for (i = 0; i < 3; i++) {
490
+ s16 r = piece_y + (s16)i;
491
+ u8 col = piece_col[i] ? piece_col[i] : 1;
492
+ if (playing && r >= 0) {
493
+ obj_set_attr(&obj_buffer[i], ATTR0_SQUARE, ATTR1_SIZE_8,
494
+ (u16)(ATTR2_PALBANK(col - 1) | TILE_TRIO));
495
+ obj_set_pos(&obj_buffer[i], (WELL_TX + piece_x) * 8, (WELL_TY + r) * 8);
496
+ } else {
497
+ obj_set_attr(&obj_buffer[i], ATTR0_SQUARE, ATTR1_SIZE_8,
498
+ (u16)(ATTR2_PALBANK(0) | TILE_TRIO));
499
+ obj_set_pos(&obj_buffer[i], 250, 200);
162
500
  }
163
501
  }
164
- draw_grid();
165
502
  }
166
503
 
167
504
  int main(void) {
168
- /* TTE first — it may touch pal_bg_mem + REG_BG#CNT, so set up
169
- * our BG0 grid resources AFTER its init runs. */
170
- tte_init_chr4c_default(1, BG_CBB(2) | BG_SBB(30));
171
- tte_write("#{P:88,8}SCORE 00000");
172
- tte_write("#{P:8,148}LR MOVE A ROT START DROP");
173
-
174
- /* BG palette for grid cells. */
175
- pal_bg_mem[0] = CLR_BLACK;
176
- pal_bg_mem[1] = CLR_RED;
177
- pal_bg_mem[2] = CLR_LIME;
178
- pal_bg_mem[3] = CLR_BLUE;
179
- pal_bg_mem[4] = RGB15(6, 6, 9); /* steel grey backdrop */
180
-
181
- /* BG tile graphics in char-block 3 (separate from TTE which used 2). */
182
- tonccpy(&tile_mem[3][TILE_RED], tile_red, sizeof(tile_red));
183
- tonccpy(&tile_mem[3][TILE_GREEN], tile_green, sizeof(tile_green));
184
- tonccpy(&tile_mem[3][TILE_BLUE], tile_blue, sizeof(tile_blue));
185
- tonccpy(&tile_mem[3][TILE_BACK], tile_back, sizeof(tile_back));
186
-
187
- /* Fill screen-block 28 (BG0 map) with the backdrop tile so the whole
188
- * screen is covered; the grid cells draw over it. (A blank/black map left
189
- * the playfield floating on black reads as blank.) */
190
- SCR_ENTRY *map = se_mem[28];
191
- for (int i = 0; i < 32 * 32; i++) map[i] = SE_BUILD(TILE_BACK, 0, 0, 0);
192
-
193
- REG_BG0CNT = BG_CBB(3) | BG_SBB(28) | BG_REG_32x32 | BG_4BPP | BG_PRIO(0);
194
- /* Bump TTE's BG1 to a LOWER priority so the grid (BG0, prio 0) renders
195
- * in front. (Higher prio number = drawn behind.) */
196
- REG_BG1CNT |= BG_PRIO(1);
197
-
198
- REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_BG1;
199
-
200
- for (int r = 0; r < ROWS; r++)
201
- for (int c = 0; c < COLS; c++)
202
- grid[r][c] = 0;
203
-
204
- score = 0;
205
- fall_timer = 0;
206
-
207
- /* IRQ setup required for VBlankIntrWait() to function. */
505
+ int k;
506
+
507
+ /* ── HARDWARE IDIOM (load-bearing reshape gameplay around this; see TROUBLESHOOTING) ──
508
+ * Init order: tiles/palettes → oam_init → irq_init + II_VBLANK → TTE init
509
+ * → DISPCNT last. VBlankIntrWait() HANGS FOREVER without the vblank IRQ
510
+ * registered (the #1 "frozen on frame 1" cause), and enabling DISPCNT
511
+ * layers before their tiles/maps exist flashes garbage. TTE owns BG1
512
+ * (CBB 2 / SBB 30) — keep other layers off those blocks.
513
+ * requires: nothing prior; this IS the boot. */
514
+
515
+ /* BG palette (bank 0). Vivid faceted jewels: each colour gets a 3-index
516
+ * slice body / glint / rim. The GBA's 15-bit RGB gives saturated stones
517
+ * the GB/NES can only hint at. */
518
+ pal_bg_mem[0] = RGB15(2, 2, 5); /* backdrop / transparent base */
519
+ pal_bg_mem[1] = RGB15(7, 7, 10); /* cabinet dither A */
520
+ pal_bg_mem[2] = RGB15(4, 4, 7); /* cabinet dither B */
521
+ pal_bg_mem[3] = RGB15(2, 2, 3); /* HUD band near-black */
522
+ pal_bg_mem[BG_FRAME] = RGB15(10, 11, 14); /* frame steel */
523
+ pal_bg_mem[BG_FRAME+1] = RGB15(18, 19, 23); /* frame lip */
524
+ /* jewel palette slices: index (3*k + n), n in 1..3 body/glint/rim.
525
+ * colour 1 = ruby, 2 = emerald, 3 = sapphire. */
526
+ pal_bg_mem[3*0+1] = RGB15(26, 4, 8); pal_bg_mem[3*0+2] = RGB15(31, 18, 20); pal_bg_mem[3*0+3] = RGB15(13, 1, 3);
527
+ pal_bg_mem[3*1+1] = RGB15(4, 24, 8); pal_bg_mem[3*1+2] = RGB15(20, 31, 18); pal_bg_mem[3*1+3] = RGB15(1, 11, 4);
528
+ pal_bg_mem[3*2+1] = RGB15(6, 12, 30); pal_bg_mem[3*2+2] = RGB15(20, 24, 31); pal_bg_mem[3*2+3] = RGB15(2, 4, 14);
529
+
530
+ /* BG tile graphics → char-block 0 (TTE uses CBB 2 kept clear). */
531
+ tonccpy(&tile_mem[0][BG_BACK], bg_tile_back, sizeof(bg_tile_back));
532
+ tonccpy(&tile_mem[0][BG_BAND], bg_tile_band, sizeof(bg_tile_band));
533
+ tonccpy(&tile_mem[0][BG_FRAME], bg_tile_frame, sizeof(bg_tile_frame));
534
+ tonccpy(&tile_mem[0][BG_INNER], bg_tile_inner, sizeof(bg_tile_inner));
535
+ build_gem_tiles();
536
+ tonccpy(&tile_mem[0][BG_GEM1], gem_ram[0], sizeof(gem_ram[0]));
537
+ tonccpy(&tile_mem[0][BG_GEM2], gem_ram[1], sizeof(gem_ram[1]));
538
+ tonccpy(&tile_mem[0][BG_GEM3], gem_ram[2], sizeof(gem_ram[2]));
539
+
540
+ /* Trio sprite: the same faceted jewel shape at OBJ tile 1, drawn per
541
+ * colour via three OBJ palbanks (0/1/2) that mirror the BG jewel slices. */
542
+ tonccpy(&tile_mem[4][TILE_TRIO], gem_shape, sizeof(gem_shape));
543
+ /* The sprite tile uses the RAW gem_shape (nibbles 1/2/3 = body/glint/rim),
544
+ * so the trio's colour is picked by the OBJ PALBANK at draw time: bank k
545
+ * carries colour-(k+1)'s body/glint/rim at indices 1/2/3. One tile, three
546
+ * vivid jewels — exactly mirroring the BG jewel palette slices. */
547
+ for (k = 0; k < 3; k++) {
548
+ pal_obj_bank[k][1] = pal_bg_mem[3*k+1]; /* body */
549
+ pal_obj_bank[k][2] = pal_bg_mem[3*k+2]; /* glint */
550
+ pal_obj_bank[k][3] = pal_bg_mem[3*k+3]; /* rim */
551
+ }
552
+
553
+ REG_BG0CNT = BG_CBB(0) | BG_SBB(28) | BG_REG_32x32 | BG_4BPP | BG_PRIO(2);
554
+
555
+ oam_init(obj_buffer, 128); /* hides all 128 */
556
+
208
557
  irq_init(NULL);
209
558
  irq_add(II_VBLANK, NULL);
210
559
 
211
- sfx_init();
212
- new_piece();
213
- draw_grid();
560
+ sfx_init(); /* APU on; music loop ticks below */
561
+
562
+ /* TTE text on BG1 (4bpp char block 2, screenblock 30), priority 0 so text
563
+ * draws over everything. Mode 0 = all four BGs regular/tiled. */
564
+ tte_init_chr4c_default(1, BG_CBB(2) | BG_SBB(30));
565
+ REG_BG1CNT |= BG_PRIO(0);
566
+ REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_BG1 | DCNT_OBJ | DCNT_OBJ_1D;
214
567
 
215
- u16 prev = 0;
568
+ hiscore = hiscore_load(); /* cartridge SRAM — 0 on first boot */
569
+ enter_title();
216
570
 
217
571
  while (1) {
572
+ /* Idiomatic Tonc heartbeat: wait vblank, poll keys, update, then
573
+ * commit OAM while still inside vblank (the update is far quicker than
574
+ * the 4.9ms vblank window). */
218
575
  VBlankIntrWait();
219
576
  key_poll();
220
-
221
- u16 now = key_curr_state();
222
-
223
- /* Erase the active piece visual. */
224
- draw_piece(piece_x, piece_y, 1);
225
-
226
- if ((now & KEY_LEFT) && !(prev & KEY_LEFT)
227
- && !collides(piece_x - 1, piece_y)) piece_x--;
228
- if ((now & KEY_RIGHT) && !(prev & KEY_RIGHT)
229
- && !collides(piece_x + 1, piece_y)) piece_x++;
230
- if ((now & KEY_A) && !(prev & KEY_A)) {
231
- u8 t = piece[0]; piece[0] = piece[1]; piece[1] = piece[2]; piece[2] = t;
232
- sfx_tone(2, 1400, 3); /* rotate click */
577
+ sfx_music_tick(); /* forget this → silent game */
578
+
579
+ if (state == ST_TITLE) {
580
+ if (key_hit(KEY_START | KEY_A)) enter_play();
581
+ } else if (state == ST_OVER) {
582
+ if (key_hit(KEY_START)) enter_title();
583
+ } else {
584
+ update_play();
233
585
  }
234
- if ((now & KEY_START) && !(prev & KEY_START)) {
235
- while (!collides(piece_x, piece_y + 1)) piece_y++;
236
- lock_piece();
237
- new_piece();
238
- prev = now;
239
- tte_erase_rect(88 + 6*8, 8, 88 + 11*8, 16);
240
- draw_score(88 + 6*8, score);
241
- continue;
242
- }
243
- prev = now;
244
-
245
- u16 fall_rate = (now & KEY_DOWN) ? 4 : 30;
246
- if (++fall_timer >= fall_rate) {
247
- fall_timer = 0;
248
- if (collides(piece_x, piece_y + 1)) {
249
- lock_piece();
250
- new_piece();
251
- } else {
252
- piece_y++;
253
- }
254
- }
255
-
256
- /* Re-draw piece in its new position. */
257
- draw_piece(piece_x, piece_y, 0);
258
586
 
259
- tte_erase_rect(88 + 6*8, 8, 88 + 11*8, 16);
260
- draw_score(88 + 6*8, score);
587
+ stage_sprites();
588
+ oam_copy(oam_mem, obj_buffer, 128);
261
589
  }
262
590
  return 0;
263
591
  }