romdevtools 0.15.0 → 0.21.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.
- package/AGENTS.md +61 -13
- package/CHANGELOG.md +289 -0
- package/README.md +1 -1
- package/examples/README.md +2 -0
- package/examples/atari2600/templates/platformer.asm +460 -0
- package/examples/atari2600/templates/racing.asm +463 -0
- package/examples/atari2600/templates/shmup.asm +386 -0
- package/examples/atari2600/templates/sports.asm +362 -0
- package/examples/atari7800/templates/default.c +49 -5
- package/examples/atari7800/templates/platformer.c +43 -4
- package/examples/atari7800/templates/puzzle.c +39 -4
- package/examples/atari7800/templates/racing.c +39 -4
- package/examples/atari7800/templates/shmup.c +40 -2
- package/examples/atari7800/templates/sports.c +36 -5
- package/examples/c64/templates/platformer.c +19 -5
- package/examples/c64/templates/puzzle.c +32 -2
- package/examples/c64/templates/shmup.c +28 -2
- package/examples/c64/templates/sports.c +30 -2
- package/examples/gb/templates/default.c +110 -16
- package/examples/gb/templates/platformer.c +25 -4
- package/examples/gb/templates/puzzle.c +32 -2
- package/examples/gb/templates/racing.c +72 -8
- package/examples/gb/templates/shmup.c +38 -1
- package/examples/gb/templates/sports.c +48 -1
- package/examples/gba/templates/gba_hello.c +29 -11
- package/examples/gba/templates/puzzle.c +15 -3
- package/examples/gba/templates/racing.c +65 -3
- package/examples/gba/templates/shmup.c +41 -4
- package/examples/gba/templates/sports.c +36 -2
- package/examples/gba/templates/tonc_hello.c +41 -5
- package/examples/gbc/templates/default.c +103 -26
- package/examples/gbc/templates/platformer.c +25 -4
- package/examples/gbc/templates/puzzle.c +32 -2
- package/examples/gbc/templates/racing.c +85 -19
- package/examples/gbc/templates/shmup.c +34 -1
- package/examples/gbc/templates/sports.c +45 -1
- package/examples/genesis/templates/puzzle.c +37 -3
- package/examples/genesis/templates/racing.c +44 -11
- package/examples/genesis/templates/sgdk_hello.c +34 -1
- package/examples/genesis/templates/shmup.c +31 -1
- package/examples/gg/templates/default.c +56 -18
- package/examples/gg/templates/platformer.c +18 -12
- package/examples/gg/templates/puzzle.c +38 -7
- package/examples/gg/templates/racing.c +51 -5
- package/examples/gg/templates/shmup.c +47 -3
- package/examples/gg/templates/sports.c +46 -3
- package/examples/lynx/templates/default.c +39 -8
- package/examples/lynx/templates/puzzle.c +28 -1
- package/examples/lynx/templates/racing.c +34 -7
- package/examples/lynx/templates/shmup.c +42 -3
- package/examples/lynx/templates/sports.c +29 -2
- package/examples/msx/platformer/main.c +213 -0
- package/examples/msx/puzzle/main.c +250 -0
- package/examples/msx/racing/main.c +249 -0
- package/examples/msx/shmup/main.c +288 -0
- package/examples/msx/sports/main.c +182 -0
- package/examples/nes/templates/default.c +67 -19
- package/examples/nes/templates/platformer.c +65 -6
- package/examples/nes/templates/puzzle.c +67 -6
- package/examples/nes/templates/racing.c +45 -13
- package/examples/nes/templates/shmup.c +51 -2
- package/examples/nes/templates/sports.c +51 -6
- package/examples/pce/platformer/main.c +283 -0
- package/examples/pce/puzzle/main.c +304 -0
- package/examples/pce/racing/main.c +304 -0
- package/examples/pce/shmup/main.c +346 -0
- package/examples/pce/sports/main.c +254 -0
- package/examples/sms/main.c +35 -6
- package/examples/sms/templates/puzzle.c +34 -5
- package/examples/sms/templates/racing.c +39 -2
- package/examples/sms/templates/shmup.c +41 -2
- package/examples/sms/templates/sports.c +43 -2
- package/examples/snes/templates/default.c +50 -28
- package/examples/snes/templates/platformer-data.asm +22 -0
- package/examples/snes/templates/platformer.c +16 -1
- package/examples/snes/templates/puzzle-data.asm +22 -0
- package/examples/snes/templates/puzzle.c +17 -1
- package/examples/snes/templates/racing-data.asm +22 -0
- package/examples/snes/templates/racing.c +17 -1
- package/examples/snes/templates/shmup-data.asm +22 -0
- package/examples/snes/templates/shmup.c +20 -1
- package/examples/snes/templates/sports-data.asm +22 -0
- package/examples/snes/templates/sports.c +16 -1
- package/package.json +1 -1
- package/src/cores/wasm/vice_x64_libretro.js +1 -1
- package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
- package/src/host/LibretroHost.js +122 -1
- package/src/host/callbacks.js +9 -1
- package/src/host/types.js +15 -8
- package/src/http/skill-doc.js +1 -1
- package/src/http/tool-registry.js +27 -2
- package/src/mcp/tools/cart-parts.js +75 -3
- package/src/mcp/tools/disasm-rebuild.js +507 -0
- package/src/mcp/tools/disasm.js +95 -6
- package/src/mcp/tools/frame.js +168 -3
- package/src/mcp/tools/index.js +4 -4
- package/src/mcp/tools/lifecycle.js +4 -2
- package/src/mcp/tools/project.js +54 -9
- package/src/mcp/tools/state.js +201 -14
- package/src/mcp/tools/toolchain.js +89 -4
- package/src/mcp/tools/watch-memory.js +125 -14
- package/src/platforms/c64/MENTAL_MODEL.md +45 -1
- package/src/platforms/c64/d64.js +281 -0
- package/src/platforms/gb/MENTAL_MODEL.md +10 -0
- package/src/platforms/msx/MENTAL_MODEL.md +10 -6
- package/src/platforms/nes/MENTAL_MODEL.md +63 -2
- package/src/platforms/pce/MENTAL_MODEL.md +9 -4
- package/src/platforms/pce/lib/c/pce_video.c +1 -1
- package/src/rom-id/identifier.js +15 -0
- package/src/toolchains/cc65/cc65.js +8 -1
- package/src/toolchains/cc65/ines.js +145 -0
- package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
- package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
- package/src/toolchains/common/reassemble.js +10 -2
- package/src/toolchains/gba-c/gba-c.js +6 -1
- package/src/toolchains/genesis-c/genesis-c.js +10 -2
- package/src/toolchains/parse-errors.js +67 -5
|
@@ -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:
|
|
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 =
|
|
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
|
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
#define P0C1 (*(volatile uint8_t*)0x21)
|
|
15
15
|
#define P0C2 (*(volatile uint8_t*)0x22)
|
|
16
16
|
#define P0C3 (*(volatile uint8_t*)0x23)
|
|
17
|
+
#define P1C1 (*(volatile uint8_t*)0x25)
|
|
18
|
+
#define P2C1 (*(volatile uint8_t*)0x29)
|
|
17
19
|
#define MSTAT (*(volatile uint8_t*)0x28)
|
|
18
20
|
#define DPPH (*(volatile uint8_t*)0x2C)
|
|
19
21
|
#define DPPL (*(volatile uint8_t*)0x30)
|
|
@@ -44,6 +46,40 @@ MK_DL(dl_row4); MK_DL(dl_row5); MK_DL(dl_row6); MK_DL(dl_row7);
|
|
|
44
46
|
|
|
45
47
|
static uint8_t dl_empty[2] = { 0, 0 };
|
|
46
48
|
|
|
49
|
+
/* ── Background playfield ─────────────────────────────────────────
|
|
50
|
+
* Without a full-screen drawable the display list emits only the
|
|
51
|
+
* player and ~99% of the screen stays the flat BACKGRND colour
|
|
52
|
+
* (reads as "blank"). These full-width bands give the level a sky,
|
|
53
|
+
* a field, and a solid ground strip the player stands on.
|
|
54
|
+
*
|
|
55
|
+
* A single DL drawable is at most 32 bytes = 128 px wide, so a full
|
|
56
|
+
* 160-px line needs TWO drawables. Width = byte[3] low 5 bits (32-n);
|
|
57
|
+
* high 3 bits = palette. */
|
|
58
|
+
static const uint8_t band_pix[32] = {
|
|
59
|
+
0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,
|
|
60
|
+
0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
|
|
61
|
+
};
|
|
62
|
+
#define MK_BAND(name, pal) static uint8_t name[11] = { \
|
|
63
|
+
0, 0x40, 0, ((pal) << 5) | 0, 0, /* 128 px @ x0 */ \
|
|
64
|
+
0, 0x40, 0, ((pal) << 5) | 24, 128, /* 32 px @ x128 */ \
|
|
65
|
+
0 }
|
|
66
|
+
MK_BAND(dl_field, 1);
|
|
67
|
+
MK_BAND(dl_ground, 2);
|
|
68
|
+
/* Ground strip starts just below where the player rests (GROUND_Y). */
|
|
69
|
+
#define GROUND_ZONE 200
|
|
70
|
+
|
|
71
|
+
static void set_band_addr(uint8_t* dl) {
|
|
72
|
+
uint16_t a = (uint16_t)(uintptr_t)band_pix;
|
|
73
|
+
dl[0] = dl[5] = (uint8_t)(a & 0xFF);
|
|
74
|
+
dl[2] = dl[7] = (uint8_t)(a >> 8);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static uint16_t bg_zone_dl(int zone) {
|
|
78
|
+
if (zone >= GROUND_ZONE) return (uint16_t)(uintptr_t)dl_ground;
|
|
79
|
+
if (zone >= 28) return (uint16_t)(uintptr_t)dl_field;
|
|
80
|
+
return (uint16_t)(uintptr_t)dl_empty;
|
|
81
|
+
}
|
|
82
|
+
|
|
47
83
|
#define DLL_ZONES 243
|
|
48
84
|
static uint8_t dll[DLL_ZONES * 3];
|
|
49
85
|
|
|
@@ -60,7 +96,6 @@ static void set_dll_entry(int idx, uint16_t dl_ptr) {
|
|
|
60
96
|
}
|
|
61
97
|
|
|
62
98
|
static void build_dll(uint8_t y) {
|
|
63
|
-
uint16_t empty = (uint16_t)(uintptr_t)dl_empty;
|
|
64
99
|
int i;
|
|
65
100
|
for (i = 0; i < DLL_ZONES; i++) {
|
|
66
101
|
uint16_t dl;
|
|
@@ -74,7 +109,7 @@ static void build_dll(uint8_t y) {
|
|
|
74
109
|
case 5: dl = (uint16_t)(uintptr_t)dl_row5; break;
|
|
75
110
|
case 6: dl = (uint16_t)(uintptr_t)dl_row6; break;
|
|
76
111
|
case 7: dl = (uint16_t)(uintptr_t)dl_row7; break;
|
|
77
|
-
default: dl =
|
|
112
|
+
default: dl = bg_zone_dl(i); break;
|
|
78
113
|
}
|
|
79
114
|
set_dll_entry(i, dl);
|
|
80
115
|
}
|
|
@@ -112,13 +147,17 @@ void main(void) {
|
|
|
112
147
|
set_dl_addr(dl_row5, player_row5);
|
|
113
148
|
set_dl_addr(dl_row6, player_row6);
|
|
114
149
|
set_dl_addr(dl_row7, player_row7);
|
|
150
|
+
set_band_addr(dl_field);
|
|
151
|
+
set_band_addr(dl_ground);
|
|
115
152
|
set_x((uint8_t)px);
|
|
116
153
|
build_dll((uint8_t)(py16 >> 4));
|
|
117
154
|
|
|
118
|
-
BACKGRND = 0x84;
|
|
119
|
-
P0C1 = 0x46;
|
|
155
|
+
BACKGRND = 0x84; /* sky */
|
|
156
|
+
P0C1 = 0x46; /* player */
|
|
120
157
|
P0C2 = 0x0F;
|
|
121
158
|
P0C3 = 0x36;
|
|
159
|
+
P1C1 = 0x96; /* distant field (teal) */
|
|
160
|
+
P2C1 = 0x24; /* ground (brown) */
|
|
122
161
|
CHARBASE = 0;
|
|
123
162
|
OFFSET = 0;
|
|
124
163
|
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
#define P0C1 (*(volatile uint8_t*)0x21)
|
|
21
21
|
#define P0C2 (*(volatile uint8_t*)0x22)
|
|
22
22
|
#define P0C3 (*(volatile uint8_t*)0x23)
|
|
23
|
+
#define P1C1 (*(volatile uint8_t*)0x25)
|
|
24
|
+
#define P2C1 (*(volatile uint8_t*)0x29)
|
|
23
25
|
#define MSTAT (*(volatile uint8_t*)0x28)
|
|
24
26
|
#define DPPH (*(volatile uint8_t*)0x2C)
|
|
25
27
|
#define DPPL (*(volatile uint8_t*)0x30)
|
|
@@ -53,6 +55,37 @@ MK_DL(dl_row4); MK_DL(dl_row5); MK_DL(dl_row6); MK_DL(dl_row7);
|
|
|
53
55
|
|
|
54
56
|
static uint8_t dl_empty[2] = { 0, 0 };
|
|
55
57
|
|
|
58
|
+
/* ── Background well ──────────────────────────────────────────────
|
|
59
|
+
* Without a full-screen drawable the display list emits only the
|
|
60
|
+
* falling block and ~99% of the screen stays the flat BACKGRND colour
|
|
61
|
+
* (reads as "blank"). Each well zone draws three full-width segments:
|
|
62
|
+
* a side wall (palette 2), the playfield well in the centre where the
|
|
63
|
+
* piece falls (palette 1), the other wall (palette 2). Width =
|
|
64
|
+
* byte[3] low 5 bits (32-n); high 3 bits = palette. */
|
|
65
|
+
static const uint8_t band_pix[16] = {
|
|
66
|
+
0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,
|
|
67
|
+
0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
|
|
68
|
+
};
|
|
69
|
+
/* 12 bytes (48 px) wall @ x0, 16 bytes (64 px) well @ x48,
|
|
70
|
+
* 12 bytes (48 px) wall @ x112, terminator. */
|
|
71
|
+
static uint8_t dl_well[16] = {
|
|
72
|
+
0, 0x40, 0, (2 << 5) | 20, 0,
|
|
73
|
+
0, 0x40, 0, (1 << 5) | 16, 48,
|
|
74
|
+
0, 0x40, 0, (2 << 5) | 20, 112,
|
|
75
|
+
0
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
static void set_well_addr(void) {
|
|
79
|
+
uint16_t a = (uint16_t)(uintptr_t)band_pix;
|
|
80
|
+
dl_well[0] = dl_well[5] = dl_well[10] = (uint8_t)(a & 0xFF);
|
|
81
|
+
dl_well[2] = dl_well[7] = dl_well[12] = (uint8_t)(a >> 8);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static uint16_t bg_zone_dl(int zone) {
|
|
85
|
+
if (zone >= 32 && zone < 200) return (uint16_t)(uintptr_t)dl_well;
|
|
86
|
+
return (uint16_t)(uintptr_t)dl_empty;
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
#define DLL_ZONES 243
|
|
57
90
|
static uint8_t dll[DLL_ZONES * 3];
|
|
58
91
|
|
|
@@ -80,7 +113,6 @@ static void set_x(uint8_t x) {
|
|
|
80
113
|
}
|
|
81
114
|
|
|
82
115
|
static void build_dll(int y) {
|
|
83
|
-
uint16_t empty = (uint16_t)(uintptr_t)dl_empty;
|
|
84
116
|
int i;
|
|
85
117
|
for (i = 0; i < DLL_ZONES; i++) {
|
|
86
118
|
uint16_t dl;
|
|
@@ -94,7 +126,7 @@ static void build_dll(int y) {
|
|
|
94
126
|
case 5: dl = (uint16_t)(uintptr_t)dl_row5; break;
|
|
95
127
|
case 6: dl = (uint16_t)(uintptr_t)dl_row6; break;
|
|
96
128
|
case 7: dl = (uint16_t)(uintptr_t)dl_row7; break;
|
|
97
|
-
default: dl =
|
|
129
|
+
default: dl = bg_zone_dl(i); break;
|
|
98
130
|
}
|
|
99
131
|
set_dll_entry(i, dl);
|
|
100
132
|
}
|
|
@@ -120,13 +152,16 @@ void main(void) {
|
|
|
120
152
|
piece_x_col = COLS / 2;
|
|
121
153
|
piece_y = TOP_Y;
|
|
122
154
|
color_cycle = 0;
|
|
155
|
+
set_well_addr();
|
|
123
156
|
set_x((uint8_t)(60 + piece_x_col * CELL_W_PIX));
|
|
124
157
|
build_dll(piece_y);
|
|
125
158
|
|
|
126
|
-
BACKGRND = 0x00;
|
|
127
|
-
P0C1 = 0x46; /* red */
|
|
159
|
+
BACKGRND = 0x00; /* black surround */
|
|
160
|
+
P0C1 = 0x46; /* falling piece (red) */
|
|
128
161
|
P0C2 = 0x46;
|
|
129
162
|
P0C3 = 0x46;
|
|
163
|
+
P1C1 = 0x02; /* well interior (dark blue-grey) */
|
|
164
|
+
P2C1 = 0x08; /* well walls (steel) */
|
|
130
165
|
CHARBASE = 0;
|
|
131
166
|
OFFSET = 0;
|
|
132
167
|
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
#define P0C1 (*(volatile uint8_t*)0x21)
|
|
18
18
|
#define P0C2 (*(volatile uint8_t*)0x22)
|
|
19
19
|
#define P0C3 (*(volatile uint8_t*)0x23)
|
|
20
|
+
#define P1C1 (*(volatile uint8_t*)0x25)
|
|
21
|
+
#define P2C1 (*(volatile uint8_t*)0x29)
|
|
20
22
|
#define MSTAT (*(volatile uint8_t*)0x28)
|
|
21
23
|
#define DPPH (*(volatile uint8_t*)0x2C)
|
|
22
24
|
#define DPPL (*(volatile uint8_t*)0x30)
|
|
@@ -44,6 +46,37 @@ MK_DL(dl_row4); MK_DL(dl_row5); MK_DL(dl_row6); MK_DL(dl_row7);
|
|
|
44
46
|
|
|
45
47
|
static uint8_t dl_empty[2] = { 0, 0 };
|
|
46
48
|
|
|
49
|
+
/* ── Background road ──────────────────────────────────────────────
|
|
50
|
+
* Without a full-screen drawable the display list emits only the car
|
|
51
|
+
* and ~99% of the screen stays the flat BACKGRND colour (reads as
|
|
52
|
+
* "blank"). Each road zone draws three full-width segments: grass on
|
|
53
|
+
* the left (palette 1), the grey road down the centre (palette 2),
|
|
54
|
+
* grass on the right (palette 1). Width = byte[3] low 5 bits (32-n);
|
|
55
|
+
* high 3 bits = palette. */
|
|
56
|
+
static const uint8_t band_pix[16] = {
|
|
57
|
+
0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,
|
|
58
|
+
0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
|
|
59
|
+
};
|
|
60
|
+
/* 8 bytes (32 px) grass @ x0, 16 bytes (64 px) road @ x32,
|
|
61
|
+
* 8 bytes (32 px) grass @ x96, terminator. */
|
|
62
|
+
static uint8_t dl_road[16] = {
|
|
63
|
+
0, 0x40, 0, (1 << 5) | 24, 0,
|
|
64
|
+
0, 0x40, 0, (2 << 5) | 16, 32,
|
|
65
|
+
0, 0x40, 0, (1 << 5) | 24, 96,
|
|
66
|
+
0
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
static void set_road_addr(void) {
|
|
70
|
+
uint16_t a = (uint16_t)(uintptr_t)band_pix;
|
|
71
|
+
dl_road[0] = dl_road[5] = dl_road[10] = (uint8_t)(a & 0xFF);
|
|
72
|
+
dl_road[2] = dl_road[7] = dl_road[12] = (uint8_t)(a >> 8);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static uint16_t bg_zone_dl(int zone) {
|
|
76
|
+
if (zone >= 16 && zone < 220) return (uint16_t)(uintptr_t)dl_road;
|
|
77
|
+
return (uint16_t)(uintptr_t)dl_empty;
|
|
78
|
+
}
|
|
79
|
+
|
|
47
80
|
#define DLL_ZONES 243
|
|
48
81
|
static uint8_t dll[DLL_ZONES * 3];
|
|
49
82
|
|
|
@@ -71,7 +104,6 @@ static void set_x(uint8_t x) {
|
|
|
71
104
|
}
|
|
72
105
|
|
|
73
106
|
static void build_dll(void) {
|
|
74
|
-
uint16_t empty = (uint16_t)(uintptr_t)dl_empty;
|
|
75
107
|
int i;
|
|
76
108
|
for (i = 0; i < DLL_ZONES; i++) {
|
|
77
109
|
uint16_t dl;
|
|
@@ -85,7 +117,7 @@ static void build_dll(void) {
|
|
|
85
117
|
case 5: dl = (uint16_t)(uintptr_t)dl_row5; break;
|
|
86
118
|
case 6: dl = (uint16_t)(uintptr_t)dl_row6; break;
|
|
87
119
|
case 7: dl = (uint16_t)(uintptr_t)dl_row7; break;
|
|
88
|
-
default: dl =
|
|
120
|
+
default: dl = bg_zone_dl(i); break;
|
|
89
121
|
}
|
|
90
122
|
set_dll_entry(i, dl);
|
|
91
123
|
}
|
|
@@ -110,13 +142,16 @@ void main(void) {
|
|
|
110
142
|
set_dl_addr(dl_row7, car_row7);
|
|
111
143
|
|
|
112
144
|
lane = 1;
|
|
145
|
+
set_road_addr();
|
|
113
146
|
set_x(lane_xs[lane]);
|
|
114
147
|
build_dll();
|
|
115
148
|
|
|
116
|
-
BACKGRND = 0x88;
|
|
117
|
-
P0C1 = 0x46;
|
|
149
|
+
BACKGRND = 0x88; /* sky/horizon */
|
|
150
|
+
P0C1 = 0x46; /* car */
|
|
118
151
|
P0C2 = 0x0F;
|
|
119
152
|
P0C3 = 0x36;
|
|
153
|
+
P1C1 = 0xC8; /* roadside grass (green) */
|
|
154
|
+
P2C1 = 0x06; /* road surface (grey) */
|
|
120
155
|
CHARBASE = 0;
|
|
121
156
|
OFFSET = 0;
|
|
122
157
|
|