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
package/src/mcp/tools/project.js
CHANGED
|
@@ -68,7 +68,16 @@ const TEMPLATES = {
|
|
|
68
68
|
linkerConfig: { presetSrc: "presets/nes/chr-ram-runtime.cfg", dst: "chr-ram-runtime.cfg" },
|
|
69
69
|
lang: "C (cc65)",
|
|
70
70
|
ext: ".nes",
|
|
71
|
-
describe: "
|
|
71
|
+
describe: "NOVA SENTRY — complete vertical shooter: title shell (1P/2P co-op select), shared-lives co-op, bullet/enemy pools, wave spawner, score + battery hi-score, music + SFX, sprite-0-hit split (fixed HUD over a drifting starfield).",
|
|
72
|
+
players: "1-2 (simultaneous co-op)",
|
|
73
|
+
sram: "battery hi-score at $6000 (hiscore_load/save; iNES battery bit in the crt0)",
|
|
74
|
+
mechanics: ["projectile pools", "wave spawner", "AABB collision", "shared-lives co-op", "title/play/game-over state machine"],
|
|
75
|
+
techniques: [
|
|
76
|
+
"sprite-0-hit split scroll (fixed HUD over scrolling field)",
|
|
77
|
+
"vblank-budget VRAM queue (asm drain in the crt0 NMI)",
|
|
78
|
+
"battery SRAM hi-score (magic + checksum)",
|
|
79
|
+
"CHR-RAM tile upload + 1bpp font",
|
|
80
|
+
],
|
|
72
81
|
},
|
|
73
82
|
platformer: {
|
|
74
83
|
main: "templates/platformer.c",
|
|
@@ -80,7 +89,17 @@ const TEMPLATES = {
|
|
|
80
89
|
linkerConfig: { presetSrc: "presets/nes/chr-ram-runtime.cfg", dst: "chr-ram-runtime.cfg" },
|
|
81
90
|
lang: "C (cc65)",
|
|
82
91
|
ext: ".nes",
|
|
83
|
-
describe: "
|
|
92
|
+
describe: "LEDGE LEAPER — side-scrolling platformer: gravity + Q4.4 sub-pixel jump physics, one-way platforms, pits and spikes, coins + distance scoring, battery hi-score. 2P is classic alternating turns (P2 on controller 2) with per-player score and lives. Sprite-0-hit split: fixed HUD over a seamlessly looping scrolling level.",
|
|
93
|
+
players: "1-2 (alternating turns; P2 on controller 2)",
|
|
94
|
+
sram: "battery hi-score (hiscore_load/save)",
|
|
95
|
+
mechanics: ["gravity-jump physics (Q4.4 fixed point)", "one-way platform collision via column map", "horizontal scrolling with camera wall", "pits + spike hazards", "coin pickup + distance scoring", "alternating 2P turns with per-player lives"],
|
|
96
|
+
techniques: [
|
|
97
|
+
"sprite-0-hit split scroll (two-phase PPUSTATUS poll)",
|
|
98
|
+
"dual-nametable seamless 256px level loop",
|
|
99
|
+
"world-anchored sprite objects",
|
|
100
|
+
"queued VRAM HUD updates",
|
|
101
|
+
"battery SRAM hi-score",
|
|
102
|
+
],
|
|
84
103
|
},
|
|
85
104
|
puzzle: {
|
|
86
105
|
main: "templates/puzzle.c",
|
|
@@ -92,7 +111,17 @@ const TEMPLATES = {
|
|
|
92
111
|
linkerConfig: { presetSrc: "presets/nes/chr-ram-runtime.cfg", dst: "chr-ram-runtime.cfg" },
|
|
93
112
|
lang: "C (cc65)",
|
|
94
113
|
ext: ".nes",
|
|
95
|
-
describe: "
|
|
114
|
+
describe: "GEM DUEL — falling-gem match-3: 1P marathon with levels and cascade chains; 2P simultaneous split-board versus where chains send garbage rows to the opponent. Battery hi-score.",
|
|
115
|
+
players: "1-2 (2P = simultaneous versus, split boards)",
|
|
116
|
+
sram: "battery hi-score (hiscore_load/save)",
|
|
117
|
+
mechanics: ["falling-piece control", "match-3 in 4 directions", "cascade chains with multipliers", "garbage attack rows", "soft drop + levels", "split-board versus"],
|
|
118
|
+
techniques: [
|
|
119
|
+
"vblank-budgeted board repaint (dirty-row bitmask, 1 row/frame)",
|
|
120
|
+
"attribute-table palette regions (2-aligned wells)",
|
|
121
|
+
"absolute-RAM arrays in the $0500 user scratch page",
|
|
122
|
+
"battery SRAM hi-score",
|
|
123
|
+
"stage-then-wait OAM order",
|
|
124
|
+
],
|
|
96
125
|
},
|
|
97
126
|
sports: {
|
|
98
127
|
main: "templates/sports.c",
|
|
@@ -104,7 +133,17 @@ const TEMPLATES = {
|
|
|
104
133
|
linkerConfig: { presetSrc: "presets/nes/chr-ram-runtime.cfg", dst: "chr-ram-runtime.cfg" },
|
|
105
134
|
lang: "C (cc65)",
|
|
106
135
|
ext: ".nes",
|
|
107
|
-
describe: "
|
|
136
|
+
describe: "COURT CLASH — head-to-head court game: 1P vs a beatable CPU or 2P simultaneous versus, first to 5, battery-backed best CPU win streak.",
|
|
137
|
+
players: "1-2 (1P vs CPU / 2P simultaneous versus)",
|
|
138
|
+
sram: "longest 1P win streak vs the CPU (hiscore_load/save)",
|
|
139
|
+
mechanics: ["versus match flow (first-to-5, result screen)", "CPU opponent (speed-capped ball chase)", "2P simultaneous input (both ports)", "edge-hit ball deflection with random spin", "serve pause + alternating serve angle"],
|
|
140
|
+
techniques: [
|
|
141
|
+
"queued HUD text (text_draw_u16) during rendering",
|
|
142
|
+
"PPU-off court/title paint (vram_unsafe_set/text_draw_unsafe)",
|
|
143
|
+
"stage-then-wait OAM order with deterministic sprite slots",
|
|
144
|
+
"xorshift16 PRNG to break deterministic-rally limit cycles",
|
|
145
|
+
"battery PRG-RAM record via hiscore_save",
|
|
146
|
+
],
|
|
108
147
|
},
|
|
109
148
|
racing: {
|
|
110
149
|
main: "templates/racing.c",
|
|
@@ -116,7 +155,18 @@ const TEMPLATES = {
|
|
|
116
155
|
linkerConfig: { presetSrc: "presets/nes/chr-ram-runtime.cfg", dst: "chr-ram-runtime.cfg" },
|
|
117
156
|
lang: "C (cc65)",
|
|
118
157
|
ext: ".nes",
|
|
119
|
-
describe: "
|
|
158
|
+
describe: "THROTTLE FEUD — top-down vertically-scrolling road racer: scroll_y BG scroll with the wrap-at-240 idiom, streamed roadside scenery via queued tile writes, sprite-digit HUD. 1P: 4 lanes, A/B speed, best distance to battery SRAM. 2P: simultaneous split-lane versus (solid divider, first to 3 crashes loses).",
|
|
159
|
+
players: "1-2 (2P = simultaneous versus, split lanes)",
|
|
160
|
+
sram: "best 1P distance (uint16, 1 unit = 16 scrolled px; hiscore_load/save)",
|
|
161
|
+
mechanics: ["lane steering", "speed control (1P)", "traffic dodging", "crash lives + invulnerability blink", "distance checkpoints", "split-lane versus"],
|
|
162
|
+
techniques: [
|
|
163
|
+
"vertical BG scroll with 240-wrap",
|
|
164
|
+
"streaming-row scenery via queued tile writes",
|
|
165
|
+
"sprite-based HUD (8-per-scanline budgeting)",
|
|
166
|
+
"battery SRAM hi-score",
|
|
167
|
+
"PPU-off full repaint screens",
|
|
168
|
+
"xorshift16 PRNG",
|
|
169
|
+
],
|
|
120
170
|
},
|
|
121
171
|
/* R44 (2026-05-26): bundled-driver music demo. FamiTone2 engine +
|
|
122
172
|
* cc65 bridge + example track, all ship as source under lib/asm. */
|
|
@@ -188,7 +238,18 @@ const TEMPLATES = {
|
|
|
188
238
|
],
|
|
189
239
|
lang: "C (SDCC sm83)",
|
|
190
240
|
ext: ".gb",
|
|
191
|
-
describe: "
|
|
241
|
+
describe: "METEOR MILITIA — complete GB vertical shooter: press-start title shell with battery-persistent hi-score (MBC1+RAM+BATTERY declared in the crt0 header, $0A enable sequence, magic+checksum record, survives power cycles), and the GB signature — a WINDOW-layer fixed HUD (WX=7/WY=128, LCDC bit 5) over an SCY-scrolling starfield, no raster tricks. Wave spawner, AABB collisions, APU tune + SFX, divide-free painters (the sm83 has no divider). 1P by design: link-cable multiplayer can't be emulated single-instance (stated honestly in-file).",
|
|
242
|
+
players: "1 (one controller; link cable unemulatable single-instance)",
|
|
243
|
+
sram: "MBC1 cart RAM via the save_ram region (8KB) — crt0-declared battery cart, checksummed record, verified across hardReset",
|
|
244
|
+
mechanics: ["projectile pools", "wave spawner", "AABB collision", "lives + respawn knockback", "battery-persistent hi-score", "title/play/game-over state machine"],
|
|
245
|
+
techniques: [
|
|
246
|
+
"window-layer fixed HUD (WX+7 quirk, bottom-strip placement)",
|
|
247
|
+
"MBC1 $0A RAM-enable sequence",
|
|
248
|
+
"shadow OAM + HRAM OAM-DMA stub",
|
|
249
|
+
"one-item-per-vblank VRAM commit queue",
|
|
250
|
+
"HALT-driven vblank wait",
|
|
251
|
+
"divide-free pattern + decimal math",
|
|
252
|
+
],
|
|
192
253
|
},
|
|
193
254
|
platformer: {
|
|
194
255
|
main: "templates/platformer.c",
|
|
@@ -201,7 +262,18 @@ const TEMPLATES = {
|
|
|
201
262
|
],
|
|
202
263
|
lang: "C (SDCC sm83)",
|
|
203
264
|
ext: ".gb",
|
|
204
|
-
describe: "
|
|
265
|
+
describe: "GULLY GALLOP — complete GB side-scrolling platformer: press-start title shell with battery-persistent hi-score (MBC1+RAM+BATTERY crt0 header, $0A enable sequence, magic+checksum record, survives power cycles), and the GB signature WINDOW-layer fixed HUD (WX=7/WY=128) over an SCX-scrolled, seamlessly looping 256-px column-map level. Gravity + Q4.4 sub-pixel jump physics, one-way platforms, lethal pits, drifting spikes, coins + distance scoring, one-way runner camera, APU tune + SFX, divide-free painters. 1P by design: link-cable multiplayer can't be emulated single-instance (stated honestly in-file).",
|
|
266
|
+
players: "1 (one controller; link cable unemulatable single-instance)",
|
|
267
|
+
sram: "MBC1 cart RAM via the save_ram region (8KB) — crt0-declared battery cart, checksummed record, verified across hardReset",
|
|
268
|
+
mechanics: ["gravity + Q4.4 jump physics", "one-way platforms (6-px landing window)", "pits + spikes + coins", "distance + coin scoring", "one-way scroll-wall camera", "lives + respawn breather", "battery-persistent hi-score"],
|
|
269
|
+
techniques: [
|
|
270
|
+
"window-layer fixed HUD (WX+7 quirk, bottom strip)",
|
|
271
|
+
"seamless uint8 SCX wrap over a 32-column level map",
|
|
272
|
+
"MBC1 $0A RAM-enable sequence",
|
|
273
|
+
"shadow OAM + HRAM OAM-DMA stub",
|
|
274
|
+
"one-item-per-vblank VRAM commit queue (wrap-aware text)",
|
|
275
|
+
"divide-free pattern + decimal math",
|
|
276
|
+
],
|
|
205
277
|
},
|
|
206
278
|
puzzle: {
|
|
207
279
|
main: "templates/puzzle.c",
|
|
@@ -214,7 +286,17 @@ const TEMPLATES = {
|
|
|
214
286
|
],
|
|
215
287
|
lang: "C (SDCC sm83)",
|
|
216
288
|
ext: ".gb",
|
|
217
|
-
describe: "
|
|
289
|
+
describe: "SHALE WELL — falling-stone match-3 to the full contract (the monochrome DMG take on a jewel matcher): an 8x15 well, five stone KINDS told apart by 2bpp TILE SHAPE through one DMG BGP palette (stripe/checker/ring/brick/diamond — the honest DMG answer to the GBC's six colors), move/cycle/soft-drop/hard-drop, 3+ clears in all 4 directions, gravity cascades chain for bonus, magic stone every 18th piece, levels speed up. 1P marathon (link-cable 2P unemulatable single-instance — honest in-file). Locked well rides the vblank COLLECT/FLUSH queue with an idle scrub; window-layer HUD; battery hi-score (MBC1+RAM+BATTERY, verified across power cycles); APU melody + SFX. Board arrays pinned via __at($C200) so it builds with the default recipe.",
|
|
290
|
+
players: "1 (handheld — link-cable 2P not emulatable single-instance)",
|
|
291
|
+
sram: "MBC1+RAM+BATTERY, 8KB at $A000 ($0A-gated, magic+checksum), verified across hardReset",
|
|
292
|
+
mechanics: ["falling-trio control", "match-3 in 4 directions", "gravity + cascade chains", "magic-stone target clear", "levels", "battery hi-score"],
|
|
293
|
+
techniques: [
|
|
294
|
+
"DMG tile-shape stone kinds (one BGP palette, no CGB regs)",
|
|
295
|
+
"vblank COLLECT/FLUSH queue + idle scrub",
|
|
296
|
+
"window-layer HUD",
|
|
297
|
+
"__at() WRAM pinning past the shadow-OAM page (default-recipe build)",
|
|
298
|
+
"battery SRAM save ($0A enable dance)",
|
|
299
|
+
],
|
|
218
300
|
},
|
|
219
301
|
sports: {
|
|
220
302
|
main: "templates/sports.c",
|
|
@@ -227,7 +309,18 @@ const TEMPLATES = {
|
|
|
227
309
|
],
|
|
228
310
|
lang: "C (SDCC sm83)",
|
|
229
311
|
ext: ".gb",
|
|
230
|
-
describe: "
|
|
312
|
+
describe: "CAROM COAST — head-to-head court game to the full contract (the monochrome DMG take on a versus paddle game): press-start title, 1P vs a beatable chase-AI CPU, first-to-5 match flow into a result screen, GB APU ch1 melody + ch2 SFX. The ball 'caroms' — rail ricochets + edge-deflection where it strikes your paddle; a +/-1 PRNG spin guarantees an idle rally ENDS (no infinite limit cycle). Paddles told apart by SHADE on the 4-grey DMG (you black OBP0, CPU lighter OBP1). Window-layer fixed HUD; score/record/result-text ride the vblank COMMIT queue (<=5 cells/frame — a full line dropped in one batch). Longest 1P win streak persists to battery SRAM (MBC1+RAM+BATTERY, magic+checksum, verified across power cycles). 1P by design — link-cable 2P unemulatable single-instance (honest in-file).",
|
|
313
|
+
players: "1 (1P vs a beatable CPU — no link-cable 2P single-instance)",
|
|
314
|
+
sram: "longest 1P win streak vs the CPU (MBC1+RAM+BATTERY, magic+checksum, verified across hardReset)",
|
|
315
|
+
mechanics: ["versus match flow (first-to-5, result screen)", "beatable chase-AI CPU (speed-capped ball chase)", "edge-hit deflection + rail caroms with PRNG spin", "serve pause + alternating serve angle", "win-streak record that dies on a loss"],
|
|
316
|
+
techniques: [
|
|
317
|
+
"window-layer fixed HUD",
|
|
318
|
+
"vblank COMMIT queue for score/record/result text (<=5 cells/frame)",
|
|
319
|
+
"shadow-OAM + HRAM OAM-DMA stub, paddles by OBP0/OBP1 shade",
|
|
320
|
+
"xorshift16 PRNG to break deterministic-rally limit cycles",
|
|
321
|
+
"battery SRAM record via the $0A MBC1 RAM-gate dance",
|
|
322
|
+
"LCD-off court/title paint",
|
|
323
|
+
],
|
|
231
324
|
},
|
|
232
325
|
racing: {
|
|
233
326
|
main: "templates/racing.c",
|
|
@@ -240,7 +333,17 @@ const TEMPLATES = {
|
|
|
240
333
|
],
|
|
241
334
|
lang: "C (SDCC sm83)",
|
|
242
335
|
ext: ".gb",
|
|
243
|
-
describe: "
|
|
336
|
+
describe: "TARMAC TILT — top-down vertical road racer to the full contract: press-start title (honest no-2P — link cable unemulatable single-instance), the road scrolls via SCY into a 256-px map (seamless uint8 wrap, no helper — contrast taught vs NES 240 / SMS 224 garbage-row / Genesis hardware-masked plane), four lanes, A/UP accelerate + B/DOWN brake (speed 1-4), LEFT/RIGHT lane tilt, overtaking traffic pool, 3-crash lives with invuln blink, best DISTANCE to battery SRAM (magic+checksum, verified across power cycles), window-layer HUD, GB APU music + SFX, divide-free digit math.",
|
|
337
|
+
players: "1 (handheld — link-cable 2P not emulatable single-instance)",
|
|
338
|
+
sram: "MBC1 cart RAM via the save_ram region (8KB) — crt0-declared battery cart, best-distance magic+checksum record, verified across hardReset",
|
|
339
|
+
mechanics: ["lane steering", "speed control 1-4", "overtaking traffic pool", "crash lives + invuln blink", "best-distance persistence"],
|
|
340
|
+
techniques: [
|
|
341
|
+
"SCY vertical road scroll (256-px seamless uint8 wrap)",
|
|
342
|
+
"window-layer fixed HUD",
|
|
343
|
+
"one-item-per-vblank VRAM commit queue",
|
|
344
|
+
"battery SRAM best-distance ($0A enable dance)",
|
|
345
|
+
"divide-free digit math",
|
|
346
|
+
],
|
|
244
347
|
},
|
|
245
348
|
/* R45 — hUGEDriver music demo. Ships a compact SDCC-native music
|
|
246
349
|
* driver with the upstream hUGEDriver function surface plus a
|
|
@@ -282,11 +385,81 @@ const TEMPLATES = {
|
|
|
282
385
|
sprite_move: mk("sprite_move", "Joypad-controlled 16x16 sprite over a tiled background. d-pad moves the sprite; verified visible + responsive. Build up an action game from here."),
|
|
283
386
|
music_sfx: mk("music_sfx", "HuC6280 PSG demo: a looping melody plus a button-fired SFX. Shows psg_tone/psg_off across the PSG's wavetable channels."),
|
|
284
387
|
catch_game: mk("catch_game", "A complete tiny game: a paddle catches a falling object with the d-pad; full game loop with waitvsync(), two sprites, collision, scoring."),
|
|
285
|
-
shmup:
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
388
|
+
shmup: {
|
|
389
|
+
...mk("shmup", "ZENITH BARRAGE — complete PCE vertical shooter: title shell with in-session hi-score (a bare HuCard can't save — BRAM is peripheral-only; the bank-$F7 TAM/$1807-unlock dance is documented in-file as the real-hardware path), and the PCE signature — a 64x32 boss built from exactly TWO 32x32 SATB entries moving as one unit. Wave spawner, AABB collisions, 3-song PSG music + SFX, banded twinkling starfield. 1P by design: geargrafx ships TurboTap disabled, so port-2 input cannot reach the game (stated honestly in-file)."),
|
|
390
|
+
players: "1 (stock PCE has one pad port; TurboTap exists in-core but disabled — future host core-option round)",
|
|
391
|
+
sram: "none — a bare HuCard cannot save; BRAM (bank $F7) is PERIPHERAL-ONLY on real hardware (CD-ROM² unit / Tennokoe Bank / Memory Base 128). In-session hi-score only, like the 2600/Lynx; the BRAM mapping + write-lock are documented in-file as the real-hardware path.",
|
|
392
|
+
mechanics: ["projectile pools", "wave spawner", "AABB collision", "multi-sprite boss with HP/phases", "lives + mercy invulnerability", "in-session hi-score (HuCards can't save)", "title/play/game-over state machine"],
|
|
393
|
+
techniques: [
|
|
394
|
+
"HuC6270 large sprites (32x32 CGX/CGY, 4-aligned patterns)",
|
|
395
|
+
"two-entry composite boss",
|
|
396
|
+
"shadow SATB + R19 vblank DMA",
|
|
397
|
+
"TAM bank-mapping thunks from C",
|
|
398
|
+
"BRAM $1807 write-unlock",
|
|
399
|
+
"BAT glyph font + partial HUD repaint",
|
|
400
|
+
"PSG divider-table music",
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
platformer: {
|
|
404
|
+
...mk("platformer", "GLADE DASH — complete PC Engine side-scrolling platformer: title/1P/2P-alternating-turns shell, gravity + Q4.4 sub-pixel jump physics, one-way slabs, lethal pits + spikes, coins + distance scoring, in-session hi-score (a bare HuCard can't save — BRAM is peripheral-only; the bank-$F7 TAM/$1807-unlock dance is documented in-file as the real-hardware path), 3-song PSG music + SFX. The PCE signature on top: hardware BG scroll via the BXR register with column-streaming for a 768px looping world, plus a 32x32 large multi-cell hero (one SATB entry, 4-aligned pattern) with a walk cycle. Real 2P alternating turns — the host enables the TurboTap so port-1 input reaches player 2 (verified). HONEST CAVEAT: no hardware window/raster split in the minimal lib, so the HUD is a painted band that scrolls with the world but reads continuously (a raster-IRQ BXR reset can make it truly fixed — TROUBLESHOOTING note)."),
|
|
405
|
+
players: "1-2 (2P alternating turns; P2 via TurboTap port 1, host-enabled — verified port-1 reaches P2)",
|
|
406
|
+
sram: "none — a bare HuCard cannot save; BRAM (bank $F7) is PERIPHERAL-ONLY on real hardware (CD-ROM² unit / Tennokoe Bank / Memory Base 128). In-session hi-score only, like the 2600/Lynx; the BRAM mapping + write-lock are documented in-file as the real-hardware path.",
|
|
407
|
+
mechanics: ["gravity + sub-pixel jump physics", "one-way platforms", "pits + spikes", "coins + distance scoring", "one-way scroll-wall camera", "alternating 2P turns (per-player score/lives)", "in-session hi-score (HuCards can't save)"],
|
|
408
|
+
techniques: [
|
|
409
|
+
"hardware BG scroll (VDC BXR) + column streaming",
|
|
410
|
+
"32x32 large hero sprite (CGX/CGY, 4-aligned pattern)",
|
|
411
|
+
"2-frame walk-cycle VRAM swap",
|
|
412
|
+
"shadow SATB + R19 vblank DMA",
|
|
413
|
+
"TAM bank-mapping thunks from C",
|
|
414
|
+
"BRAM $1807 write-unlock",
|
|
415
|
+
"TurboTap 2P via joy_read(JOY_2)",
|
|
416
|
+
],
|
|
417
|
+
},
|
|
418
|
+
puzzle: {
|
|
419
|
+
...mk("puzzle", "TUMBLE TIDE — complete PC Engine falling-trio versus puzzle: title/1P-marathon/2P-simultaneous-versus shell, falling-trio match-3 (4-direction clears, gravity, cascade chains, levels), in-session hi-score (a bare HuCard can't save — BRAM is peripheral-only; the bank-$F7 TAM/$1807-unlock dance is documented in-file as the real-hardware path), PSG music + SFX. The board is the VDC BAT tilemap with whole-board repaints — the inverse of the NES vblank-queue famine (taught in-file). Real 2P simultaneous versus with garbage attacks: a cascade chain floods garbage rows into your rival's well; P2 on the TurboTap (host-enabled port 1, verified). 6x12 wells, split board in versus."),
|
|
420
|
+
players: "1-2 (2P simultaneous versus; P2 via TurboTap port 1, host-enabled — verified port-1 reaches P2)",
|
|
421
|
+
sram: "none — a bare HuCard cannot save; BRAM (bank $F7) is PERIPHERAL-ONLY on real hardware (CD-ROM² unit / Tennokoe Bank / Memory Base 128). In-session hi-score only, like the 2600/Lynx; the BRAM mapping + write-lock are documented in-file as the real-hardware path.",
|
|
422
|
+
mechanics: ["falling-trio match-3", "4-direction line clears", "gravity + cascade chains (multiplied score)", "levels (1P speed-up)", "2P simultaneous versus split board", "garbage-row attacks", "in-session hi-score (HuCards can't save)"],
|
|
423
|
+
techniques: [
|
|
424
|
+
"whole-board VDC BAT repaint (vs NES vblank-queue famine)",
|
|
425
|
+
"BAT glyph font + HUD",
|
|
426
|
+
"shadow SATB + R19 vblank DMA (3 trio sprites/player)",
|
|
427
|
+
"per-colour BG sub-palettes for one cell-tile shape",
|
|
428
|
+
"TAM bank-mapping thunks from C",
|
|
429
|
+
"BRAM $1807 write-unlock",
|
|
430
|
+
"TurboTap 2P via joy_read(JOY_2)",
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
sports: {
|
|
434
|
+
...mk("sports", "SPIKE SURGE — complete PC Engine versus court game (Pong lineage): title/1P-vs-CPU/2P-simultaneous-versus shell, first-to-5 match flow with a result screen, beatable chase-AI CPU, PRNG rally spin so idle matches provably END, in-session best-win-streak record (a bare HuCard can't save — BRAM is peripheral-only; documented in-file), 3-song PSG music + SFX. Real 2P simultaneous versus: P2 on the TurboTap (host-enabled port 1, verified). Court is the VDC BAT tilemap; paddles + ball are SATB sprites."),
|
|
435
|
+
players: "1-2 (1P vs beatable CPU, or 2P simultaneous versus; P2 via TurboTap port 1, host-enabled — verified port-1 reaches P2)",
|
|
436
|
+
sram: "none — a bare HuCard cannot save; BRAM (bank $F7) is PERIPHERAL-ONLY on real hardware (CD-ROM² unit / Tennokoe Bank / Memory Base 128). In-session best-win-streak only, like the 2600/Lynx; the BRAM mapping + write-lock are documented in-file as the real-hardware path.",
|
|
437
|
+
mechanics: ["paddle/ball court physics", "edge-deflection parry angle", "1P beatable chase-AI CPU", "2P simultaneous versus", "first-to-5 match + result screen", "PRNG rally spin (idle matches end)", "in-session win-streak record (HuCards can't save)"],
|
|
438
|
+
techniques: [
|
|
439
|
+
"whole-screen VDC BAT paint (court)",
|
|
440
|
+
"BAT glyph font + HUD band",
|
|
441
|
+
"shadow SATB + R19 vblank DMA (7 sprites)",
|
|
442
|
+
"TAM bank-mapping thunks from C",
|
|
443
|
+
"BRAM $1807 write-unlock",
|
|
444
|
+
"PSG divider-table 2-channel music",
|
|
445
|
+
"TurboTap 2P via joy_read(JOY_2)",
|
|
446
|
+
],
|
|
447
|
+
},
|
|
448
|
+
racing: {
|
|
449
|
+
...mk("racing", "PINION PURSUIT — complete PC Engine top-down road racer: title/1P-race/2P-simultaneous-split-lane-versus shell, hardware BG Y-scroll road via the VDC BYR register with per-row scenery streaming (no NES 240-wrap / SMS 224-wrap — the VDC masks BYR to the 256px BAT in hardware), 1P speed control + an in-session best distance (a bare HuCard can't save — BRAM is peripheral-only; the bank-$F7 TAM/$1807-unlock dance is documented in-file as the real-hardware path), 2-channel PSG music + SFX. Real 2P simultaneous versus: P2 on the TurboTap (host-enabled port 1, verified). HONEST CAVEAT: no hardware window/raster split in the minimal lib, so the HUD is a SPRITE HUD (screen-space digits) and the title/result screens use a static road backdrop — only the play state scrolls."),
|
|
450
|
+
players: "1-2 (1P endless race, or 2P simultaneous split-lane versus; P2 via TurboTap port 1, host-enabled — verified port-1 reaches P2)",
|
|
451
|
+
sram: "none — a bare HuCard cannot save; BRAM (bank $F7) is PERIPHERAL-ONLY on real hardware (CD-ROM² unit / Tennokoe Bank / Memory Base 128). In-session best-distance only, like the 2600/Lynx; the BRAM mapping + write-lock are documented in-file as the real-hardware path.",
|
|
452
|
+
mechanics: ["lane steering", "speed control (1P)", "traffic pool + AABB", "crash/lives", "best-distance scoring", "2P split-lane versus", "in-session best distance (HuCards can't save)"],
|
|
453
|
+
techniques: [
|
|
454
|
+
"hardware BG Y-scroll (VDC BYR) + per-row streaming",
|
|
455
|
+
"sprite HUD digits (screen-space over a scrolling road)",
|
|
456
|
+
"shadow SATB + R19 vblank DMA",
|
|
457
|
+
"TAM bank-mapping thunks from C",
|
|
458
|
+
"BRAM $1807 write-unlock",
|
|
459
|
+
"PSG divider-table 2-channel music",
|
|
460
|
+
"TurboTap 2P via joy_read(JOY_2)",
|
|
461
|
+
],
|
|
462
|
+
},
|
|
290
463
|
};
|
|
291
464
|
})(),
|
|
292
465
|
|
|
@@ -303,11 +476,74 @@ const TEMPLATES = {
|
|
|
303
476
|
sprite_move: mk("sprite_move", "Joystick-controlled sprite on a screen-2 background. d-pad moves the sprite; verified visible + responsive. The base for any action game."),
|
|
304
477
|
music_sfx: mk("music_sfx", "AY-3-8910 PSG demo: a looping melody on channel A plus a trigger-fired SFX on channel C, with an on-screen indicator."),
|
|
305
478
|
catch_game: mk("catch_game", "A complete tiny game: a paddle catches falling fruit with the joystick; full game loop with vblank sync, two sprites, collision, scoring."),
|
|
306
|
-
shmup:
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
479
|
+
shmup: {
|
|
480
|
+
...mk("shmup", "NEBULA WARDEN — complete MSX vertical shooter (screen 2): title shell with 1P/2P select and session hi-score, simultaneous 2-ship co-op (P2 = joystick port 2), shared-lives arcade scoring, PSG tune-table music + noise SFX, and the MSX signature — screen-2 per-row color (three independent color thirds: depth-banded starfield, HUD band, an 8-color gradient inside one tile). Hi-score is in-session only (the bundled bluemsx build exposes no SAVE_RAM — stated honestly in-file)."),
|
|
481
|
+
players: "1-2 (simultaneous co-op)",
|
|
482
|
+
sram: "none — core exposes no SAVE_RAM region (in-session hi-score; ASCII8-SRAM mapper exists in-core but unsurfaced; future core round)",
|
|
483
|
+
mechanics: ["projectile pools", "wave spawner", "AABB collision", "2P simultaneous co-op (shared lives)", "session hi-score", "title/play/game-over state machine"],
|
|
484
|
+
techniques: [
|
|
485
|
+
"screen-2 per-row color (3 color thirds + per-8x1-row color bytes)",
|
|
486
|
+
"single-tile 8-color gradient",
|
|
487
|
+
"interrupt-free vsync via VDP S#0 poll",
|
|
488
|
+
"sprite Y=208 terminator + offscreen parking",
|
|
489
|
+
"AY-3-8910 noise SFX + per-frame tune-table music",
|
|
490
|
+
"dual joystick ports via GTSTCK/GTTRIG",
|
|
491
|
+
],
|
|
492
|
+
},
|
|
493
|
+
platformer: {
|
|
494
|
+
...mk("platformer", "MESA HOPPER — complete MSX side-scrolling platformer (screen 2): title shell with 1P / 2P-alternating-turns select (P2 on joystick port 2, per-player score + lives) and session hi-score, gravity + Q4.4 jump physics, one-way platforms, lethal pits, patrolling spikes, coin + traversal scoring, PSG tune-table music + SFX, and the MSX signature — screen-2 per-row color (3 color thirds: depth-banded fixed-screen level, HUD band, one-tile horizon gradient). Fixed single-screen arena because screen 2 has no hardware scroll (stated in-file). Hi-score is in-session only (bundled bluemsx build exposes no SAVE_RAM — stated honestly in-file)."),
|
|
495
|
+
players: "1-2 (alternating turns, P2 on joystick port 2)",
|
|
496
|
+
sram: "none — core exposes no SAVE_RAM region (in-session hi-score)",
|
|
497
|
+
mechanics: ["gravity + Q4.4 jump", "one-way platforms", "pits + patrolling spikes", "coin + traversal scoring", "2P alternating turns (per-player score/lives)", "session hi-score", "title/play/game-over state machine"],
|
|
498
|
+
techniques: [
|
|
499
|
+
"screen-2 per-row color (3 color thirds + per-8x1-row color bytes)",
|
|
500
|
+
"single-tile 8-color horizon gradient",
|
|
501
|
+
"fixed-screen level (no screen-2 hardware scroll)",
|
|
502
|
+
"interrupt-free vsync via VDP S#0 poll",
|
|
503
|
+
"sprite Y=208 terminator + offscreen parking",
|
|
504
|
+
"dual joystick ports via GTSTCK/GTTRIG",
|
|
505
|
+
],
|
|
506
|
+
},
|
|
507
|
+
puzzle: {
|
|
508
|
+
...mk("puzzle", "STOKE STACK — complete MSX falling-trio match-3 (screen 2): title shell with 1P-marathon / 2P-simultaneous-versus select (P2 = joystick port 2) and session hi-score, levels that speed the fall, cascade-chain scoring, 2P garbage attacks, PSG tune-table music + SFX, and the MSX signature — screen-2 per-row color (gem-colour-per-third one-tile trick + a one-tile ember gradient seam). Hi-score is in-session only (bundled bluemsx build exposes no SAVE_RAM — stated honestly in-file)."),
|
|
509
|
+
players: "1-2 (simultaneous versus, P2 on joystick port 2)",
|
|
510
|
+
sram: "none — core exposes no SAVE_RAM region (in-session hi-score)",
|
|
511
|
+
mechanics: ["falling-trio match-3", "4-direction runs", "cascade chains", "levels (1P speed-up)", "2P versus garbage rows", "session hi-score", "title/play/game-over state machine"],
|
|
512
|
+
techniques: [
|
|
513
|
+
"screen-2 per-row color (3 thirds + per-8x1-row bytes)",
|
|
514
|
+
"one-tile three-colour gem (colour-per-third)",
|
|
515
|
+
"single-tile ember gradient seam",
|
|
516
|
+
"interrupt-free vsync via VDP S#0 poll",
|
|
517
|
+
"sprite Y=208 terminator + offscreen parking",
|
|
518
|
+
"dual joystick ports via GTSTCK/GTTRIG",
|
|
519
|
+
],
|
|
520
|
+
},
|
|
521
|
+
sports: {
|
|
522
|
+
...mk("sports", "SPARK SWAT — complete MSX head-to-head court sports (screen 2): title shell with 1P-vs-beatable-CPU / 2P-simultaneous-versus select (P2 = joystick port 2), first-to-5 match flow into a result screen, longest-win-streak record, PSG tune-table music + SFX, and the MSX signature — screen-2 per-row color (banded court + a one-tile net 'pulse' gradient). A +/-1 PRNG deflection spin guarantees idle 1P rallies END. Record is in-session only (bundled bluemsx build exposes no SAVE_RAM — stated honestly in-file)."),
|
|
523
|
+
players: "1-2 (1P vs beatable CPU, or 2P simultaneous versus, P2 on joystick port 2)",
|
|
524
|
+
sram: "none — core exposes no SAVE_RAM region (in-session win-streak record)",
|
|
525
|
+
mechanics: ["paddle/ball court physics", "edge-deflection angle", "beatable chase-AI CPU", "2P simultaneous versus", "first-to-5 match + result screen", "PRNG rally spin (idle matches end)", "in-session win-streak record"],
|
|
526
|
+
techniques: [
|
|
527
|
+
"screen-2 per-row color (banded court + one-tile net pulse gradient)",
|
|
528
|
+
"interrupt-free vsync via VDP S#0 poll",
|
|
529
|
+
"sprite Y=208 terminator + offscreen parking",
|
|
530
|
+
"dual joystick ports via GTSTCK/GTTRIG",
|
|
531
|
+
"PSG tune-table music + SFX",
|
|
532
|
+
],
|
|
533
|
+
},
|
|
534
|
+
racing: {
|
|
535
|
+
...mk("racing", "TURBO TANGLE — complete MSX top-down four-lane road racer (screen 2): title shell with 1P / 2P-split-lane-versus select, 1P speed control (UP/A gas, DOWN/B brake, speed 1-4) banking DISTANCE, 3 crashes end the run; 2P versus shares one road (P1 left two lanes / P2 right two, P2 on port 2), first to wreck out loses. Per-row color signature (depth-banded thirds + a one-tile shimmer divider gradient), PSG music + SFX. HONEST: screen 2 has no scroll register, so the road motion is the marching lane-dash + roadside columns redrawn one phase-step per frame (static asphalt painted once) — taught against the NES's true BG scroll. Best distance is in-session only (bluemsx exposes no SAVE_RAM — stated in-file)."),
|
|
536
|
+
players: "1-2 (2P split-lane versus, P2 on joystick port 2)",
|
|
537
|
+
sram: "none — core exposes no SAVE_RAM region (in-session best distance)",
|
|
538
|
+
mechanics: ["lane steering", "speed control 1-4", "best-distance persistence (in-session)", "obstacle pool + AABB crashes", "crash lives", "2P split-lane versus"],
|
|
539
|
+
techniques: [
|
|
540
|
+
"software road scroll (no screen-2 hw scroll — redraw dashes/tufts per phase step)",
|
|
541
|
+
"screen-2 per-row color (banded thirds + one-tile shimmer divider)",
|
|
542
|
+
"interrupt-free vsync via VDP S#0 poll",
|
|
543
|
+
"dual joystick ports via GTSTCK/GTTRIG",
|
|
544
|
+
"PSG tune-table music + SFX",
|
|
545
|
+
],
|
|
546
|
+
},
|
|
311
547
|
};
|
|
312
548
|
})(),
|
|
313
549
|
};
|
|
@@ -322,6 +558,7 @@ const GBC_RUNTIME = [
|
|
|
322
558
|
{ src: "lib/c/gb_runtime.c", dst: "gb_runtime.c" },
|
|
323
559
|
{ src: "lib/c/gb_crt0.s", dst: "gb_crt0.s" },
|
|
324
560
|
{ src: "lib/c/patch-header.js", dst: "patch-header.js" },
|
|
561
|
+
{ src: "lib/c/font.h", dst: "font.h" }, /* digits+A-Z 2bpp glyphs; every gbc example #includes it */
|
|
325
562
|
];
|
|
326
563
|
const GBC_LANG = "C (SDCC sm83, GBC color)";
|
|
327
564
|
TEMPLATES.gbc = {
|
|
@@ -343,27 +580,82 @@ TEMPLATES.gbc = {
|
|
|
343
580
|
shmup: {
|
|
344
581
|
main: "templates/shmup.c", runtime: GBC_RUNTIME,
|
|
345
582
|
lang: GBC_LANG, ext: ".gbc",
|
|
346
|
-
describe: "
|
|
583
|
+
describe: "PHOTON DRIFT — Game Boy Color vertical shooter to the full contract: press-start title shell with battery-persistent hi-score (MBC1+RAM+BATTERY crt0 header, $0A enable dance, magic+checksum, survives power cycles), object-pool ship/bullets/enemies + wave spawner + AABB collision, GB-signature window-layer fixed HUD over an SCY-scrolled starfield — and the GBC SIGNATURE on top: TRUE per-tile color, a 4-band nebula starfield (blue/teal/green/magenta) as real CGB palettes (BCPS/BCPD) assigned per BG cell through the VRAM bank-1 attribute map, plus cyan ship / gold bullet / red enemy OBJ palettes (OCPS) — not colorized mono. GB APU music + SFX. KEY GOTCHA: HUD/text commits write bank-0 tiles only and stage text out of the vblank slice. Statics need dataLoc 0xC200. 1P by design (link-cable 2P not emulatable single-instance).",
|
|
584
|
+
players: "1 (handheld — link-cable 2P not emulatable single-instance)",
|
|
585
|
+
sram: "MBC1+RAM+BATTERY, 8KB at $A000 ($0A-gated, magic+checksum), verified across hardReset",
|
|
586
|
+
mechanics: ["projectile pools", "wave spawner", "AABB collision", "title/play/game-over state machine", "battery hi-score"],
|
|
587
|
+
techniques: [
|
|
588
|
+
"CGB per-tile color (4-band nebula starfield via bank-1 attribute map)",
|
|
589
|
+
"OBJ palettes (OCPS) for ship/bullet/enemy",
|
|
590
|
+
"window-layer fixed HUD over SCY starfield",
|
|
591
|
+
"two-phase vblank commit (bank-0-only HUD + pre-staged text)",
|
|
592
|
+
"battery SRAM save ($0A enable dance)",
|
|
593
|
+
],
|
|
347
594
|
},
|
|
348
595
|
platformer: {
|
|
349
596
|
main: "templates/platformer.c", runtime: GBC_RUNTIME,
|
|
350
597
|
lang: GBC_LANG, ext: ".gbc",
|
|
351
|
-
describe: "
|
|
598
|
+
describe: "SPECTRA BOUND — Game Boy Color side-scrolling platformer to the full contract: the GB runner core (Q4.4 sub-pixel gravity/jump, one-way platforms, lethal pits, drifting spikes, coins + distance scoring, one-way scroll-wall camera, seamlessly looping SCX 256-px column-map level, window-layer fixed HUD, divide-free math) with the GBC SIGNATURE on top — TRUE per-tile color: sky/grass/dirt/platform/HUD are 5 real CGB palettes (BCPS/BCPD) assigned per BG cell through the VRAM bank-1 attribute map, plus colorful player/coin/spike OBJ palettes (OCPS) — not colorized mono. Press-start title, persistent battery hi-score (MBC1+RAM+BATTERY SRAM, magic+checksum, verified across power cycles), GB APU music + SFX. KEY GOTCHA: HUD/text commits write bank-0 tiles only and stage text out of the vblank slice — per-cell VBK toggles or in-vblank char_tile overrun mode 3 and drop writes. Statics need dataLoc 0xC200. 1P by design (link-cable 2P not emulatable single-instance).",
|
|
599
|
+
players: "1 (handheld — link-cable 2P not emulatable single-instance)",
|
|
600
|
+
sram: "MBC1+RAM+BATTERY, 8KB at $A000 ($0A-gated, magic+checksum), verified across hardReset",
|
|
601
|
+
mechanics: ["gravity + Q4.4 jump physics", "one-way platforms (6-px landing window)", "pits + spikes + coins", "distance + coin scoring", "one-way scroll-wall camera", "lives + respawn breather", "battery-persistent hi-score"],
|
|
602
|
+
techniques: [
|
|
603
|
+
"CGB palette RAM (BCPS/BCPD + OCPS/OCPD, mode-3 write constraint)",
|
|
604
|
+
"VRAM bank-1 attribute map (VBK per-tile palettes)",
|
|
605
|
+
"window-layer HUD",
|
|
606
|
+
"SCX seamless looping scroll (uint8 wrap)",
|
|
607
|
+
"two-phase vblank commit (bank-0-only HUD + pre-staged text)",
|
|
608
|
+
"battery SRAM save ($0A enable dance)",
|
|
609
|
+
],
|
|
352
610
|
},
|
|
353
611
|
puzzle: {
|
|
354
|
-
main: "templates/puzzle.c",
|
|
612
|
+
main: "templates/puzzle.c",
|
|
613
|
+
runtime: GBC_RUNTIME,
|
|
355
614
|
lang: GBC_LANG, ext: ".gbc",
|
|
356
|
-
describe: "
|
|
615
|
+
describe: "CHROMA WELL — falling-jewel matcher, to the full contract: 8x15 well, 6 jewel colors as 6 REAL CGB palettes (BCPS/BCPD + the VRAM bank-1 attribute map — true per-tile color, not colorized mono), 4-direction matches with gravity cascades + chain scoring, magic jewel every 18th piece, window-layer HUD strip, persistent battery hi-score (MBC1+RAM+BATTERY SRAM, magic+checksum, verified across power cycles), title/play/game-over shell, ch1 music + ch2 SFX. The locked well paints via the COLLECT/FLUSH vblank queue (writes outside vblank silently drop — never bypass it). Statics need dataLoc 0xC200 (the project recipe sets it).",
|
|
616
|
+
players: "1 (handheld — link-cable 2P not emulatable single-instance)",
|
|
617
|
+
sram: "MBC1+RAM+BATTERY, 8KB at $A000 ($0A-gated), verified across hardReset",
|
|
618
|
+
mechanics: ["grid logic", "falling-piece matching", "gravity + cascade chains", "scoring/levels", "battery hi-score", "title/play/game-over state machine"],
|
|
619
|
+
techniques: [
|
|
620
|
+
"CGB palette RAM (BCPS/BCPD + OCPS/OCPD, mode-3 write constraint)",
|
|
621
|
+
"VRAM bank-1 attribute map (VBK per-tile palettes)",
|
|
622
|
+
"window-layer HUD",
|
|
623
|
+
"vblank COLLECT/FLUSH queue + idle scrub",
|
|
624
|
+
"OAM DMA HRAM stub",
|
|
625
|
+
"battery SRAM save ($0A enable dance)",
|
|
626
|
+
],
|
|
357
627
|
},
|
|
358
628
|
sports: {
|
|
359
629
|
main: "templates/sports.c", runtime: GBC_RUNTIME,
|
|
360
630
|
lang: GBC_LANG, ext: ".gbc",
|
|
361
|
-
describe: "Pong
|
|
631
|
+
describe: "HUE HUSTLE — Game Boy Color versus court game (Pong lineage) to the full contract: press-start title, 1P vs a beatable chase-AI CPU, first-to-5 match flow into a result screen, a PRNG +/-1 rally spin so an idle match provably ENDS, GB APU ch1 music + ch2 SFX, window-layer fixed HUD — and the GBC SIGNATURE: TRUE per-tile color. The two paddles are told apart by distinct CGB OBJ PALETTE (azure you / red CPU via OCPS), not DMG shade; the court is a real color scene (teal floor / gold rails / violet net) as CGB palettes assigned per BG cell through the VRAM bank-1 attribute map. Longest 1P win streak persists to battery SRAM (MBC1+RAM+BATTERY, magic+checksum, verified across power cycles). HUD/result commits write bank-0 tiles only and stage text out of the vblank slice. dataLoc 0xC200. 1P by design (link-cable 2P not emulatable single-instance).",
|
|
632
|
+
players: "1 (handheld — link-cable 2P not emulatable single-instance)",
|
|
633
|
+
sram: "MBC1+RAM+BATTERY, 8KB at $A000 ($0A-gated, magic+checksum), verified across hardReset",
|
|
634
|
+
mechanics: ["paddle/ball physics with edge-deflection", "beatable chase-AI CPU", "first-to-5 match flow", "PRNG rally spin (idle match ends)", "battery win-streak record", "title/play/result state machine"],
|
|
635
|
+
techniques: [
|
|
636
|
+
"CGB OBJ palettes (OCPS) for distinct team paddles",
|
|
637
|
+
"CGB per-tile color court (bank-1 attribute map)",
|
|
638
|
+
"window-layer fixed HUD",
|
|
639
|
+
"two-phase vblank commit (bank-0-only HUD + pre-staged result text)",
|
|
640
|
+
"battery SRAM save ($0A enable dance)",
|
|
641
|
+
"xorshift16 PRNG for deterministic-versus rally break",
|
|
642
|
+
],
|
|
362
643
|
},
|
|
363
644
|
racing: {
|
|
364
645
|
main: "templates/racing.c", runtime: GBC_RUNTIME,
|
|
365
646
|
lang: GBC_LANG, ext: ".gbc",
|
|
366
|
-
describe: "
|
|
647
|
+
describe: "TWILIGHT LANE — Game Boy Color top-down road racer to the full contract: press-start title (honest no-2P), the road scrolls via SCY into a 256-px map (seamless uint8 wrap — contrast vs NES 240 / SMS 224 garbage-row / Genesis hardware-masked plane), four lanes, A/UP accelerate + B/DOWN brake (speed 1-4), LEFT/RIGHT lane tilt, 6-slot traffic pool, crash + 3 lives with invuln blink, window-layer HUD, best distance to battery SRAM (verified across power cycles). The GBC SIGNATURE: 5 real CGB BG palettes (violet dusk asphalt / evening grass / pine trees / cyan-glow dividers / HUD) assigned per cell via the bank-1 attribute map, plus cyan-car / red-traffic OBJ palettes (OCPS) — not colorized mono. GB APU music + SFX, two-phase vblank commit, dataLoc 0xC200.",
|
|
648
|
+
players: "1 (handheld — link-cable 2P not emulatable single-instance)",
|
|
649
|
+
sram: "MBC1+RAM+BATTERY, 8KB at $A000 ($0A-gated, best-distance magic+checksum), verified across hardReset",
|
|
650
|
+
mechanics: ["lane steering", "speed control 1-4", "best-distance persistence", "traffic pool + AABB crashes", "crash lives + invuln blink"],
|
|
651
|
+
techniques: [
|
|
652
|
+
"CGB per-tile color road (5 palettes via bank-1 attribute map)",
|
|
653
|
+
"OBJ palettes (OCPS) for car + traffic",
|
|
654
|
+
"SCY vertical road scroll (256-px seamless uint8 wrap)",
|
|
655
|
+
"window-layer fixed HUD",
|
|
656
|
+
"two-phase vblank commit (bank-0-only HUD + streamed roadside restamp)",
|
|
657
|
+
"battery SRAM best-distance ($0A enable dance)",
|
|
658
|
+
],
|
|
367
659
|
},
|
|
368
660
|
/* R45 — same hUGEDriver music_demo as GB, with BCPS/BCPD palette
|
|
369
661
|
* writes so it boots in CGB mode (gambatte flips on .gbc + $0143=$80).
|
|
@@ -398,6 +690,11 @@ TEMPLATES.gbc = {
|
|
|
398
690
|
// against. Factored to a constant so adding a new template is a one-line
|
|
399
691
|
// change at the bottom.
|
|
400
692
|
const SMS_RUNTIME = [
|
|
693
|
+
// The crt0 ships IN the project (like GG/MSX) so the dir is genuinely
|
|
694
|
+
// self-contained: build({output:'project'}) routes it via the crt0 channel
|
|
695
|
+
// (projectBuildRecipe), and an external stock-SDCC rebuild has the real
|
|
696
|
+
// boot stub on disk instead of silently linking SDCC's non-booting one.
|
|
697
|
+
{ src: "lib/c/sms_crt0.s", dst: "sms_crt0.s" },
|
|
401
698
|
{ src: "lib/c/sms_hw.h", dst: "sms_hw.h" },
|
|
402
699
|
{ src: "lib/c/vdp_init.c", dst: "vdp_init.c" },
|
|
403
700
|
{ src: "lib/c/load_palette.c", dst: "load_palette.c" },
|
|
@@ -445,35 +742,87 @@ TEMPLATES.sms = {
|
|
|
445
742
|
runtime: SMS_RUNTIME,
|
|
446
743
|
lang: SMS_LANG,
|
|
447
744
|
ext: ".sms",
|
|
448
|
-
describe: "
|
|
745
|
+
describe: "ASTRO PICKET — complete SMS vertical shooter: title shell with 1P/2P select and hi-score, simultaneous 2-ship co-op (P2 on port 1), PSG music + SFX, and the SMS signature LINE-INTERRUPT split (VDP register-10 line counter: fixed HUD strip over a scrolling starfield — the programmable cousin of the NES sprite-0 trick). Hi-score persists to Sega-mapper cart RAM on 64KB+ builds (verified); 32KB builds are honestly in-session.",
|
|
746
|
+
players: "1-2 (simultaneous co-op)",
|
|
747
|
+
sram: "Sega-mapper cart RAM at $8000 ($FFFC bit 3) on 64KB+ builds; in-session at 32KB (gpgx maps mapper RAM only above 48KB — documented in-file)",
|
|
748
|
+
mechanics: ["projectile pools", "wave spawner", "AABB collision", "2P simultaneous co-op", "title/play/game-over state machine"],
|
|
749
|
+
techniques: [
|
|
750
|
+
"VDP line-interrupt split (fixed HUD over scrolling field)",
|
|
751
|
+
"Sega-mapper cart RAM persistence ($FFFC control)",
|
|
752
|
+
"PSG tune-table music + noise SFX",
|
|
753
|
+
"SAT slot pre-allocation (no flicker)",
|
|
754
|
+
"IM1 interrupt handshake (VDP status ack discipline)",
|
|
755
|
+
],
|
|
449
756
|
},
|
|
450
757
|
platformer: {
|
|
451
758
|
main: "templates/platformer.c",
|
|
452
759
|
runtime: SMS_RUNTIME,
|
|
453
760
|
lang: SMS_LANG,
|
|
454
761
|
ext: ".sms",
|
|
455
|
-
describe: "
|
|
762
|
+
describe: "GULLY VAULT — side-scrolling platformer: gravity + Q4.4 sub-pixel jump physics, one-way platforms, pits and spikes, coins + distance scoring, PSG music + SFX. 2P is classic alternating turns (P2 on port B) with per-player score and lives. The SMS signature LINE-INTERRUPT split holds a fixed HUD over the scrolling level, and the 32-cell name table wraps at exactly 256 px — the level loops seamlessly with no second nametable or column streaming. Hi-score persists to Sega-mapper cart RAM on 64KB+ builds (verified incl. power-cycle); 32KB builds are honestly in-session.",
|
|
763
|
+
players: "1-2 (alternating turns, P2 on port B)",
|
|
764
|
+
sram: "Sega-mapper cart RAM at $8000 ($FFFC bit 3) on 64KB+ builds (verified across soft reset AND power-cycle); in-session at 32KB",
|
|
765
|
+
mechanics: ["gravity-jump physics (Q4.4 fixed point)", "one-way platform collision", "one-way camera with scroll wall", "pits + spike hazards", "coin + distance scoring", "alternating 2P turns with per-player lives"],
|
|
766
|
+
techniques: [
|
|
767
|
+
"VDP line-interrupt split (fixed HUD over scrolling level)",
|
|
768
|
+
"hardware-wrapping 256-px name table as a seamless looping world (R8 = -scroll_x)",
|
|
769
|
+
"Sega-mapper cart RAM persistence ($FFFC control)",
|
|
770
|
+
"PSG tune-table music + voice-2 SFX arbitration",
|
|
771
|
+
"IM1 interrupt handshake (VDP status ack discipline)",
|
|
772
|
+
],
|
|
456
773
|
},
|
|
457
774
|
puzzle: {
|
|
458
775
|
main: "templates/puzzle.c",
|
|
459
776
|
runtime: SMS_RUNTIME,
|
|
460
777
|
lang: SMS_LANG,
|
|
461
778
|
ext: ".sms",
|
|
462
|
-
describe: "
|
|
779
|
+
describe: "GEODE GAMBIT — falling-trio match-3 to the full contract: 1P marathon with levels and cascade chains; 2P simultaneous split-board versus where chains send garbage rows (both wells update every frame). The board is BG tiles via sms_set_tilemap_cell — a whole well repaints in one vblank (Mode-4 has the VDP bandwidth; taught against the NES's 16-entry vblank budget). Fixed HUD under the line-IRQ split, Sega-mapper cart-RAM hi-score (verified across power-cycle on 64KB), PSG music + SFX.",
|
|
780
|
+
players: "1-2 (2P = simultaneous versus, split boards with garbage attacks)",
|
|
781
|
+
sram: "Sega-mapper cart RAM at $8000 ($FFFC bit 3) on 64KB+ builds (verified across hardReset); in-session at 32KB (gpgx maps mapper RAM only above 48KB — documented in-file)",
|
|
782
|
+
mechanics: ["falling-trio control", "match-3 in 4 directions", "cascade chains with multipliers", "garbage attack rows", "levels", "split-board simultaneous versus"],
|
|
783
|
+
techniques: [
|
|
784
|
+
"whole-well repaint via sms_set_tilemap_cell (one vblank)",
|
|
785
|
+
"VDP line-interrupt split (fixed HUD strip)",
|
|
786
|
+
"Sega-mapper cart RAM persistence ($FFFC control)",
|
|
787
|
+
"PSG music + voice-0 SFX arbitration",
|
|
788
|
+
"IM1 interrupt handshake (VDP status ack discipline)",
|
|
789
|
+
],
|
|
463
790
|
},
|
|
464
791
|
sports: {
|
|
465
792
|
main: "templates/sports.c",
|
|
466
793
|
runtime: SMS_RUNTIME,
|
|
467
794
|
lang: SMS_LANG,
|
|
468
795
|
ext: ".sms",
|
|
469
|
-
describe: "
|
|
796
|
+
describe: "DEUCE DASH — head-to-head court sports to the full contract: title shell with 1P-vs-CPU / 2P-versus select, a beatable chase-AI CPU, 2P simultaneous versus (P2 on PORT B via sms_joypad_read_p2), first-to-5 match flow into a result screen, PSG music + SFX. A +/-1 PRNG deflection spin guarantees idle rallies END (no infinite limit cycle). Longest 1P win streak persists to Sega-mapper cart RAM (verified across power-cycle on 64KB; honest in-session at 32KB). Fixed HUD under the SMS line-IRQ split.",
|
|
797
|
+
players: "1-2 (1P = vs beatable CPU; 2P = simultaneous versus, P2 on port B)",
|
|
798
|
+
sram: "Sega-mapper cart RAM at $8000 ($FFFC bit 3) on 64KB+ builds (verified across soft reset AND power-cycle); in-session at 32KB",
|
|
799
|
+
mechanics: ["paddle vs ball court play", "position-based deflection angle", "beatable chase-AI CPU", "simultaneous 2P versus", "first-to-5 match flow + result screen", "PRNG rally spin (no limit cycle)", "longest-win-streak record"],
|
|
800
|
+
techniques: [
|
|
801
|
+
"VDP line-interrupt split (fixed HUD over the court)",
|
|
802
|
+
"split static/dynamic HUD to fit the vblank VRAM budget",
|
|
803
|
+
"Sega-mapper cart RAM persistence ($FFFC control)",
|
|
804
|
+
"PSG tune-table music + voice-0/1 SFX arbitration",
|
|
805
|
+
"PORT B P2 reassembly (sms_joypad_read_p2)",
|
|
806
|
+
"IM1 interrupt handshake (VDP status ack discipline)",
|
|
807
|
+
],
|
|
470
808
|
},
|
|
471
809
|
racing: {
|
|
472
810
|
main: "templates/racing.c",
|
|
473
811
|
runtime: SMS_RUNTIME,
|
|
474
812
|
lang: SMS_LANG,
|
|
475
813
|
ext: ".sms",
|
|
476
|
-
describe: "
|
|
814
|
+
describe: "FENDER FURY — top-down vertical road racer to the full contract: 1P endless race with speed control (button1/UP gas, button2/DOWN brake, speed 1-4) and persistent best DISTANCE; 2P simultaneous split-lane VERSUS (both cars on screen, P2 on port B), solid center divider splitting territories, first to wreck out loses. The road is the BG scrolled vertically by R9 (whole-plane, latched once per frame) — the SMS twist on the Genesis full-plane VSCROLL, with the 224-px name-table Y-wrap footgun handled (vs NES 240 / Genesis 256). Streamed roadside rows, line-IRQ-split fixed HUD (sprite-digit HUD on the fixed top line + per-strip R8 road sway below), Sega-mapper cart-RAM best (verified across power-cycle on 64KB; in-session at 32KB), PSG music + SFX.",
|
|
815
|
+
players: "1-2 (2P = simultaneous split-lane versus, P2 on port B)",
|
|
816
|
+
sram: "Sega-mapper cart RAM at $8000 ($FFFC bit 3) on 64KB+ builds (verified across soft reset AND power-cycle); in-session at 32KB",
|
|
817
|
+
mechanics: ["lane steering (edge-detected)", "1P speed control 1-4", "best-distance persistence", "traffic object pool + AABB crashes", "crash lives + invuln grace", "2P split-lane versus with shared road"],
|
|
818
|
+
techniques: [
|
|
819
|
+
"whole-plane R9 vertical road scroll (224-px Y-wrap)",
|
|
820
|
+
"streamed roadside rows",
|
|
821
|
+
"line-IRQ split: fixed sprite HUD + per-strip R8 road sway",
|
|
822
|
+
"Sega-mapper cart RAM persistence ($FFFC)",
|
|
823
|
+
"PSG music + multi-channel SFX",
|
|
824
|
+
"IM1 interrupt handshake (VDP status ack discipline)",
|
|
825
|
+
],
|
|
477
826
|
},
|
|
478
827
|
shmup_2p: {
|
|
479
828
|
main: "templates/shmup_2p.c",
|
|
@@ -544,35 +893,91 @@ TEMPLATES.gg = {
|
|
|
544
893
|
runtime: GG_RUNTIME,
|
|
545
894
|
lang: GG_LANG,
|
|
546
895
|
ext: ".gg",
|
|
547
|
-
describe: "
|
|
896
|
+
describe: "PRISM PATROL — complete GG vertical shooter: press-START title shell with hi-score, PSG music + SFX, and the GG/SMS signature LINE-INTERRUPT split (fixed HUD over a scrolling starfield) taught against the GG's #1 footgun — the 160x144 window centered in the 256x192 frame (VIS_* offsets; line-counter values are FULL-frame scanlines, so the split lands at 47, not the SMS's 23). 12-bit CRAM palette shows the 4096 colors. Hi-score persists to Sega-mapper cart RAM on 64KB+ builds (verified incl. power-cycle); 32KB builds are honestly in-session.",
|
|
897
|
+
players: "1 (one controller; Gear-to-Gear link 2P can't be emulated single-instance — honest note in-file)",
|
|
898
|
+
sram: "Sega-mapper cart RAM at $8000 ($FFFC bit 3) on 64KB+ builds (verified across soft reset AND power-cycle); in-session at 32KB",
|
|
899
|
+
mechanics: ["projectile pools", "wave spawner", "AABB collision", "title/play/game-over state machine", "persistent hi-score"],
|
|
900
|
+
techniques: [
|
|
901
|
+
"VDP line-interrupt split with GG-window scanline math",
|
|
902
|
+
"GG 160x144 visible-window placement (VIS_* offset idiom)",
|
|
903
|
+
"GG 12-bit CRAM palette (2-byte entries vs SMS 1-byte)",
|
|
904
|
+
"Sega-mapper cart RAM persistence ($FFFC control)",
|
|
905
|
+
"PSG note-table music + noise SFX",
|
|
906
|
+
"IM1 handshake + DI/EI repaint bracket (line-IRQ ack races the VDP address latch)",
|
|
907
|
+
],
|
|
548
908
|
},
|
|
549
909
|
platformer: {
|
|
550
910
|
main: "templates/platformer.c",
|
|
551
911
|
runtime: GG_RUNTIME,
|
|
552
912
|
lang: GG_LANG,
|
|
553
913
|
ext: ".gg",
|
|
554
|
-
describe: "
|
|
914
|
+
describe: "SCARP SPRINT — side-scrolling platformer for the Game Gear: gravity + Q4.4 sub-pixel jump physics, one-way platforms, pits and spikes, coins + distance scoring, PSG music + SFX. The GG twin of the SMS platformer, fitted to the 160x144 visible window (VIS_* offsets; the line-IRQ split lands at full-frame scanline 47, not the SMS's 23). 2P is classic alternating turns (P2 on port B) with per-player score and lives. The GG/SMS signature LINE-INTERRUPT split holds a fixed HUD over the scrolling level, and the 32-cell name table wraps at exactly 256 px — the level loops seamlessly with no second nametable or column streaming. GG 12-bit CRAM palette. Hi-score persists to Sega-mapper cart RAM on 64KB+ builds (verified incl. power-cycle); 32KB builds are honestly in-session.",
|
|
915
|
+
players: "1-2 (alternating turns, P2 on port B; GG has 2 controller ports — not the link cable)",
|
|
916
|
+
sram: "Sega-mapper cart RAM at $8000 ($FFFC bit 3) on 64KB+ builds (verified across soft reset AND power-cycle); in-session at 32KB",
|
|
917
|
+
mechanics: ["gravity-jump physics (Q4.4 fixed point)", "one-way platform collision", "one-way camera with scroll wall", "pits + spike hazards", "coin + distance scoring", "alternating 2P turns with per-player lives"],
|
|
918
|
+
techniques: [
|
|
919
|
+
"VDP line-interrupt split with GG-window scanline math (split at 47)",
|
|
920
|
+
"GG 160x144 visible-window placement (VIS_* offset idiom)",
|
|
921
|
+
"GG 12-bit CRAM palette (2-byte entries vs SMS 1-byte)",
|
|
922
|
+
"hardware-wrapping 256-px name table as a seamless looping world (R8 = -scroll_x)",
|
|
923
|
+
"Sega-mapper cart RAM persistence ($FFFC control)",
|
|
924
|
+
"IM1 handshake + DI/EI repaint bracket (line-IRQ ack races the VDP address latch)",
|
|
925
|
+
],
|
|
555
926
|
},
|
|
556
927
|
puzzle: {
|
|
557
928
|
main: "templates/puzzle.c",
|
|
558
929
|
runtime: GG_RUNTIME,
|
|
559
930
|
lang: GG_LANG,
|
|
560
931
|
ext: ".gg",
|
|
561
|
-
describe: "
|
|
932
|
+
describe: "SLUICE STACK — falling-gem versus match-3 to the full contract, fit to the GG's 160x144 visible window (VIS_* offset idiom): title shell, 1P MARATHON (levels speed the fall as you clear) and 2P SIMULTANEOUS versus (P2 on PORT B via gg_joypad_read_p2 — gpgx wires the SMS second pad for GG) on two narrow side-by-side wells where cascade chains lay garbage rows on your rival. The GG-window adaptation: wells are 5 cells wide (vs the SMS's 6) so two + a centre gutter fit the 20-col window — documented in-file. Move/cycle-colour/soft-drop/hard-drop, 3+ clears in all 4 directions, gravity cascades chain for multiplied score. Fixed HUD under the GG line-IRQ split (split at full-frame scanline 47, not the SMS's 23). 12-bit CRAM palette. Hi-score persists to Sega-mapper cart RAM (verified across power-cycle on 64KB; honest in-session at 32KB).",
|
|
933
|
+
players: "1-2 (1P marathon; 2P = simultaneous split-board versus, P2 on port B)",
|
|
934
|
+
sram: "Sega-mapper cart RAM at $8000 ($FFFC bit 3) on 64KB+ builds (verified across soft reset AND power-cycle); in-session at 32KB",
|
|
935
|
+
mechanics: ["falling-trio match-3", "3+ clears in 4 directions", "gravity cascade chains (multiplied score)", "1P marathon with levels", "simultaneous 2P versus with garbage-row attacks", "persistent hi-score"],
|
|
936
|
+
techniques: [
|
|
937
|
+
"GG 160x144 visible-window placement (VIS_* offset idiom)",
|
|
938
|
+
"narrow-well geometry to fit two boards in 20 cols",
|
|
939
|
+
"VDP line-interrupt split with GG-window scanline math (split at 47)",
|
|
940
|
+
"whole-well repaint in one vblank (vs NES per-row queue)",
|
|
941
|
+
"GG 12-bit CRAM palette (2-byte entries vs SMS 1-byte)",
|
|
942
|
+
"PORT B P2 reassembly (gg_joypad_read_p2)",
|
|
943
|
+
],
|
|
562
944
|
},
|
|
563
945
|
sports: {
|
|
564
946
|
main: "templates/sports.c",
|
|
565
947
|
runtime: GG_RUNTIME,
|
|
566
948
|
lang: GG_LANG,
|
|
567
949
|
ext: ".gg",
|
|
568
|
-
describe: "
|
|
950
|
+
describe: "BAFFLE BOUNCE — head-to-head court sports to the full contract, fit to the GG's 160x144 visible window (VIS_* offset idiom): title shell with 1P-vs-CPU / 2P-versus select, a beatable chase-AI CPU, 2P SIMULTANEOUS versus (P2 on PORT B via gg_joypad_read_p2 — gpgx wires the SMS second pad for GG), first-to-5 match flow into a result screen, PSG music + SFX. A +/-1 PRNG deflection spin guarantees idle rallies END. Longest 1P win streak persists to Sega-mapper cart RAM (verified across power-cycle on 64KB; honest in-session at 32KB). Fixed HUD under the GG line-IRQ split (split at full-frame scanline 47, not the SMS's 23). 12-bit CRAM palette.",
|
|
951
|
+
players: "1-2 (1P = vs beatable CPU; 2P = simultaneous versus, P2 on port B)",
|
|
952
|
+
sram: "Sega-mapper cart RAM at $8000 ($FFFC bit 3) on 64KB+ builds (verified across soft reset AND power-cycle); in-session at 32KB",
|
|
953
|
+
mechanics: ["paddle vs ball court play", "position-based deflection angle", "beatable chase-AI CPU", "simultaneous 2P versus", "first-to-5 match flow + result screen", "PRNG rally spin (no limit cycle)", "longest-win-streak record"],
|
|
954
|
+
techniques: [
|
|
955
|
+
"GG 160x144 visible-window placement (VIS_* offset idiom)",
|
|
956
|
+
"VDP line-interrupt split with GG-window scanline math (split at 47)",
|
|
957
|
+
"split static/dynamic HUD to fit the vblank VRAM budget",
|
|
958
|
+
"GG 12-bit CRAM palette (2-byte entries vs SMS 1-byte)",
|
|
959
|
+
"di/ei repaint bracket (IRQ address-latch race)",
|
|
960
|
+
"Sega-mapper cart RAM persistence ($FFFC control)",
|
|
961
|
+
"PORT B P2 reassembly (gg_joypad_read_p2)",
|
|
962
|
+
],
|
|
569
963
|
},
|
|
570
964
|
racing: {
|
|
571
965
|
main: "templates/racing.c",
|
|
572
966
|
runtime: GG_RUNTIME,
|
|
573
967
|
lang: GG_LANG,
|
|
574
968
|
ext: ".gg",
|
|
575
|
-
describe: "
|
|
969
|
+
describe: "CHICANE DASH — top-down vertical road racer to the full contract, the GG twin of the SMS FENDER FURY fitted to the 160x144 visible window (VIS_* offsets; line-IRQ split at full-frame scanline 47). 1P endless race with speed control (button1/UP gas, button2/DOWN brake, speed 1-4) + persistent best DISTANCE; 2P simultaneous split-lane VERSUS (both cars on screen, P2 on port B), center divider, first to wreck out loses. R9 whole-plane vertical road (224-px Y-wrap), streamed roadside rows, fixed sprite-digit HUD + per-strip R8 road sway (the chicane curve), GG 12-bit CRAM palette, Sega-mapper cart-RAM best (verified across power-cycle on 64KB; in-session at 32KB), PSG music + SFX.",
|
|
970
|
+
players: "1-2 (2P = simultaneous split-lane versus, P2 on port B)",
|
|
971
|
+
sram: "Sega-mapper cart RAM at $8000 ($FFFC bit 3) on 64KB+ builds (verified across soft reset AND power-cycle); in-session at 32KB",
|
|
972
|
+
mechanics: ["lane steering", "speed control 1-4", "best-distance persistence", "traffic + AABB crashes", "crash lives", "2P split-lane versus"],
|
|
973
|
+
techniques: [
|
|
974
|
+
"GG 160x144 visible-window placement (VIS_* offset idiom)",
|
|
975
|
+
"whole-plane R9 vertical road scroll (224-px Y-wrap)",
|
|
976
|
+
"line-IRQ split at GG scanline 47: fixed sprite HUD + per-strip R8 road sway",
|
|
977
|
+
"GG 12-bit CRAM palette",
|
|
978
|
+
"Sega-mapper cart RAM persistence ($FFFC control)",
|
|
979
|
+
"streamed roadside rows (verge cols only, off the centered title)",
|
|
980
|
+
],
|
|
576
981
|
},
|
|
577
982
|
music_demo: {
|
|
578
983
|
main: "templates/music_demo.c",
|
|
@@ -621,27 +1026,79 @@ TEMPLATES.c64 = {
|
|
|
621
1026
|
shmup: {
|
|
622
1027
|
main: "templates/shmup.c", runtime: C64_RUNTIME, runtimeDirs: C64_VENDOR_DIRS,
|
|
623
1028
|
lang: C64_LANG, ext: ".prg",
|
|
624
|
-
describe: "
|
|
1029
|
+
describe: "ION SQUALL — complete horizontal shooter: title shell (port-2 fire = 1P, port-1 fire = 2P co-op), shared-lives co-op, bullet/enemy pools, score + session hi-score, 2-voice SID music with the signature filter sweep + voice-2 SFX, and the C64 signature raster-IRQ split (fixed score bar over a fine-scrolling starfield). Hi-score persists via a 1541 DISK SAVE when run from a .d64 (KERNAL write to drive 8, committed to the live disk; in-session only as a bare .prg) — documented in-file.",
|
|
1030
|
+
players: "1-2 (simultaneous co-op; P1 on joystick port 2, P2 on port 1)",
|
|
1031
|
+
sram: "1541 DISK SAVE — the honest C64 medium (no battery SRAM): the game writes a 2-byte record to a SEQ file 'HI' on drive 8 via the KERNAL (cbm_open/read/write), VICE commits it into the live .d64 (true-drive write-back). Requires running from a .d64 (state({op:exportDisk}) captures the save; reload restores it); as a bare .prg the save is a silent no-op (in-session only).",
|
|
1032
|
+
mechanics: ["projectile pools", "altitude-seeking enemy spawner", "AABB collision", "shared-lives co-op", "title/play/game-over state machine"],
|
|
1033
|
+
techniques: [
|
|
1034
|
+
"raster-IRQ split (mid-frame $D016 rewrite: fixed bar over scrolling field)",
|
|
1035
|
+
"dual joystick-port reads with keyboard-conflict awareness ($DC00/$DC01)",
|
|
1036
|
+
"9th-X-bit sprite staging ($D010 batch commit)",
|
|
1037
|
+
"SID filter sweep (11-bit cutoff LFO; shared volume/mode register)",
|
|
1038
|
+
"beam-racing coarse scroll scheduled off the bottom IRQ",
|
|
1039
|
+
"transition repaints budgeted to text bands (full 880-cell paints freeze ~50 frames)",
|
|
1040
|
+
],
|
|
625
1041
|
},
|
|
626
1042
|
platformer: {
|
|
627
1043
|
main: "templates/platformer.c", runtime: C64_RUNTIME, runtimeDirs: C64_VENDOR_DIRS,
|
|
628
1044
|
lang: C64_LANG, ext: ".prg",
|
|
629
|
-
describe: "
|
|
1045
|
+
describe: "TALUS TROT — complete C64 side-scrolling platformer: title shell (port-2 fire = 1P, port-1 fire = 2P alternating turns, per-player score/lives), gravity + Q4.4 sub-pixel jump physics, one-way platforms, pits + spikes, coins + distance scoring, raster-IRQ split (fixed score bar over a fine ($D016) + coarse (screen-RAM shift) hardware-scrolled level), 2-voice SID music with the filter sweep + SFX. Hi-score persists via 1541 disk save when run from a .d64 (in-session as a bare .prg). KEY SCROLL FINDING: shifting both screen AND color RAM per coarse step crawls cc65; a STATIC row-based color texture + screen-RAM-only shift keeps the coarse scroll real-time (taught in-file).",
|
|
1046
|
+
players: "1-2 (alternating turns; P1 on joystick port 2, P2 on port 1)",
|
|
1047
|
+
sram: "1541 DISK SAVE — the honest C64 medium (no battery SRAM): the game writes a 2-byte record to a SEQ file 'HI' on drive 8 via the KERNAL (cbm_open/read/write), VICE commits it into the live .d64 (true-drive write-back). Requires running from a .d64 (state({op:exportDisk}) captures the save; reload restores it); as a bare .prg the save is a silent no-op (in-session only).",
|
|
1048
|
+
mechanics: ["gravity + Q4.4 sub-pixel jump", "one-way platforms", "pits + spikes (lethal)", "coins + distance scoring", "alternating-turns 2P", "title/play/game-over state machine"],
|
|
1049
|
+
techniques: [
|
|
1050
|
+
"raster-IRQ split (fixed bar over scrolling level)",
|
|
1051
|
+
"fine ($D016) + coarse (screen-RAM shift) hardware scroll",
|
|
1052
|
+
"two-layer static-color-texture trick (keeps coarse scroll real-time)",
|
|
1053
|
+
"9th-X-bit sprite staging",
|
|
1054
|
+
"SID filter sweep",
|
|
1055
|
+
"dual joystick-port reads with keyboard-conflict idiom",
|
|
1056
|
+
],
|
|
630
1057
|
},
|
|
631
1058
|
puzzle: {
|
|
632
1059
|
main: "templates/puzzle.c", runtime: C64_RUNTIME, runtimeDirs: C64_VENDOR_DIRS,
|
|
633
1060
|
lang: C64_LANG, ext: ".prg",
|
|
634
|
-
describe: "
|
|
1061
|
+
describe: "MAGMA MATCH — complete C64 falling-trio versus puzzle: title/1P-marathon/2P-simultaneous-versus shell, falling-trio match-3 (4-direction clears, per-column gravity, cascade chains, 9 levels), hi-score persisted via 1541 disk save when run from a .d64 (in-session as a bare .prg), 2-voice SID music with the filter sweep + SFX, raster-IRQ split fixed HUD. The board is screen RAM ($0400) chars + color RAM ($D800) repainted via a CELL-DIFF (shadow buffers; only changed cells touch RAM) — dodging the C64 full-repaint freeze (taught in-file, the inverse of the NES vblank-queue famine). Real 2P simultaneous versus with garbage: a cascade chain erupts garbage rows into your rival's well; P1 on control port 2, P2 on control port 1.",
|
|
1062
|
+
players: "1-2 (2P = simultaneous versus, split boards; P1 port 2, P2 port 1)",
|
|
1063
|
+
sram: "1541 DISK SAVE — the honest C64 medium (no battery SRAM): the game writes a 2-byte record to a SEQ file 'HI' on drive 8 via the KERNAL (cbm_open/read/write), VICE commits it into the live .d64 (true-drive write-back). Requires running from a .d64 (state({op:exportDisk}) captures the save; reload restores it); as a bare .prg the save is a silent no-op (in-session only).",
|
|
1064
|
+
mechanics: ["falling-trio control", "match-3 in 4 directions", "cascade chains with multipliers", "garbage attack rows", "soft/hard drop + levels", "simultaneous split-board versus"],
|
|
1065
|
+
techniques: [
|
|
1066
|
+
"cell-diff screen/color-RAM repaint (shadow buffers; only changed cells)",
|
|
1067
|
+
"raster-IRQ split fixed HUD",
|
|
1068
|
+
"dual joystick-port reads with keyboard-conflict idiom",
|
|
1069
|
+
"SID filter sweep",
|
|
1070
|
+
"coloured border for render-health without a full-screen repaint freeze",
|
|
1071
|
+
],
|
|
635
1072
|
},
|
|
636
1073
|
sports: {
|
|
637
1074
|
main: "templates/sports.c", runtime: C64_RUNTIME, runtimeDirs: C64_VENDOR_DIRS,
|
|
638
1075
|
lang: C64_LANG, ext: ".prg",
|
|
639
|
-
describe: "Pong with
|
|
1076
|
+
describe: "DELTA DUEL — complete C64 head-to-head court sports (Pong lineage): title shell with 1P-vs-beatable-CPU / 2P-SIMULTANEOUS-versus select (P1 control port 2, P2 control port 1), first-to-5 match flow into a result screen, beatable chase-AI CPU, a +/-1 PRNG deflection spin so idle 1P rallies provably END, best 1P-vs-CPU win-streak record persisted via 1541 disk save when run from a .d64 (in-session as a bare .prg), 2-voice SID music with the filter sweep + SFX, raster-IRQ split fixed HUD. Paddles + ball are VIC-II HARDWARE SPRITES (9th-X-bit staging for the right paddle past X=255); the court is static screen-RAM chars painted once per match (no per-frame repaint).",
|
|
1077
|
+
players: "1-2 (2P = simultaneous versus; P1 control port 2, P2 control port 1)",
|
|
1078
|
+
sram: "1541 DISK SAVE — the honest C64 medium (no battery SRAM): the game writes a 2-byte record to a SEQ file 'HI' on drive 8 via the KERNAL (cbm_open/read/write), VICE commits it into the live .d64 (true-drive write-back). Requires running from a .d64 (state({op:exportDisk}) captures the save; reload restores it); as a bare .prg the save is a silent no-op (in-session only). (record = longest 1P win streak vs the CPU).",
|
|
1079
|
+
mechanics: ["1P vs beatable chase-AI CPU", "2P simultaneous versus", "first-to-5 match flow + result screen", "PRNG deflection spin (rallies END)", "longest-win-streak record", "title/play/result state machine"],
|
|
1080
|
+
techniques: [
|
|
1081
|
+
"VIC-II hardware sprites (paddles + ball) with 9th-X-bit batch staging ($D010)",
|
|
1082
|
+
"raster-IRQ split fixed HUD over a static court",
|
|
1083
|
+
"dual joystick-port reads with keyboard-conflict idiom ($DC00/$DC01)",
|
|
1084
|
+
"SID filter sweep (11-bit cutoff LFO; shared volume/mode register)",
|
|
1085
|
+
"static char court (no per-frame repaint; dodges the full-repaint freeze)",
|
|
1086
|
+
],
|
|
640
1087
|
},
|
|
641
1088
|
racing: {
|
|
642
1089
|
main: "templates/racing.c", runtime: C64_RUNTIME, runtimeDirs: C64_VENDOR_DIRS,
|
|
643
1090
|
lang: C64_LANG, ext: ".prg",
|
|
644
|
-
describe: "
|
|
1091
|
+
describe: "VAPOR VECTOR — complete C64 top-down vertical road racer: title/1P-race/2P-split-lane-versus shell, vertical hardware scroll via $D011 YSCROLL fine-Y + software coarse row-shift with the static-color-texture trick (coarse shift touches only screen RAM → real-time), raster-IRQ split fixed HUD over the moving road, player cars as VIC-II hardware sprites. 1P: four lanes, UP/FIRE accelerate + DOWN brake (speed 1-5), 3 crashes end the run, best DISTANCE; 2P: real simultaneous split-lane versus (P1 control port 2 left two lanes / P2 control port 1 right two), first to wreck out loses. Best DISTANCE persists via 1541 disk save when run from a .d64 (KERNAL write to drive 8, committed to the live disk; in-session only as a bare .prg). SID music + filter sweep + SFX.",
|
|
1092
|
+
players: "1-2 (2P = simultaneous split-lane versus; P1 control port 2, P2 control port 1)",
|
|
1093
|
+
sram: "1541 DISK SAVE — the honest C64 medium (no battery SRAM): the game writes a 2-byte record to a SEQ file 'HI' on drive 8 via the KERNAL (cbm_open/read/write), VICE commits it into the live .d64 (true-drive write-back). Requires running from a .d64 (state({op:exportDisk}) captures the save; reload restores it); as a bare .prg the save is a silent no-op (in-session only). (record = best distance).",
|
|
1094
|
+
mechanics: ["lane steering", "speed control 1-5 (1P)", "best-distance (in-session)", "traffic dodging + crashes", "crash lives", "2P split-lane versus"],
|
|
1095
|
+
techniques: [
|
|
1096
|
+
"vertical hardware scroll ($D011 fine-Y + coarse row-shift, static-color-texture trick)",
|
|
1097
|
+
"raster-IRQ split fixed HUD over the moving road",
|
|
1098
|
+
"VIC-II hardware sprite player cars ($D000+)",
|
|
1099
|
+
"dual joystick-port reads with keyboard-conflict idiom ($DC00/$DC01)",
|
|
1100
|
+
"SID filter sweep + multi-voice music/SFX",
|
|
1101
|
+
],
|
|
645
1102
|
},
|
|
646
1103
|
music_demo: {
|
|
647
1104
|
main: "templates/music_demo.c", runtime: C64_MUSIC_RUNTIME, runtimeDirs: C64_VENDOR_DIRS,
|
|
@@ -737,56 +1194,110 @@ TEMPLATES.snes = {
|
|
|
737
1194
|
main: "templates/shmup.c",
|
|
738
1195
|
extraSources: [
|
|
739
1196
|
{ src: "templates/shmup-data.asm", dst: "data.asm" },
|
|
1197
|
+
{ src: "templates/shmup-hdr.asm", dst: "hdr.asm" }, /* battery-SRAM cart header */
|
|
740
1198
|
],
|
|
741
1199
|
runtime: SNES_SFX_RUNTIME,
|
|
742
1200
|
runtimeDirs: SNES_PVSNESLIB_VENDOR_DIRS,
|
|
743
1201
|
lang: "C (tcc-65816 + PVSnesLib)",
|
|
744
1202
|
ext: ".sfc",
|
|
745
|
-
describe: "
|
|
1203
|
+
describe: "SOLAR BULWARK — complete SNES vertical shooter: title shell with 1P/2P co-op select, 2P SIMULTANEOUS co-op (P2 on controller 2, port-isolated), bullet/enemy pools, wave spawner, battery-SRAM hi-score at $70:0000 (bundled hdr.asm), SPC music + SFX with the init-race idiom, Mode 1 scrolling starfield.",
|
|
1204
|
+
players: "1-2 (simultaneous co-op; P2 on controller 2)",
|
|
1205
|
+
sram: "battery SRAM at $70:0000 (CARTRIDGETYPE $02 via bundled hdr.asm; magic+checksum), verified across hardReset",
|
|
1206
|
+
mechanics: ["projectile pools", "wave spawner", "AABB collision", "2P simultaneous co-op", "battery hi-score", "title/play/game-over state machine"],
|
|
1207
|
+
techniques: [
|
|
1208
|
+
"battery SRAM at $70:0000 (long-addressed asm helpers)",
|
|
1209
|
+
"SPC700 init-race avoidance",
|
|
1210
|
+
"Mode 1 BG scroll + BG text HUD",
|
|
1211
|
+
"oamSet/oamUpdate sprite pooling",
|
|
1212
|
+
],
|
|
746
1213
|
},
|
|
747
1214
|
platformer: {
|
|
748
1215
|
main: "templates/platformer.c",
|
|
749
1216
|
extraSources: [
|
|
750
1217
|
{ src: "templates/platformer-data.asm", dst: "data.asm" },
|
|
1218
|
+
{ src: "templates/platformer-hdr.asm", dst: "hdr.asm" }, /* battery-SRAM cart header */
|
|
751
1219
|
],
|
|
752
1220
|
runtime: SNES_SFX_RUNTIME,
|
|
753
1221
|
runtimeDirs: SNES_PVSNESLIB_VENDOR_DIRS,
|
|
754
1222
|
lang: "C (tcc-65816 + PVSnesLib)",
|
|
755
1223
|
ext: ".sfc",
|
|
756
|
-
describe: "
|
|
1224
|
+
describe: "CRAG CAPER — side-scrolling platformer to the full contract: subpixel gravity/jump physics, one-way platforms, pits + spikes, coins + distance scoring, alternating 2P turns (P2 on controller 2, per-player score and lives, GO-banner handoffs), battery-SRAM hi-score at $70:0000 (bundled hdr.asm, survives power cycles), SPC music + SFX, two-layer split (fixed HUD text layer over the scrolling level — no raster tricks needed on SNES, taught vs the NES sprite-0 idiom).",
|
|
1225
|
+
players: "1-2 (alternating turns; P2 on controller 2)",
|
|
1226
|
+
sram: "battery SRAM at $70:0000 (CARTRIDGETYPE $02 via bundled hdr.asm; magic+checksum), verified across hardReset",
|
|
1227
|
+
mechanics: ["gravity-jump physics (sub-pixel)", "one-way platform collision", "one-way camera + world scroll", "pits + spike hazards", "coins + distance scoring", "alternating 2P turns"],
|
|
1228
|
+
techniques: [
|
|
1229
|
+
"two-layer split (fixed HUD BG over scrolling level)",
|
|
1230
|
+
"battery SRAM at $70:0000 (long-addressed asm helpers)",
|
|
1231
|
+
"SPC700 init-race avoidance",
|
|
1232
|
+
"telemetry block for headless verification",
|
|
1233
|
+
],
|
|
757
1234
|
},
|
|
758
1235
|
puzzle: {
|
|
759
1236
|
main: "templates/puzzle.c",
|
|
760
1237
|
extraSources: [
|
|
761
1238
|
{ src: "templates/puzzle-data.asm", dst: "data.asm" },
|
|
1239
|
+
{ src: "templates/puzzle-hdr.asm", dst: "hdr.asm" }, /* battery-SRAM cart header */
|
|
762
1240
|
],
|
|
763
1241
|
runtime: SNES_SFX_RUNTIME,
|
|
764
1242
|
runtimeDirs: SNES_PVSNESLIB_VENDOR_DIRS,
|
|
765
1243
|
lang: "C (tcc-65816 + PVSnesLib)",
|
|
766
1244
|
ext: ".sfc",
|
|
767
|
-
describe: "
|
|
1245
|
+
describe: "JEWEL JOUST — falling-trio match-3 to the full contract: 1P marathon + 2P SIMULTANEOUS split-board versus with garbage attacks (random matchable rows with one gap — a skilled victim digs out), 4-direction clears with cascade chains, battery-SRAM hi-score at $70:0000 (bundled hdr.asm, survives power cycles), SPC music + SFX, animated title jewel stripe.",
|
|
1246
|
+
players: "1-2 (2P = simultaneous versus, split boards with garbage attacks)",
|
|
1247
|
+
sram: "battery SRAM at $70:0000 (CARTRIDGETYPE $02 via bundled hdr.asm; magic+checksum), verified across hardReset",
|
|
1248
|
+
mechanics: ["falling-trio control", "match-3 in 4 directions", "cascade chains", "garbage attack rows", "split-board versus", "battery hi-score"],
|
|
1249
|
+
techniques: [
|
|
1250
|
+
"battery SRAM at $70:0000 (long-addressed asm helpers)",
|
|
1251
|
+
"SPC700 init-race avoidance",
|
|
1252
|
+
"BG tilemap board repaints + frozen-board game-over",
|
|
1253
|
+
"telemetry block for headless verification",
|
|
1254
|
+
],
|
|
768
1255
|
},
|
|
769
1256
|
sports: {
|
|
770
1257
|
main: "templates/sports.c",
|
|
771
1258
|
extraSources: [
|
|
772
1259
|
{ src: "templates/sports-data.asm", dst: "data.asm" },
|
|
1260
|
+
{ src: "templates/sports-hdr.asm", dst: "hdr.asm" }, /* battery-SRAM cart header */
|
|
773
1261
|
],
|
|
774
1262
|
runtime: SNES_SFX_RUNTIME,
|
|
775
1263
|
runtimeDirs: SNES_PVSNESLIB_VENDOR_DIRS,
|
|
776
1264
|
lang: "C (tcc-65816 + PVSnesLib)",
|
|
777
1265
|
ext: ".sfc",
|
|
778
|
-
describe: "
|
|
1266
|
+
describe: "NET SURGE — complete versus court game: title shell with 1P-vs-CPU and 2P simultaneous versus (padsCurrent(0)/(1)), first to 5 with a result screen, beatable CPU, PRNG rally spin (deterministic rallies provably end), battery-SRAM best-CPU-win-streak record, SPC music + SFX with the init-race idiom.",
|
|
1267
|
+
players: "1-2 (1P vs CPU / 2P simultaneous versus)",
|
|
1268
|
+
sram: "battery SRAM at $70:0000 (CARTRIDGETYPE $02 via bundled hdr.asm; magic+checksum), verified across hardReset",
|
|
1269
|
+
mechanics: ["versus match flow (first-to-5, result screen)", "beatable CPU", "2P simultaneous input on both pads", "PRNG rally spin", "persistent best streak"],
|
|
1270
|
+
techniques: [
|
|
1271
|
+
"battery SRAM at $70:0000 (long-addressed asm helpers)",
|
|
1272
|
+
"SPC700 init-race avoidance (WaitForVBlank before first command)",
|
|
1273
|
+
"BG text HUD",
|
|
1274
|
+
"PRNG tick to break deterministic-rally limit cycles",
|
|
1275
|
+
],
|
|
779
1276
|
},
|
|
780
1277
|
racing: {
|
|
781
1278
|
main: "templates/racing.c",
|
|
782
1279
|
extraSources: [
|
|
783
1280
|
{ src: "templates/racing-data.asm", dst: "data.asm" },
|
|
1281
|
+
{ src: "templates/racing-hdr.asm", dst: "hdr.asm" }, /* battery-SRAM cart header — without it saves silently don't persist */
|
|
784
1282
|
],
|
|
785
1283
|
runtime: SNES_SFX_RUNTIME,
|
|
786
1284
|
runtimeDirs: SNES_PVSNESLIB_VENDOR_DIRS,
|
|
787
1285
|
lang: "C (tcc-65816 + PVSnesLib)",
|
|
788
1286
|
ext: ".sfc",
|
|
789
|
-
describe: "
|
|
1287
|
+
describe: "EMBER CIRCUIT — the Mode 7 racer: a rotating-perspective ground plane (per-scanline matrix via 5 HDMA channels + a hardware-multiply table builder rebuilt every frame) — steer to yaw the camera and the whole world swings around your car. Ring circuit with lap timing, 1P time trial, 2P relay duel (P2 on controller 2), battery-SRAM best time (header-declared, survives power cycles), SPC music + surface SFX, Mode-1 HUD strip split above the Mode 7 ground.",
|
|
1288
|
+
players: "1-2 (relay duel — P1 laps, then P2 on controller 2; lower time wins)",
|
|
1289
|
+
sram: "battery SRAM at $70:0000 (CARTRIDGETYPE $02 via the bundled hdr.asm; magic+checksum, magic written last), verified across hardReset",
|
|
1290
|
+
mechanics: ["heading+speed driving model (fixed-point sin table)", "ring-track surface model (per-row half-width tables)", "quadrant lap counter", "lap timing + DNF cap", "persistent best time (torn-write-safe)", "title/ready/race/result state machine"],
|
|
1291
|
+
techniques: [
|
|
1292
|
+
"Mode 7 rotating perspective (HDMA matrix per 2-line band)",
|
|
1293
|
+
"BGMODE mid-frame split (Mode 1 HUD over Mode 7 ground)",
|
|
1294
|
+
"double-buffered HDMA tables flipped in vblank",
|
|
1295
|
+
"S-CPU hardware multiplier from asm",
|
|
1296
|
+
"Mode 7 VMAIN low/high byte streams (dmaCopyVram7)",
|
|
1297
|
+
"write-twice M7 register protocol",
|
|
1298
|
+
"HDMA-vs-OAM-DMA channel budgeting (PVSnesLib NMI owns ch 7)",
|
|
1299
|
+
"SPC700 driver init-race avoidance",
|
|
1300
|
+
],
|
|
790
1301
|
},
|
|
791
1302
|
// R46: continuous-music demo on the SPC700 driver. Showcases
|
|
792
1303
|
// sfx_music_play / sfx_music_stop alongside the existing sfx_play
|
|
@@ -915,7 +1426,18 @@ TEMPLATES.genesis = {
|
|
|
915
1426
|
runtimeDirs: SGDK_RUNTIME_DIRS,
|
|
916
1427
|
lang: SGDK_LANG,
|
|
917
1428
|
ext: ".bin",
|
|
918
|
-
describe: "
|
|
1429
|
+
describe: "PULSAR RAMPART — complete vertical shooter: title shell (1P/2P co-op select), 2P SIMULTANEOUS co-op (P2 on controller 2, palette-swap ship, shared lives + score), bullet/enemy pools on fixed SAT slots, wave spawner, SRAM hi-score, PSG music + SFX, and the Genesis vertical-shooter signature: VSCROLL_COLUMN per-column scroll — a three-depth falling starfield from one plane, under a hardware-fixed WINDOW-plane HUD.",
|
|
1430
|
+
players: "1-2 (simultaneous co-op; P2 on controller 2, shared lives + score)",
|
|
1431
|
+
sram: "header-declared cartridge SRAM at $200000 odd bytes (hi-score magic+checksum record), verified across hardReset",
|
|
1432
|
+
mechanics: ["object pools (bullets/enemies)", "wave spawner", "autofire cooldown", "2P simultaneous co-op", "shared-lives arcade scoring", "SRAM hi-score save"],
|
|
1433
|
+
techniques: [
|
|
1434
|
+
"per-column vertical scroll (VSCROLL_COLUMN three-depth starfield)",
|
|
1435
|
+
"window-plane fixed HUD",
|
|
1436
|
+
"sprite palette-swap second player",
|
|
1437
|
+
"cartridge SRAM via the $A130F1 mapper gate",
|
|
1438
|
+
"DMA_QUEUE vblank batching",
|
|
1439
|
+
"fixed SAT slot pooling (VDP_linkSprites chain)",
|
|
1440
|
+
],
|
|
919
1441
|
},
|
|
920
1442
|
platformer: {
|
|
921
1443
|
main: "templates/platformer.c",
|
|
@@ -923,7 +1445,18 @@ TEMPLATES.genesis = {
|
|
|
923
1445
|
runtimeDirs: SGDK_RUNTIME_DIRS,
|
|
924
1446
|
lang: SGDK_LANG,
|
|
925
1447
|
ext: ".bin",
|
|
926
|
-
describe: "
|
|
1448
|
+
describe: "CINDER SPRINT — complete side-scrolling platformer: title/1P/2P-alternating-turns shell, coins + distance scoring, SRAM hi-score, PSG music + SFX, and the Genesis signature dual-plane parallax (HSCROLL_TILE strip bands: plane A 1:1, plane B sky 1/8 + mountains 1/2) under a hardware-fixed WINDOW-plane HUD. Endless 512-px looping world, zero per-frame tilemap writes.",
|
|
1449
|
+
players: "1-2 (alternating turns; P2 on controller 2)",
|
|
1450
|
+
sram: "header-declared cartridge SRAM at $200000 odd bytes (hi-score magic+checksum record)",
|
|
1451
|
+
mechanics: ["scrolling camera", "gravity + one-way platform collision", "coin pickups + hazards", "distance scoring", "2P alternating turns", "SRAM hi-score save"],
|
|
1452
|
+
techniques: [
|
|
1453
|
+
"dual-plane parallax (HSCROLL_TILE strip bands)",
|
|
1454
|
+
"window-plane fixed HUD",
|
|
1455
|
+
"cartridge SRAM via the $A130F1 mapper gate",
|
|
1456
|
+
"DMA_QUEUE vblank batching",
|
|
1457
|
+
"SAT link-chain sprites (VDP_linkSprites)",
|
|
1458
|
+
"seamless 512-px plane-wrap camera",
|
|
1459
|
+
],
|
|
927
1460
|
},
|
|
928
1461
|
two_plane_parallax: {
|
|
929
1462
|
main: "templates/two_plane_parallax.c",
|
|
@@ -939,7 +1472,16 @@ TEMPLATES.genesis = {
|
|
|
939
1472
|
runtimeDirs: SGDK_RUNTIME_DIRS,
|
|
940
1473
|
lang: SGDK_LANG,
|
|
941
1474
|
ext: ".bin",
|
|
942
|
-
describe: "
|
|
1475
|
+
describe: "SHARD SIEGE — falling-trio match-3 to the full contract: 1P marathon with levels and cascade chains; 2P simultaneous split-board versus where chains send garbage rows (both wells update every frame — the Genesis has the VDP bandwidth, taught against the NES's 16-entry vblank budget). Whole-well repaints go as ONE DMA-queued rect. Battery-SRAM hi-score under a WINDOW-plane HUD, PSG music + SFX.",
|
|
1476
|
+
players: "1-2 (2P = simultaneous versus, split boards with garbage attacks)",
|
|
1477
|
+
sram: "header-declared cartridge SRAM at $200000 odd bytes (hi-score magic+checksum), verified across hardReset",
|
|
1478
|
+
mechanics: ["falling-trio control", "match-3 in 4 directions", "cascade chains with multipliers", "garbage attack rows", "levels", "split-board versus"],
|
|
1479
|
+
techniques: [
|
|
1480
|
+
"whole-well repaint as one DMA-queued tilemap rect",
|
|
1481
|
+
"window-plane fixed HUD (layout-change row clear)",
|
|
1482
|
+
"cartridge SRAM via the $A130F1 mapper gate",
|
|
1483
|
+
"PSG music + SFX",
|
|
1484
|
+
],
|
|
943
1485
|
},
|
|
944
1486
|
sports: {
|
|
945
1487
|
main: "templates/sports.c",
|
|
@@ -947,7 +1489,17 @@ TEMPLATES.genesis = {
|
|
|
947
1489
|
runtimeDirs: SGDK_RUNTIME_DIRS,
|
|
948
1490
|
lang: SGDK_LANG,
|
|
949
1491
|
ext: ".bin",
|
|
950
|
-
describe: "
|
|
1492
|
+
describe: "VOLT VOLLEY — complete versus court game: title shell with 1P-vs-CPU and 2P simultaneous versus (P2 on controller 2), first to 5 with a result screen, beatable half-speed CPU, PRNG spin so rallies never loop, battery-SRAM best-CPU-win-streak record under a hardware-fixed WINDOW-plane HUD, PSG music + SFX.",
|
|
1493
|
+
players: "1-2 (1P vs CPU / 2P simultaneous versus)",
|
|
1494
|
+
sram: "header-declared cartridge SRAM at $200000 odd bytes (best win streak vs CPU, magic+checksum), verified across hardReset",
|
|
1495
|
+
mechanics: ["versus match flow (first-to-5, result screen)", "beatable CPU (speed-capped, dead zone, edge-deflection counterplay)", "2P simultaneous input", "PRNG rally spin + random serve angle", "persistent best streak"],
|
|
1496
|
+
techniques: [
|
|
1497
|
+
"window-plane fixed HUD over a plane-B band",
|
|
1498
|
+
"cartridge SRAM via the $A130F1 mapper gate",
|
|
1499
|
+
"SAT link-chain sprites + the sprite-mask x=0 footgun",
|
|
1500
|
+
"PRNG tick to break deterministic-rally limit cycles",
|
|
1501
|
+
"PSG music + SFX",
|
|
1502
|
+
],
|
|
951
1503
|
},
|
|
952
1504
|
racing: {
|
|
953
1505
|
main: "templates/racing.c",
|
|
@@ -955,7 +1507,17 @@ TEMPLATES.genesis = {
|
|
|
955
1507
|
runtimeDirs: SGDK_RUNTIME_DIRS,
|
|
956
1508
|
lang: SGDK_LANG,
|
|
957
1509
|
ext: ".bin",
|
|
958
|
-
describe: "
|
|
1510
|
+
describe: "MIRAGE MILE — complete top-down road racer: VSCROLL road plane (hardware scroll — contrast with the NES 240-wrap taught in-file), a LIVE per-scanline HSCROLL_LINE heat-haze band (the only live line-scroll demo in the example set), WINDOW HUD, 1P speed control with best-distance battery SRAM (survives power cycles), 2P simultaneous split-lane versus on controller 2, PSG music + SFX.",
|
|
1511
|
+
players: "1-2 (2P = simultaneous versus, split lanes)",
|
|
1512
|
+
sram: "header-declared cartridge SRAM at $200000 odd bytes (best distance, magic+checksum), verified across hardReset",
|
|
1513
|
+
mechanics: ["lane steering", "speed control (1P)", "traffic dodging", "crash lives", "distance scoring", "split-lane versus"],
|
|
1514
|
+
techniques: [
|
|
1515
|
+
"vertical plane scroll (hardware VSCROLL)",
|
|
1516
|
+
"per-scanline HSCROLL_LINE heat-haze band (live line scroll)",
|
|
1517
|
+
"window-plane fixed HUD",
|
|
1518
|
+
"cartridge SRAM via the $A130F1 mapper gate",
|
|
1519
|
+
"PSG music + SFX",
|
|
1520
|
+
],
|
|
959
1521
|
},
|
|
960
1522
|
shmup_2p: {
|
|
961
1523
|
main: "templates/shmup_2p.c",
|
|
@@ -1011,27 +1573,76 @@ TEMPLATES.lynx = {
|
|
|
1011
1573
|
shmup: {
|
|
1012
1574
|
main: "templates/shmup.c", runtime: LYNX_RUNTIME, runtimeDirs: LYNX_VENDOR_DIRS,
|
|
1013
1575
|
lang: LYNX_LANG, ext: ".lnx",
|
|
1014
|
-
describe: "
|
|
1576
|
+
describe: "VOID PLUNGE — complete Lynx depth-dive shooter: title shell with attract demo, in-session hi-score, and the Lynx signature — Suzy HARDWARE sprite scaling: divers grow 2px to 20px as they approach (HSIZE/VSIZE recomputed per frame from depth, hitbox tracking the hardware scale, far kills pay more). MIKEY 4-voice music + SFX. Honest 1P (ComLynx needs a second Lynx); honest no-save (handy's libretro build exposes no SAVE_RAM — probed; cart 93Cxx EEPROM is the real-hardware path, future core round).",
|
|
1577
|
+
players: "1 (handheld — ComLynx multiplayer needs a second physical Lynx)",
|
|
1578
|
+
sram: "none — probe: regionSize(save_ram)=0, retro_get_memory(SAVE_RAM)=NULL; cart EEPROM named in-file as the real path (future core round)",
|
|
1579
|
+
mechanics: ["depth-corridor enemy dives (screen-Y as depth)", "scaled collision boxes (hitbox = hardware sprite size)", "range-weighted scoring", "projectile pool", "level ramp", "title/play/game-over state machine", "attract-mode demo"],
|
|
1580
|
+
techniques: [
|
|
1581
|
+
"Suzy hardware sprite scaling (SCB HSIZE/VSIZE 8.8, per-frame rescale)",
|
|
1582
|
+
"raw SCB authoring (literal 4bpp data, penpal remap) via tgi_ioctl(0)",
|
|
1583
|
+
"canonical TGI full-redraw loop (tgi_busy wait → draw → updatedisplay)",
|
|
1584
|
+
"vblank-deferred MIKEY voice writes",
|
|
1585
|
+
],
|
|
1015
1586
|
},
|
|
1016
1587
|
platformer: {
|
|
1017
1588
|
main: "templates/platformer.c", runtime: LYNX_RUNTIME, runtimeDirs: LYNX_VENDOR_DIRS,
|
|
1018
1589
|
lang: LYNX_LANG, ext: ".lnx",
|
|
1019
|
-
describe: "
|
|
1590
|
+
describe: "RIDGE ROMP — complete Lynx side-scrolling platformer: title shell with breathing-gem attract, gravity + Q4.4 sub-pixel jump physics, one-way platforms, lethal pits, spikes, coins + distance scoring, in-session hi-score, MIKEY 4-voice music + SFX. The Lynx signature — Suzy HARDWARE sprite scaling — runs throughout: collectible gems pulse 0.75x to 1.75x every frame (HSIZE/VSIZE in the SCB, grab box tracking the live scale) and the hero rides the same scaling SCB path. Scrolling is a software camera over a looping 384px column map (the Lynx has no hardware tilemap/scroll), redrawing the visible slice each frame. Honest 1P (ComLynx needs a second Lynx); honest no-save (handy's libretro build exposes no SAVE_RAM — probed; cart 93Cxx EEPROM is the real-hardware path, future core round).",
|
|
1591
|
+
players: "1 (handheld — ComLynx multiplayer needs a second physical Lynx)",
|
|
1592
|
+
sram: "none — probe: regionSize(save_ram)=0, retro_get_memory(SAVE_RAM)=NULL; cart EEPROM named in-file as the real path (future core round)",
|
|
1593
|
+
mechanics: ["gravity + Q4.4 sub-pixel jump physics", "one-way platforms (4-px landing window)", "lethal pits + spikes", "coins + scaling gems (live-size grab box)", "distance scoring", "software-camera scrolling over a looping column map", "title/play/game-over state machine"],
|
|
1594
|
+
techniques: [
|
|
1595
|
+
"Suzy hardware sprite scaling (SCB HSIZE/VSIZE 8.8, per-frame rescale — hero + pulsing gems)",
|
|
1596
|
+
"raw SCB authoring (literal 4bpp data, penpal remap) via tgi_ioctl(0)",
|
|
1597
|
+
"software-camera scrolling (no hardware tilemap — redraw visible slice per frame)",
|
|
1598
|
+
"canonical TGI full-redraw loop (tgi_busy wait -> draw -> updatedisplay)",
|
|
1599
|
+
"vblank-deferred MIKEY voice writes",
|
|
1600
|
+
],
|
|
1020
1601
|
},
|
|
1021
1602
|
puzzle: {
|
|
1022
1603
|
main: "templates/puzzle.c", runtime: LYNX_RUNTIME, runtimeDirs: LYNX_VENDOR_DIRS,
|
|
1023
1604
|
lang: LYNX_LANG, ext: ".lnx",
|
|
1024
|
-
describe: "
|
|
1605
|
+
describe: "QUARRY QUELL — complete Lynx falling-trio match-3: 1P marathon with cascade chains + ramping levels (29->5 frames/row), a 6x12 well + slim HUD fit into 160x102, 4-direction 3+ clears with multiplied cascade scoring, in-session hi-score, MIKEY 4-voice music + SFX. The Lynx signature Suzy HARDWARE sprite scaling is woven in: the trio renders as scaling SCB sprites and every match fires a clear-pop scale flash (well gems swell >1.0x then ease back). One 8x8 gem art recoloured per-draw via the SCB penpal (1 art block, 3 colours); the well repaints cell-by-cell each frame (no hardware tilemap). Honest 1P (ComLynx needs a 2nd Lynx); honest no-save (handy exposes no SAVE_RAM — probed; cart EEPROM is the real path).",
|
|
1606
|
+
players: "1 (handheld — ComLynx multiplayer needs a second physical Lynx)",
|
|
1607
|
+
sram: "none — probe: regionSize(save_ram)=0, retro_get_memory(SAVE_RAM)=NULL; cart EEPROM named in-file as the real path",
|
|
1608
|
+
mechanics: ["falling-trio match-3", "4-direction 3+ clears", "gravity + cascade chains (multiplied score)", "ramping levels", "session hi-score", "title/play/game-over state machine"],
|
|
1609
|
+
techniques: [
|
|
1610
|
+
"Suzy hardware sprite scaling (SCB clear-pop flash on every match)",
|
|
1611
|
+
"one art block, 3 colours via SCB penpal recolour",
|
|
1612
|
+
"cell-by-cell well repaint (no hardware tilemap)",
|
|
1613
|
+
"canonical TGI full-redraw loop (tgi_busy -> draw -> updatedisplay)",
|
|
1614
|
+
"vblank-deferred MIKEY voice writes",
|
|
1615
|
+
],
|
|
1025
1616
|
},
|
|
1026
1617
|
sports: {
|
|
1027
1618
|
main: "templates/sports.c", runtime: LYNX_RUNTIME, runtimeDirs: LYNX_VENDOR_DIRS,
|
|
1028
1619
|
lang: LYNX_LANG, ext: ".lnx",
|
|
1029
|
-
describe: "
|
|
1620
|
+
describe: "PULSE PARRY — complete Lynx versus court game fit to 160x102: 1P vs a beatable chase-AI CPU (deflect at the paddle edge to out-angle it), first-to-5 -> result screen, a PRNG +/-1 rally spin so an idle match provably ENDS, in-session win-streak record, MIKEY 4-voice music + SFX. The Lynx signature Suzy HARDWARE sprite scaling is woven in two ways: the ball is a scaling SCB sprite whose HSIZE/VSIZE tracks its speed (fast volleys loom larger), and the result screen pops the winner glyph to ~2.0x then eases back. Honest 1P (ComLynx needs a second physical Lynx); honest no-save (handy exposes no SAVE_RAM — probed; cart EEPROM is the real path).",
|
|
1621
|
+
players: "1 (handheld — ComLynx multiplayer needs a second physical Lynx)",
|
|
1622
|
+
sram: "none — probe: regionSize(save_ram)=0, retro_get_memory(SAVE_RAM)=NULL; in-session win-streak; cart EEPROM named in-file as the real path",
|
|
1623
|
+
mechanics: ["paddle vs ball court play", "edge-deflection angle", "beatable chase-AI CPU", "first-to-5 match flow + result screen", "PRNG rally spin (no limit cycle)", "in-session win-streak record"],
|
|
1624
|
+
techniques: [
|
|
1625
|
+
"Suzy hardware sprite scaling (ball scales with speed + result-screen pop)",
|
|
1626
|
+
"raw SCB authoring via tgi_ioctl(0)",
|
|
1627
|
+
"canonical TGI full-redraw loop (tgi_busy -> draw -> updatedisplay)",
|
|
1628
|
+
"xorshift16 PRNG to break deterministic-rally limit cycles",
|
|
1629
|
+
"vblank-deferred MIKEY voice writes",
|
|
1630
|
+
],
|
|
1030
1631
|
},
|
|
1031
1632
|
racing: {
|
|
1032
1633
|
main: "templates/racing.c", runtime: LYNX_RUNTIME, runtimeDirs: LYNX_VENDOR_DIRS,
|
|
1033
1634
|
lang: LYNX_LANG, ext: ".lnx",
|
|
1034
|
-
describe: "
|
|
1635
|
+
describe: "DEPTH DODGE — complete Lynx top-down vertical road racer fit to 160x102: title shell with an approaching-car attract, 1P endless run with LEFT/RIGHT lane steering + UP/DOWN speed control (1-5), 3 lives, best-distance record, MIKEY 4-voice music + SFX. The Lynx signature Suzy HARDWARE sprite scaling is the CORE mechanic — obstacle cars enter tiny at the horizon and SWELL toward you as they approach (HSIZE/VSIZE recomputed per frame from screen-Y, the hitbox tracking the live hardware scale), an OutRun-ish pseudo-3D depth built from honest sprite scaling, NOT Mode-7. Result screen pops the glyph to ~2.0x then eases back. The road has no hardware tilemap/scroll: the full-redraw loop repaints it each frame and the lane-dash phase animation IS the scroll. Honest 1P (ComLynx needs a second physical Lynx); honest no-save (handy exposes no SAVE_RAM — probed; cart EEPROM is the real path).",
|
|
1636
|
+
players: "1 (handheld — ComLynx multiplayer needs a second physical Lynx)",
|
|
1637
|
+
sram: "none — probe: regionSize(save_ram)=0, retro_get_memory(SAVE_RAM)=NULL; in-session best distance; cart EEPROM named in-file as the real path",
|
|
1638
|
+
mechanics: ["3-lane top-down racing", "lane steering + speed control (1-5)", "depth-scaled approaching obstacles (hitbox = hardware sprite size)", "distance scoring", "3 crashes end the run", "in-session best-distance record", "attract-mode demo"],
|
|
1639
|
+
techniques: [
|
|
1640
|
+
"Suzy hardware sprite scaling for pseudo-3D depth (SCB HSIZE/VSIZE 8.8, per-frame rescale from screen-Y)",
|
|
1641
|
+
"raw SCB authoring (literal 4bpp data, penpal recolour) via tgi_ioctl(0)",
|
|
1642
|
+
"phase-animated road scroll (no hardware tilemap/scroll — redraw + dash phase per frame)",
|
|
1643
|
+
"canonical TGI full-redraw loop (tgi_busy wait -> draw -> updatedisplay)",
|
|
1644
|
+
"vblank-deferred MIKEY voice writes",
|
|
1645
|
+
],
|
|
1035
1646
|
},
|
|
1036
1647
|
};
|
|
1037
1648
|
|
|
@@ -1119,7 +1730,18 @@ TEMPLATES.gba = {
|
|
|
1119
1730
|
runtimeDirs: GBA_LIBTONC_RUNTIME_DIRS,
|
|
1120
1731
|
lang: GBA_TONC_LANG,
|
|
1121
1732
|
ext: ".gba",
|
|
1122
|
-
describe: "
|
|
1733
|
+
describe: "GYRE GUNNER — vertical shooter built around the GBA's affine hardware: a rotating, zoom-pulsing vortex backdrop (affine BG2, Mode 1, the 8.8 matrix + reference-point pivot taught register-by-register) and a spinning, scale-pulsing 32x32 boss (OAM affine slot 0, double-size flag). Waves gate the boss fight; hi-score persists in cartridge SRAM ('SRAM_V' marker, byte-wide bus discipline), verified across power cycles. 1P (handheld — link-cable 2P not emulatable single-instance).",
|
|
1734
|
+
players: "1 (handheld; link-cable 2P not emulatable single-instance)",
|
|
1735
|
+
sram: "cartridge SRAM at 0x0E000000 ('SRAM_V' ROM marker for save-type detection; magic+checksum record), verified across hardReset",
|
|
1736
|
+
mechanics: ["projectile pools", "wave spawner", "AABB collision", "affine boss with HP + sine strafe + minions", "SRAM-persistent hi-score", "title/play/game-over state machine"],
|
|
1737
|
+
techniques: [
|
|
1738
|
+
"affine background (BG2PA-PD 8.8 matrix + BG2X/Y centered pivot, Mode 1)",
|
|
1739
|
+
"affine sprite (OAM affine slots, double-size flag)",
|
|
1740
|
+
"8bpp BG tiles + 1-byte affine map via VRAM-safe staging",
|
|
1741
|
+
"TTE palbank-15 coexistence (8bpp palette footgun)",
|
|
1742
|
+
"PSG music loop + SFX channel discipline",
|
|
1743
|
+
"lu_sin/lu_cos fixed-point math",
|
|
1744
|
+
],
|
|
1123
1745
|
},
|
|
1124
1746
|
platformer: {
|
|
1125
1747
|
main: "templates/platformer.c",
|
|
@@ -1127,7 +1749,17 @@ TEMPLATES.gba = {
|
|
|
1127
1749
|
runtimeDirs: GBA_LIBTONC_RUNTIME_DIRS,
|
|
1128
1750
|
lang: GBA_TONC_LANG,
|
|
1129
1751
|
ext: ".gba",
|
|
1130
|
-
describe: "
|
|
1752
|
+
describe: "GEAR GROTTO — complete GBA side-scrolling platformer: press-start title with battery-persistent cartridge-SRAM hi-score ('SRAM_V' marker, byte-wide bus, magic+checksum, verified across power cycles), gravity + Q.4 sub-pixel jump physics, one-way platforms, lethal pits, coins + distance scoring, DMA/PSG music + SFX. The GBA signature is an AFFINE OBJ hazard — a spinning, scale-pulsing 32x32 gear (OAM affine slot 0, double-size, 8.8 matrix taught register-by-register). The scrolling tile level is a Mode-0 64x32 BG that wraps in hardware at 512 px (cam & 511 / col & 63) for a seamlessly looping endless run under a fixed TTE HUD. 1P by design — link-cable 2P can't be emulated single-instance (stated honestly in-file).",
|
|
1753
|
+
players: "1 (handheld — link-cable 2P not emulatable single-instance)",
|
|
1754
|
+
sram: "cartridge SRAM hi-score at 0x0E000000 ('SRAM_V' marker, byte-wide bus, magic+checksum; survives power cycles)",
|
|
1755
|
+
mechanics: ["gravity-jump physics (Q.4 fixed point)", "one-way platform collision via column map", "endless one-way runner camera", "lethal pits", "coin pickup + distance scoring", "spinning affine gear hazard"],
|
|
1756
|
+
techniques: [
|
|
1757
|
+
"OAM affine sprite (slot 0, double-size, 8.8 matrix)",
|
|
1758
|
+
"hardware-wrapping 64x32 BG as a seamless looping world (cam & 511)",
|
|
1759
|
+
"world-anchored sprite objects recycled ahead of the camera",
|
|
1760
|
+
"cartridge SRAM hi-score (SRAM_V marker + byte-wide bus)",
|
|
1761
|
+
"TTE HUD on a fixed second BG",
|
|
1762
|
+
],
|
|
1131
1763
|
},
|
|
1132
1764
|
puzzle: {
|
|
1133
1765
|
main: "templates/puzzle.c",
|
|
@@ -1135,7 +1767,17 @@ TEMPLATES.gba = {
|
|
|
1135
1767
|
runtimeDirs: GBA_LIBTONC_RUNTIME_DIRS,
|
|
1136
1768
|
lang: GBA_TONC_LANG,
|
|
1137
1769
|
ext: ".gba",
|
|
1138
|
-
describe: "
|
|
1770
|
+
describe: "FACET FALL — complete GBA falling-jewel match-3: press-start title, 1P marathon (handheld — link-cable 2P not emulatable single-instance), falling-trio with 4-direction clears, cascade chains, levels that speed the fall, vivid faceted jewels (15-bit palette, one shape remapped to 3 colour slices), DMA/PSG music + SFX, persistent cartridge-SRAM hi-score ('SRAM_V' marker, byte-wide bus, magic+checksum, verified across power cycles). Teaches the BG0-tilemap well + the no-vblank-queue-famine repaint contrast vs the NES.",
|
|
1771
|
+
players: "1 (handheld — link-cable 2P not emulatable single-instance)",
|
|
1772
|
+
sram: "cartridge SRAM hi-score at 0x0E000000 ('SRAM_V' marker, byte-wide bus, magic+checksum; survives power cycles)",
|
|
1773
|
+
mechanics: ["falling-trio control", "match-3 in 4 directions", "cascade chains with multipliers", "levels that speed the fall", "battery hi-score"],
|
|
1774
|
+
techniques: [
|
|
1775
|
+
"BG0 tilemap board (faceted jewels via 15-bit palette slices)",
|
|
1776
|
+
"full-tilemap repaint (no vblank queue needed — GBA bandwidth)",
|
|
1777
|
+
"cartridge SRAM hi-score (SRAM_V marker + byte-wide bus)",
|
|
1778
|
+
"libtonc key_hit/key_held edge input",
|
|
1779
|
+
"headless decode from VRAM/OAM/save_ram (GBA C globals not host-readable)",
|
|
1780
|
+
],
|
|
1139
1781
|
},
|
|
1140
1782
|
sports: {
|
|
1141
1783
|
main: "templates/sports.c",
|
|
@@ -1143,7 +1785,18 @@ TEMPLATES.gba = {
|
|
|
1143
1785
|
runtimeDirs: GBA_LIBTONC_RUNTIME_DIRS,
|
|
1144
1786
|
lang: GBA_TONC_LANG,
|
|
1145
1787
|
ext: ".gba",
|
|
1146
|
-
describe: "Pong
|
|
1788
|
+
describe: "RALLY ROVER — complete GBA versus court game (Pong lineage): press-start title, 1P vs a beatable CPU (chases at a third your speed with a dead-zone — steep edge-deflections beat it), first-to-5 match flow into a result screen, a PRNG +/-1 rally spin so an idle match provably ENDS (the deterministic-versus footgun, taught in-file), DMA/PSG music + SFX, vivid 15-bit court (blue vs red teams via OBJ palbank, white net/rails/ball). Persistent RECORD = longest win streak vs the CPU in cartridge SRAM ('SRAM_V' marker, byte-wide bus, magic+checksum, verified across power cycles). KEY IDIOM: the score is surfaced onto hardware as BG score-pip tiles so headless verification reads it from VRAM (GBA C globals are not host-readable). 1P by design — link-cable 2P not emulatable single-instance (stated honestly in-file).",
|
|
1789
|
+
players: "1 (handheld — link-cable 2P not emulatable single-instance)",
|
|
1790
|
+
sram: "cartridge SRAM record at 0x0E000000 — longest CPU-mode win streak ('SRAM_V' marker, byte-wide bus, magic+checksum; survives power cycles)",
|
|
1791
|
+
mechanics: ["versus match flow (first-to-5, result screen)", "beatable CPU opponent (speed-capped ball chase with dead zone)", "edge-hit ball deflection with PRNG spin", "serve pause + alternating serve angle", "battery win-streak record"],
|
|
1792
|
+
techniques: [
|
|
1793
|
+
"BG0 score-pip HUD (score surfaced to VRAM for headless decode)",
|
|
1794
|
+
"one paddle tile, two team colours via OBJ palbank",
|
|
1795
|
+
"xorshift16 PRNG spin to break deterministic-rally limit cycles",
|
|
1796
|
+
"cartridge SRAM record (SRAM_V marker + byte-wide bus)",
|
|
1797
|
+
"TTE HUD/labels on a fixed second BG",
|
|
1798
|
+
"headless decode from OAM/VRAM/save_ram (GBA C globals not host-readable)",
|
|
1799
|
+
],
|
|
1147
1800
|
},
|
|
1148
1801
|
racing: {
|
|
1149
1802
|
main: "templates/racing.c",
|
|
@@ -1151,7 +1804,17 @@ TEMPLATES.gba = {
|
|
|
1151
1804
|
runtimeDirs: GBA_LIBTONC_RUNTIME_DIRS,
|
|
1152
1805
|
lang: GBA_TONC_LANG,
|
|
1153
1806
|
ext: ".gba",
|
|
1154
|
-
describe: "
|
|
1807
|
+
describe: "VERGE PILOT — complete GBA top-down road racer: press-start title, 1P endless race (handheld — link-cable 2P not emulatable single-instance, stated honestly in-file), lane steering + A/B throttle, traffic dodging with crash/lives, vivid 15-bit colour, DMA/PSG music + SFX, persistent best distance in cartridge SRAM ('SRAM_V' marker, byte-wide bus, magic+checksum, verified across power cycles). The GBA signature is an AFFINE BG2 ROAD (Mode 1, the console's Mode-7 trick) that recedes/scrolls, scales with speed, and banks as you steer — the 8.8 matrix taught register-by-register (a single-matrix demo; a full per-scanline perspective floor is noted as the heavier next step).",
|
|
1808
|
+
players: "1 (handheld — link-cable 2P not emulatable single-instance)",
|
|
1809
|
+
sram: "cartridge SRAM best distance at 0x0E000000 ('SRAM_V' marker, byte-wide bus, magic+checksum; survives power cycles)",
|
|
1810
|
+
mechanics: ["lane steering", "A/B throttle", "traffic dodging", "crash + lives", "best-distance persistence"],
|
|
1811
|
+
techniques: [
|
|
1812
|
+
"affine BG2 road (Mode 1, 8.8 matrix: recede + scale-with-speed + bank)",
|
|
1813
|
+
"cartridge SRAM best-distance (SRAM_V marker + byte-wide bus)",
|
|
1814
|
+
"OBJ car steering across lanes",
|
|
1815
|
+
"DMA/PSG music + SFX",
|
|
1816
|
+
"headless decode from OAM/VRAM/save_ram (GBA C globals not host-readable)",
|
|
1817
|
+
],
|
|
1155
1818
|
},
|
|
1156
1819
|
// Opt-in libgba path for users who prefer the devkitPro SDK or are
|
|
1157
1820
|
// porting an existing libgba codebase.
|
|
@@ -1240,42 +1903,102 @@ TEMPLATES.atari2600 = {
|
|
|
1240
1903
|
ext: ".a26",
|
|
1241
1904
|
describe: "Gallery-shooter (Space-Invaders-shaped) done with the RIGHT TIA objects, not playfield 'barcode' bars: P0 = double-width cannon, P1 + NUSIZ1=%011 = a row of THREE hardware-replicated invaders (one GRP1 write draws all three), M0 = the player shot. Aliens march left/right and drop a step at the edges; fire with the joystick button. The honest 2600-idiomatic way to do this genre — extend by reusing P1 lower for shields or adding M1 as an alien bomb. Verified: marches + renders cannon/aliens/shot.",
|
|
1242
1905
|
},
|
|
1243
|
-
// ── Genre
|
|
1244
|
-
// The 2600 maps cleanly onto only SOME of the five canonical genres.
|
|
1906
|
+
// ── Genre games (all five, complete to the contract) ───────────────
|
|
1245
1907
|
// shmup + sports are the console's native idioms (Space Invaders /
|
|
1246
1908
|
// Pong); racing (top-down) and platformer (single-screen) are honest,
|
|
1247
|
-
// period-correct fits. puzzle
|
|
1248
|
-
//
|
|
1249
|
-
//
|
|
1250
|
-
//
|
|
1251
|
-
//
|
|
1909
|
+
// period-correct fits. puzzle is a MEMORY MATCH-PAIRS game (TILE TWINS),
|
|
1910
|
+
// NOT match-3: a 6x12 multi-colour falling-block grid is not renderable
|
|
1911
|
+
// on a tilemap-less, one-COLUPF-per-line TIA — but a static, turn-based
|
|
1912
|
+
// match-pairs board drawn as full-width COLUPF bands IS a clean fit and
|
|
1913
|
+
// is a real puzzle. Genre id == template key (fork maps 1:1).
|
|
1252
1914
|
shmup: {
|
|
1253
1915
|
main: "templates/shmup.asm",
|
|
1254
1916
|
runtime: [],
|
|
1255
1917
|
lang: "6507 assembly (dasm)",
|
|
1256
1918
|
ext: ".a26",
|
|
1257
|
-
describe: "
|
|
1919
|
+
describe: "FLAK FRENZY — Atari 2600 gallery shooter (a genre the TIA suits) to the full contract: a drawn FLAK/FRENZY title banner (asymmetric playfield, not text mode), title/play/game-over state machine, P0 ship + P1 with NUSIZ replication for a 2x3 invader formation (one GRP1 write draws the whole replicated row) + M0 shot, TIA hardware-collision hit detection, score + in-session hi-score on the title (honest no-battery), TIA SFX + a title jingle + game-over tune on separate voices, RIOT-timer frame pacing, the SBC-#15 RESP/HMOVE positioning idiom, SWCHA per-check re-read discipline. 1P by design — a gallery-shooter kernel already spends its scanline budget on the ship + replicated formation + shot (2P alternating turns left as a cheap fork).",
|
|
1920
|
+
players: "1 (honest — gallery-shooter kernel budget; 2P alternating turns left as a fork)",
|
|
1921
|
+
sram: "none — no persistent storage on real 2600 hardware; hi-score is in-session only",
|
|
1922
|
+
mechanics: ["player ship + shot", "NUSIZ-replicated invader formation", "TIA hardware-collision hit detection", "formation march + score", "session hi-score", "title/play/game-over state machine"],
|
|
1923
|
+
techniques: [
|
|
1924
|
+
"drawn asymmetric-playfield title banner (no text mode)",
|
|
1925
|
+
"P1 + NUSIZ replication (one write draws the row)",
|
|
1926
|
+
"SBC-#15 RESP/HMOVE fine positioning",
|
|
1927
|
+
"SWCHA per-check re-read discipline",
|
|
1928
|
+
"RIOT-timer frame pacing",
|
|
1929
|
+
"score-mode dual-color HUD + multi-voice TIA music/SFX",
|
|
1930
|
+
],
|
|
1258
1931
|
},
|
|
1259
1932
|
sports: {
|
|
1260
1933
|
main: "templates/sports.asm",
|
|
1261
1934
|
runtime: [],
|
|
1262
1935
|
lang: "6507 assembly (dasm)",
|
|
1263
1936
|
ext: ".a26",
|
|
1264
|
-
describe: "
|
|
1937
|
+
describe: "RAPID RALLY — complete 2600 head-to-head paddle game: drawn title screen, 1P vs AI or 2P versus (port-1 stick drives the right paddle), rally counter, TIA SFX + title jingle, auto-return to title, IN-SESSION hi-score (no battery on real 2600 hardware — stated honestly in-source). Teaches the machine itself: 2-line kernel, RESP positioning, SWCHA re-read discipline, score-mode dual color.",
|
|
1938
|
+
players: "1-2 (1P vs AI / 2P simultaneous versus)",
|
|
1939
|
+
sram: "none — the 2600 has no persistent storage on real hardware; hi-score is in-session only",
|
|
1940
|
+
mechanics: ["paddle versus (1P AI / 2P)", "rally counter", "score-to-limit match flow", "auto title return", "session hi-score"],
|
|
1941
|
+
techniques: [
|
|
1942
|
+
"2-line kernel (racing the beam)",
|
|
1943
|
+
"RESP0/RESP1/RESBL coarse+HMxx fine positioning",
|
|
1944
|
+
"SWCHA per-check re-read (both sticks, one register)",
|
|
1945
|
+
"score-mode dual-color HUD",
|
|
1946
|
+
"TIA sound effects + title jingle",
|
|
1947
|
+
],
|
|
1265
1948
|
},
|
|
1266
1949
|
racing: {
|
|
1267
1950
|
main: "templates/racing.asm",
|
|
1268
1951
|
runtime: [],
|
|
1269
1952
|
lang: "6507 assembly (dasm)",
|
|
1270
1953
|
ext: ".a26",
|
|
1271
|
-
describe: "
|
|
1954
|
+
describe: "SWERVE STREAK — complete Atari 2600 top-down road racer to the full contract: a drawn SWERVE/STREAK title banner (asymmetric playfield, not text mode), title/play/game-over state machine, P0 player car (LEFT/RIGHT to weave) on a reflected-playfield road (PF0 rails + a PF2 centre dash that crawls DOWN every frame via a scroll-phase offset to convey speed), P1/M0 descending traffic you dodge, TIA hardware-collision crash, distance score + in-session hi-score on the title (honest no-battery), TIA engine/crash SFX + a title jingle + game-over tune, RIOT-timer pacing, SBC-#15 RESP positioning. 1P by design — the road kernel already spends its scanline budget on the road + your car + a rival + a hazard (2P best-distance alternating runs left as a fork). HONEST: the 2600 has no hardware scroll, so forward motion is the dashed line + descending traffic animated each frame.",
|
|
1955
|
+
players: "1 (honest — road kernel budget; 2P best-distance alternating runs left as a fork)",
|
|
1956
|
+
sram: "none — no persistent storage on real 2600 hardware; best distance is in-session only",
|
|
1957
|
+
mechanics: ["lane weaving", "descending traffic dodging", "TIA hardware-collision crash", "distance scoring + speed ramp", "session hi-score", "title/play/game-over state machine"],
|
|
1958
|
+
techniques: [
|
|
1959
|
+
"reflected-playfield road (PF0 rails + PF2 dash)",
|
|
1960
|
+
"dashed centre line scroll-phase (fake forward motion, no hw scroll)",
|
|
1961
|
+
"descending traffic objects (P1/M0)",
|
|
1962
|
+
"drawn asymmetric-playfield title banner",
|
|
1963
|
+
"SBC-#15 RESP/HMOVE positioning + SWCHA re-read discipline",
|
|
1964
|
+
"score-mode dual-color HUD + multi-voice TIA music/SFX",
|
|
1965
|
+
],
|
|
1272
1966
|
},
|
|
1273
1967
|
platformer: {
|
|
1274
1968
|
main: "templates/platformer.asm",
|
|
1275
1969
|
runtime: [],
|
|
1276
1970
|
lang: "6507 assembly (dasm)",
|
|
1277
1971
|
ext: ".a26",
|
|
1278
|
-
describe: "
|
|
1972
|
+
describe: "PERCH PATROL — complete Atari 2600 single-screen platformer (Pitfall! / Montezuma / Kangaroo idiom) to the full contract: a drawn PERCH/PATROL title banner (asymmetric playfield, not text mode), title/play/game-over state machine, P0 hero with fixed-point gravity + a jump arc (FIRE), land-on-ledge collision tested in CODE against a per-row playfield LEVEL table (the same table the kernel draws, so picture and physics never disagree), a bouncing coin (BL) to grab and a patrolling spike (M0) to dodge via TIA hardware-collision, score + in-session hi-score on the title (honest no-battery), TIA SFX + a title jingle + game-over tune, RIOT-timer pacing, SBC-#15 RESP/HMOVE positioning. The 2600 has NO hardware scroll/tilemap — the honest platformer is a FIXED screen (real games flip whole screens), so this one is too.",
|
|
1973
|
+
players: "1 (honest — single-screen kernel budget; an enemy/second hero is left as a fork)",
|
|
1974
|
+
sram: "none — no persistent storage on real 2600 hardware; hi-score is in-session only",
|
|
1975
|
+
mechanics: ["gravity + jump arc", "land-on-ledge collision in CODE (PF LEVEL table)", "coin pickup (BL) + spike dodge (M0) via TIA collision", "score + session hi-score", "title/play/game-over state machine"],
|
|
1976
|
+
techniques: [
|
|
1977
|
+
"per-row playfield LEVEL table as the level (code + picture share it)",
|
|
1978
|
+
"reflect-mode symmetric arena (author the left half only)",
|
|
1979
|
+
"drawn asymmetric-playfield title banner (no text mode)",
|
|
1980
|
+
"SBC-#15 RESP/HMOVE fine positioning + SWCHA re-read discipline",
|
|
1981
|
+
"RIOT-timer frame pacing",
|
|
1982
|
+
"score-mode dual-color HUD + multi-voice TIA music/SFX",
|
|
1983
|
+
],
|
|
1984
|
+
},
|
|
1985
|
+
puzzle: {
|
|
1986
|
+
main: "templates/puzzle.asm",
|
|
1987
|
+
runtime: [],
|
|
1988
|
+
lang: "6507 assembly (dasm)",
|
|
1989
|
+
ext: ".a26",
|
|
1990
|
+
describe: "TILE TWINS — complete Atari 2600 memory match-pairs puzzle to the full contract: a drawn TILE/TWINS title banner (asymmetric playfield), title/play/game-over state machine, an 8-tile board (4 pairs) drawn as a vertical stack of full-width playfield BANDS (one COLUPF per band = per-tile color, the honest way to show distinct values on a tilemap-less TIA), a joystick UP/DOWN cursor with a bright separator-bar highlight, FIRE to flip a tile, match-clears with a chime + mismatch flip-back after a pause, a move counter + session best (fewest flips), TIA SFX + a title jingle + win tune, RIOT-timer pacing. A REAL puzzle (deliberate, turn-based, memory-driven) — not a reflex game — and a clean 2600 fit since a static turn-based board needs no per-frame motion. The board is a Fisher-Yates LFSR shuffle, fair every game.",
|
|
1991
|
+
players: "1 (turn-based memory puzzle; alternating-2P fewest-flips is left as a fork)",
|
|
1992
|
+
sram: "none — no persistent storage on real 2600 hardware; best is in-session only",
|
|
1993
|
+
mechanics: ["8-tile / 4-pair memory board", "cursor move + flip", "match-clear + mismatch flip-back", "Fisher-Yates board shuffle", "move counter + session best", "title/play/win state machine"],
|
|
1994
|
+
techniques: [
|
|
1995
|
+
"playfield BANDS as tiles (per-band COLUPF = per-tile color)",
|
|
1996
|
+
"separator-bar cursor highlight (lit gap line on the selected band)",
|
|
1997
|
+
"8-bit LFSR + Fisher-Yates shuffle",
|
|
1998
|
+
"drawn asymmetric-playfield title banner (no text mode)",
|
|
1999
|
+
"SWCHA active-low direction edge-detect (Up=bit4 Down=bit5)",
|
|
2000
|
+
"score-mode dual-color HUD + multi-voice TIA music/SFX",
|
|
2001
|
+
],
|
|
1279
2002
|
},
|
|
1280
2003
|
};
|
|
1281
2004
|
|
|
@@ -1318,35 +2041,93 @@ TEMPLATES.atari7800 = {
|
|
|
1318
2041
|
runtime: ATARI7800_SFX_RUNTIME,
|
|
1319
2042
|
lang: "C (cc65)",
|
|
1320
2043
|
ext: ".a78",
|
|
1321
|
-
describe: "
|
|
2044
|
+
describe: "COMET FLURRY — dense-field meteor shooter built on MARIA's signature object quantity: 24 meteors + 2 ships + 4 shots = 30 independent display-list objects, beyond what the 2600 or stock NES can draw. 1P and 2P simultaneous co-op (shared life pool), score-scaled difficulty, two-voice TIA music with SFX voice-stealing, session hi-score (honest: the bundled prosystem core has no High Score Cart support — comments wire the real HSC path for a future core round).",
|
|
2045
|
+
players: "1-2 (simultaneous co-op; port-1 fire starts it)",
|
|
2046
|
+
sram: "none — 7800 persistence is the High Score Cart, unimplemented in the bundled core (SAVE_RAM size 0); in-session hi-score with the HSC path documented",
|
|
2047
|
+
mechanics: ["dense-swarm dodging", "twin-ship co-op (shared life pool)", "shot/meteor scoring (fast rocks pay more)", "score-scaled difficulty", "spawn-shield shimmer invulnerability", "session hi-score"],
|
|
2048
|
+
techniques: [
|
|
2049
|
+
"per-scanline display-list pool (120 one-line zones, 3-objects-per-line DMA budget)",
|
|
2050
|
+
"DLL zone repointing under DMA-off for state transitions",
|
|
2051
|
+
"RAM-canvas text via wide DL entries (no text mode)",
|
|
2052
|
+
"#pragma optimize(on) as the cc65 frame budget",
|
|
2053
|
+
"two-voice TIA music with SFX voice arbitration",
|
|
2054
|
+
"SWCHA nibble-order input idiom",
|
|
2055
|
+
],
|
|
1322
2056
|
},
|
|
1323
2057
|
platformer: {
|
|
1324
2058
|
main: "templates/platformer.c",
|
|
1325
2059
|
runtime: ATARI7800_SFX_RUNTIME,
|
|
1326
2060
|
lang: "C (cc65)",
|
|
1327
2061
|
ext: ".a78",
|
|
1328
|
-
describe: "
|
|
2062
|
+
describe: "STRATA STRIDE — complete Atari 7800 single-screen platformer built on MARIA's signature object quantity: a multi-tier arena (long floor + lethal pit + three one-way slabs) of ledges, coins and spikes all drawn as display-list objects — more than a 2600 draws. Title/1P/2P-alternating-turns shell (P2 on joystick port 1, per-player score + lives), gravity + sub-pixel jump physics, coins with an all-collected bonus, two-voice TIA music with SFX voice-stealing, session hi-score. HONEST CAVEATS: MARIA has no hardware scroll so the arena is fixed single-screen; the bundled prosystem core has no High Score Cart, so hi-score is in-session only (the real HSC path is documented in-file).",
|
|
2063
|
+
players: "1-2 (alternating turns; port-1 fire selects 2P, per-player score + lives)",
|
|
2064
|
+
sram: "none — 7800 persistence is the High Score Cart, unimplemented in the bundled core (SAVE_RAM size 0); in-session hi-score with the HSC path documented",
|
|
2065
|
+
mechanics: ["gravity + sub-pixel jump", "one-way ledges", "lethal pit + spikes", "coin + all-collected-bonus scoring", "2P alternating turns (per-player score/lives)", "session hi-score", "title/play/game-over state machine"],
|
|
2066
|
+
techniques: [
|
|
2067
|
+
"per-scanline display-list pool (120 one-line zones, 3-objects-per-line DMA budget)",
|
|
2068
|
+
"ledges-as-objects (no tilemap / no hardware scroll)",
|
|
2069
|
+
"DLL zone repointing under DMA-off for state transitions",
|
|
2070
|
+
"RAM-canvas text via wide DL entries (no text mode)",
|
|
2071
|
+
"#pragma optimize(on) as the cc65 frame budget",
|
|
2072
|
+
"two-voice TIA music with SFX voice arbitration",
|
|
2073
|
+
"SWCHA nibble-order input idiom (both ports for 2P)",
|
|
2074
|
+
],
|
|
1329
2075
|
},
|
|
1330
2076
|
puzzle: {
|
|
1331
2077
|
main: "templates/puzzle.c",
|
|
1332
2078
|
runtime: ATARI7800_SFX_RUNTIME,
|
|
1333
2079
|
lang: "C (cc65)",
|
|
1334
2080
|
ext: ".a78",
|
|
1335
|
-
describe: "
|
|
2081
|
+
describe: "PIVOT PURGE — complete Atari 7800 falling-trio match-3: title/1P-marathon/2P-simultaneous-versus shell, 4-direction clears, cascade chains with multipliers, levels (1P speed-up), 2P split-board versus with capped garbage attacks (P1 port 0 / P2 port 1, both wells falling at once), two-voice TIA music with SFX voice-stealing, session hi-score. KEY MARIA IDIOM: a puzzle well is MARIA's worst case (6 cells = 6 objects on the same 8 scanlines, double the ~3/line DMA budget), so each well row is composited into a 14-byte RAM canvas and drawn as ONE wide 5-byte object per scanline — which is what lets TWO wells fit for 2P. Well DLs rebuilt on board-change, the trio overlaid per frame (naive per-frame re-emit overran 60Hz ~19x). HONEST CAVEATS: no hardware tilemap; prosystem has no HSC, so hi-score is in-session only (HSC path documented in-file). #pragma optimize(on) is load-bearing.",
|
|
2082
|
+
players: "1-2 (2P simultaneous split-board versus; P1 port 0 / P2 port 1)",
|
|
2083
|
+
sram: "none — prosystem has no High Score Cart (SAVE_RAM size 0); in-session hi-score with the HSC path documented",
|
|
2084
|
+
mechanics: ["falling-trio match-3", "4-direction clears", "cascade chains (multiplied score)", "levels (1P speed-up)", "2P simultaneous versus split board", "garbage-row attacks", "session hi-score"],
|
|
2085
|
+
techniques: [
|
|
2086
|
+
"one-wide-object-per-well-row (frame baked into the RAM canvas, trio overlaid)",
|
|
2087
|
+
"REBUILD-vs-PATCH (well DLs built on board-change, trio per frame)",
|
|
2088
|
+
"per-scanline display-list pool + RAM3 line pushing for RAM fit",
|
|
2089
|
+
"#pragma optimize(on) as the cc65 frame budget",
|
|
2090
|
+
"two-voice TIA music with SFX voice arbitration",
|
|
2091
|
+
"SWCHA nibble-order input (both ports for 2P)",
|
|
2092
|
+
],
|
|
1336
2093
|
},
|
|
1337
2094
|
sports: {
|
|
1338
2095
|
main: "templates/sports.c",
|
|
1339
2096
|
runtime: ATARI7800_SFX_RUNTIME,
|
|
1340
2097
|
lang: "C (cc65)",
|
|
1341
2098
|
ext: ".a78",
|
|
1342
|
-
describe: "
|
|
2099
|
+
describe: "FLUX FENCE — complete Atari 7800 versus court game (Pong lineage): title/1P-vs-beatable-CPU/2P-simultaneous-versus shell (P2 on joystick port 1), first-to-5 match flow with a result screen, PRNG rally spin so an idle match always ENDS, two-voice TIA music with SFX voice-stealing, in-session record (longest 1P-vs-CPU win streak). The two paddles, the ball, and the dashed centre net are all MARIA display-list objects emitted into the per-scanline pool — a court is the sparse/easy case of the same object budget the 7800 shmup spends on a swarm. HONEST CAVEATS: no hardware tilemap; prosystem has no HSC, so the record is in-session only (the HSC path is documented in-file). #pragma optimize(on) is load-bearing.",
|
|
2100
|
+
players: "1-2 (1P vs beatable CPU; 2P simultaneous versus, P1 port 0 / P2 port 1)",
|
|
2101
|
+
sram: "none — prosystem has no High Score Cart (SAVE_RAM size 0); in-session win-streak record with the HSC path documented",
|
|
2102
|
+
mechanics: ["1P vs beatable CPU", "2P simultaneous versus", "first-to-5 match -> result screen", "PRNG rally spin (idle match ends)", "angle-deflection paddle physics", "in-session win-streak record"],
|
|
2103
|
+
techniques: [
|
|
2104
|
+
"paddles/ball/net as per-scanline display-list objects",
|
|
2105
|
+
"per-scanline display-list pool (120 one-line zones, 3-objects-per-line DMA budget)",
|
|
2106
|
+
"DLL zone repointing under DMA-off for state transitions",
|
|
2107
|
+
"RAM-canvas text via wide DL entries (no text mode)",
|
|
2108
|
+
"#pragma optimize(on) as the cc65 frame budget",
|
|
2109
|
+
"two-voice TIA music with SFX voice arbitration",
|
|
2110
|
+
"SWCHA nibble-order input (both ports for 2P)",
|
|
2111
|
+
],
|
|
1343
2112
|
},
|
|
1344
2113
|
racing: {
|
|
1345
2114
|
main: "templates/racing.c",
|
|
1346
2115
|
runtime: ATARI7800_SFX_RUNTIME,
|
|
1347
2116
|
lang: "C (cc65)",
|
|
1348
2117
|
ext: ".a78",
|
|
1349
|
-
describe: "
|
|
2118
|
+
describe: "PISTON PINCH — complete Atari 7800 top-down road racer built on MARIA's signature object quantity: a thick descending traffic stream (up to 10 cars) + player car(s), all display-list objects. Title/1P-race/2P-simultaneous-split-lane-versus shell (P2 on joystick port 1, P1 left two lanes / P2 right two), 1P speed control (UP/A gas, DOWN/B brake, speed 1-4) banking best DISTANCE, 3 crashes end the run; 2P shares one road, first to wreck out loses. Two-voice TIA music + SFX voice-stealing. KEY 7800 IDIOM: MARIA has NO scroll register, so vertical road motion is FAKED — the centre lane DASHES march downward (per-frame DLL phase repoint, no scroll) and traffic descends as objects. HONEST CAVEATS: prosystem has no High Score Cart, so best distance is in-session only (HSC path documented in-file). #pragma optimize(on) is load-bearing.",
|
|
2119
|
+
players: "1-2 (2P simultaneous split-lane versus; P1 port 0 / P2 port 1)",
|
|
2120
|
+
sram: "none — prosystem has no High Score Cart (SAVE_RAM size 0); in-session best distance with the HSC path documented",
|
|
2121
|
+
mechanics: ["top-down lane racing", "1P speed control (gas/brake)", "descending traffic dodging", "best-distance (in-session)", "2P simultaneous split-lane versus", "crash/lives rules", "title/play/game-over state machine"],
|
|
2122
|
+
techniques: [
|
|
2123
|
+
"marching-dash fake scroll (no MARIA scroll register)",
|
|
2124
|
+
"descending-traffic object stream (MARIA quantity signature)",
|
|
2125
|
+
"per-scanline display-list pool (cars-as-objects, <=3/line DMA budget)",
|
|
2126
|
+
"DLL zone repointing under DMA-off for state transitions",
|
|
2127
|
+
"RAM-canvas text via wide DL entries (no text mode)",
|
|
2128
|
+
"#pragma optimize(on) as the cc65 frame budget",
|
|
2129
|
+
"SWCHA nibble-order input (both ports for 2P)",
|
|
2130
|
+
],
|
|
1350
2131
|
},
|
|
1351
2132
|
music_demo: {
|
|
1352
2133
|
main: "templates/music_demo.c",
|
|
@@ -1754,10 +2535,21 @@ Compiles **C89**, not C99/C11. Stick to:
|
|
|
1754
2535
|
buildBlock = "```js\nbuild({ output: \"run\", platform: \"" + platform + "\", sourcePath: \"" + mainFilename + "\", frames: 240 })\n```";
|
|
1755
2536
|
}
|
|
1756
2537
|
|
|
1757
|
-
let filesSection =
|
|
2538
|
+
let filesSection = `- \`${mainFilename}\` — the game. Title screen, game loop, all the GAME LOGIC clay.\n`;
|
|
1758
2539
|
if (tmpl?.runtime) {
|
|
1759
2540
|
for (const { dst } of tmpl.runtime) {
|
|
1760
|
-
|
|
2541
|
+
if (dst === "patch-header.js") {
|
|
2542
|
+
// NOT game code — calling it a "runtime helper" implied it compiles
|
|
2543
|
+
// into the ROM and confused readers. It's a standalone sidecar tool.
|
|
2544
|
+
filesSection += `- \`${dst}\` — sidecar TOOL, not game code (never compiled into the ROM). ` +
|
|
2545
|
+
`Stamps the Nintendo logo + header/global checksums a GB ROM needs to boot ` +
|
|
2546
|
+
`(\`node patch-header.js game.gb\`) — a zero-install stand-in for RGBDS's rgbfix when you ` +
|
|
2547
|
+
`rebuild OUTSIDE romdev with stock SDCC. romdev's own builds fix the header automatically.\n`;
|
|
2548
|
+
} else if (dst.endsWith("_crt0.s")) {
|
|
2549
|
+
filesSection += `- \`${dst}\` — startup assembly (reset/interrupt vectors, RAM init; routed as the crt0 by the project build). **Load-bearing**: replacing a bundled crt0 once black-screened every project on a platform for a month. Edit with the platform TROUBLESHOOTING doc open.\n`;
|
|
2550
|
+
} else {
|
|
2551
|
+
filesSection += `- \`${dst}\` — runtime library (rendering/input/sound helpers the game calls). Yours to extend; the HARDWARE IDIOM markers inside say which parts are load-bearing.\n`;
|
|
2552
|
+
}
|
|
1761
2553
|
}
|
|
1762
2554
|
}
|
|
1763
2555
|
if (tmpl?.crt0) {
|
|
@@ -1773,7 +2565,41 @@ Compiles **C89**, not C99/C11. Stick to:
|
|
|
1773
2565
|
// source" variant, shown second.
|
|
1774
2566
|
const projectBuildBlock =
|
|
1775
2567
|
"```js\nbuild({\n output: \"project\",\n platform: \"" + platform + "\",\n path: \"" + projPath + "\",\n outputPath: \"" + name + romExt + "\",\n})\n```";
|
|
1776
|
-
const readme = `# ${title ?? name}
|
|
2568
|
+
const readme = `# ${title ?? name}
|
|
2569
|
+
|
|
2570
|
+
**A working ${platform} starting point** (${lang}) — forked from the romdev \`${platform}/${template ?? "default"}\` example. It builds, runs, and renders RIGHT NOW, before you change a line.
|
|
2571
|
+
|
|
2572
|
+
This is SCAFFOLDING, not a finished game. The gameplay is deliberately thin — treat it as placeholder and make it yours. Its value is that the hard part is already done and working: the ${platform} boot sequence, hardware init, and APIs are wired up correctly, so you evolve a running ROM instead of getting a long chain of fragile setup right from a blank file.
|
|
2573
|
+
|
|
2574
|
+
${tmpl?.describe ? tmpl.describe + "\n\n" : ""}## How to make it yours
|
|
2575
|
+
|
|
2576
|
+
Modify ONE thing at a time and re-run the build after each change — the working game is your regression oracle (it rendered before your edit; if it stops, your last edit broke it):
|
|
2577
|
+
|
|
2578
|
+
${projectBuildBlock}
|
|
2579
|
+
|
|
2580
|
+
Use \`output:"run"\` to build + load + run + screenshot in one round trip. Don't start over in a blank file — retro bring-up is a chain of fragile hardware init with no partial credit; evolve this game instead, even into a very different game.
|
|
2581
|
+
|
|
2582
|
+
## Marker legend (read before restructuring anything)
|
|
2583
|
+
|
|
2584
|
+
- \`/* ── HARDWARE IDIOM (load-bearing) ── */\` — this code dodges a documented hardware footgun (the comment says which). **Reshape your gameplay around these regions**; if you must change one, read the cited TROUBLESHOOTING entry first. Each block's header lists what it needs (interrupt hooks, memory regions, register modes) — that's also what a transplant into another game must satisfy.
|
|
2585
|
+
- \`/* ── GAME LOGIC (clay) ── */\` — enemy patterns, scoring, art, tuning. **Reshape freely** — this is where your game happens.
|
|
2586
|
+
|
|
2587
|
+
Need a technique this game doesn't have (another example does)? \`examples({op:"show", example:"<platform>/<name>", technique:"..."})\` extracts that example's marked block with its dependency header — graft it here instead of rewriting it.
|
|
2588
|
+
|
|
2589
|
+
## Files
|
|
2590
|
+
|
|
2591
|
+
${filesSection}${c89Note}<details>
|
|
2592
|
+
<summary>Alternative: build from a hand-specified source manifest (when compiling edited loose source, not a project dir)</summary>
|
|
2593
|
+
|
|
2594
|
+
${buildBlock}
|
|
2595
|
+
</details>
|
|
2596
|
+
|
|
2597
|
+
## Inspecting + playtesting
|
|
2598
|
+
|
|
2599
|
+
- Byte level: \`memory({op:"read"})\`, \`sprites({op:"inspect"})\`, \`palette({source:"live"})\`, \`background({view:"rendered"})\`.
|
|
2600
|
+
- No-vision render health: \`frame({op:"verify"})\` — "is the game actually rendering?" in one call.
|
|
2601
|
+
- Human eyes: \`playtest({op:"open"})\` — a live window that follows your rebuilds; the emulator stays available to every other tool.
|
|
2602
|
+
`;
|
|
1777
2603
|
await fs.writeFile(path.join(projPath, "README.md"), readme, "utf-8");
|
|
1778
2604
|
writtenFiles.push("README.md");
|
|
1779
2605
|
|
|
@@ -1924,62 +2750,230 @@ async function createGameCore({ platform, genre, name, path: projPath, title, ov
|
|
|
1924
2750
|
return { ...result, genre, template: templateId };
|
|
1925
2751
|
}
|
|
1926
2752
|
|
|
2753
|
+
// ── The examples tool — the fork-don't-create surface (0.29.0) ──────────────
|
|
2754
|
+
// "Scaffold" died as a concept: there are no empty frames, only complete
|
|
2755
|
+
// working example games. Making a new game = forking the nearest example and
|
|
2756
|
+
// modifying it. See internal plan: the weak-model case for this is that retro
|
|
2757
|
+
// bring-up is a long conjunction of fragile steps with zero partial credit —
|
|
2758
|
+
// modifying a working game converts "get 15 things right" into "change 2
|
|
2759
|
+
// while 13 keep working", with a bisectable regression oracle.
|
|
2760
|
+
|
|
2761
|
+
const CANONICAL_GENRES = ["shmup", "platformer", "puzzle", "sports", "racing"];
|
|
2762
|
+
const HANDHELDS = new Set(["gb", "gbc", "gba", "gg", "lynx"]);
|
|
2763
|
+
|
|
2764
|
+
// Mechanics inventory per genre — what an agent learns by forking each.
|
|
2765
|
+
// (Hardware-technique anchors get added per-game as the Complete Game
|
|
2766
|
+
// Contract lands; list derives the rest from the manifest.)
|
|
2767
|
+
const GENRE_MECHANICS = {
|
|
2768
|
+
shmup: ["scrolling field", "projectile pools", "enemy waves + spawning", "collision (point/rect)", "score + lives"],
|
|
2769
|
+
platformer: ["side-scrolling camera", "gravity + jump arc", "tile collision (walk/land/fall)", "world map"],
|
|
2770
|
+
puzzle: ["grid logic", "piece falling + lock", "match detection (4-dir)", "gravity cascades + chain scoring"],
|
|
2771
|
+
sports: ["versus court", "ball physics + paddle bounce", "2P input (second pad, AI fallback)", "serve/score states"],
|
|
2772
|
+
racing: ["forward-scrolling road", "lane steering", "obstacle spawning", "speed/crash states"],
|
|
2773
|
+
};
|
|
2774
|
+
|
|
2775
|
+
// Fork guidance for genres we don't ship — points at the nearest core loop.
|
|
2776
|
+
const UNCOVERED_GENRE_GUIDANCE =
|
|
2777
|
+
"No example matches your genre exactly? Fork the NEAREST CORE LOOP and reshape it: " +
|
|
2778
|
+
"RPG/adventure → puzzle (grid + state machines) or platformer (world + camera); " +
|
|
2779
|
+
"tower defense → shmup (spawning + projectiles); " +
|
|
2780
|
+
"card/board game → puzzle (grid + turn logic); " +
|
|
2781
|
+
"beat-em-up → platformer (movement + collision); " +
|
|
2782
|
+
"pinball/breakout → sports (ball physics). " +
|
|
2783
|
+
"Fork for the core loop; read other examples (op:'show') for techniques to graft.";
|
|
2784
|
+
|
|
2785
|
+
/** "<platform>/<template>" → {platform, template}; also accepts separate args. */
|
|
2786
|
+
function resolveExampleId({ example, platform, template }) {
|
|
2787
|
+
if (example) {
|
|
2788
|
+
const m = /^([a-z0-9]+)\/(.+)$/.exec(example);
|
|
2789
|
+
if (!m) throw new Error(`examples: bad example id '${example}' — use "<platform>/<name>" (e.g. "nes/shmup"). examples({op:'list'}) shows them all.`);
|
|
2790
|
+
return { platform: m[1], template: m[2] };
|
|
2791
|
+
}
|
|
2792
|
+
if (platform && template) return { platform, template };
|
|
2793
|
+
throw new Error("examples: pass `example` (\"nes/shmup\") or `platform` + `template`.");
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
/** One list entry from a TEMPLATES manifest record (defaults derived). */
|
|
2797
|
+
function exampleEntry(platform, templateId, tmpl) {
|
|
2798
|
+
const isGame = CANONICAL_GENRES.includes(templateId);
|
|
2799
|
+
const players = tmpl.players ?? (templateId === "sports" && !HANDHELDS.has(platform) ? 2 : 1);
|
|
2800
|
+
return {
|
|
2801
|
+
example: `${platform}/${templateId}`,
|
|
2802
|
+
kind: tmpl.kind ?? (isGame ? "game" : "reference"),
|
|
2803
|
+
...(isGame ? { genre: templateId } : {}),
|
|
2804
|
+
description: tmpl.describe ?? "",
|
|
2805
|
+
mechanics: tmpl.mechanics ?? (isGame ? GENRE_MECHANICS[templateId] : []),
|
|
2806
|
+
// Hardware techniques demonstrated, each with a file + marker anchor for
|
|
2807
|
+
// op:'show' extraction — populated per-game as the contract lands.
|
|
2808
|
+
techniques: tmpl.techniques ?? [],
|
|
2809
|
+
players,
|
|
2810
|
+
sram: tmpl.sram ?? false,
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
/** Extract HARDWARE IDIOM / GAME LOGIC marked blocks from source text. */
|
|
2815
|
+
function extractMarkedBlocks(text) {
|
|
2816
|
+
const blocks = [];
|
|
2817
|
+
const re = /\/\* ── (HARDWARE IDIOM|GAME LOGIC)([^\n]*?)── \*\/([\s\S]*?)(?=\/\* ── (?:HARDWARE IDIOM|GAME LOGIC)|$)/g;
|
|
2818
|
+
let m;
|
|
2819
|
+
while ((m = re.exec(text)) !== null) {
|
|
2820
|
+
blocks.push({ kind: m[1], header: m[2].trim(), body: m[3].trimEnd() });
|
|
2821
|
+
}
|
|
2822
|
+
return blocks;
|
|
2823
|
+
}
|
|
2824
|
+
|
|
1927
2825
|
export function registerProjectTools(server, z) {
|
|
1928
2826
|
server.tool(
|
|
1929
|
-
"
|
|
1930
|
-
"
|
|
2827
|
+
"examples",
|
|
2828
|
+
"The example-game library — one buildable, rendering starting point per platform×genre, and the ONLY way to start " +
|
|
2829
|
+
"a new project: **never start from a blank file — fork the nearest example and modify it into your game, even a " +
|
|
2830
|
+
"very different game.** These are SCAFFOLDING, not showcases: the gameplay is intentionally thin (treat it as " +
|
|
2831
|
+
"placeholder and reshape it) — their value is that they already carry the platform's boot sequence, APIs, and " +
|
|
2832
|
+
"syntax wired up and WORKING, so you change 2 things while 13 keep working instead of getting 15 right from " +
|
|
2833
|
+
"nothing. (Retro bring-up is a long chain of fragile hardware init with zero partial credit; a working game is a " +
|
|
2834
|
+
"regression oracle.) `op`: 'list' | 'fork' | 'show' | " +
|
|
1931
2835
|
"'snippets' | 'copySnippets'.\n" +
|
|
1932
|
-
"'
|
|
1933
|
-
"
|
|
1934
|
-
"
|
|
1935
|
-
"
|
|
1936
|
-
"
|
|
1937
|
-
"
|
|
1938
|
-
"
|
|
1939
|
-
"
|
|
1940
|
-
"
|
|
1941
|
-
"
|
|
2836
|
+
"'list': the mechanics map — every example with its kind (game vs minimal reference), mechanics inventory, " +
|
|
2837
|
+
"hardware techniques demonstrated (with file+marker anchors for op:'show'), players, SRAM. Use it to pick the " +
|
|
2838
|
+
"example whose CORE LOOP is nearest your game; fork that one, then op:'show' OTHER examples for techniques to graft.\n" +
|
|
2839
|
+
"'fork': copy an example into a NEW project dir as YOUR game — sources + every runtime file + crt0 + linker cfg + " +
|
|
2840
|
+
"README, self-contained, renamed throughout (project name, game title where the code carries one). Builds and runs " +
|
|
2841
|
+
"before you change a line. Then: modify one thing at a time, re-running build({output:'run'}) after each.\n" +
|
|
2842
|
+
"'show': read a donor example WITHOUT forking it — a whole file, or one marked technique block (extracted by its " +
|
|
2843
|
+
"HARDWARE IDIOM marker, including the dependency header that says what the block needs to survive a transplant).\n" +
|
|
2844
|
+
"'snippets'/'copySnippets': the legacy vetted-snippet library (browse/fetch/copy). Prefer forking + grafting from " +
|
|
2845
|
+
"real games; snippets remain for one-off references.",
|
|
1942
2846
|
{
|
|
1943
|
-
op: z.enum(["
|
|
1944
|
-
platform: z.string().describe("
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
snippetName: z.string().optional().describe("op=snippets mode:'get': snippet name ('read_pad') or filename ('read_pad.s')."),
|
|
2847
|
+
op: z.enum(["list", "fork", "show", "snippets", "copySnippets"]).describe("list the library; fork an example into your game; show donor source/technique without forking; legacy snippets."),
|
|
2848
|
+
platform: z.string().optional().describe("op=list: filter to one platform. op=fork/show/snippets/copySnippets: platform id (or encode it in `example`)."),
|
|
2849
|
+
example: z.string().optional().describe("op=fork/show: example id as \"<platform>/<name>\" (e.g. \"nes/shmup\", \"gb/puzzle\") — from op:'list'."),
|
|
2850
|
+
template: z.string().optional().describe("op=fork/show: example name when passing `platform` separately (alias of the id's second half)."),
|
|
2851
|
+
name: z.string().optional().describe("op=fork: YOUR game's name (project dir naming, output binary, and the in-game title where the example carries one). Required."),
|
|
2852
|
+
path: z.string().optional().describe("op=fork: absolute path where the project dir is created. Required."),
|
|
2853
|
+
title: z.string().optional().describe("op=fork: human-readable title for the README (defaults to `name`)."),
|
|
2854
|
+
overwrite: z.boolean().default(false).describe("op=fork: allow writing into an existing non-empty dir. op=copySnippets: overwrite existing files."),
|
|
2855
|
+
verbose: z.boolean().default(false).describe("op=fork: echo the FULL flat file manifest incl. vendor/** (default: only the files you own + a vendorFileCount)."),
|
|
2856
|
+
file: z.string().optional().describe("op=show: which file of the example to read (default: the main source)."),
|
|
2857
|
+
technique: z.string().optional().describe("op=show: extract ONE marked technique block whose HARDWARE IDIOM header matches this string (case-insensitive substring), instead of the whole file."),
|
|
2858
|
+
// legacy snippets passthrough
|
|
2859
|
+
mode: z.enum(["list", "get", "getAll"]).default("list").describe("op=snippets: 'list' (names), 'get' (one, needs snippetName), 'getAll' (joined)."),
|
|
2860
|
+
snippetName: z.string().optional().describe("op=snippets mode:'get': snippet name."),
|
|
1958
2861
|
language: z.string().optional().describe("op=snippets/copySnippets: filter 'c' | 'asm'."),
|
|
1959
2862
|
outputPath: z.string().optional().describe("op=snippets mode:'getAll': write the joined snippets here (or inline:true)."),
|
|
1960
|
-
inline: z.boolean().default(false).describe("op=snippets mode:'getAll': return `combined`
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
include: z.array(z.string()).optional().describe("op=copySnippets: whitelist of bare snippet names to copy."),
|
|
2863
|
+
inline: z.boolean().default(false).describe("op=snippets mode:'getAll': return `combined` inline."),
|
|
2864
|
+
destinationDir: z.string().optional().describe("op=copySnippets: directory to write snippets into."),
|
|
2865
|
+
include: z.array(z.string()).optional().describe("op=copySnippets: whitelist of snippet names."),
|
|
1964
2866
|
},
|
|
1965
2867
|
safeTool(async (args) => {
|
|
1966
2868
|
switch (args.op) {
|
|
1967
|
-
case "
|
|
1968
|
-
|
|
1969
|
-
|
|
2869
|
+
case "list": {
|
|
2870
|
+
const platforms = args.platform ? [args.platform] : Object.keys(TEMPLATES);
|
|
2871
|
+
const examples = [];
|
|
2872
|
+
for (const p of platforms) {
|
|
2873
|
+
const t = TEMPLATES[p];
|
|
2874
|
+
if (!t) continue;
|
|
2875
|
+
for (const id of Object.keys(t)) examples.push(exampleEntry(p, id, t[id]));
|
|
2876
|
+
}
|
|
2877
|
+
// Games first (the forkable starting points), references after.
|
|
2878
|
+
examples.sort((a, b) => (a.kind === b.kind ? a.example.localeCompare(b.example) : a.kind === "game" ? -1 : 1));
|
|
2879
|
+
return jsonContent({
|
|
2880
|
+
count: examples.length,
|
|
2881
|
+
doctrine: "Fork the example whose CORE LOOP matches your game; op:'show' the others for techniques to graft. " +
|
|
2882
|
+
"Ranked: nearest fork alone > fork + one graft > fork + many grafts — prefer the leftmost that gets your game made.",
|
|
2883
|
+
uncoveredGenres: UNCOVERED_GENRE_GUIDANCE,
|
|
2884
|
+
examples,
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
case "fork": {
|
|
2888
|
+
const { platform, template } = resolveExampleId(args);
|
|
2889
|
+
if (!args.name || !args.path) throw new Error("examples({op:'fork'}): `name` and `path` are required (your game's name + where to create it).");
|
|
2890
|
+
if (!TEMPLATES[platform]?.[template]) {
|
|
2891
|
+
const have = TEMPLATES[platform] ? Object.keys(TEMPLATES[platform]).join(", ") : "(no examples for this platform)";
|
|
2892
|
+
throw new Error(`examples({op:'fork'}): no example '${platform}/${template}'. This platform has: ${have}.`);
|
|
2893
|
+
}
|
|
2894
|
+
const result = await createProjectImpl({
|
|
2895
|
+
platform, template, name: args.name, path: args.path, title: args.title,
|
|
2896
|
+
overwrite: args.overwrite, verbose: args.verbose,
|
|
2897
|
+
});
|
|
2898
|
+
// Rename the game THROUGH: where the example carries a GAME_TITLE
|
|
2899
|
+
// define, stamp the new name so the title screen says YOUR game
|
|
2900
|
+
// (identity transfer is the cheap defense against base-game-concept
|
|
2901
|
+
// leakage — an agent working on "CAVERN RUN" treats leftover shmup
|
|
2902
|
+
// scoring as a bug in ITS game).
|
|
2903
|
+
let titleStamped = false;
|
|
2904
|
+
try {
|
|
2905
|
+
const fs = await import("node:fs/promises");
|
|
2906
|
+
const path = await import("node:path");
|
|
2907
|
+
const stamp = String(args.name).toUpperCase().replace(/[^A-Z0-9 \-]/g, "").slice(0, 16) || "MY GAME";
|
|
2908
|
+
for (const f of result.files ?? []) {
|
|
2909
|
+
if (!/\.(c|h|s|asm)$/i.test(f)) continue;
|
|
2910
|
+
const fp = path.join(result.path, f);
|
|
2911
|
+
let src;
|
|
2912
|
+
try { src = await fs.readFile(fp, "utf-8"); } catch { continue; }
|
|
2913
|
+
const re = /(#define\s+GAME_TITLE\s+")[^"]*(")/;
|
|
2914
|
+
if (re.test(src)) {
|
|
2915
|
+
await fs.writeFile(fp, src.replace(re, `$1${stamp}$2`), "utf-8");
|
|
2916
|
+
titleStamped = true;
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
} catch { /* best-effort; the fork itself succeeded */ }
|
|
2920
|
+
return jsonContent({
|
|
2921
|
+
...result,
|
|
2922
|
+
forkedFrom: `${platform}/${template}`,
|
|
2923
|
+
template,
|
|
2924
|
+
...(CANONICAL_GENRES.includes(template) ? { genre: template } : {}),
|
|
2925
|
+
...(titleStamped ? { gameTitle: true } : {}),
|
|
2926
|
+
note: `Forked ${platform}/${template} → '${args.name}'. It builds and runs RIGHT NOW — verify with the build({output:"run"}) call in its README before changing anything, then modify ONE thing at a time, re-running after each. The README's marker legend says which regions are hardware idiom (reshape gameplay around them) vs game logic (clay).`,
|
|
2927
|
+
});
|
|
1970
2928
|
}
|
|
1971
|
-
case "
|
|
1972
|
-
|
|
1973
|
-
|
|
2929
|
+
case "show": {
|
|
2930
|
+
const { platform, template } = resolveExampleId(args);
|
|
2931
|
+
const tmpl = TEMPLATES[platform]?.[template];
|
|
2932
|
+
if (!tmpl) {
|
|
2933
|
+
const have = TEMPLATES[platform] ? Object.keys(TEMPLATES[platform]).join(", ") : "(none)";
|
|
2934
|
+
throw new Error(`examples({op:'show'}): no example '${platform}/${template}'. This platform has: ${have}.`);
|
|
2935
|
+
}
|
|
2936
|
+
const fs = await import("node:fs/promises");
|
|
2937
|
+
const path = await import("node:path");
|
|
2938
|
+
const { fileURLToPath } = await import("node:url");
|
|
2939
|
+
const baseDir = path.dirname(fileURLToPath(import.meta.url));
|
|
2940
|
+
const exDir = path.resolve(baseDir, "..", "..", "..", "examples");
|
|
2941
|
+
// Default file = the template's `main` source (relative to
|
|
2942
|
+
// examples/<platform>/). An explicit `file` resolves the same way.
|
|
2943
|
+
const rel = args.file ?? tmpl.main;
|
|
2944
|
+
if (!rel) throw new Error(`examples({op:'show'}): example '${platform}/${template}' has no default source — pass the file arg.`);
|
|
2945
|
+
const fp = path.resolve(exDir, platform, rel);
|
|
2946
|
+
if (!fp.startsWith(exDir)) throw new Error("examples({op:'show'}): file path escapes the examples directory.");
|
|
2947
|
+
let text;
|
|
2948
|
+
try { text = await fs.readFile(fp, "utf-8"); }
|
|
2949
|
+
catch { throw new Error(`examples({op:'show'}): can't read '${rel}' for ${platform}/${template}.`); }
|
|
2950
|
+
if (args.technique) {
|
|
2951
|
+
const blocks = extractMarkedBlocks(text).filter((b) => b.kind === "HARDWARE IDIOM");
|
|
2952
|
+
const hit = blocks.find((b) => b.header.toLowerCase().includes(args.technique.toLowerCase()));
|
|
2953
|
+
if (!hit) {
|
|
2954
|
+
return jsonContent({
|
|
2955
|
+
example: `${platform}/${template}`, technique: args.technique, found: false,
|
|
2956
|
+
availableTechniques: blocks.map((b) => b.header),
|
|
2957
|
+
note: blocks.length
|
|
2958
|
+
? "No HARDWARE IDIOM block matches — availableTechniques lists this file's blocks."
|
|
2959
|
+
: "This example has no marked technique blocks yet (markers land as games reach the Complete Game Contract). op:'show' without `technique` returns the whole file.",
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
return jsonContent({
|
|
2963
|
+
example: `${platform}/${template}`, technique: hit.header, found: true,
|
|
2964
|
+
code: hit.body,
|
|
2965
|
+
note: "The block header states its DEPENDENCIES (interrupt hooks, memory regions, register modes) — satisfy those in your game before transplanting the code.",
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2968
|
+
return jsonContent({ example: `${platform}/${template}`, file: rel, source: text });
|
|
1974
2969
|
}
|
|
1975
2970
|
case "snippets":
|
|
1976
|
-
// map snippetName -> name for the core's expected shape.
|
|
1977
2971
|
return await starterSnippetsCore({ ...args, name: args.snippetName });
|
|
1978
2972
|
case "copySnippets": {
|
|
1979
|
-
if (!args.destinationDir) throw new Error("
|
|
2973
|
+
if (!args.destinationDir) throw new Error("examples({op:'copySnippets'}): `destinationDir` is required.");
|
|
1980
2974
|
return await copyStarterSnippetsCore({ ...args, overwrite: args.overwrite ?? true });
|
|
1981
2975
|
}
|
|
1982
|
-
default: throw new Error(`
|
|
2976
|
+
default: throw new Error(`examples: unknown op '${args.op}'`);
|
|
1983
2977
|
}
|
|
1984
2978
|
}),
|
|
1985
2979
|
);
|