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.
- package/AGENTS.md +56 -44
- package/CHANGELOG.md +355 -0
- package/README.md +4 -4
- package/examples/README.md +7 -7
- package/examples/atari2600/templates/platformer.asm +1227 -325
- package/examples/atari2600/templates/puzzle.asm +1056 -0
- package/examples/atari2600/templates/racing.asm +909 -257
- package/examples/atari2600/templates/shmup.asm +1035 -218
- package/examples/atari2600/templates/sports.asm +1143 -229
- package/examples/atari7800/templates/hello_sprite.c +8 -4
- package/examples/atari7800/templates/platformer.c +991 -152
- package/examples/atari7800/templates/puzzle.c +1091 -145
- package/examples/atari7800/templates/racing.c +949 -118
- package/examples/atari7800/templates/shmup.c +812 -130
- package/examples/atari7800/templates/sports.c +820 -181
- package/examples/c64/templates/platformer.c +876 -157
- package/examples/c64/templates/puzzle.c +881 -143
- package/examples/c64/templates/racing.c +873 -88
- package/examples/c64/templates/shmup.c +762 -154
- package/examples/c64/templates/sports.c +755 -95
- package/examples/gb/templates/platformer.c +841 -175
- package/examples/gb/templates/puzzle.c +1094 -176
- package/examples/gb/templates/racing.c +761 -169
- package/examples/gb/templates/shmup.c +679 -169
- package/examples/gb/templates/sports.c +790 -153
- package/examples/gba/templates/platformer.c +624 -169
- package/examples/gba/templates/puzzle.c +535 -207
- package/examples/gba/templates/racing.c +513 -196
- package/examples/gba/templates/shmup.c +565 -168
- package/examples/gba/templates/sports.c +454 -162
- package/examples/gbc/templates/platformer.c +944 -176
- package/examples/gbc/templates/puzzle.c +1131 -177
- package/examples/gbc/templates/racing.c +891 -175
- package/examples/gbc/templates/shmup.c +827 -179
- package/examples/gbc/templates/sports.c +870 -156
- package/examples/genesis/templates/platformer.c +747 -129
- package/examples/genesis/templates/puzzle.c +702 -208
- package/examples/genesis/templates/racing.c +728 -193
- package/examples/genesis/templates/shmup.c +535 -142
- package/examples/genesis/templates/shmup_2p.c +13 -1
- package/examples/genesis/templates/sports.c +495 -158
- package/examples/gg/templates/platformer.c +883 -214
- package/examples/gg/templates/puzzle.c +906 -181
- package/examples/gg/templates/racing.c +919 -160
- package/examples/gg/templates/shmup.c +716 -177
- package/examples/gg/templates/sports.c +735 -128
- package/examples/lynx/templates/platformer.c +604 -50
- package/examples/lynx/templates/puzzle.c +533 -130
- package/examples/lynx/templates/racing.c +538 -102
- package/examples/lynx/templates/shmup.c +461 -122
- package/examples/lynx/templates/sports.c +496 -69
- package/examples/msx/platformer/main.c +648 -159
- package/examples/msx/puzzle/main.c +750 -185
- package/examples/msx/racing/main.c +669 -177
- package/examples/msx/shmup/main.c +460 -177
- package/examples/msx/sports/main.c +591 -124
- package/examples/nes/templates/platformer.c +586 -160
- package/examples/nes/templates/puzzle.c +603 -222
- package/examples/nes/templates/racing.c +505 -197
- package/examples/nes/templates/shmup.c +339 -144
- package/examples/nes/templates/sports.c +341 -182
- package/examples/pce/platformer/main.c +875 -204
- package/examples/pce/puzzle/main.c +797 -216
- package/examples/pce/racing/main.c +782 -206
- package/examples/pce/shmup/main.c +638 -211
- package/examples/pce/sports/main.c +585 -167
- package/examples/porting-across-platforms/README.md +1 -1
- package/examples/sms/templates/platformer.c +765 -176
- package/examples/sms/templates/puzzle.c +783 -177
- package/examples/sms/templates/racing.c +812 -133
- package/examples/sms/templates/shmup.c +601 -148
- package/examples/sms/templates/shmup_2p.c +17 -1
- package/examples/sms/templates/sports.c +633 -121
- package/examples/snes/templates/music_demo.c +7 -0
- package/examples/snes/templates/platformer-data.asm +123 -24
- package/examples/snes/templates/platformer-hdr.asm +57 -0
- package/examples/snes/templates/platformer.c +587 -149
- package/examples/snes/templates/puzzle-data.asm +116 -21
- package/examples/snes/templates/puzzle-hdr.asm +57 -0
- package/examples/snes/templates/puzzle.c +632 -185
- package/examples/snes/templates/racing-data.asm +390 -32
- package/examples/snes/templates/racing-hdr.asm +57 -0
- package/examples/snes/templates/racing.c +807 -177
- package/examples/snes/templates/shmup-data.asm +87 -29
- package/examples/snes/templates/shmup-hdr.asm +57 -0
- package/examples/snes/templates/shmup.c +459 -180
- package/examples/snes/templates/sports-data.asm +48 -2
- package/examples/snes/templates/sports-hdr.asm +57 -0
- package/examples/snes/templates/sports.c +414 -156
- package/package.json +12 -12
- package/src/cores/wasm/bluemsx_libretro.js +1 -1
- package/src/cores/wasm/bluemsx_libretro.wasm +0 -0
- package/src/cores/wasm/fceumm_libretro.js +1 -1
- package/src/cores/wasm/fceumm_libretro.wasm +0 -0
- package/src/cores/wasm/gambatte_libretro.js +1 -1
- package/src/cores/wasm/gambatte_libretro.wasm +0 -0
- package/src/cores/wasm/geargrafx_libretro.js +1 -1
- package/src/cores/wasm/geargrafx_libretro.wasm +0 -0
- package/src/cores/wasm/genesis_plus_gx_libretro.js +1 -1
- package/src/cores/wasm/genesis_plus_gx_libretro.wasm +0 -0
- package/src/cores/wasm/handy_libretro.js +1 -1
- package/src/cores/wasm/handy_libretro.wasm +0 -0
- package/src/cores/wasm/mgba_libretro.js +1 -1
- package/src/cores/wasm/mgba_libretro.wasm +0 -0
- package/src/cores/wasm/prosystem_libretro.js +1 -1
- package/src/cores/wasm/prosystem_libretro.wasm +0 -0
- package/src/cores/wasm/snes9x_libretro.js +1 -1
- package/src/cores/wasm/snes9x_libretro.wasm +0 -0
- package/src/cores/wasm/stella2014_libretro.js +1 -1
- package/src/cores/wasm/stella2014_libretro.wasm +0 -0
- 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 +304 -11
- package/src/http/tool-registry.js +11 -11
- package/src/mcp/server.js +6 -0
- package/src/mcp/tools/cheats.js +2 -1
- package/src/mcp/tools/disasm-rebuild.js +315 -65
- package/src/mcp/tools/disasm.js +149 -28
- package/src/mcp/tools/find-references.js +216 -51
- package/src/mcp/tools/frame.js +14 -6
- package/src/mcp/tools/index.js +18 -4
- package/src/mcp/tools/input.js +31 -7
- package/src/mcp/tools/lifecycle.js +6 -4
- package/src/mcp/tools/memory.js +208 -39
- package/src/mcp/tools/platform-docs.js +1 -1
- package/src/mcp/tools/playtest.js +56 -4
- package/src/mcp/tools/preview-tile.js +6 -2
- package/src/mcp/tools/project.js +1114 -120
- package/src/mcp/tools/rom-id.js +5 -1
- package/src/mcp/tools/run-until.js +4 -2
- package/src/mcp/tools/snippets.js +6 -6
- package/src/mcp/tools/sprite-pipeline.js +14 -2
- package/src/mcp/tools/state.js +2 -1
- package/src/mcp/tools/tile-inspect.js +8 -1
- package/src/mcp/tools/toolchain.js +55 -11
- package/src/mcp/tools/watch-memory.js +145 -27
- package/src/observer/bus.js +73 -0
- package/src/observer/livestream.html +4 -2
- package/src/observer/tool-wrap.js +17 -14
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +64 -17
- package/src/platforms/atari2600/MENTAL_MODEL.md +5 -1
- package/src/platforms/atari2600/TROUBLESHOOTING.md +40 -0
- package/src/platforms/atari7800/MENTAL_MODEL.md +32 -11
- package/src/platforms/atari7800/TROUBLESHOOTING.md +5 -5
- package/src/platforms/c64/MENTAL_MODEL.md +11 -4
- package/src/platforms/c64/TROUBLESHOOTING.md +13 -0
- package/src/platforms/gb/MENTAL_MODEL.md +19 -4
- package/src/platforms/gb/TROUBLESHOOTING.md +101 -6
- package/src/platforms/gb/lib/c/README.md +10 -11
- package/src/platforms/gb/lib/c/gb_crt0.s +27 -3
- package/src/platforms/gb/lib/c/patch-header.js +19 -6
- package/src/platforms/gba/MENTAL_MODEL.md +4 -4
- package/src/platforms/gba/TROUBLESHOOTING.md +3 -3
- package/src/platforms/gba/lib/c/gba_sfx.c +40 -0
- package/src/platforms/gba/lib/c/gba_sfx.h +10 -0
- package/src/platforms/gbc/MENTAL_MODEL.md +16 -4
- package/src/platforms/gbc/TROUBLESHOOTING.md +24 -3
- package/src/platforms/gbc/UPSTREAM_SOURCES.md +1 -1
- package/src/platforms/gbc/lib/c/README.md +10 -11
- package/src/platforms/gbc/lib/c/font.h +43 -0
- package/src/platforms/gbc/lib/c/gb_crt0.s +26 -3
- package/src/platforms/gbc/lib/c/patch-header.js +19 -6
- package/src/platforms/genesis/MENTAL_MODEL.md +43 -9
- package/src/platforms/genesis/TROUBLESHOOTING.md +2 -2
- package/src/platforms/genesis/lib/c/genesis_sfx.c +37 -0
- package/src/platforms/genesis/lib/c/genesis_sfx.h +1 -0
- package/src/platforms/gg/MENTAL_MODEL.md +4 -4
- package/src/platforms/gg/TROUBLESHOOTING.md +14 -18
- package/src/platforms/gg/UPSTREAM_SOURCES.md +1 -1
- package/src/platforms/gg/lib/c/gg_crt0.s +14 -2
- package/src/platforms/gg/lib/c/joypad_read.c +29 -0
- package/src/platforms/lynx/MENTAL_MODEL.md +1 -1
- package/src/platforms/lynx/TROUBLESHOOTING.md +3 -3
- package/src/platforms/lynx/lib/c/lynx_sfx.c +38 -2
- package/src/platforms/lynx/lib/c/lynx_sfx.h +1 -0
- package/src/platforms/msx/MENTAL_MODEL.md +11 -5
- package/src/platforms/msx/TROUBLESHOOTING.md +21 -0
- package/src/platforms/msx/lib/c/msx_crt0.s +27 -0
- package/src/platforms/msx/lib/c/msx_hw.h +3 -0
- package/src/platforms/msx/lib/c/msx_vdp.c +70 -0
- package/src/platforms/nes/MENTAL_MODEL.md +12 -5
- package/src/platforms/nes/lib/c/nes_runtime.c +190 -34
- package/src/platforms/nes/lib/c/nes_runtime.h +35 -0
- package/src/platforms/pce/MENTAL_MODEL.md +14 -5
- package/src/platforms/pce/TROUBLESHOOTING.md +9 -0
- package/src/platforms/pce/lib/c/pce_hw.h +13 -1
- package/src/platforms/pce/lib/c/pce_sound.c +22 -0
- package/src/platforms/pce/lib/c/pce_video.c +32 -0
- package/src/platforms/sms/MENTAL_MODEL.md +11 -6
- package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
- package/src/platforms/sms/lib/c/sms_crt0.s +14 -2
- package/src/platforms/snes/MENTAL_MODEL.md +7 -2
- package/src/platforms/snes/TROUBLESHOOTING.md +40 -1
- package/src/playtest/playtest.js +73 -3
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +13 -8
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.crt0.s +58 -5
- package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +52 -3
- package/src/toolchains/cc65/presets/pce/rom32k.cfg +52 -0
- package/src/toolchains/index.js +64 -19
|
@@ -1,95 +1,328 @@
|
|
|
1
|
-
/* ── racing/main.c — MSX top-down
|
|
1
|
+
/* ── racing/main.c — MSX top-down road racer (complete example game) ─────────
|
|
2
2
|
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* TURBO TANGLE — a COMPLETE, working game: title screen, 1P endless race with
|
|
4
|
+
* speed control + a best-distance record, 2P SIMULTANEOUS VERSUS (P2 on
|
|
5
|
+
* JOYSTICK PORT 2) on one shared road, crash/lives rules into a result screen,
|
|
6
|
+
* music + SFX on the AY-3-8910 PSG, and the MSX's signature SCREEN-2 PER-ROW
|
|
7
|
+
* COLOR: the asphalt, the grass shoulders, the centre divider and the HUD band
|
|
8
|
+
* are all ONE tile set differentiated purely by which screen-2 color third they
|
|
9
|
+
* sit in — plus a one-tile vertical "shimmer" gradient down the divider — at
|
|
10
|
+
* zero extra tiles.
|
|
5
11
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
12
|
+
* The game (top-down vertical racer): a four-lane road scrolls toward you; you
|
|
13
|
+
* steer LEFT/RIGHT between lanes to weave through slower traffic. In 1P,
|
|
14
|
+
* UP/A accelerates and DOWN/B brakes (speed 1-4) and the run banks DISTANCE;
|
|
15
|
+
* 3 crashes end it. In 2P, both cars share one road at a fixed speed — P1 owns
|
|
16
|
+
* the left two lanes, P2 (port 2) the right two — and the first driver to burn
|
|
17
|
+
* all 3 crashes LOSES.
|
|
11
18
|
*
|
|
12
|
-
*
|
|
19
|
+
* THIS FILE IS MEANT TO BE FORKED AND MODIFIED into your own game — even a
|
|
20
|
+
* very different one. The markers tell you what's what:
|
|
21
|
+
* HARDWARE IDIOM (load-bearing) — dodges a documented MSX footgun; reshape
|
|
22
|
+
* your gameplay around it (see TROUBLESHOOTING before changing).
|
|
23
|
+
* GAME LOGIC (clay) — traffic patterns, speeds, scoring, art: reshape freely.
|
|
13
24
|
*
|
|
14
|
-
*
|
|
25
|
+
* What depends on what:
|
|
26
|
+
* msx_hw.h / msx_vdp.c — VDP + PSG + joystick helpers (direct Z80 ports;
|
|
27
|
+
* the PSG functions carry a DI/EI guard against the BIOS KEYINT race —
|
|
28
|
+
* read msx_vdp.c before adding your own PSG pokes).
|
|
29
|
+
* msx_crt0.s — the $4000 "AB" cart header + static-init copy. Load-bearing;
|
|
30
|
+
* INIT must NEVER return, so main() ends in for(;;).
|
|
31
|
+
*
|
|
32
|
+
* A TEACHING POINT vs the NES version of this game
|
|
33
|
+
* (examples/nes/templates/racing.c): the NES scrolls the road as the
|
|
34
|
+
* BACKGROUND — it decrements the PPU's hardware scroll_y every frame and the
|
|
35
|
+
* whole nametable slides for free. The MSX SCREEN 2 has NO HARDWARE SCROLL at
|
|
36
|
+
* all (see the idiom below), so TURBO TANGLE fakes the motion by REDRAWING the
|
|
37
|
+
* road's dashes + shoulder texture one phase further down the name table each
|
|
38
|
+
* frame: a moving-stripe pattern, recomputed from a single scrolling offset.
|
|
39
|
+
* Same genre, the opposite hardware reality — and the honest way to teach it.
|
|
40
|
+
*
|
|
41
|
+
* Controls: JOYSTICK PORT 1 (or keyboard cursors) LEFT/RIGHT steers; UP/trigger
|
|
42
|
+
* A accelerates, DOWN/trigger B brakes (1P only). In 2P versus, JOYSTICK
|
|
43
|
+
* PORT 2 LEFT/RIGHT steers player 2. On the title screen trigger A starts the
|
|
44
|
+
* 1P race; trigger B starts 2P versus. On the result screen any fire returns
|
|
45
|
+
* to the title.
|
|
46
|
+
*
|
|
47
|
+
* Record honesty: the bundled bluemsx core build exposes NO battery save path
|
|
48
|
+
* (retro_get_memory(SAVE_RAM) is unimplemented for MSX carts), so BEST (the
|
|
49
|
+
* best 1P distance) lives in plain RAM: it survives title↔race cycles but NOT
|
|
50
|
+
* a power cycle / hardReset. Never fake persistence — if you need real saves,
|
|
51
|
+
* that's a future core round (ASCII8-SRAM mapper carts exist; the core just
|
|
52
|
+
* doesn't surface their RAM yet). The Genesis/NES/SMS versions of this game
|
|
53
|
+
* DO persist the same best distance to cartridge SRAM.
|
|
15
54
|
*/
|
|
16
55
|
#include "msx_hw.h"
|
|
17
56
|
|
|
18
|
-
/*
|
|
57
|
+
/* The title screen renders this — examples({op:'fork'}) stamps your game's
|
|
58
|
+
* name here automatically. Keep it ≤16 chars of A-Z 0-9 space dash. */
|
|
59
|
+
#define GAME_TITLE "TURBO TANGLE"
|
|
60
|
+
|
|
61
|
+
/* ── HARDWARE IDIOM (load-bearing — reshape gameplay around this; see TROUBLESHOOTING) ──
|
|
62
|
+
* Interrupt-free vblank sync: poll VDP status S#0 bit 7 (port 0x99). Reading
|
|
63
|
+
* the port ALSO clears the flag, so one read per frame = one game step per
|
|
64
|
+
* frame. We deliberately do NOT use the BIOS JIFFY counter here: this poll
|
|
65
|
+
* works even with interrupts masked, and never depends on the BIOS ISR
|
|
66
|
+
* keeping pace. (The BIOS KEYINT also reads S#0 — on rare frames it eats the
|
|
67
|
+
* flag first and this loop just waits for the next one; a one-frame hiccup,
|
|
68
|
+
* never a hang.) */
|
|
19
69
|
__sfr __at 0x99 VDPSTATUS;
|
|
20
70
|
static void vsync(void) {
|
|
21
|
-
(void)VDPSTATUS;
|
|
71
|
+
(void)VDPSTATUS; /* throw away a possibly-stale flag */
|
|
22
72
|
while (!(VDPSTATUS & 0x80)) {
|
|
23
73
|
}
|
|
24
74
|
}
|
|
25
75
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/*
|
|
60
|
-
|
|
61
|
-
/*
|
|
62
|
-
|
|
63
|
-
|
|
76
|
+
/* ── HARDWARE IDIOM (load-bearing — reshape gameplay around this; see TROUBLESHOOTING) ──
|
|
77
|
+
* NO HARDWARE SCROLL ON SCREEN 2. The TMS9918 GRAPHIC-II mode has no smooth
|
|
78
|
+
* pixel-scroll register at all (the V9938's R23 is a whole-screen vertical
|
|
79
|
+
* LINE shift, not a per-layer camera, and MSX1 lacks even that). So a vertical
|
|
80
|
+
* road racer cannot just "scroll the background down" the way the NES version
|
|
81
|
+
* does — there is no register to turn.
|
|
82
|
+
*
|
|
83
|
+
* TURBO TANGLE fakes the scroll the only cheap way screen 2 allows: it keeps a
|
|
84
|
+
* single 0-7 "road phase" that advances with the car's speed, and each frame it
|
|
85
|
+
* REDRAWS just the road's moving parts — the dashed lane markers and a sparse
|
|
86
|
+
* shoulder speckle — one phase-step further DOWN the name table. The static
|
|
87
|
+
* parts (asphalt fill, solid shoulders, centre divider) are painted ONCE and
|
|
88
|
+
* never touched. Redrawing only ~2 columns of dashes + a speckle column per
|
|
89
|
+
* frame keeps the per-frame VRAM burst tiny, so it never fights the per-row
|
|
90
|
+
* color idiom below (the color tables still upload ONCE). The eye reads the
|
|
91
|
+
* marching dashes as forward motion — exactly the trick fixed-screen arcade
|
|
92
|
+
* racers used before hardware scroll was common.
|
|
93
|
+
*
|
|
94
|
+
* If you want REAL smooth scroll on MSX2, that is an R23 line-shift routine
|
|
95
|
+
* plus re-streaming the name + color tables as rows enter — the single biggest
|
|
96
|
+
* MSX scroller footgun; see TROUBLESHOOTING before attempting it. */
|
|
97
|
+
|
|
98
|
+
/* ── GAME LOGIC (clay — reshape freely) ──────────────────────────────────────
|
|
99
|
+
* Tile font: index 0 = space, 1-26 = A-Z, 27-36 = 0-9, 37 = dash, then the
|
|
100
|
+
* road tiles. One 8x8 pattern = 8 bytes, one bit per pixel; set bits draw in
|
|
101
|
+
* the tile's FOREGROUND color, clear bits in its BACKGROUND color (both come
|
|
102
|
+
* from the screen-2 color table — see the per-row-color idiom below). */
|
|
103
|
+
#define T_SPACE 0
|
|
104
|
+
#define T_A 1 /* 'A'..'Z' = T_A + (c - 'A') */
|
|
105
|
+
#define T_0 27 /* '0'..'9' = T_0 + (c - '0') */
|
|
106
|
+
#define T_DASH 37
|
|
107
|
+
#define T_ASPHALT 38 /* plain road surface (faint tarmac speck) */
|
|
108
|
+
#define T_GRASS 39 /* roadside shoulder (hatch texture) */
|
|
109
|
+
#define T_LANE 40 /* the marching dashed lane marker (scrolls) */
|
|
110
|
+
#define T_DIVIDER 41 /* solid centre divider — its COLOR shimmers */
|
|
111
|
+
#define T_TUFT 42 /* roadside scenery tuft (rides the speckle) */
|
|
112
|
+
#define NUM_TILES 43
|
|
113
|
+
|
|
114
|
+
static const uint8_t font[NUM_TILES][8] = {
|
|
115
|
+
/* SPACE */ {0,0,0,0,0,0,0,0},
|
|
116
|
+
/* 1 A */ {0x38,0x6C,0xC6,0xC6,0xFE,0xC6,0xC6,0x00},
|
|
117
|
+
/* 2 B */ {0xFC,0xC6,0xC6,0xFC,0xC6,0xC6,0xFC,0x00},
|
|
118
|
+
/* 3 C */ {0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00},
|
|
119
|
+
/* 4 D */ {0xF8,0xCC,0xC6,0xC6,0xC6,0xCC,0xF8,0x00},
|
|
120
|
+
/* 5 E */ {0xFE,0xC0,0xC0,0xF8,0xC0,0xC0,0xFE,0x00},
|
|
121
|
+
/* 6 F */ {0xFE,0xC0,0xC0,0xF8,0xC0,0xC0,0xC0,0x00},
|
|
122
|
+
/* 7 G */ {0x7C,0xC6,0xC0,0xCE,0xC6,0xC6,0x7C,0x00},
|
|
123
|
+
/* 8 H */ {0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0x00},
|
|
124
|
+
/* 9 I */ {0x7E,0x18,0x18,0x18,0x18,0x18,0x7E,0x00},
|
|
125
|
+
/* 10 J */ {0x1E,0x06,0x06,0x06,0xC6,0xC6,0x7C,0x00},
|
|
126
|
+
/* 11 K */ {0xC6,0xCC,0xD8,0xF0,0xD8,0xCC,0xC6,0x00},
|
|
127
|
+
/* 12 L */ {0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xFE,0x00},
|
|
128
|
+
/* 13 M */ {0xC6,0xEE,0xFE,0xD6,0xC6,0xC6,0xC6,0x00},
|
|
129
|
+
/* 14 N */ {0xC6,0xE6,0xF6,0xDE,0xCE,0xC6,0xC6,0x00},
|
|
130
|
+
/* 15 O */ {0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00},
|
|
131
|
+
/* 16 P */ {0xFC,0xC6,0xC6,0xFC,0xC0,0xC0,0xC0,0x00},
|
|
132
|
+
/* 17 Q */ {0x7C,0xC6,0xC6,0xC6,0xD6,0xCC,0x76,0x00},
|
|
133
|
+
/* 18 R */ {0xFC,0xC6,0xC6,0xFC,0xD8,0xCC,0xC6,0x00},
|
|
134
|
+
/* 19 S */ {0x7C,0xC0,0xC0,0x78,0x0C,0x0C,0xF8,0x00},
|
|
135
|
+
/* 20 T */ {0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00},
|
|
136
|
+
/* 21 U */ {0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00},
|
|
137
|
+
/* 22 V */ {0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00},
|
|
138
|
+
/* 23 W */ {0xC6,0xC6,0xC6,0xD6,0xFE,0xEE,0xC6,0x00},
|
|
139
|
+
/* 24 X */ {0xC6,0x6C,0x38,0x10,0x38,0x6C,0xC6,0x00},
|
|
140
|
+
/* 25 Y */ {0x66,0x66,0x66,0x3C,0x18,0x18,0x18,0x00},
|
|
141
|
+
/* 26 Z */ {0xFE,0x0C,0x18,0x30,0x60,0xC0,0xFE,0x00},
|
|
142
|
+
/* 27 0 */ {0x7C,0xCE,0xDE,0xF6,0xE6,0xC6,0x7C,0x00},
|
|
143
|
+
/* 28 1 */ {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00},
|
|
144
|
+
/* 29 2 */ {0x7C,0xC6,0x06,0x1C,0x70,0xC0,0xFE,0x00},
|
|
145
|
+
/* 30 3 */ {0x7C,0xC6,0x06,0x3C,0x06,0xC6,0x7C,0x00},
|
|
146
|
+
/* 31 4 */ {0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x00},
|
|
147
|
+
/* 32 5 */ {0xFE,0xC0,0xFC,0x06,0x06,0xC6,0x7C,0x00},
|
|
148
|
+
/* 33 6 */ {0x3C,0x60,0xC0,0xFC,0xC6,0xC6,0x7C,0x00},
|
|
149
|
+
/* 34 7 */ {0xFE,0x06,0x0C,0x18,0x30,0x30,0x30,0x00},
|
|
150
|
+
/* 35 8 */ {0x7C,0xC6,0xC6,0x7C,0xC6,0xC6,0x7C,0x00},
|
|
151
|
+
/* 36 9 */ {0x7C,0xC6,0xC6,0x7E,0x06,0x0C,0x78,0x00},
|
|
152
|
+
/* 37 - */ {0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00},
|
|
153
|
+
/* 38 ASPHALT (sparse tarmac speck so the road isn't a flat void) */
|
|
154
|
+
{0x00,0x00,0x00,0x10,0x00,0x00,0x02,0x00},
|
|
155
|
+
/* 39 GRASS (roadside hatch texture) */
|
|
156
|
+
{0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55},
|
|
157
|
+
/* 40 LANE (a vertical dash segment — half on, half off; phase-shifted
|
|
158
|
+
* by which name-table row it lands on for the marching look) */
|
|
159
|
+
{0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00},
|
|
160
|
+
/* 41 DIVIDER(solid bar — its 8 COLOR bytes carry the shimmer gradient) */
|
|
161
|
+
{0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C},
|
|
162
|
+
/* 42 TUFT (a little roadside bush over the grass) */
|
|
163
|
+
{0x00,0x18,0x3C,0x7E,0xFF,0x7E,0x3C,0x00},
|
|
64
164
|
};
|
|
65
165
|
|
|
66
|
-
/*
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
166
|
+
/* ── HARDWARE IDIOM (load-bearing — reshape gameplay around this; see TROUBLESHOOTING) ──
|
|
167
|
+
* SCREEN-2 PER-ROW COLOR — the MSX's signature background trick.
|
|
168
|
+
*
|
|
169
|
+
* Screen 2 (GRAPHIC II) is NOT "one color byte per tile" like most consoles:
|
|
170
|
+
*
|
|
171
|
+
* 1. The 256x192 screen is THREE INDEPENDENT THIRDS of 8 rows each
|
|
172
|
+
* (name-table rows 0-7, 8-15, 16-23). Each third has its OWN 2KB
|
|
173
|
+
* pattern table slice and its OWN 2KB color table slice:
|
|
174
|
+
* patterns: VRAM_PATTERN + third*0x800, colors: VRAM_COLOR + third*0x800
|
|
175
|
+
* The SAME tile index can look completely different in each third. We
|
|
176
|
+
* exploit exactly that to make ONE road tile set read as a depth-shaded
|
|
177
|
+
* track: the HUD band (third 0) gets bright text colors; the asphalt cools
|
|
178
|
+
* from a hazy distance grey at the top toward a darker near-road grey at
|
|
179
|
+
* the bottom, and the grass deepens the same way — one tile set, three
|
|
180
|
+
* bands, zero extra tiles (the racing twin of the shmup's depth starfield).
|
|
181
|
+
*
|
|
182
|
+
* 2. Within a tile, the color table holds EIGHT bytes — one per 8x1 pixel
|
|
183
|
+
* row — each packing (foreground<<4)|background from the fixed TMS9918
|
|
184
|
+
* palette. So one tile can carry an 8-color vertical gradient: T_DIVIDER's
|
|
185
|
+
* whole "shimmer" running down the centre divider is a single tile,
|
|
186
|
+
* colors only.
|
|
187
|
+
*
|
|
188
|
+
* Requires: the screen-2 table layout set by msx_set_screen2() (R3=0xFF,
|
|
189
|
+
* R4=0x03 — the "thirds" configuration), and pattern + color uploads to
|
|
190
|
+
* EVERY third a tile is used in. Tile N's slot is pattern[N*8] / color[N*8].
|
|
191
|
+
*
|
|
192
|
+
* TMS9918 fixed palette used here: 1 black, 4 dark blue, 6 dark red, 8 medium
|
|
193
|
+
* red, 12 dark green, 13 light green, 14 gray, 15 white, 10 dark yellow,
|
|
194
|
+
* 11 light yellow (high nibble = fg, low nibble = bg of each row byte). */
|
|
195
|
+
static const uint8_t col_text[3] = { 0xF1, 0xF1, 0xF1 }; /* white-on-black text everywhere */
|
|
196
|
+
/* The asphalt speck, banded by third: hazy light-grey far off, mid grey, dark
|
|
197
|
+
* near-road grey close — pure per-third recolor of one tile (bg = the road). */
|
|
198
|
+
static const uint8_t col_asphalt[3] = { 0xE4, 0xE1, 0x1E };
|
|
199
|
+
/* The grass shoulders, banded so distant grass reads cooler/darker and near
|
|
200
|
+
* grass brightens — same hatch tile, three colors. */
|
|
201
|
+
static const uint8_t col_grass[3] = { 0xC1, 0xD1, 0xD1 };
|
|
202
|
+
/* The marching lane dashes: bright yellow on the road bg, banded subtly. */
|
|
203
|
+
static const uint8_t col_lane[3] = { 0xB1, 0xB4, 0xB1 };
|
|
204
|
+
/* Roadside scenery tuft: green bush over the grass band. */
|
|
205
|
+
static const uint8_t col_tuft[3] = { 0xC1, 0xC1, 0xD1 };
|
|
206
|
+
/* T_DIVIDER: 8 DIFFERENT color bytes inside ONE tile = an 8-pixel-row shimmer
|
|
207
|
+
* down the centre divider (black → grey → white and back). The divider pattern
|
|
208
|
+
* is a solid 4px bar so the fg nibbles show. Recolored again per third free. */
|
|
209
|
+
static const uint8_t col_div[8] = { 0x11,0xE1,0xF1,0xF1,0xF1,0xF1,0xE1,0x11 };
|
|
210
|
+
|
|
211
|
+
static void load_tiles(void) {
|
|
212
|
+
uint8_t third, i;
|
|
213
|
+
uint16_t patbase, colbase;
|
|
214
|
+
for (third = 0; third < 3; third++) {
|
|
215
|
+
patbase = (uint16_t)(VRAM_PATTERN + ((uint16_t)third << 11));
|
|
216
|
+
colbase = (uint16_t)(VRAM_COLOR + ((uint16_t)third << 11));
|
|
217
|
+
for (i = 0; i < NUM_TILES; i++) {
|
|
218
|
+
uint8_t col;
|
|
219
|
+
/* pattern bits are the same in every third — only COLOR varies */
|
|
220
|
+
msx_vram_write((uint16_t)(patbase + ((uint16_t)i << 3)), font[i], 8);
|
|
221
|
+
if (i == T_DIVIDER) { /* the one per-pixel-row gradient */
|
|
222
|
+
msx_vram_write((uint16_t)(colbase + ((uint16_t)i << 3)), col_div, 8);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (i == T_ASPHALT) col = col_asphalt[third];
|
|
226
|
+
else if (i == T_GRASS) col = col_grass[third];
|
|
227
|
+
else if (i == T_LANE) col = col_lane[third];
|
|
228
|
+
else if (i == T_TUFT) col = col_tuft[third];
|
|
229
|
+
else col = col_text[third];
|
|
230
|
+
msx_fill_vram((uint16_t)(colbase + ((uint16_t)i << 3)), 8, col);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* ── GAME LOGIC (clay — reshape freely) — name-table drawing helpers ────────
|
|
236
|
+
* Screen 2 VRAM writes are safe at any point in the frame at C speed: the
|
|
237
|
+
* TMS9918 needs ~29 Z80 cycles between VRAM accesses during active display,
|
|
238
|
+
* and SDCC-compiled loops are slower than that. (Hand-tuned asm OTIR bursts
|
|
239
|
+
* are the thing that outruns the VDP — see TROUBLESHOOTING.) */
|
|
240
|
+
static void put_tile(uint8_t col, uint8_t row, uint8_t tile) {
|
|
241
|
+
msx_vram_write((uint16_t)(VRAM_NAME + (uint16_t)row * 32 + col), &tile, 1);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static void draw_text(uint8_t col, uint8_t row, const char *s) {
|
|
245
|
+
uint8_t buf[32];
|
|
246
|
+
uint8_t n = 0;
|
|
247
|
+
while (*s && n < 32) {
|
|
248
|
+
char c = *s++;
|
|
249
|
+
if (c >= 'A' && c <= 'Z') buf[n] = (uint8_t)(T_A + c - 'A');
|
|
250
|
+
else if (c >= '0' && c <= '9') buf[n] = (uint8_t)(T_0 + c - '0');
|
|
251
|
+
else if (c == '-') buf[n] = T_DASH;
|
|
252
|
+
else buf[n] = T_SPACE;
|
|
253
|
+
n++;
|
|
254
|
+
}
|
|
255
|
+
msx_vram_write((uint16_t)(VRAM_NAME + (uint16_t)row * 32 + col), buf, n);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
static void draw_num4(uint8_t col, uint8_t row, uint16_t v) {
|
|
259
|
+
uint8_t buf[4];
|
|
260
|
+
buf[0] = (uint8_t)(T_0 + (v / 1000) % 10);
|
|
261
|
+
buf[1] = (uint8_t)(T_0 + (v / 100) % 10);
|
|
262
|
+
buf[2] = (uint8_t)(T_0 + (v / 10) % 10);
|
|
263
|
+
buf[3] = (uint8_t)(T_0 + v % 10);
|
|
264
|
+
msx_vram_write((uint16_t)(VRAM_NAME + (uint16_t)row * 32 + col), buf, 4);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* ── GAME LOGIC (clay — reshape freely) — road geometry + race rules ─────────
|
|
268
|
+
* The road fills the 32x24 screen-2 name table. Four 2-cell lanes sit between
|
|
269
|
+
* grass shoulders, with a solid divider down the middle (it is also the 2P
|
|
270
|
+
* territory line). Tile columns:
|
|
271
|
+
* COL_EDGE_L/R — solid grass-edge shoulders bounding the asphalt
|
|
272
|
+
* COL_LANE_1/2 — the two dashed inner lane lines
|
|
273
|
+
* COL_DIVIDER — the solid centre divider
|
|
274
|
+
* Row 0 is the HUD band (third 0's text colors make it a distinct strip). */
|
|
275
|
+
#define COL_EDGE_L 8
|
|
276
|
+
#define COL_LANE_1 12
|
|
277
|
+
#define COL_DIVIDER 16
|
|
278
|
+
#define COL_LANE_2 20
|
|
279
|
+
#define COL_EDGE_R 24
|
|
280
|
+
/* Lane centre X for the 8px-wide car sprite (4 lanes across the asphalt). */
|
|
281
|
+
static const uint8_t lane_x[4] = { 80, 112, 136, 168 };
|
|
282
|
+
|
|
283
|
+
#define MAX_TRAFFIC 5
|
|
284
|
+
#define CAR_Y 168 /* both cars' fixed screen Y (near the bottom) */
|
|
285
|
+
#define SPAWN_Y 16 /* traffic entry Y (just under the HUD band) */
|
|
286
|
+
#define DESPAWN_Y 184 /* traffic leaves the road past here */
|
|
287
|
+
#define START_LIVES 3 /* crashes per run / per player */
|
|
288
|
+
#define SPAWN_PERIOD 40 /* frames between traffic spawns */
|
|
289
|
+
#define SPEED_2P 2 /* fixed shared road speed in versus */
|
|
290
|
+
|
|
291
|
+
/* Players: index 0 = P1 (port 1 + keyboard), 1 = P2 (port 2, versus only). */
|
|
292
|
+
static uint8_t car_lane[2]; /* 0..3 */
|
|
293
|
+
static uint8_t car_active[2];
|
|
294
|
+
static uint8_t crashes_left[2];
|
|
295
|
+
static uint8_t invuln[2]; /* post-crash blink / no-collide frames */
|
|
296
|
+
static uint8_t lane_min[2], lane_max[2]; /* 2P: split territories */
|
|
297
|
+
static uint8_t prev_dir[2]; /* per-player steer edge detection */
|
|
298
|
+
static uint8_t prev_acc; /* 1P accel/brake edge detection */
|
|
299
|
+
static uint8_t two_player;
|
|
300
|
+
static uint8_t winner; /* versus result: 0 = P1 wins, 1 = P2 wins */
|
|
301
|
+
|
|
302
|
+
static uint8_t traffic_alive[MAX_TRAFFIC];
|
|
303
|
+
static uint8_t traffic_lane[MAX_TRAFFIC];
|
|
304
|
+
static uint8_t traffic_y[MAX_TRAFFIC];
|
|
305
|
+
|
|
306
|
+
static uint8_t speed; /* road px/frame, 1-4 */
|
|
307
|
+
static uint16_t dist; /* 1P distance, 1 unit = 16 scrolled px */
|
|
308
|
+
static uint8_t dist_frac;
|
|
309
|
+
static uint16_t best; /* SESSION-ONLY best 1P distance — see header.
|
|
310
|
+
* No SAVE_RAM on this core, so it lives in
|
|
311
|
+
* plain RAM: survives title↔race cycles, NOT
|
|
312
|
+
* a power cycle (honest, not faked). */
|
|
85
313
|
static uint8_t spawn_timer;
|
|
86
|
-
static uint8_t
|
|
87
|
-
static uint8_t player_lane;
|
|
88
|
-
static uint16_t rng;
|
|
89
|
-
static uint8_t blip;
|
|
314
|
+
static uint8_t road_phase; /* 0..7 — the faked-scroll offset (idiom) */
|
|
90
315
|
|
|
91
|
-
|
|
316
|
+
#define ST_TITLE 0
|
|
317
|
+
#define ST_PLAY 1
|
|
318
|
+
#define ST_OVER 2
|
|
319
|
+
static uint8_t state;
|
|
320
|
+
static uint8_t prev_t1, prev_t2; /* title/over trigger edge detection */
|
|
92
321
|
|
|
322
|
+
/* ── GAME LOGIC (clay — reshape freely) — xorshift16 PRNG.
|
|
323
|
+
* Traffic lanes + spawn timing read from this so two runs never play the same;
|
|
324
|
+
* ticked once per play frame so identical states a few seconds apart diverge. */
|
|
325
|
+
static uint16_t rng;
|
|
93
326
|
static uint8_t next_rand(void) {
|
|
94
327
|
rng ^= (uint16_t)(rng << 7);
|
|
95
328
|
rng ^= (uint16_t)(rng >> 9);
|
|
@@ -97,153 +330,412 @@ static uint8_t next_rand(void) {
|
|
|
97
330
|
return (uint8_t)(rng & 0xFF);
|
|
98
331
|
}
|
|
99
332
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
333
|
+
/* ── GAME LOGIC (clay — reshape freely) — music + SFX on the AY-3-8910 ──────
|
|
334
|
+
* Channel plan: A = lane-tick / engine blips, B = crash + brake noise, C =
|
|
335
|
+
* music. The PSG has 3 tone channels + ONE shared noise generator, mixed
|
|
336
|
+
* per-channel in reg 7. All register traffic goes through msx_psg_tone/noise/
|
|
337
|
+
* off — they wrap the PSGADDR/PSGWRITE pair in DI/EI because the BIOS KEYINT
|
|
338
|
+
* ISR clobbers the PSG address latch every frame (the bug that once silenced
|
|
339
|
+
* every MSX scaffold — see msx_vdp.c).
|
|
340
|
+
*
|
|
341
|
+
* The tune: one period entry per half-beat, 0 = rest. AY period =
|
|
342
|
+
* 1789773 / (16 * freq) — e.g. A4 (440Hz) -> 254. Ticked once per frame; a
|
|
343
|
+
* note advances every 8 frames. The lib's built-in demo loop (msx_music_tick)
|
|
344
|
+
* also uses channel C, so we switch it OFF in main() and run THIS table
|
|
345
|
+
* instead — edit this table to rescore. */
|
|
346
|
+
static const uint16_t tune[32] = {
|
|
347
|
+
254, 0, 254, 214, 254, 0, 285, 254, /* A4 A4 C5 A4 G4 A4 (driving riff) */
|
|
348
|
+
214, 0, 254, 285, 254, 0, 0, 0, /* C5 A4 G4 A4 rest */
|
|
349
|
+
190, 0, 214, 254, 190, 0, 214, 190, /* D5 C5 A4 D5 C5 D5 */
|
|
350
|
+
254, 0, 285, 254, 214, 0, 0, 0, /* A4 G4 A4 C5 rest */
|
|
351
|
+
};
|
|
352
|
+
static uint8_t music_step, music_timer;
|
|
353
|
+
static uint8_t sfx_a_t, sfx_b_t; /* frames left on the A/B SFX channels */
|
|
354
|
+
|
|
355
|
+
static void music_tick(void) {
|
|
356
|
+
if (music_timer == 0) {
|
|
357
|
+
uint16_t p = tune[music_step & 31];
|
|
358
|
+
if (p) msx_psg_tone(2, p, 9);
|
|
359
|
+
else msx_psg_off(2);
|
|
360
|
+
music_step++;
|
|
361
|
+
}
|
|
362
|
+
music_timer++;
|
|
363
|
+
if (music_timer >= 8) music_timer = 0;
|
|
103
364
|
}
|
|
104
365
|
|
|
105
|
-
static void
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
for (third = 0; third < 3; third++) {
|
|
109
|
-
pat = (uint16_t)(VRAM_PATTERN + ((uint16_t)third << 11));
|
|
110
|
-
col = (uint16_t)(VRAM_COLOR + ((uint16_t)third << 11));
|
|
111
|
-
for (i = 0; i < 19; i++) {
|
|
112
|
-
uint8_t cc = COL_TEXT;
|
|
113
|
-
if (i == T_GRASS) cc = COL_GRASS;
|
|
114
|
-
else if (i == T_ROAD) cc = COL_ROAD;
|
|
115
|
-
else if (i == T_LANE) cc = COL_LANE;
|
|
116
|
-
msx_vram_write((uint16_t)(pat + ((uint16_t)i << 3)), font[i], 8);
|
|
117
|
-
msx_fill_vram((uint16_t)(col + ((uint16_t)i << 3)), 8, cc);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
366
|
+
static void sfx_tick(void) {
|
|
367
|
+
if (sfx_a_t) { sfx_a_t--; if (!sfx_a_t) msx_psg_off(0); }
|
|
368
|
+
if (sfx_b_t) { sfx_b_t--; if (!sfx_b_t) msx_psg_noise(1, 0, 0); }
|
|
120
369
|
}
|
|
121
370
|
|
|
122
|
-
static void
|
|
123
|
-
|
|
371
|
+
static void sfx_lane(void) { msx_psg_tone(0, 0x180, 10); sfx_a_t = 3; }
|
|
372
|
+
static void sfx_accel(void) { msx_psg_tone(0, 0x110, 11); sfx_a_t = 5; }
|
|
373
|
+
static void sfx_brake(void) { msx_psg_noise(1, 18, 9); sfx_b_t = 5; }
|
|
374
|
+
static void sfx_pass(void) { msx_psg_tone(0, 0x0C0, 8); sfx_a_t = 2; }
|
|
375
|
+
static void sfx_crash(void) { msx_psg_noise(1, 28, 15); sfx_b_t = 20; }
|
|
376
|
+
static void sfx_start(void) { msx_psg_tone(0, 0x130, 12); sfx_a_t = 6; }
|
|
377
|
+
|
|
378
|
+
/* ── GAME LOGIC (clay — reshape freely) — HUD ──────────────────────────────
|
|
379
|
+
* Row 0 = the HUD band (third 0's text colors make it a distinct strip).
|
|
380
|
+
* 1P: LIVES left, DIST right. 2P: P1 crashes-left | VS | P2 crashes-left. */
|
|
381
|
+
static void draw_hud_labels(void) {
|
|
382
|
+
if (two_player) {
|
|
383
|
+
draw_text(1, 0, "P1");
|
|
384
|
+
draw_text(14, 0, "VS");
|
|
385
|
+
draw_text(26, 0, "P2");
|
|
386
|
+
} else {
|
|
387
|
+
draw_text(1, 0, "LIVES");
|
|
388
|
+
draw_text(20, 0, "DIST");
|
|
389
|
+
}
|
|
124
390
|
}
|
|
391
|
+
static void draw_lives(void) {
|
|
392
|
+
if (two_player) {
|
|
393
|
+
put_tile(4, 0, (uint8_t)(T_0 + crashes_left[0]));
|
|
394
|
+
put_tile(29, 0, (uint8_t)(T_0 + crashes_left[1]));
|
|
395
|
+
} else {
|
|
396
|
+
put_tile(7, 0, (uint8_t)(T_0 + crashes_left[0]));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
static void draw_dist(void) { if (!two_player) draw_num4(25, 0, dist); }
|
|
400
|
+
|
|
401
|
+
/* ── GAME LOGIC (clay — reshape freely) — paint the road (name table) ───────
|
|
402
|
+
* The whole 32x24 name table: HUD band on row 0, grass shoulders outside the
|
|
403
|
+
* asphalt, solid edges + centre divider, asphalt fill between. The marching
|
|
404
|
+
* lane dashes and roadside speckle are NOT written here — restripe_road()
|
|
405
|
+
* redraws those each frame to fake the scroll (see the no-hw-scroll idiom).
|
|
406
|
+
* The per-third color idiom shades the whole thing into depth bands for free. */
|
|
407
|
+
static void clear_field(void) { msx_fill_vram(VRAM_NAME, 32u * 24u, T_SPACE); }
|
|
125
408
|
|
|
126
|
-
|
|
127
|
-
* markers between the three lanes (cols 14 and 17) on alternating rows. */
|
|
128
|
-
static void draw_track(void) {
|
|
409
|
+
static void paint_road(void) {
|
|
129
410
|
uint8_t row, col, t;
|
|
130
411
|
for (row = 0; row < 24; row++) {
|
|
131
412
|
for (col = 0; col < 32; col++) {
|
|
132
|
-
|
|
133
|
-
if (
|
|
413
|
+
if (row == 0) t = T_SPACE; /* HUD band */
|
|
414
|
+
else if (col < COL_EDGE_L || col > COL_EDGE_R) t = T_GRASS;
|
|
415
|
+
else if (col == COL_EDGE_L || col == COL_EDGE_R) t = T_GRASS; /* shoulder */
|
|
416
|
+
else if (col == COL_DIVIDER) t = T_DIVIDER;
|
|
417
|
+
else t = T_ASPHALT;
|
|
134
418
|
put_tile(col, row, t);
|
|
135
419
|
}
|
|
136
420
|
}
|
|
137
421
|
}
|
|
138
422
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
423
|
+
/* ── HARDWARE IDIOM (load-bearing — reshape gameplay around this; see TROUBLESHOOTING) ──
|
|
424
|
+
* The faked vertical scroll. Because screen 2 has no scroll register (see the
|
|
425
|
+
* idiom above), we redraw only the MOVING cells each frame from road_phase:
|
|
426
|
+
* - The two dashed lane lines: a cell shows a dash when (row+phase) is in the
|
|
427
|
+
* "on" half of an 8-row cycle, asphalt otherwise — so the dash pattern
|
|
428
|
+
* marches DOWN one row per phase step, reading as forward motion.
|
|
429
|
+
* - One roadside speckle column: a tuft drops down the grass with the phase,
|
|
430
|
+
* giving the shoulder a sense of speed too.
|
|
431
|
+
* Only ~3 columns × 23 rows are touched (well inside the frame's VRAM budget),
|
|
432
|
+
* and the static asphalt/edges/divider painted by paint_road() are left alone. */
|
|
433
|
+
static void restripe_road(uint8_t phase) {
|
|
434
|
+
uint8_t row, on, t;
|
|
435
|
+
for (row = 1; row < 24; row++) {
|
|
436
|
+
on = (uint8_t)(((row + phase) & 7) < 4); /* dash on/off cycle */
|
|
437
|
+
t = on ? T_LANE : T_ASPHALT;
|
|
438
|
+
put_tile(COL_LANE_1, row, t);
|
|
439
|
+
put_tile(COL_LANE_2, row, t);
|
|
440
|
+
/* a single roadside tuft riding down the left grass band */
|
|
441
|
+
put_tile(2, row, (((row + phase) & 7) == 0) ? T_TUFT : T_GRASS);
|
|
442
|
+
put_tile(29, row, (((row + phase + 4) & 7) == 0) ? T_TUFT : T_GRASS);
|
|
443
|
+
}
|
|
142
444
|
}
|
|
143
445
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
446
|
+
/* ── GAME LOGIC (clay — reshape freely) — sprites: cars + traffic ───────────
|
|
447
|
+
* 8x8 one-color hardware sprites. Plane layout: 0 = P1 car, 1 = P2 car,
|
|
448
|
+
* 2..2+MAX_TRAFFIC-1 = traffic. Road art is tiles, not sprites, so the list
|
|
449
|
+
* never exceeds 2 + MAX_TRAFFIC planes. */
|
|
450
|
+
static const uint8_t spr_car[8] = {0x18,0x3C,0x5A,0x7E,0x3C,0x7E,0x5A,0x66};
|
|
451
|
+
static const uint8_t spr_traffic[8] = {0x66,0x5A,0x7E,0x3C,0x7E,0x5A,0x3C,0x18};
|
|
452
|
+
#define PAT_CAR 0
|
|
453
|
+
#define PAT_TRAFFIC 1
|
|
454
|
+
#define COL_P1 15 /* white */
|
|
455
|
+
#define COL_P2 13 /* light green */
|
|
456
|
+
#define COL_TRAFFIC 8 /* medium red */
|
|
457
|
+
|
|
458
|
+
/* ── HARDWARE IDIOM (load-bearing — reshape gameplay around this; see TROUBLESHOOTING) ──
|
|
459
|
+
* Sprite limits + the Y=208 terminator:
|
|
460
|
+
* - A sprite Y of 0xD0 (208) tells the TMS9918 to STOP SCANNING the
|
|
461
|
+
* attribute table — every higher-numbered plane vanishes, not just that
|
|
462
|
+
* one. (msx_clear_sprites parks ALL planes at 0xD0, which is fine at the
|
|
463
|
+
* END of the list.) To hide ONE sprite mid-list, park it OFFSCREEN at
|
|
464
|
+
* PARK_Y (192 = first line below the display) — never at 0xD0.
|
|
465
|
+
* (On MSX2's V9938 sprite mode 2 the terminator moves to 0xD8 and 0xD0
|
|
466
|
+
* is "just offscreen" — code that leans on that breaks on MSX1.)
|
|
467
|
+
* - Per scanline the TMS9918 draws only 4 sprites (V9938: 8). Traffic is
|
|
468
|
+
* spread across 4 lanes and four screen-Y bands, so a single scanline
|
|
469
|
+
* almost never carries more than 2-3 of our planes; if you raise
|
|
470
|
+
* MAX_TRAFFIC, watch for 4-on-a-line flicker. */
|
|
471
|
+
#define PARK_Y 192
|
|
472
|
+
|
|
473
|
+
static void push_sprites(void) {
|
|
474
|
+
uint8_t i, plane = 0;
|
|
475
|
+
uint8_t actors = (state == ST_PLAY);
|
|
476
|
+
/* P1 car — blink while invulnerable after a crash (skip on odd frames). */
|
|
477
|
+
msx_set_sprite(plane++, lane_x[car_lane[0]],
|
|
478
|
+
(actors && car_active[0] && !(invuln[0] & 2)) ? CAR_Y : PARK_Y,
|
|
479
|
+
PAT_CAR, COL_P1);
|
|
480
|
+
/* P2 car. */
|
|
481
|
+
msx_set_sprite(plane++, lane_x[car_lane[1]],
|
|
482
|
+
(actors && car_active[1] && !(invuln[1] & 2)) ? CAR_Y : PARK_Y,
|
|
483
|
+
PAT_CAR, COL_P2);
|
|
484
|
+
for (i = 0; i < MAX_TRAFFIC; i++)
|
|
485
|
+
msx_set_sprite(plane++, lane_x[traffic_lane[i]],
|
|
486
|
+
(actors && traffic_alive[i]) ? traffic_y[i] : PARK_Y,
|
|
487
|
+
PAT_TRAFFIC, COL_TRAFFIC);
|
|
149
488
|
}
|
|
150
489
|
|
|
151
|
-
|
|
490
|
+
/* ── GAME LOGIC (clay — reshape freely) — traffic pool (fixed slots) ── */
|
|
491
|
+
static void spawn_traffic(void) {
|
|
152
492
|
uint8_t i;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
493
|
+
for (i = 0; i < MAX_TRAFFIC; i++) {
|
|
494
|
+
if (!traffic_alive[i]) {
|
|
495
|
+
traffic_alive[i] = 1;
|
|
496
|
+
traffic_lane[i] = (uint8_t)(next_rand() & 3);
|
|
497
|
+
traffic_y[i] = SPAWN_Y;
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/* AABB, both boxes 8x8. */
|
|
504
|
+
static uint8_t hits(uint8_t ax, uint8_t ay, uint8_t bx, uint8_t by) {
|
|
505
|
+
uint8_t dx = (ax > bx) ? (uint8_t)(ax - bx) : (uint8_t)(bx - ax);
|
|
506
|
+
uint8_t dy = (ay > by) ? (uint8_t)(ay - by) : (uint8_t)(by - ay);
|
|
507
|
+
return (dx < 8) && (dy < 8);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/* ── GAME LOGIC (clay — reshape freely) — the screens ──────────────────────
|
|
511
|
+
* Title rows land across the play thirds — recolored for free by the thirds
|
|
512
|
+
* idiom. A clean name table behind the text. */
|
|
513
|
+
static void paint_title(void) {
|
|
514
|
+
uint8_t len = 0, col;
|
|
515
|
+
const char *p = GAME_TITLE;
|
|
516
|
+
while (*p++) len++;
|
|
517
|
+
col = (uint8_t)((32 - len) / 2);
|
|
518
|
+
clear_field();
|
|
519
|
+
draw_text(col, 6, GAME_TITLE);
|
|
520
|
+
draw_text(7, 11, "1P RACE - FIRE A");
|
|
521
|
+
draw_text(7, 13, "2P VERSUS - FIRE B");
|
|
522
|
+
draw_text(11, 16, "STEER L-R");
|
|
523
|
+
draw_text(11, 19, "BEST 0000"); /* the space blanks the cell between */
|
|
524
|
+
draw_num4(16, 19, best);
|
|
162
525
|
}
|
|
163
526
|
|
|
164
|
-
static void
|
|
527
|
+
static void paint_over(void) {
|
|
528
|
+
clear_field();
|
|
529
|
+
if (two_player) {
|
|
530
|
+
draw_text(11, 7, winner ? "P2 WINS" : "P1 WINS");
|
|
531
|
+
draw_text(8, 10, "RIVAL CRASHED OUT");
|
|
532
|
+
} else {
|
|
533
|
+
draw_text(11, 7, "WRECKED");
|
|
534
|
+
draw_text(11, 10, "DIST"); draw_num4(16, 10, dist);
|
|
535
|
+
draw_text(11, 13, "BEST"); draw_num4(16, 13, best);
|
|
536
|
+
}
|
|
537
|
+
draw_text(8, 17, "FIRE FOR TITLE");
|
|
538
|
+
prev_t1 = prev_t2 = 1; /* swallow a fire still held from play */
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/* ── GAME LOGIC (clay — reshape freely) — start a run ── */
|
|
542
|
+
static void start_game(uint8_t versus) {
|
|
165
543
|
uint8_t i;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
544
|
+
two_player = versus;
|
|
545
|
+
for (i = 0; i < MAX_TRAFFIC; i++) traffic_alive[i] = 0;
|
|
546
|
+
for (i = 0; i < 2; i++) { crashes_left[i] = START_LIVES; invuln[i] = 0; prev_dir[i] = 0; }
|
|
547
|
+
if (versus) {
|
|
548
|
+
car_active[0] = 1; car_active[1] = 1;
|
|
549
|
+
lane_min[0] = 0; lane_max[0] = 1; car_lane[0] = 0; /* P1: left half */
|
|
550
|
+
lane_min[1] = 2; lane_max[1] = 3; car_lane[1] = 3; /* P2: right half */
|
|
551
|
+
speed = SPEED_2P; /* one road, fixed shared speed */
|
|
552
|
+
} else {
|
|
553
|
+
car_active[0] = 1; car_active[1] = 0;
|
|
554
|
+
lane_min[0] = 0; lane_max[0] = 3; car_lane[0] = 1; /* whole road */
|
|
555
|
+
speed = 1;
|
|
556
|
+
}
|
|
557
|
+
dist = 0; dist_frac = 0;
|
|
558
|
+
spawn_timer = 0;
|
|
559
|
+
road_phase = 0;
|
|
560
|
+
prev_acc = 1;
|
|
561
|
+
paint_road();
|
|
562
|
+
restripe_road(road_phase);
|
|
563
|
+
draw_hud_labels();
|
|
564
|
+
draw_lives();
|
|
565
|
+
draw_dist();
|
|
566
|
+
sfx_start();
|
|
567
|
+
state = ST_PLAY;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/* ── GAME LOGIC (clay — reshape freely) — run over: result + record.
|
|
571
|
+
* Persistence choice: a 1P run banks its DISTANCE; the best is the stat a
|
|
572
|
+
* returning player chases. 2P matches never touch it (humans beating each
|
|
573
|
+
* other isn't a record). On THIS core the best is session-only RAM (no
|
|
574
|
+
* SAVE_RAM — see the file header); the Genesis/NES/SMS builds persist the
|
|
575
|
+
* identical best distance to cartridge SRAM. ── */
|
|
576
|
+
static void end_run(void) {
|
|
577
|
+
if (!two_player && dist > best) best = dist;
|
|
578
|
+
sfx_crash();
|
|
579
|
+
paint_over();
|
|
580
|
+
state = ST_OVER;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/* ── GAME LOGIC (clay — reshape freely) — a crash ── */
|
|
584
|
+
static void crash(uint8_t p) {
|
|
585
|
+
sfx_crash();
|
|
586
|
+
invuln[p] = 60; /* blink + no-collide grace */
|
|
587
|
+
if (!two_player) speed = 1; /* a wreck kills your momentum */
|
|
588
|
+
if (crashes_left[p] > 0) --crashes_left[p];
|
|
589
|
+
draw_lives();
|
|
590
|
+
if (crashes_left[p] == 0) {
|
|
591
|
+
winner = (uint8_t)(1 - p); /* versus: the OTHER player wins */
|
|
592
|
+
end_run();
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/* ── GAME LOGIC (clay — reshape freely) — per-player input ───────────────────
|
|
597
|
+
* P0 reads JOYSTICK PORT 1 (keyboard cursors fall back); P1 reads PORT 2.
|
|
598
|
+
* LEFT/RIGHT steer between lanes (edge-detected — a held stick must NOT
|
|
599
|
+
* machine-gun across the road). 1P only: UP/A accelerate, DOWN/B brake. */
|
|
600
|
+
static void update_player(uint8_t p) {
|
|
601
|
+
uint8_t dir, left, right;
|
|
602
|
+
if (p == 0) {
|
|
603
|
+
dir = msx_read_joystick(1);
|
|
604
|
+
if (dir == STICK_CENTER) dir = msx_read_joystick(0);
|
|
605
|
+
} else {
|
|
606
|
+
dir = msx_read_joystick(2);
|
|
607
|
+
}
|
|
608
|
+
left = (dir == STICK_LEFT || dir == STICK_UL || dir == STICK_DL);
|
|
609
|
+
right = (dir == STICK_RIGHT || dir == STICK_UR || dir == STICK_DR);
|
|
610
|
+
{
|
|
611
|
+
uint8_t pl = prev_dir[p];
|
|
612
|
+
uint8_t prev_left = (pl == STICK_LEFT || pl == STICK_UL || pl == STICK_DL);
|
|
613
|
+
uint8_t prev_right = (pl == STICK_RIGHT || pl == STICK_UR || pl == STICK_DR);
|
|
614
|
+
if (left && !prev_left && car_lane[p] > lane_min[p]) { --car_lane[p]; sfx_lane(); }
|
|
615
|
+
if (right && !prev_right && car_lane[p] < lane_max[p]) { ++car_lane[p]; sfx_lane(); }
|
|
616
|
+
}
|
|
617
|
+
prev_dir[p] = dir;
|
|
618
|
+
|
|
619
|
+
if (!two_player) { /* speed is shared — only 1P gets it */
|
|
620
|
+
uint8_t up = (dir == STICK_UP || dir == STICK_UL || dir == STICK_UR) || gttrig(1) || gttrig(0);
|
|
621
|
+
uint8_t down = (dir == STICK_DOWN || dir == STICK_DL || dir == STICK_DR);
|
|
622
|
+
uint8_t acc = (uint8_t)(up ? 1 : (down ? 2 : 0));
|
|
623
|
+
if (acc && acc != prev_acc) {
|
|
624
|
+
if (up && speed < 4) { ++speed; sfx_accel(); }
|
|
625
|
+
if (down && speed > 1) { --speed; sfx_brake(); }
|
|
172
626
|
}
|
|
627
|
+
prev_acc = acc;
|
|
173
628
|
}
|
|
629
|
+
if (invuln[p] > 0) --invuln[p];
|
|
174
630
|
}
|
|
175
631
|
|
|
176
632
|
void main(void) {
|
|
177
|
-
uint8_t i,
|
|
178
|
-
int16_t step;
|
|
633
|
+
uint8_t i, p, t1, t2;
|
|
179
634
|
|
|
635
|
+
/* ── HARDWARE IDIOM (load-bearing — reshape gameplay around this; see TROUBLESHOOTING) ──
|
|
636
|
+
* Init order: set the video mode FIRST (INIGRP also clears VRAM — any
|
|
637
|
+
* upload done before it is wiped), then tiles, then sprites. The crt0's
|
|
638
|
+
* INIT contract means main() must NEVER return — the BIOS has nothing
|
|
639
|
+
* sane to fall back to — hence the for(;;) below. */
|
|
180
640
|
msx_set_screen2();
|
|
181
641
|
msx_clear_sprites();
|
|
182
642
|
load_tiles();
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_PLAYER * 8), spr_player, 8);
|
|
187
|
-
msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_ENEMY * 8), spr_enemy, 8);
|
|
643
|
+
msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_CAR * 8), spr_car, 8);
|
|
644
|
+
msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_TRAFFIC * 8), spr_traffic, 8);
|
|
188
645
|
|
|
646
|
+
msx_music(0); /* the lib's demo loop also owns channel C —
|
|
647
|
+
* hand the channel to OUR tune table instead */
|
|
648
|
+
best = 0; /* session record (no SAVE_RAM on this core) */
|
|
189
649
|
rng = 0xACE1;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
650
|
+
music_step = music_timer = 0;
|
|
651
|
+
sfx_a_t = sfx_b_t = 0;
|
|
652
|
+
prev_t1 = prev_t2 = 1; /* swallow a held trigger across state changes */
|
|
653
|
+
two_player = 0;
|
|
654
|
+
car_lane[0] = car_lane[1] = 1;
|
|
655
|
+
state = ST_TITLE;
|
|
656
|
+
paint_title();
|
|
193
657
|
|
|
194
658
|
for (;;) {
|
|
195
659
|
vsync();
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (game_over_timer > 0) {
|
|
209
|
-
game_over_timer--;
|
|
210
|
-
if (game_over_timer == 0) reset_run();
|
|
211
|
-
prev_dir = dir;
|
|
212
|
-
if (blip) { blip--; if (!blip) msx_psg_off(0); }
|
|
660
|
+
music_tick();
|
|
661
|
+
sfx_tick();
|
|
662
|
+
|
|
663
|
+
if (state == ST_TITLE) {
|
|
664
|
+
/* ── GAME LOGIC (clay) — title: trig A = 1P race; trig B = 2P. */
|
|
665
|
+
t1 = (uint8_t)(gttrig(1) || gttrig(0));
|
|
666
|
+
t2 = (uint8_t)(gttrig(3) || gttrig(2));
|
|
667
|
+
if (t2 && !prev_t2) start_game(1);
|
|
668
|
+
else if (t1 && !prev_t1) start_game(0);
|
|
669
|
+
prev_t1 = t1; prev_t2 = t2;
|
|
670
|
+
push_sprites();
|
|
213
671
|
continue;
|
|
214
672
|
}
|
|
215
673
|
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
for (i = 0; i < MAX_OBSTACLES; i++) {
|
|
229
|
-
if (!obstacles[i].alive) continue;
|
|
230
|
-
obstacles[i].y = (uint8_t)(obstacles[i].y + step);
|
|
231
|
-
if (obstacles[i].y >= 184) obstacles[i].alive = 0;
|
|
674
|
+
if (state == ST_OVER) {
|
|
675
|
+
/* Freeze the final frame; any fire button returns to the title. */
|
|
676
|
+
t1 = (uint8_t)(gttrig(1) || gttrig(0) || gttrig(2));
|
|
677
|
+
if (t1 && !prev_t1) {
|
|
678
|
+
state = ST_TITLE;
|
|
679
|
+
msx_clear_sprites();
|
|
680
|
+
two_player = 0;
|
|
681
|
+
paint_title();
|
|
682
|
+
}
|
|
683
|
+
prev_t1 = t1; prev_t2 = t1;
|
|
684
|
+
push_sprites();
|
|
685
|
+
continue;
|
|
232
686
|
}
|
|
233
687
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
688
|
+
/* ── ST_PLAY — GAME LOGIC (clay) ────────────────────────────────────
|
|
689
|
+
* Both players (or just P1) update EVERY frame — a simultaneous
|
|
690
|
+
* versus race, not alternating turns. */
|
|
691
|
+
next_rand(); /* tick the noise source every play frame */
|
|
692
|
+
|
|
693
|
+
update_player(0);
|
|
694
|
+
if (two_player) update_player(1);
|
|
695
|
+
|
|
696
|
+
/* Advance the faked scroll by the current speed, then restripe only
|
|
697
|
+
* the moving cells (see the no-hw-scroll idiom). */
|
|
698
|
+
road_phase = (uint8_t)((road_phase + speed) & 7);
|
|
699
|
+
restripe_road(road_phase);
|
|
700
|
+
|
|
701
|
+
/* 1P distance: 1 unit per 16 scrolled pixels. */
|
|
702
|
+
if (!two_player) {
|
|
703
|
+
dist_frac = (uint8_t)(dist_frac + speed);
|
|
704
|
+
if (dist_frac >= 16) {
|
|
705
|
+
dist_frac = (uint8_t)(dist_frac - 16);
|
|
706
|
+
if (dist < 9999u) ++dist;
|
|
707
|
+
draw_dist();
|
|
242
708
|
}
|
|
243
709
|
}
|
|
244
710
|
|
|
245
|
-
|
|
711
|
+
/* Traffic flows DOWN the road at road speed (reads as slower cars you
|
|
712
|
+
* overtake); despawn past the bottom with a little pass tick. */
|
|
713
|
+
for (i = 0; i < MAX_TRAFFIC; i++) {
|
|
714
|
+
if (!traffic_alive[i]) continue;
|
|
715
|
+
if (traffic_y[i] >= (uint8_t)(DESPAWN_Y - speed)) {
|
|
716
|
+
traffic_alive[i] = 0;
|
|
717
|
+
sfx_pass();
|
|
718
|
+
} else {
|
|
719
|
+
traffic_y[i] = (uint8_t)(traffic_y[i] + speed);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (++spawn_timer >= SPAWN_PERIOD) { spawn_timer = 0; spawn_traffic(); }
|
|
723
|
+
|
|
724
|
+
/* Traffic ↔ cars. A just-wrecked car blinks + can't collide for 60f. */
|
|
725
|
+
for (i = 0; i < MAX_TRAFFIC; i++) {
|
|
726
|
+
if (!traffic_alive[i]) continue;
|
|
727
|
+
for (p = 0; p < 2; p++) {
|
|
728
|
+
if (!car_active[p] || invuln[p]) continue;
|
|
729
|
+
if (hits(lane_x[traffic_lane[i]], traffic_y[i], lane_x[car_lane[p]], CAR_Y)) {
|
|
730
|
+
traffic_alive[i] = 0;
|
|
731
|
+
crash(p);
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (state != ST_PLAY) break; /* a crash may have ended the run */
|
|
736
|
+
}
|
|
737
|
+
if (state != ST_PLAY) continue;
|
|
246
738
|
|
|
247
|
-
|
|
739
|
+
push_sprites();
|
|
248
740
|
}
|
|
249
741
|
}
|