romdevtools 0.16.0 → 0.22.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 (209) hide show
  1. package/AGENTS.md +75 -16
  2. package/CHANGELOG.md +316 -0
  3. package/examples/README.md +2 -0
  4. package/examples/atari2600/templates/platformer.asm +460 -0
  5. package/examples/atari2600/templates/racing.asm +463 -0
  6. package/examples/atari2600/templates/shmup.asm +386 -0
  7. package/examples/atari2600/templates/sports.asm +362 -0
  8. package/examples/atari7800/templates/default.c +49 -5
  9. package/examples/atari7800/templates/hello_sprite.c +48 -4
  10. package/examples/atari7800/templates/music_demo.c +47 -2
  11. package/examples/atari7800/templates/platformer.c +43 -4
  12. package/examples/atari7800/templates/puzzle.c +39 -4
  13. package/examples/atari7800/templates/racing.c +39 -4
  14. package/examples/atari7800/templates/shmup.c +40 -2
  15. package/examples/atari7800/templates/sports.c +36 -5
  16. package/examples/c64/templates/platformer.c +19 -5
  17. package/examples/c64/templates/puzzle.c +32 -2
  18. package/examples/c64/templates/shmup.c +28 -2
  19. package/examples/c64/templates/sports.c +30 -2
  20. package/examples/c64/templates/tile_engine.c +77 -27
  21. package/examples/gb/templates/default.c +110 -16
  22. package/examples/gb/templates/hello_sprite.c +15 -6
  23. package/examples/gb/templates/music_demo.c +36 -0
  24. package/examples/gb/templates/platformer.c +28 -6
  25. package/examples/gb/templates/puzzle.c +35 -4
  26. package/examples/gb/templates/racing.c +75 -10
  27. package/examples/gb/templates/shmup.c +41 -3
  28. package/examples/gb/templates/sports.c +51 -3
  29. package/examples/gb/templates/tile_engine.c +3 -2
  30. package/examples/gba/templates/gba_hello.c +29 -11
  31. package/examples/gba/templates/maxmod_demo.c +36 -2
  32. package/examples/gba/templates/platformer.c +3 -1
  33. package/examples/gba/templates/puzzle.c +15 -3
  34. package/examples/gba/templates/racing.c +65 -3
  35. package/examples/gba/templates/shmup.c +41 -4
  36. package/examples/gba/templates/sports.c +36 -2
  37. package/examples/gba/templates/tonc_hello.c +41 -5
  38. package/examples/gba/templates/tonc_hello_sprite.c +35 -1
  39. package/examples/gbc/templates/default.c +103 -26
  40. package/examples/gbc/templates/hello_sprite.c +12 -3
  41. package/examples/gbc/templates/music_demo.c +56 -12
  42. package/examples/gbc/templates/platformer.c +28 -6
  43. package/examples/gbc/templates/puzzle.c +35 -4
  44. package/examples/gbc/templates/racing.c +88 -21
  45. package/examples/gbc/templates/shmup.c +37 -3
  46. package/examples/gbc/templates/sports.c +48 -3
  47. package/examples/gbc/templates/tile_engine.c +3 -2
  48. package/examples/genesis/main.s +53 -1
  49. package/examples/genesis/templates/hello_sprite.c +25 -3
  50. package/examples/genesis/templates/puzzle.c +37 -3
  51. package/examples/genesis/templates/racing.c +44 -11
  52. package/examples/genesis/templates/sgdk_hello.c +34 -1
  53. package/examples/genesis/templates/shmup.c +31 -1
  54. package/examples/genesis/templates/shmup_2p.c +31 -0
  55. package/examples/genesis/templates/xgm2_demo.c +20 -0
  56. package/examples/gg/templates/default.c +56 -18
  57. package/examples/gg/templates/hello_sprite.c +25 -2
  58. package/examples/gg/templates/music_demo.c +24 -2
  59. package/examples/gg/templates/platformer.c +18 -12
  60. package/examples/gg/templates/puzzle.c +38 -7
  61. package/examples/gg/templates/racing.c +58 -9
  62. package/examples/gg/templates/shmup.c +47 -3
  63. package/examples/gg/templates/sports.c +57 -16
  64. package/examples/gg/templates/tile_engine.c +12 -6
  65. package/examples/lynx/templates/default.c +39 -8
  66. package/examples/lynx/templates/hello_sprite.c +15 -1
  67. package/examples/lynx/templates/music_demo.c +13 -1
  68. package/examples/lynx/templates/puzzle.c +28 -1
  69. package/examples/lynx/templates/racing.c +34 -7
  70. package/examples/lynx/templates/shmup.c +42 -3
  71. package/examples/lynx/templates/sports.c +29 -2
  72. package/examples/msx/platformer/main.c +213 -0
  73. package/examples/msx/puzzle/main.c +250 -0
  74. package/examples/msx/racing/main.c +249 -0
  75. package/examples/msx/shmup/main.c +288 -0
  76. package/examples/msx/sports/main.c +182 -0
  77. package/examples/nes/templates/default.c +67 -19
  78. package/examples/nes/templates/hello_sprite.c +35 -0
  79. package/examples/nes/templates/music_demo.c +40 -0
  80. package/examples/nes/templates/platformer.c +65 -6
  81. package/examples/nes/templates/puzzle.c +67 -6
  82. package/examples/nes/templates/racing.c +45 -13
  83. package/examples/nes/templates/shmup.c +51 -2
  84. package/examples/nes/templates/sports.c +51 -6
  85. package/examples/pce/catch_game/main.c +22 -3
  86. package/examples/pce/music_sfx/main.c +28 -1
  87. package/examples/pce/platformer/main.c +283 -0
  88. package/examples/pce/puzzle/main.c +304 -0
  89. package/examples/pce/racing/main.c +304 -0
  90. package/examples/pce/shmup/main.c +346 -0
  91. package/examples/pce/sports/main.c +254 -0
  92. package/examples/pce/sprite_move/main.c +7 -2
  93. package/examples/sms/main.c +35 -6
  94. package/examples/sms/templates/hello_sprite.c +29 -3
  95. package/examples/sms/templates/music_demo.c +18 -4
  96. package/examples/sms/templates/puzzle.c +34 -5
  97. package/examples/sms/templates/racing.c +39 -2
  98. package/examples/sms/templates/shmup.c +41 -2
  99. package/examples/sms/templates/shmup_2p.c +24 -1
  100. package/examples/sms/templates/sports.c +47 -4
  101. package/examples/snes/main.asm +108 -17
  102. package/examples/snes/templates/c-hello-data.asm +23 -0
  103. package/examples/snes/templates/c-hello.c +18 -1
  104. package/examples/snes/templates/default.c +50 -28
  105. package/examples/snes/templates/hello_sprite-data.asm +23 -0
  106. package/examples/snes/templates/hello_sprite.c +17 -1
  107. package/examples/snes/templates/music_demo-data.asm +23 -0
  108. package/examples/snes/templates/music_demo.c +22 -4
  109. package/examples/snes/templates/platformer-data.asm +22 -0
  110. package/examples/snes/templates/platformer.c +20 -2
  111. package/examples/snes/templates/puzzle-data.asm +22 -0
  112. package/examples/snes/templates/puzzle.c +21 -2
  113. package/examples/snes/templates/racing-data.asm +22 -0
  114. package/examples/snes/templates/racing.c +17 -1
  115. package/examples/snes/templates/shmup-data.asm +22 -0
  116. package/examples/snes/templates/shmup.c +20 -1
  117. package/examples/snes/templates/sports-data.asm +22 -0
  118. package/examples/snes/templates/sports.c +16 -1
  119. package/package.json +1 -1
  120. package/src/cheats/gamegenie.js +0 -1
  121. package/src/cli/smoke.js +1 -3
  122. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  123. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  124. package/src/host/LibretroHost.js +191 -16
  125. package/src/host/callbacks.js +9 -1
  126. package/src/host/chafa-render.js +2 -0
  127. package/src/host/dsp-state.js +2 -2
  128. package/src/host/gpgx-state.js +4 -0
  129. package/src/host/types.js +15 -8
  130. package/src/http/routes.js +1 -1
  131. package/src/http/tool-registry.js +26 -1
  132. package/src/mcp/server.js +1 -1
  133. package/src/mcp/state.js +36 -0
  134. package/src/mcp/tools/address-to-symbol.js +0 -1
  135. package/src/mcp/tools/art-loaders.js +1 -1
  136. package/src/mcp/tools/cart-parts.js +75 -4
  137. package/src/mcp/tools/classify-region.js +1 -1
  138. package/src/mcp/tools/diff-roms.js +1 -1
  139. package/src/mcp/tools/disasm-rebuild.js +507 -0
  140. package/src/mcp/tools/disasm.js +97 -9
  141. package/src/mcp/tools/find-references.js +1 -2
  142. package/src/mcp/tools/font-map.js +1 -1
  143. package/src/mcp/tools/frame.js +168 -3
  144. package/src/mcp/tools/index.js +0 -49
  145. package/src/mcp/tools/input-layout.js +0 -1
  146. package/src/mcp/tools/input.js +33 -3
  147. package/src/mcp/tools/lifecycle.js +18 -4
  148. package/src/mcp/tools/lospec.js +0 -19
  149. package/src/mcp/tools/platform-docs.js +1 -1
  150. package/src/mcp/tools/platform-tools.js +4 -4
  151. package/src/mcp/tools/project.js +54 -11
  152. package/src/mcp/tools/reinject.js +0 -1
  153. package/src/mcp/tools/rom-id.js +2 -2
  154. package/src/mcp/tools/snippets.js +2 -2
  155. package/src/mcp/tools/sprite-pipeline.js +1 -2
  156. package/src/mcp/tools/state.js +201 -14
  157. package/src/mcp/tools/tile-inspect.js +1 -1
  158. package/src/mcp/tools/toolchain.js +105 -12
  159. package/src/mcp/tools/watch-memory.js +137 -16
  160. package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +34 -0
  161. package/src/platforms/atari2600/TROUBLESHOOTING.md +6 -0
  162. package/src/platforms/atari7800/TROUBLESHOOTING.md +6 -0
  163. package/src/platforms/c64/MENTAL_MODEL.md +45 -1
  164. package/src/platforms/c64/TROUBLESHOOTING.md +6 -0
  165. package/src/platforms/c64/d64.js +280 -0
  166. package/src/platforms/c64/sid.js +0 -2
  167. package/src/platforms/common/metasprite-adapters.js +1 -1
  168. package/src/platforms/common/metasprite-codegen.js +3 -3
  169. package/src/platforms/common/registers.js +5 -3
  170. package/src/platforms/gb/MENTAL_MODEL.md +10 -0
  171. package/src/platforms/gb/TROUBLESHOOTING.md +6 -0
  172. package/src/platforms/gb/lib/c/gb_runtime.c +4 -4
  173. package/src/platforms/gba/TROUBLESHOOTING.md +6 -0
  174. package/src/platforms/gbc/TROUBLESHOOTING.md +6 -0
  175. package/src/platforms/gbc/lib/c/gb_runtime.c +4 -4
  176. package/src/platforms/genesis/TROUBLESHOOTING.md +6 -0
  177. package/src/platforms/gg/TROUBLESHOOTING.md +6 -0
  178. package/src/platforms/lynx/TROUBLESHOOTING.md +6 -0
  179. package/src/platforms/msx/MENTAL_MODEL.md +10 -6
  180. package/src/platforms/msx/TROUBLESHOOTING.md +6 -0
  181. package/src/platforms/nes/MENTAL_MODEL.md +63 -2
  182. package/src/platforms/nes/TROUBLESHOOTING.md +6 -0
  183. package/src/platforms/nes/image-to-tilemap.js +3 -0
  184. package/src/platforms/nes/lib/asm/famitone2.s +5 -1
  185. package/src/platforms/pce/MENTAL_MODEL.md +9 -4
  186. package/src/platforms/pce/TROUBLESHOOTING.md +6 -0
  187. package/src/platforms/pce/lib/c/pce_video.c +1 -1
  188. package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
  189. package/src/platforms/snes/TROUBLESHOOTING.md +6 -0
  190. package/src/platforms/snes/brr.js +0 -2
  191. package/src/playtest/playtest.js +0 -7
  192. package/src/rom-id/identifier.js +15 -0
  193. package/src/toolchains/asar/asar.js +0 -9
  194. package/src/toolchains/assemble-snippet.js +30 -12
  195. package/src/toolchains/cc65/ines.js +145 -0
  196. package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +14 -1
  197. package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
  198. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
  199. package/src/toolchains/common/reassemble.js +10 -3
  200. package/src/toolchains/common/sdk-cache.js +1 -1
  201. package/src/toolchains/genesis-c/genesis-c.js +5 -3
  202. package/src/toolchains/index.js +27 -3
  203. package/src/toolchains/parse-errors.js +78 -1
  204. package/src/toolchains/sdcc/preflight-lint.js +5 -1
  205. package/src/toolchains/sdcc/sdcc.js +1 -1
  206. package/src/toolchains/sjasm/sjasm.js +1 -1
  207. package/src/toolchains/snes-c/snes-c.js +2 -2
  208. package/src/toolchains/vasm68k/vasm68k.js +2 -4
  209. package/src/toolchains/wladx/wladx.js +1 -1
