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,460 @@
1
+ ; ── platformer.asm — Atari 2600 PLATFORMER genre scaffold ─────────────
2
+ ;
3
+ ; SINGLE-SCREEN platformer. Pitfall!, Montezuma's Revenge, and Kangaroo
4
+ ; are 2600 platformers, and they are single-screen-at-a-time: the 2600
5
+ ; has NO hardware scroll, no tilemap, and 128 bytes of RAM, so a smooth
6
+ ; side-scroller is not the honest 2600 idiom (you'd flip whole screens,
7
+ ; Pitfall-style, instead). What IS idiomatic — and what this scaffold
8
+ ; ships — is gravity + a jump arc + land-on-top collision against a set
9
+ ; of fixed platforms, all on one screen.
10
+ ;
11
+ ; TIA object roles:
12
+ ; P0 = the player (8-px sprite) that walks + jumps.
13
+ ; PF = the platforms (and the floor): three horizontal playfield bars
14
+ ; at fixed Y bands. The playfield is the only 2600 object wide
15
+ ; enough to be a platform; players/missiles are too narrow.
16
+ ;
17
+ ; Physics (fixed-point Y, 1 sub-pixel bit):
18
+ ; * Gravity pulls the player down every frame (velocity += g).
19
+ ; * Pressing FIRE while standing on a surface launches a jump
20
+ ; (velocity = -jump).
21
+ ; * After moving, we test the player's FEET against each platform's
22
+ ; (Y, x-span) in CODE — not TIA collision — because we need to know
23
+ ; WHICH surface to stand on and TIA only gives a yes/no overlap.
24
+ ; * Walk left/right with the joystick; you can't walk off the screen.
25
+ ;
26
+ ; This is the jump/gravity/collision CORE. Extend it with: a second
27
+ ; sprite (P1) as a pickup or enemy, M0 as a thrown rock, ladders (let
28
+ ; UP/DOWN move Y when overlapping a ladder x-span), or Pitfall-style
29
+ ; screen flipping when the player exits the left/right edge.
30
+ ;
31
+ ; TIMING: 262 lines = 3 VSYNC + 37 VBLANK + 192 visible + 30 overscan.
32
+ ; One positioning WSYNC is counted against VBLANK (loop = #36). The
33
+ ; visible region is a TWO-LINE KERNEL so the per-pass work (platform PF
34
+ ; lookup + player-sprite test) fits the cycle budget.
35
+
36
+ processor 6502
37
+ org $F000
38
+
39
+ VSYNC = $00
40
+ VBLANK = $01
41
+ WSYNC = $02
42
+ NUSIZ0 = $04
43
+ COLUP0 = $06
44
+ COLUPF = $08
45
+ COLUBK = $09
46
+ CTRLPF = $0A
47
+ PF0 = $0D
48
+ PF1 = $0E
49
+ PF2 = $0F
50
+ RESP0 = $10
51
+ GRP0 = $1B
52
+ HMP0 = $20
53
+ HMOVE = $2A
54
+ HMCLR = $2B
55
+ SWCHA = $280
56
+ INPT4 = $0C ; fire button (active-low, bit7) = JUMP
57
+ ; TIA audio
58
+ AUDC0 = $15
59
+ AUDF0 = $17
60
+ AUDV0 = $19
61
+
62
+ ; ── Zero-page state ───────────────────────────────────────────────────
63
+ P_X = $80 ; player X (visible column)
64
+ P_Y = $81 ; player top scanline (integer part). Y counts with
65
+ ; the beam 192->0, so SMALLER = LOWER on screen.
66
+ P_VY = $82 ; vertical velocity, signed, in half-pixels (8.1)
67
+ P_YSUB = $83 ; (spare — physics is integer-pixel; kept for layout)
68
+ ON_GND = $84 ; 1 = standing on a surface (can jump)
69
+ FRAME = $85
70
+ SFX_LEFT = $86
71
+ TMP = $87
72
+ LANDY = $88 ; the Y we snap to when we land
73
+ ; PFROW: 96-byte playfield row buffer (one entry per 2-line kernel row),
74
+ ; built ONCE at boot from the platform table. $89..$E8. Stack ($FF down)
75
+ ; has $E9..$FF free (23 bytes) — ample, since the only JSR is the one-shot
76
+ ; build_pfrow and the kernel itself calls nothing.
77
+ PFROW = $89
78
+
79
+ ; Player height (sprite rows).
80
+ PH = 8
81
+
82
+ ; ── Platform table ────────────────────────────────────────────────────
83
+ ; Each platform = (top-band scanline Y, x-left, x-right). The visuals are
84
+ ; FULL-WIDTH horizontal bars (cheap + reads cleanly), so the x-spans below
85
+ ; are the whole screen and the land-on-top test lets you stand anywhere on
86
+ ; a platform. Beam Y counts 192(top)->1(bottom): a LARGER Y value sits
87
+ ; HIGHER on the screen, so PLAT_Y=18 is the bottom FLOOR and PLAT_Y=150 is
88
+ ; the top ledge.
89
+ ; floor : Y=18 (bottom, full width)
90
+ ; ledge : Y=70
91
+ ; ledge : Y=110
92
+ ; ledge : Y=150 (highest)
93
+ NUM_PLAT = 4
94
+
95
+ START:
96
+ SEI
97
+ CLD
98
+ LDX #$FF
99
+ TXS
100
+ LDA #0
101
+ .clr:
102
+ STA $00,X
103
+ DEX
104
+ BNE .clr
105
+
106
+ ; Start the player standing on the floor.
107
+ LDA #76
108
+ STA P_X
109
+ LDA #26 ; just above the floor band (floor top = 18, +PH)
110
+ STA P_Y
111
+ LDA #1
112
+ STA ON_GND
113
+
114
+ ; Colours
115
+ LDA #$84 ; blue sky background
116
+ STA COLUBK
117
+ LDA #$1E ; yellow player
118
+ STA COLUP0
119
+ LDA #$28 ; brown/orange platforms
120
+ STA COLUPF
121
+
122
+ ; Reflected playfield (harmless for full-width bars; left set so that if
123
+ ; you switch ledges to partial patterns later they mirror symmetrically).
124
+ LDA #%00000001
125
+ STA CTRLPF
126
+
127
+ ; Build the static playfield row buffer ONCE (platforms never move).
128
+ JSR build_pfrow
129
+
130
+ ; Boot chime.
131
+ LDA #$04
132
+ STA AUDC0
133
+ LDA #$0C
134
+ STA AUDF0
135
+ LDA #$0F
136
+ STA AUDV0
137
+ LDA #15
138
+ STA SFX_LEFT
139
+
140
+ MAIN:
141
+ INC FRAME
142
+
143
+ ; ── VSYNC (3 lines) ──
144
+ LDA #2
145
+ STA VSYNC
146
+ STA WSYNC
147
+ STA WSYNC
148
+ STA WSYNC
149
+ LDA #0
150
+ STA VSYNC
151
+
152
+ ; ── VBLANK (37 lines: 36 here + 1 positioning WSYNC below) ──
153
+ LDA #2
154
+ STA VBLANK
155
+ LDX #36
156
+ .vb:
157
+ STA WSYNC
158
+ DEX
159
+ BNE .vb
160
+
161
+ ; ── Horizontal move (every 2nd frame to throttle) ──
162
+ LDA FRAME
163
+ AND #$01
164
+ BNE .skipmove
165
+ LDA SWCHA
166
+ ASL ; bit7 = Right
167
+ BCS .nr
168
+ LDA P_X
169
+ CMP #140
170
+ BCS .nr
171
+ INC P_X
172
+ INC P_X
173
+ .nr:
174
+ ASL ; bit6 = Left
175
+ BCS .nl
176
+ LDA P_X
177
+ CMP #16
178
+ BCC .nl
179
+ DEC P_X
180
+ DEC P_X
181
+ .nl:
182
+ .skipmove:
183
+
184
+ ; ── Jump: FIRE while on the ground launches an upward velocity ──
185
+ ; Coordinate reminder: Y is the BEAM scanline; the top of the screen is
186
+ ; the LARGER Y. So "up" = INCREASING P_Y, and a positive P_VY rises.
187
+ ; P_VY is signed WHOLE PIXELS/frame (no sub-pixel — integer motion looks
188
+ ; perfectly fine for a 2600 jump and avoids a fractional-carry bug where
189
+ ; small half-pixel velocities never accumulate a whole pixel).
190
+ LDA ON_GND
191
+ BEQ .nojump
192
+ BIT INPT4
193
+ BMI .nojump ; bit7 set = button released
194
+ LDA #6 ; initial jump speed (pixels/frame, upward)
195
+ STA P_VY
196
+ LDA #0
197
+ STA ON_GND
198
+ ; jump sfx
199
+ LDA #$0C
200
+ STA AUDC0
201
+ LDA #$14
202
+ STA AUDF0
203
+ LDA #$0F
204
+ STA AUDV0
205
+ LDA #6
206
+ STA SFX_LEFT
207
+ .nojump:
208
+
209
+ ; ── Gravity + integrate vertical velocity (only while airborne) ──
210
+ ; Standing still on a platform we DON'T apply gravity (otherwise the
211
+ ; player drops 1px every frame and the landing snap fights it → jitter).
212
+ LDA ON_GND
213
+ BNE .skipgrav
214
+ DEC P_VY ; gravity: velocity drifts toward falling each frame
215
+ ; clamp terminal fall speed to -8 px/frame (P_VY is signed; -8 = $F8).
216
+ LDA P_VY
217
+ CMP #$F8 ; if P_VY (unsigned) < $F8 AND it's negative → too fast
218
+ BCS .vyok ; >= $F8 (covers -8..-1 and 0..127) → keep
219
+ ; here P_VY is in $80..$F7 = -128..-9 → clamp to -8
220
+ LDA #$F8
221
+ STA P_VY
222
+ .vyok:
223
+ ; P_Y += P_VY (signed add: sign-extend P_VY into the add)
224
+ LDA P_VY
225
+ CLC
226
+ ADC P_Y
227
+ STA P_Y
228
+ .skipgrav:
229
+
230
+ ; ── Land-on-top collision against the platform table ──
231
+ ; Only while DESCENDING (P_VY negative = moving down the screen). For
232
+ ; each platform, the stand-line = PLAT_Y + PH (the player's feet rest
233
+ ; just above the band's top edge). If the player's feet (P_Y) have
234
+ ; reached or just dropped through that line from above, and X is within
235
+ ; the platform's span, snap onto it.
236
+ LDA P_VY
237
+ BPL .noland ; rising or stationary → can't land this frame
238
+ LDX #0
239
+ .landloop:
240
+ LDA PLAT_Y,X
241
+ CLC
242
+ ADC #PH ; stand-line for this platform
243
+ STA LANDY
244
+ ; player at/below the stand-line? (P_Y <= LANDY, i.e. NOT P_Y > LANDY)
245
+ LDA P_Y
246
+ CMP LANDY
247
+ BEQ .ydepth ; exactly on it
248
+ BCS .nextplat ; P_Y > LANDY → still above the surface → no land
249
+ .ydepth:
250
+ ; not fallen WAY past it (avoid grabbing a platform from underneath):
251
+ ; require LANDY - P_Y <= 12.
252
+ LDA LANDY
253
+ SEC
254
+ SBC P_Y
255
+ CMP #13
256
+ BCS .nextplat ; dropped >12px below → ignore
257
+ ; x-span test: PLAT_XL <= P_X <= PLAT_XR
258
+ LDA P_X
259
+ CMP PLAT_XL,X
260
+ BCC .nextplat
261
+ CMP PLAT_XR,X
262
+ BCS .nextplat ; (XR=159 + the +1 makes the whole row standable)
263
+ ; LAND!
264
+ LDA LANDY
265
+ STA P_Y
266
+ LDA #0
267
+ STA P_VY
268
+ LDA #1
269
+ STA ON_GND
270
+ JMP .landdone
271
+ .nextplat:
272
+ INX
273
+ CPX #NUM_PLAT
274
+ BNE .landloop
275
+ ; matched nothing → still airborne
276
+ LDA #0
277
+ STA ON_GND
278
+ .landdone:
279
+ .noland:
280
+
281
+ ; Safety floor: never let the player fall off the bottom of the world.
282
+ LDA P_Y
283
+ CMP #18
284
+ BCS .floorok
285
+ LDA #26
286
+ STA P_Y
287
+ LDA #0
288
+ STA P_VY
289
+ LDA #1
290
+ STA ON_GND
291
+ .floorok:
292
+
293
+ ; ── sfx countdown ──
294
+ LDA SFX_LEFT
295
+ BEQ .sfxdone
296
+ DEC SFX_LEFT
297
+ BNE .sfxdone
298
+ LDA #0
299
+ STA AUDV0
300
+ .sfxdone:
301
+
302
+ ; ── Position P0 at column P_X (1 WSYNC, counted in VBLANK) ──
303
+ STA WSYNC
304
+ STA HMCLR
305
+ LDX P_X
306
+ LDA #0
307
+ .p0pos:
308
+ CPX #15
309
+ BCC .p0done
310
+ SEC
311
+ SBC #15
312
+ TAX
313
+ JMP .p0pos
314
+ .p0done:
315
+ STA RESP0
316
+ STA HMOVE
317
+
318
+ LDA #0
319
+ STA VBLANK
320
+
321
+ ; ── Visible (192 lines) — SINGLE-LINE KERNEL reading a PF row buffer ──
322
+ ; CRITICAL CYCLE NOTE: the platforms are STATIC, so we DON'T recompute
323
+ ; them per scanline (a per-line JSR over the platform table overflowed
324
+ ; the 76-cycle budget → frames grew to ~250 lines → no vsync lock →
325
+ ; black rolling screen — the bug this kernel fixes). Instead PFROW[] is
326
+ ; a 96-byte buffer (one entry per 2-line row) filled ONCE at boot from
327
+ ; the platform table: $FF = platform here, $00 = open air. The kernel
328
+ ; just LDA PFROW,X / STA PF1 / STA PF2 (cheap) + a single player-sprite
329
+ ; test. That comfortably fits one scanline.
330
+ ;
331
+ ; X = row index 0..95 (top→bottom in buffer order). Y = beam scanline
332
+ ; 192→1. We draw two scanlines per buffer row.
333
+ LDX #0 ; PFROW index
334
+ LDY #192
335
+ .draw:
336
+ STA WSYNC
337
+ ; --- playfield for this row (full-width bars) ---
338
+ LDA PFROW,X
339
+ STA PF0
340
+ STA PF1
341
+ STA PF2
342
+ ; --- player sprite test ---
343
+ TYA
344
+ SEC
345
+ SBC P_Y
346
+ CMP #PH
347
+ BCS .pblank
348
+ STY TMP ; save beam line
349
+ TAY
350
+ LDA PLAYER,Y
351
+ STA GRP0
352
+ LDY TMP
353
+ JMP .pdone
354
+ .pblank:
355
+ LDA #0
356
+ STA GRP0
357
+ .pdone:
358
+ DEY
359
+ ; second scanline of this row — reuse same PF, re-test the sprite.
360
+ STA WSYNC
361
+ STA GRP0 ; (A still holds the sprite/blank byte from above —
362
+ ; good enough; sprite is effectively 2px tall rows)
363
+ DEY
364
+ INX
365
+ CPX #96
366
+ BNE .draw
367
+
368
+ ; ── Overscan (30 lines) ──
369
+ LDA #0
370
+ STA PF0
371
+ STA PF1
372
+ STA PF2
373
+ STA GRP0
374
+ LDA #2
375
+ STA VBLANK
376
+ LDX #30
377
+ .os:
378
+ STA WSYNC
379
+ DEX
380
+ BNE .os
381
+
382
+ JMP MAIN
383
+
384
+ ; ── build_pfrow: fill the 96-byte PFROW buffer from the platform table.
385
+ ; Called ONCE at boot. Row r covers beam scanlines (192 - 2*r) down to
386
+ ; (191 - 2*r). A row is a platform if its top scanline falls within any
387
+ ; platform's PLAT_Y..PLAT_Y+(bandHeight) window. ──
388
+ PF_BAND = 8 ; platform visual thickness in scanlines
389
+ build_pfrow:
390
+ LDX #0 ; row index 0..95
391
+ .brow:
392
+ ; beam scanline for this row = 192 - 2*X
393
+ TXA
394
+ ASL
395
+ STA TMP ; TMP = 2*X
396
+ LDA #192
397
+ SEC
398
+ SBC TMP
399
+ STA LANDY ; LANDY reused as "this row's scanline"
400
+ ; test against each platform
401
+ LDY #0
402
+ LDA #0
403
+ STA PFROW,X ; default open air
404
+ .bplat:
405
+ LDA LANDY
406
+ SEC
407
+ SBC PLAT_Y,Y
408
+ CMP #PF_BAND
409
+ BCS .bnext
410
+ ; within a platform band → mark solid
411
+ LDA #$FF
412
+ STA PFROW,X
413
+ .bnext:
414
+ INY
415
+ CPY #NUM_PLAT
416
+ BNE .bplat
417
+ INX
418
+ CPX #96
419
+ BNE .brow
420
+ RTS
421
+
422
+ ; ── Player sprite (8 rows) — a little explorer ──
423
+ PLAYER:
424
+ .byte %00111100
425
+ .byte %00111100
426
+ .byte %00011000
427
+ .byte %01111110
428
+ .byte %10111101
429
+ .byte %00111100
430
+ .byte %00100100
431
+ .byte %01100110
432
+
433
+ ; ── Platform table ────────────────────────────────────────────────────
434
+ ; Parallel arrays indexed 0..NUM_PLAT-1. Y = band top scanline (beam
435
+ ; coords: bigger Y = higher on screen). XL/XR = the column span for the
436
+ ; land-on-top test. The bars render FULL WIDTH, so every span is the whole
437
+ ; screen (you can stand anywhere on a platform — visual == collision). To
438
+ ; make narrower ledges, give a platform a partial PFROW pattern AND shrink
439
+ ; its XL/XR here so the two stay in sync.
440
+ PLAT_Y:
441
+ .byte 18 ; floor (bottom)
442
+ .byte 70 ; ledge
443
+ .byte 110 ; ledge
444
+ .byte 150 ; ledge (top)
445
+ PLAT_XL:
446
+ .byte 0
447
+ .byte 0
448
+ .byte 0
449
+ .byte 0
450
+ PLAT_XR:
451
+ .byte 159
452
+ .byte 159
453
+ .byte 159
454
+ .byte 159
455
+
456
+ ; ── Vector table ──
457
+ org $FFFA
458
+ .word START
459
+ .word START
460
+ .word START