@@ -0,0 +1,362 @@
1
+ ; ── sports.asm — Atari 2600 SPORTS genre scaffold (Pong) ──────────────
2
+ ;
3
+ ; Pong IS the 2600's sport — the console shipped with Combat and Video
4
+ ; Olympics; the paddle-and-ball game is the genre's archetype and a
5
+ ; flawless fit for the TIA's two players + ball. Identical in spirit to
6
+ ; the verified `paddle` template.
7
+ ;
8
+ ; Two paddles (player 0 = left, player 1 = right), one ball (BL),
9
+ ; symmetric playfield with top + bottom walls. Joystick port A up/down
10
+ ; moves the left paddle; the right paddle does simple AI (chases ball Y).
11
+ ;
12
+ ; 2600 architecture refresher:
13
+ ; - No frame buffer. Every scanline you write TIA registers describing
14
+ ; what THAT line looks like, then STA WSYNC to advance.
15
+ ; - 5 graphics objects: P0, P1, M0, M1, BL (+ PF tiles via PF0/1/2).
16
+ ; - "Race the beam": the CPU has ~76 cycles between WSYNCs.
17
+ ;
18
+ ; This scaffold uses:
19
+ ; P0 → left paddle (8-pixel-tall vertical bar)
20
+ ; P1 → right paddle (8-pixel-tall vertical bar)
21
+ ; BL → 2-pixel-wide ball
22
+ ; PF0 → top + bottom playfield walls (drawn for the top + bottom rows)
23
+
24
+ processor 6502
25
+ org $F000
26
+
27
+ VSYNC = $00
28
+ VBLANK = $01
29
+ WSYNC = $02
30
+ COLUP0 = $06
31
+ COLUP1 = $07
32
+ COLUPF = $08
33
+ COLUBK = $09
34
+ PF0 = $0D
35
+ PF1 = $0E
36
+ PF2 = $0F
37
+ RESP0 = $10
38
+ RESP1 = $11
39
+ RESBL = $14
40
+ GRP0 = $1B
41
+ GRP1 = $1C
42
+ ENABL = $1F
43
+ HMP0 = $20
44
+ HMP1 = $21
45
+ HMBL = $24
46
+ HMOVE = $2A
47
+ CTRLPF = $0A
48
+ SWCHA = $280
49
+ ; ── TIA audio (R41) ────────────────────────────────────────────────
50
+ AUDC0 = $15
51
+ AUDF0 = $17
52
+ AUDV0 = $19
53
+
54
+ ; ── Zero-page state ──────────────────────────────────────────────────
55
+ P0_Y = $80 ; left paddle top scanline (16..168)
56
+ P1_Y = $81 ; right paddle top scanline
57
+ BALL_X = $82 ; ball horizontal (TIA pixel 0..159)
58
+ BALL_Y = $83 ; ball vertical (scanline)
59
+ BALL_DX = $84 ; +1 or -1
60
+ BALL_DY = $85 ; +1 or -1
61
+ FRAME = $86
62
+ SFX_LEFT = $87 ; frames remaining on active sfx (0 = silent)
63
+
64
+ START:
65
+ SEI
66
+ CLD
67
+ LDX #$FF
68
+ TXS
69
+ LDA #0
70
+ .clr:
71
+ STA $00,X
72
+ DEX
73
+ BNE .clr
74
+
75
+ LDA #88
76
+ STA P0_Y
77
+ STA P1_Y
78
+ LDA #80
79
+ STA BALL_X
80
+ LDA #90
81
+ STA BALL_Y
82
+ LDA #1
83
+ STA BALL_DX
84
+ STA BALL_DY
85
+
86
+ ; Boot chime — confirms TIA audio is wired.
87
+ LDA #$04
88
+ STA AUDC0
89
+ LDA #$0C
90
+ STA AUDF0
91
+ LDA #$0F
92
+ STA AUDV0
93
+ LDA #20
94
+ STA SFX_LEFT
95
+
96
+ LDA #$80 ; blue background
97
+ STA COLUBK
98
+ LDA #$0F ; white paddles
99
+ STA COLUP0
100
+ STA COLUP1
101
+ LDA #$48 ; cyan playfield
102
+ STA COLUPF
103
+ LDA #$05 ; PF symmetric reflect + ball priority
104
+ STA CTRLPF
105
+
106
+ MAIN:
107
+ INC FRAME
108
+
109
+ ; ── VSYNC ─────────────────────────────────────────────────────────
110
+ LDA #2
111
+ STA VSYNC
112
+ STA WSYNC
113
+ STA WSYNC
114
+ STA WSYNC
115
+ LDA #0
116
+ STA VSYNC
117
+
118
+ ; ── VBLANK (37 lines) — game logic ────────────────────────────────
119
+ ; 34 here + the 3 STA WSYNC in the P0/P1 positioning block below = 37 VBLANK
120
+ ; lines total. (Bug fix: this loop used to be 37 AND the positioning added 3
121
+ ; more → 265 scanlines/frame → the TV/emulator can't lock vsync → rolling /
122
+ ; black picture. Exactly 262 lines = 3 VSYNC + 37 VBLANK + 192 visible + 30
123
+ ; overscan; the positioning WSYNCs MUST be counted against the 37.)
124
+ LDA #2
125
+ STA VBLANK
126
+ LDX #34
127
+ .vb:
128
+ STA WSYNC
129
+ DEX
130
+ BNE .vb
131
+
132
+ ; Move left paddle from joystick (every 2 frames to throttle).
133
+ LDA FRAME
134
+ AND #$01
135
+ BNE .skip_pad
136
+ LDA SWCHA
137
+ ASL ; right (unused)
138
+ ASL ; left (unused)
139
+ ASL ; down
140
+ BCS .nd
141
+ INC P0_Y
142
+ .nd:
143
+ ASL ; up
144
+ BCS .nu
145
+ DEC P0_Y
146
+ .nu:
147
+ ; Clamp paddle within bounds
148
+ LDA P0_Y
149
+ CMP #16
150
+ BCS .nopaddmin
151
+ LDA #16
152
+ STA P0_Y
153
+ .nopaddmin:
154
+ CMP #168
155
+ BCC .nopaddmax
156
+ LDA #168
157
+ STA P0_Y
158
+ .nopaddmax:
159
+ .skip_pad:
160
+
161
+ ; Right-paddle AI — chase the ball's Y
162
+ LDA BALL_Y
163
+ CMP P1_Y
164
+ BCC .ai_up
165
+ INC P1_Y
166
+ JMP .ai_done
167
+ .ai_up:
168
+ DEC P1_Y
169
+ .ai_done:
170
+
171
+ ; Move ball
172
+ LDA BALL_DX
173
+ CLC
174
+ ADC BALL_X
175
+ STA BALL_X
176
+ LDA BALL_DY
177
+ CLC
178
+ ADC BALL_Y
179
+ STA BALL_Y
180
+
181
+ ; Bounce off top/bottom — short blip on each bounce.
182
+ LDA BALL_Y
183
+ CMP #20
184
+ BCS .nb_top
185
+ LDA #1
186
+ STA BALL_DY
187
+ JSR sfx_wall
188
+ .nb_top:
189
+ LDA BALL_Y
190
+ CMP #180
191
+ BCC .nb_bot
192
+ LDA #$FF ; -1
193
+ STA BALL_DY
194
+ JSR sfx_wall
195
+ .nb_bot:
196
+ ; Bounce off left/right (and respawn near centre on miss) — pew on miss.
197
+ LDA BALL_X
198
+ CMP #4
199
+ BCS .nb_l
200
+ LDA #1
201
+ STA BALL_DX
202
+ LDA #80
203
+ STA BALL_X
204
+ JSR sfx_score
205
+ .nb_l:
206
+ LDA BALL_X
207
+ CMP #156
208
+ BCC .nb_r
209
+ LDA #$FF
210
+ STA BALL_DX
211
+ LDA #80
212
+ STA BALL_X
213
+ JSR sfx_score
214
+ .nb_r:
215
+
216
+ ; sfx_update: tick the countdown, silence on zero.
217
+ LDA SFX_LEFT
218
+ BEQ .sfx_done
219
+ DEC SFX_LEFT
220
+ BNE .sfx_done
221
+ LDA #0
222
+ STA AUDV0
223
+ .sfx_done:
224
+
225
+ ; ── Position P0 / P1 / HMOVE — exactly 3 WSYNC-bounded lines ───────
226
+ ; CRITICAL: every RESPx write AND the STA HMOVE must complete inside
227
+ ; the 76-cycle scanline that began with its STA WSYNC. A DEX/BNE delay
228
+ ; loop costs 5 cycles/iteration, so the loop count must be small enough
229
+ ; that RESPx still lands before the line ends. The old code used
230
+ ; LDX #38 (~189 cycles = 2.5 scanlines!) with no WSYNC before RESP1/
231
+ ; HMOVE, so it emitted ~2-3 UNCOUNTED scanlines past the 262 budget →
232
+ ; ~265 lines/frame → vsync never locks (rolling magenta band). HMOVE
233
+ ; was also issued mid-line; it must follow a fresh WSYNC.
234
+
235
+ ; Line 1 of 3: coarse-position P0 (left, ~column 16)
236
+ STA WSYNC
237
+ LDX #5
238
+ .p0d:
239
+ DEX
240
+ BNE .p0d ; ~24 cycles in → P0 lands near the left edge
241
+ STA RESP0
242
+ ; Line 2 of 3: coarse-position P1 (right, ~column 132)
243
+ STA WSYNC
244
+ LDX #13
245
+ .p1d:
246
+ DEX
247
+ BNE .p1d ; ~64 cycles in (< 76) → P1 lands near the right
248
+ STA RESP1
249
+ ; Line 3 of 3: apply HMOVE on a FRESH line, right after WSYNC
250
+ STA WSYNC
251
+ STA HMOVE
252
+
253
+ LDA #0
254
+ STA VBLANK
255
+
256
+ ; ── Visible (192 lines) — TWO-LINE KERNEL ─────────────────────────
257
+ ; CRITICAL: a single scanline is only 76 CPU cycles. The full per-line
258
+ ; render here (playfield walls + P0 + P1 + ball, each a SEC/SBC/CMP +
259
+ ; conditional store) is ~88 cycles — it does NOT fit in one line. In a
260
+ ; 1-line kernel each WSYNC iteration then spills past the line boundary,
261
+ ; so 192 iterations stretch to ~232 emitted lines → ~250-line frame →
262
+ ; vsync never locks (rolling magenta band — THE bug).
263
+ ;
264
+ ; The fix is the standard 2600 "2-line kernel": each loop pass renders
265
+ ; TWO scanlines and splits the work across two WSYNCs, doubling the
266
+ ; budget to ~152 cycles. 96 passes × 2 lines = 192 visible lines.
267
+ ; Y counts 192→2 in steps of 2; paddles/ball move in 2px steps (fine
268
+ ; for Pong). The branchless "LDA #off / CMP / BCS skip / LDA #on" form
269
+ ; also drops the JMPs the old code paid every line.
270
+ LDY #192
271
+ .draw:
272
+ ; ---- first line of the pair: playfield walls + left paddle ----
273
+ STA WSYNC
274
+ ; Top + bottom walls: full-width PF on the outer rows (Y>=189 / Y<5)
275
+ LDA #0
276
+ CPY #189
277
+ BCS .wall
278
+ CPY #5
279
+ BCS .nowall
280
+ .wall:
281
+ LDA #$FF
282
+ .nowall:
283
+ STA PF0
284
+ STA PF1
285
+ STA PF2
286
+ ; Left paddle: 8 lines starting at P0_Y
287
+ TYA
288
+ SEC
289
+ SBC P0_Y
290
+ CMP #8
291
+ LDA #0
292
+ BCS .p0off
293
+ LDA #$FF
294
+ .p0off:
295
+ STA GRP0
296
+ ; ---- second line of the pair: right paddle + ball ----
297
+ STA WSYNC
298
+ ; Right paddle: 8 lines starting at P1_Y
299
+ TYA
300
+ SEC
301
+ SBC P1_Y
302
+ CMP #8
303
+ LDA #0
304
+ BCS .p1off
305
+ LDA #$FF
306
+ .p1off:
307
+ STA GRP1
308
+ ; Ball: 2 lines starting at BALL_Y
309
+ TYA
310
+ SEC
311
+ SBC BALL_Y
312
+ CMP #2
313
+ LDA #0
314
+ BCS .bloff
315
+ LDA #2
316
+ .bloff:
317
+ STA ENABL
318
+ DEY
319
+ DEY
320
+ BNE .draw
321
+
322
+ ; ── Overscan (30 lines) ───────────────────────────────────────────
323
+ LDA #2
324
+ STA VBLANK
325
+ LDX #30
326
+ .os:
327
+ STA WSYNC
328
+ DEX
329
+ BNE .os
330
+
331
+ JMP MAIN
332
+
333
+ ; ── TIA sfx helpers (R41) ─────────────────────────────────────────
334
+ ; sfx_wall — short blip on wall bounce (4-frame ringing tone)
335
+ sfx_wall:
336
+ LDA #$04
337
+ STA AUDC0
338
+ LDA #$10
339
+ STA AUDF0
340
+ LDA #$0F
341
+ STA AUDV0
342
+ LDA #4
343
+ STA SFX_LEFT
344
+ RTS
345
+
346
+ ; sfx_score — longer chime when a player scores (16 frames)
347
+ sfx_score:
348
+ LDA #$04
349
+ STA AUDC0
350
+ LDA #$06 ; higher pitch
351
+ STA AUDF0
352
+ LDA #$0F
353
+ STA AUDV0
354
+ LDA #16
355
+ STA SFX_LEFT
356
+ RTS
357
+
358
+ ; ── Vector table ──────────────────────────────────────────────────
359
+ org $FFFA
360
+ .word START
361
+ .word START
362
+ .word START
@@ -43,6 +43,8 @@
43
43
  #define P0C1 (*(volatile uint8_t*)0x21)
44
44
  #define P0C2 (*(volatile uint8_t*)0x22)
45
45
  #define P0C3 (*(volatile uint8_t*)0x23)
46
+ #define P1C1 (*(volatile uint8_t*)0x25)
47
+ #define P2C1 (*(volatile uint8_t*)0x29)
46
48
  #define MSTAT (*(volatile uint8_t*)0x28)
47
49
  #define DPPH (*(volatile uint8_t*)0x2C)
48
50
  #define DPPL (*(volatile uint8_t*)0x30)
@@ -91,6 +93,43 @@ MK_DL(dl_row4); MK_DL(dl_row5); MK_DL(dl_row6); MK_DL(dl_row7);
91
93
  * at dp+1; if it's 0, the parse loop exits immediately. */
92
94
  static uint8_t dl_empty[2] = { 0, 0 };
93
95
 
96
+ /* ── Background playfield ─────────────────────────────────────────
97
+ * Without a full-screen drawable the display list emits only the one
98
+ * sprite above and ~99% of the screen stays the flat BACKGRND colour
99
+ * (reads as "blank"). These full-width bands fill every non-sprite
100
+ * zone with scenery so the frame has real content.
101
+ *
102
+ * One scanline of solid pixels lives in ROM (band_pix). A single DL
103
+ * drawable is at most 32 bytes = 128 px wide, so a full 160-px line
104
+ * needs TWO drawables. Width encoding (byte[3] low 5 bits) = 32-bytes;
105
+ * high 3 bits = palette: field uses palette 1, ground uses palette 2.
106
+ */
107
+ static const uint8_t band_pix[32] = {
108
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,
109
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
110
+ };
111
+ #define MK_BAND(name, pal) static uint8_t name[11] = { \
112
+ 0, 0x40, 0, ((pal) << 5) | 0, 0, /* 128 px @ x0 */ \
113
+ 0, 0x40, 0, ((pal) << 5) | 24, 128, /* 32 px @ x128 */ \
114
+ 0 }
115
+ MK_BAND(dl_field, 1);
116
+ MK_BAND(dl_ground, 2);
117
+ #define GROUND_ZONE 188
118
+
119
+ static void set_band_addr(uint8_t* dl) {
120
+ uint16_t a = (uint16_t)(uintptr_t)band_pix;
121
+ dl[0] = dl[5] = (uint8_t)(a & 0xFF);
122
+ dl[2] = dl[7] = (uint8_t)(a >> 8);
123
+ }
124
+
125
+ /* Background DL for a non-sprite zone: sky (empty) up top, field in the
126
+ * middle, ground at the bottom. */
127
+ static uint16_t bg_zone_dl(int zone) {
128
+ if (zone >= GROUND_ZONE) return (uint16_t)(uintptr_t)dl_ground;
129
+ if (zone >= 28) return (uint16_t)(uintptr_t)dl_field;
130
+ return (uint16_t)(uintptr_t)dl_empty;
131
+ }
132
+
94
133
  /* DLL: one entry per visible scanline + the 10-line top overscan
95
134
  * MARIA walks BEFORE the visible area begins. NTSC display area =
96
135
  * scanlines 16..258 = 243 lines.
@@ -130,9 +169,12 @@ static void vblank_wait(void) {
130
169
 
131
170
  void main(void) {
132
171
  uint16_t dll_addr;
133
- uint16_t empty_dl = (uint16_t)(uintptr_t)dl_empty;
134
172
  int i;
135
173
 
174
+ /* Point the background bands at their shared ROM pixel row. */
175
+ set_band_addr(dl_field);
176
+ set_band_addr(dl_ground);
177
+
136
178
  /* Patch each row's DL to point at its sprite-data row. */
137
179
  set_dl_addr(dl_row0, sprite_row0);
138
180
  set_dl_addr(dl_row1, sprite_row1);
@@ -143,9 +185,9 @@ void main(void) {
143
185
  set_dl_addr(dl_row6, sprite_row6);
144
186
  set_dl_addr(dl_row7, sprite_row7);
145
187
 
146
- /* Build the DLL: empty zones everywhere except the 8 sprite rows. */
188
+ /* Build the DLL: background scenery everywhere except the 8 sprite rows. */
147
189
  for (i = 0; i < DLL_ZONES; i++) {
148
- uint16_t dl_ptr = empty_dl;
190
+ uint16_t dl_ptr = bg_zone_dl(i);
149
191
  if (i == SPRITE_Y + 0) dl_ptr = (uint16_t)(uintptr_t)dl_row0;
150
192
  else if (i == SPRITE_Y + 1) dl_ptr = (uint16_t)(uintptr_t)dl_row1;
151
193
  else if (i == SPRITE_Y + 2) dl_ptr = (uint16_t)(uintptr_t)dl_row2;
@@ -158,10 +200,12 @@ void main(void) {
158
200
  }
159
201
 
160
202
  /* Palette + background. Atari NTSC palette: HHHL nibble form. */
161
- BACKGRND = 0x88; /* light blue */
162
- P0C1 = 0x46; /* orange-red */
203
+ BACKGRND = 0x88; /* light blue sky */
204
+ P0C1 = 0x46; /* orange-red (sprite) */
163
205
  P0C2 = 0x0F; /* white */
164
206
  P0C3 = 0x36; /* pink */
207
+ P1C1 = 0xC8; /* field green (background band) */
208
+ P2C1 = 0x14; /* ground brown (background band) */
165
209
  CHARBASE = 0;
166
210
  OFFSET = 0;
167
211
 
@@ -16,6 +16,8 @@
16
16
  #define P0C1 (*(volatile uint8_t*)0x21)
17
17
  #define P0C2 (*(volatile uint8_t*)0x22)
18
18
  #define P0C3 (*(volatile uint8_t*)0x23)
19
+ #define P1C1 (*(volatile uint8_t*)0x25)
20
+ #define P2C1 (*(volatile uint8_t*)0x29)
19
21
  #define MSTAT (*(volatile uint8_t*)0x28)
20
22
  #define DPPH (*(volatile uint8_t*)0x2C)
21
23
  #define DPPL (*(volatile uint8_t*)0x30)
@@ -47,6 +49,42 @@ MK_DL(dl_row4); MK_DL(dl_row5); MK_DL(dl_row6); MK_DL(dl_row7);
47
49
 
48
50
  static uint8_t dl_empty[2] = { 0, 0 };
49
51
 
52
+ /* ── Background playfield ─────────────────────────────────────────────
53
+ * Without a full-screen drawable the display list emits only the one
54
+ * sprite and ~99% of the screen stays the flat BACKGRND colour (reads as
55
+ * "blank"). These full-width bands fill every non-sprite zone with scenery
56
+ * so the frame has real content (same machinery as default.c).
57
+ *
58
+ * One scanline of solid pixels lives in ROM (band_pix). A single DL
59
+ * drawable is at most 32 bytes = 128 px wide, so a full 160-px line needs
60
+ * TWO drawables. Width (byte[3] low 5 bits) = 32-bytes; high 3 bits =
61
+ * palette: field uses palette 1, ground uses palette 2. */
62
+ static const uint8_t band_pix[32] = {
63
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,
64
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
65
+ };
66
+ #define MK_BAND(name, pal) static uint8_t name[11] = { \
67
+ 0, 0x40, 0, ((pal) << 5) | 0, 0, /* 128 px @ x0 */ \
68
+ 0, 0x40, 0, ((pal) << 5) | 24, 128, /* 32 px @ x128 */ \
69
+ 0 }
70
+ MK_BAND(dl_field, 1);
71
+ MK_BAND(dl_ground, 2);
72
+ #define GROUND_ZONE 188
73
+
74
+ static void set_band_addr(uint8_t* dl) {
75
+ uint16_t a = (uint16_t)(uintptr_t)band_pix;
76
+ dl[0] = dl[5] = (uint8_t)(a & 0xFF);
77
+ dl[2] = dl[7] = (uint8_t)(a >> 8);
78
+ }
79
+
80
+ /* Background DL for a non-sprite zone: sky (empty) up top, field in the
81
+ * middle, ground at the bottom. */
82
+ static uint16_t bg_zone_dl(int zone) {
83
+ if (zone >= GROUND_ZONE) return (uint16_t)(uintptr_t)dl_ground;
84
+ if (zone >= 28) return (uint16_t)(uintptr_t)dl_field;
85
+ return (uint16_t)(uintptr_t)dl_empty;
86
+ }
87
+
50
88
  #define DLL_ZONES 243
51
89
  static uint8_t dll[DLL_ZONES * 3];
52
90
 
@@ -62,9 +100,9 @@ static void set_dll_entry(int idx, uint16_t dl_ptr) {
62
100
  dll[idx * 3 + 2] = (uint8_t)(dl_ptr & 0xFF);
63
101
  }
64
102
 
65
- /* Build the DLL with the sprite's 8 rows placed at DLL index sprite_y. */
103
+ /* Build the DLL with the sprite's 8 rows placed at DLL index sprite_y;
104
+ * every other zone gets the background scenery band for its row. */
66
105
  static void build_dll(uint8_t sprite_y) {
67
- uint16_t empty = (uint16_t)(uintptr_t)dl_empty;
68
106
  int i;
69
107
  for (i = 0; i < DLL_ZONES; i++) {
70
108
  uint16_t dl;
@@ -78,7 +116,7 @@ static void build_dll(uint8_t sprite_y) {
78
116
  case 5: dl = (uint16_t)(uintptr_t)dl_row5; break;
79
117
  case 6: dl = (uint16_t)(uintptr_t)dl_row6; break;
80
118
  case 7: dl = (uint16_t)(uintptr_t)dl_row7; break;
81
- default: dl = empty; break;
119
+ default: dl = bg_zone_dl(i); break; /* field/ground scenery */
82
120
  }
83
121
  set_dll_entry(i, dl);
84
122
  }
@@ -99,6 +137,10 @@ void main(void) {
99
137
  uint8_t x = 80;
100
138
  uint8_t y = 110;
101
139
 
140
+ /* Point the background bands at their shared ROM pixel row. */
141
+ set_band_addr(dl_field);
142
+ set_band_addr(dl_ground);
143
+
102
144
  /* Wire each DL's address bytes to its sprite-row data. */
103
145
  set_dl_addr(dl_row0, sprite_row0);
104
146
  set_dl_addr(dl_row1, sprite_row1);
@@ -112,10 +154,12 @@ void main(void) {
112
154
  set_x(x);
113
155
  build_dll(y);
114
156
 
115
- BACKGRND = 0x88;
157
+ BACKGRND = 0x88; /* light blue sky */
116
158
  P0C1 = 0x46;
117
159
  P0C2 = 0x0F;
118
160
  P0C3 = 0x36;
161
+ P1C1 = 0xC8; /* field green (background band) */
162
+ P2C1 = 0x14; /* ground brown (background band) */
119
163
  CHARBASE = 0;
120
164
  OFFSET = 0;
121
165
 
@@ -19,6 +19,8 @@
19
19
  #define P0C1 (*(volatile uint8_t*)0x21)
20
20
  #define P0C2 (*(volatile uint8_t*)0x22)
21
21
  #define P0C3 (*(volatile uint8_t*)0x23)
22
+ #define P1C1 (*(volatile uint8_t*)0x25)
23
+ #define P2C1 (*(volatile uint8_t*)0x29)
22
24
  #define MSTAT (*(volatile uint8_t*)0x28)
23
25
  #define DPPH (*(volatile uint8_t*)0x2C)
24
26
  #define DPPL (*(volatile uint8_t*)0x30)
@@ -47,6 +49,42 @@ MK_DL(dl_row4); MK_DL(dl_row5); MK_DL(dl_row6); MK_DL(dl_row7);
47
49
 
48
50
  static uint8_t dl_empty[2] = { 0, 0 };
49
51
 
52
+ /* ── Background playfield ─────────────────────────────────────────────
53
+ * Without a full-screen drawable the display list emits only the banner
54
+ * and ~99% of the screen stays the flat BACKGRND colour (reads as
55
+ * "blank"). These full-width bands fill every non-banner zone with scenery
56
+ * so the frame has real content (same machinery as default.c).
57
+ *
58
+ * One scanline of solid pixels lives in ROM (band_pix). A 160-px line needs
59
+ * TWO drawables (a DL drawable is at most 32 bytes = 128 px). Width
60
+ * (byte[3] low 5 bits) = 32-bytes; high 3 bits = palette: field = palette
61
+ * 1, ground = palette 2. */
62
+ static const uint8_t band_pix[32] = {
63
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,
64
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
65
+ };
66
+ #define MK_BAND(name, pal) static uint8_t name[11] = { \
67
+ 0, 0x40, 0, ((pal) << 5) | 0, 0, /* 128 px @ x0 */ \
68
+ 0, 0x40, 0, ((pal) << 5) | 24, 128, /* 32 px @ x128 */ \
69
+ 0 }
70
+ MK_BAND(dl_field, 1);
71
+ MK_BAND(dl_ground, 2);
72
+ #define GROUND_ZONE 188
73
+
74
+ static void set_band_addr(uint8_t* dl) {
75
+ uint16_t a = (uint16_t)(uintptr_t)band_pix;
76
+ dl[0] = dl[5] = (uint8_t)(a & 0xFF);
77
+ dl[2] = dl[7] = (uint8_t)(a >> 8);
78
+ }
79
+
80
+ /* Background DL for a non-banner zone: sky (empty) up top, field in the
81
+ * middle, ground at the bottom. */
82
+ static uint16_t bg_zone_dl(int zone) {
83
+ if (zone >= GROUND_ZONE) return (uint16_t)(uintptr_t)dl_ground;
84
+ if (zone >= 28) return (uint16_t)(uintptr_t)dl_field;
85
+ return (uint16_t)(uintptr_t)dl_empty;
86
+ }
87
+
50
88
  #define DLL_ZONES 243
51
89
  static uint8_t dll[DLL_ZONES * 3];
52
90
 
@@ -71,9 +109,12 @@ static void vblank_wait(void) {
71
109
 
72
110
  void main(void) {
73
111
  uint16_t dll_addr;
74
- uint16_t empty = (uint16_t)(uintptr_t)dl_empty;
75
112
  int i;
76
113
 
114
+ /* Point the background bands at their shared ROM pixel row. */
115
+ set_band_addr(dl_field);
116
+ set_band_addr(dl_ground);
117
+
77
118
  set_dl_addr(dl_row0, banner_row0);
78
119
  set_dl_addr(dl_row1, banner_row1);
79
120
  set_dl_addr(dl_row2, banner_row2);
@@ -83,6 +124,8 @@ void main(void) {
83
124
  set_dl_addr(dl_row6, banner_row6);
84
125
  set_dl_addr(dl_row7, banner_row7);
85
126
 
127
+ /* Build the DLL: banner rows at BANNER_Y, background scenery everywhere
128
+ * else so the screen isn't an almost-blank flat field. */
86
129
  for (i = 0; i < DLL_ZONES; i++) {
87
130
  uint16_t dl;
88
131
  int d = i - BANNER_Y;
@@ -95,7 +138,7 @@ void main(void) {
95
138
  case 5: dl = (uint16_t)(uintptr_t)dl_row5; break;
96
139
  case 6: dl = (uint16_t)(uintptr_t)dl_row6; break;
97
140
  case 7: dl = (uint16_t)(uintptr_t)dl_row7; break;
98
- default: dl = empty; break;
141
+ default: dl = bg_zone_dl(i); break; /* field/ground scenery */
99
142
  }
100
143
  set_dll_entry(i, dl);
101
144
  }
@@ -104,6 +147,8 @@ void main(void) {
104
147
  P0C1 = 0x0F; /* white text (palette index 1) */
105
148
  P0C2 = 0x0F;
106
149
  P0C3 = 0x0F;
150
+ P1C1 = 0xC8; /* field green (background band) */
151
+ P2C1 = 0x14; /* ground brown (background band) */
107
152
  CHARBASE = 0;
108
153
  OFFSET = 0;
109
154