romdevtools 0.71.0 → 0.84.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 +19 -16
- package/CHANGELOG.md +196 -0
- package/README.md +5 -5
- package/examples/README.md +1 -0
- package/examples/gametank/templates/gt_draw.h +91 -0
- package/examples/gametank/templates/gt_hud.h +89 -0
- package/examples/gametank/templates/gt_palette.h +59 -0
- package/examples/gametank/templates/gt_sound.h +90 -0
- package/examples/gametank/templates/gt_sprites.h +53 -0
- package/examples/gametank/templates/platformer.c +318 -0
- package/examples/gametank/templates/puzzle.c +295 -0
- package/examples/gametank/templates/racing.c +139 -0
- package/examples/gametank/templates/shmup.c +277 -0
- package/examples/gametank/templates/sports.c +119 -0
- package/package.json +23 -22
- package/src/cores/capabilities.js +24 -0
- package/src/cores/registry.js +7 -1
- package/src/host/LibretroHost.js +90 -17
- package/src/host/callbacks.js +20 -2
- package/src/host/cpu-state.js +17 -0
- package/src/host/gametank-acp-state.js +53 -0
- package/src/http/skill-doc.js +10 -6
- package/src/mcp/tools/audio.js +1 -1
- package/src/mcp/tools/cart-parts.js +76 -0
- package/src/mcp/tools/lifecycle.js +1 -1
- package/src/mcp/tools/platform-tools.js +9 -1
- package/src/mcp/tools/platforms.js +46 -10
- package/src/mcp/tools/toolchain.js +35 -10
- package/src/platforms/gametank/lib/gt/audio/acp_image.bin +0 -0
- package/src/platforms/gametank/lib/gt/audio/audio_coprocessor.c +55 -0
- package/src/platforms/gametank/lib/gt/audio/audio_coprocessor.h +34 -0
- package/src/platforms/gametank/lib/gt/audio/audio_fw.asm +223 -0
- package/src/platforms/gametank/lib/gt/audio/audio_fw_blob.s +8 -0
- package/src/platforms/gametank/lib/gt/audio/instruments.c +79 -0
- package/src/platforms/gametank/lib/gt/audio/instruments.h +25 -0
- package/src/platforms/gametank/lib/gt/audio/music.c +370 -0
- package/src/platforms/gametank/lib/gt/audio/music.h +35 -0
- package/src/platforms/gametank/lib/gt/audio/note_numbers.h +132 -0
- package/src/platforms/gametank/lib/gt/audio/scaled_sines.raw +0 -0
- package/src/platforms/gametank/lib/gt/audio/sine_256_-63_63.bin +0 -0
- package/src/platforms/gametank/lib/gt/banking.c +29 -0
- package/src/platforms/gametank/lib/gt/banking.h +8 -0
- package/src/platforms/gametank/lib/gt/banking2.s +41 -0
- package/src/platforms/gametank/lib/gt/crt0.s +102 -0
- package/src/platforms/gametank/lib/gt/draw_logo.s +55 -0
- package/src/platforms/gametank/lib/gt/feature/persist/persist.c +103 -0
- package/src/platforms/gametank/lib/gt/feature/persist/persist.h +13 -0
- package/src/platforms/gametank/lib/gt/feature/random/random.c +40 -0
- package/src/platforms/gametank/lib/gt/feature/random/random.h +18 -0
- package/src/platforms/gametank/lib/gt/feature/text/text.c +111 -0
- package/src/platforms/gametank/lib/gt/feature/text/text.h +25 -0
- package/src/platforms/gametank/lib/gt/gametank.c +12 -0
- package/src/platforms/gametank/lib/gt/gametank.h +80 -0
- package/src/platforms/gametank/lib/gt/gametank_logo.inc +393 -0
- package/src/platforms/gametank/lib/gt/gen/assets/assets_index.h +9 -0
- package/src/platforms/gametank/lib/gt/gen/assets/sdk_default.h +5 -0
- package/src/platforms/gametank/lib/gt/gen/bank_nums.h +11 -0
- package/src/platforms/gametank/lib/gt/gen/modules_enabled.h +4 -0
- package/src/platforms/gametank/lib/gt/gen/modules_enabled.inc +4 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_direct.c +132 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_direct.h +75 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_queue.c +177 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_queue.h +35 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_util.s +157 -0
- package/src/platforms/gametank/lib/gt/gfx/gfx_sys.c +43 -0
- package/src/platforms/gametank/lib/gt/gfx/gfx_sys.h +46 -0
- package/src/platforms/gametank/lib/gt/gfx/sprites.c +216 -0
- package/src/platforms/gametank/lib/gt/gfx/sprites.h +41 -0
- package/src/platforms/gametank/lib/gt/init.c +9 -0
- package/src/platforms/gametank/lib/gt/input.c +26 -0
- package/src/platforms/gametank/lib/gt/input.h +20 -0
- package/src/platforms/gametank/lib/gt/interrupt.s +67 -0
- package/src/platforms/gametank/lib/gt/vectors.s +14 -0
- package/src/platforms/gametank/lib/gt/wait.s +53 -0
- package/src/platforms/gba/MENTAL_MODEL.md +22 -1
- package/src/playtest/playtest.js +174 -26
- package/src/playtest/resampler/build.sh +19 -0
- package/src/playtest/resampler/index.mjs +75 -0
- package/src/playtest/resampler/resampler.c +129 -0
- package/src/playtest/resampler/resampler.mjs +2 -0
- package/src/playtest/resampler/resampler.wasm +0 -0
- package/src/toolchains/arm-none-eabi-gcc/gcc.js +39 -188
- package/src/toolchains/asar/asar.js +10 -15
- package/src/toolchains/cc65/cc65.js +82 -92
- package/src/toolchains/cc65/da65.js +12 -17
- package/src/toolchains/cc65/preset-resolver.js +13 -2
- package/src/toolchains/cc65/presets/gametank/gametank.h +80 -0
- package/src/toolchains/cc65/presets/gametank/sdk.cfg +32 -0
- package/src/toolchains/cc65/presets/gametank/sdk.crt0.s +61 -0
- package/src/toolchains/cc65/presets/gametank/sdk.vectors.s +11 -0
- package/src/toolchains/cc65/presets/gametank/single-bank.cfg +28 -0
- package/src/toolchains/cc65/presets/gametank/single-bank.crt0.s +71 -0
- package/src/toolchains/cc65/presets/gametank/single-bank.vectors.s +12 -0
- package/src/toolchains/common/c-build.js +109 -0
- package/src/toolchains/common/gcc-toolchain.js +164 -0
- package/src/toolchains/common/wasm-tool.js +101 -0
- package/src/toolchains/dasm/dasm.js +12 -18
- package/src/toolchains/gba-c/gba-c.js +253 -305
- package/src/toolchains/genesis-c/genesis-c.js +196 -225
- package/src/toolchains/index.js +58 -4
- package/src/toolchains/m68k-elf-gcc/gcc.js +39 -202
- package/src/toolchains/mips-c/mips-c.js +68 -78
- package/src/toolchains/mips-elf-gcc/gcc.js +55 -118
- package/src/toolchains/rgbds/rgbds.js +7 -19
- package/src/toolchains/sdcc/sdcc.js +35 -26
- package/src/toolchains/sh-c/sh-c.js +44 -52
- package/src/toolchains/sh-elf-gcc/gcc.js +55 -110
- package/src/toolchains/sjasm/sjasm.js +10 -14
- package/src/toolchains/snes-c/snes-c.js +125 -150
- package/src/toolchains/tcc816/tcc816.js +10 -15
- package/src/toolchains/vasm68k/vasm68k.js +11 -16
- package/src/toolchains/wladx/wladx.js +5 -17
- package/src/toolchains/z80/binutils.js +5 -11
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/* ── shmup.c — GameTank vertical shooter (complete example game) ─────────────
|
|
2
|
+
*
|
|
3
|
+
* A COMPLETE, working game on the bundled GameTank SDK draw-queue runtime: an
|
|
4
|
+
* arrowhead fighter you fly along the bottom, enemy raiders that fall from the
|
|
5
|
+
* top, gold missiles you fire up, explosions on a kill, a score/lives HUD, SFX,
|
|
6
|
+
* and a title → play → game-over → restart loop. Drawn with REAL pixel-art
|
|
7
|
+
* sprites loaded into graphics RAM at runtime (gt_sprites.h) and blitted with
|
|
8
|
+
* per-pixel color + transparency.
|
|
9
|
+
*
|
|
10
|
+
* FORK THIS. Markers:
|
|
11
|
+
* HARDWARE IDIOM (load-bearing) — the GameTank draws via a blitter DRAW QUEUE
|
|
12
|
+
* (gt_draw.h): enqueue rects/sprites each frame, then gt_present() drains the
|
|
13
|
+
* queue, waits a vblank, and flips the double buffer. Sprites live in GRAM
|
|
14
|
+
* (gt_load_sprite writes the pixels once at init). Don't fire blits by hand.
|
|
15
|
+
* GAME LOGIC (clay) — sprite art, speeds, spawn timing, scoring: tune freely.
|
|
16
|
+
*
|
|
17
|
+
* SCREEN: 128x128. PLAYERS: 1. CONTROLS: D-pad move, A or START to fire/confirm.
|
|
18
|
+
*/
|
|
19
|
+
#include "gametank.h"
|
|
20
|
+
#include "draw_queue.h"
|
|
21
|
+
#include "input.h"
|
|
22
|
+
#include "gt_palette.h"
|
|
23
|
+
#include "gt_draw.h"
|
|
24
|
+
#include "gt_sprites.h"
|
|
25
|
+
#include "gt_hud.h"
|
|
26
|
+
#include "gt_sound.h"
|
|
27
|
+
|
|
28
|
+
/* ── palette ── */
|
|
29
|
+
#define C_SPACE GT_DKBLUE
|
|
30
|
+
#define C_HUDBAR GT_NAVY
|
|
31
|
+
#define C_HUD GT_WHITE
|
|
32
|
+
#define C_LIFE GT_GREEN
|
|
33
|
+
|
|
34
|
+
/* ── sprite art (palette-index bytes; 0 = transparent). short aliases for art. ── */
|
|
35
|
+
#define C GT_CYAN
|
|
36
|
+
#define W GT_WHITE
|
|
37
|
+
#define T GT_TEAL
|
|
38
|
+
#define G GT_GOLD
|
|
39
|
+
#define O GT_ORANGE
|
|
40
|
+
#define M GT_MAGENTA
|
|
41
|
+
#define U GT_PURPLE
|
|
42
|
+
#define R GT_RED
|
|
43
|
+
#define Y GT_YELLOW
|
|
44
|
+
|
|
45
|
+
#define SHIP_W 16
|
|
46
|
+
#define SHIP_H 16
|
|
47
|
+
static const unsigned char ART_SHIP[SHIP_W * SHIP_H] = {
|
|
48
|
+
0,0,0,0,0,0,0,C,C,0,0,0,0,0,0,0,
|
|
49
|
+
0,0,0,0,0,0,0,C,C,0,0,0,0,0,0,0,
|
|
50
|
+
0,0,0,0,0,0,C,W,W,C,0,0,0,0,0,0,
|
|
51
|
+
0,0,0,0,0,0,C,W,W,C,0,0,0,0,0,0,
|
|
52
|
+
0,0,0,0,0,C,C,W,W,C,C,0,0,0,0,0,
|
|
53
|
+
0,0,0,0,C,C,T,T,T,T,C,C,0,0,0,0,
|
|
54
|
+
0,0,0,C,C,T,T,T,T,T,T,C,C,0,0,0,
|
|
55
|
+
0,0,C,C,T,T,T,W,W,T,T,T,C,C,0,0,
|
|
56
|
+
0,C,C,T,T,T,T,W,W,T,T,T,T,C,C,0,
|
|
57
|
+
C,C,T,T,T,T,T,T,T,T,T,T,T,T,C,C,
|
|
58
|
+
C,C,C,T,T,T,T,T,T,T,T,T,T,C,C,C,
|
|
59
|
+
C,0,C,C,T,T,T,T,T,T,T,T,C,C,0,C,
|
|
60
|
+
0,0,0,C,C,0,T,T,T,T,0,C,C,0,0,0,
|
|
61
|
+
0,0,0,0,0,0,G,G,G,G,0,0,0,0,0,0,
|
|
62
|
+
0,0,0,0,0,G,O,G,G,O,G,0,0,0,0,0,
|
|
63
|
+
0,0,0,0,0,0,O,0,0,O,0,0,0,0,0,0,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
#define ENEMY_W 16
|
|
67
|
+
#define ENEMY_H 16
|
|
68
|
+
static const unsigned char ART_ENEMY[ENEMY_W * ENEMY_H] = {
|
|
69
|
+
0,0,0,0,0,0,R,R,R,R,0,0,0,0,0,0,
|
|
70
|
+
0,0,0,0,0,R,R,M,M,R,R,0,0,0,0,0,
|
|
71
|
+
0,0,0,0,R,R,M,M,M,M,R,R,0,0,0,0,
|
|
72
|
+
0,0,0,R,R,M,M,U,U,M,M,R,R,0,0,0,
|
|
73
|
+
0,0,R,R,M,M,U,W,W,U,M,M,R,R,0,0,
|
|
74
|
+
0,R,R,M,M,U,W,Y,Y,W,U,M,M,R,R,0,
|
|
75
|
+
R,R,M,M,U,W,Y,R,R,Y,W,U,M,M,R,R,
|
|
76
|
+
R,M,M,U,U,W,Y,R,R,Y,W,U,U,M,M,R,
|
|
77
|
+
R,M,M,U,W,W,Y,Y,Y,Y,W,W,U,M,M,R,
|
|
78
|
+
0,R,M,M,U,U,W,W,W,W,U,U,M,M,R,0,
|
|
79
|
+
0,R,R,M,M,M,U,U,U,U,M,M,M,R,R,0,
|
|
80
|
+
0,0,R,R,M,M,M,M,M,M,M,M,R,R,0,0,
|
|
81
|
+
0,0,0,R,R,R,M,M,M,M,R,R,R,0,0,0,
|
|
82
|
+
0,0,R,R,0,R,R,M,M,R,R,0,R,R,0,0,
|
|
83
|
+
0,R,R,0,0,0,R,R,R,R,0,0,0,R,R,0,
|
|
84
|
+
R,R,0,0,0,0,0,R,R,0,0,0,0,0,R,R,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
#define SHOT_W 6
|
|
88
|
+
#define SHOT_H 10
|
|
89
|
+
static const unsigned char ART_SHOT[SHOT_W * SHOT_H] = {
|
|
90
|
+
0,0,W,W,0,0,
|
|
91
|
+
0,G,W,W,G,0,
|
|
92
|
+
0,G,W,W,G,0,
|
|
93
|
+
G,G,W,W,G,G,
|
|
94
|
+
G,G,W,W,G,G,
|
|
95
|
+
G,G,G,G,G,G,
|
|
96
|
+
0,G,G,G,G,0,
|
|
97
|
+
0,O,O,O,O,0,
|
|
98
|
+
0,0,O,O,0,0,
|
|
99
|
+
0,0,Y,Y,0,0,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
#define BOOM_W 16
|
|
103
|
+
#define BOOM_H 16
|
|
104
|
+
static const unsigned char ART_BOOM[BOOM_W * BOOM_H] = {
|
|
105
|
+
0,0,0,Y,0,0,0,R,R,0,0,0,Y,0,0,0,
|
|
106
|
+
0,0,0,0,O,0,Y,O,O,Y,0,O,0,0,0,0,
|
|
107
|
+
0,Y,0,0,0,O,O,Y,Y,O,O,0,0,0,Y,0,
|
|
108
|
+
0,0,0,O,O,Y,Y,W,W,Y,Y,O,O,0,0,0,
|
|
109
|
+
Y,0,O,O,Y,W,W,W,W,W,W,Y,O,O,0,Y,
|
|
110
|
+
0,0,O,Y,W,W,Y,O,O,Y,W,W,Y,O,0,0,
|
|
111
|
+
0,O,Y,W,Y,O,O,R,R,O,O,Y,W,Y,O,0,
|
|
112
|
+
R,O,O,W,O,O,R,R,R,R,O,O,W,O,O,R,
|
|
113
|
+
R,O,O,W,O,O,R,R,R,R,O,O,W,O,O,R,
|
|
114
|
+
0,O,Y,W,Y,O,O,R,R,O,O,Y,W,Y,O,0,
|
|
115
|
+
0,0,O,Y,W,W,Y,O,O,Y,W,W,Y,O,0,0,
|
|
116
|
+
Y,0,O,O,Y,W,W,W,W,W,W,Y,O,O,0,Y,
|
|
117
|
+
0,0,0,O,O,Y,Y,W,W,Y,Y,O,O,0,0,0,
|
|
118
|
+
0,Y,0,0,0,O,O,Y,Y,O,O,0,0,0,Y,0,
|
|
119
|
+
0,0,0,0,O,0,Y,O,O,Y,0,O,0,0,0,0,
|
|
120
|
+
0,0,0,Y,0,0,0,R,R,0,0,0,Y,0,0,0,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
#undef C
|
|
124
|
+
#undef W
|
|
125
|
+
#undef T
|
|
126
|
+
#undef G
|
|
127
|
+
#undef O
|
|
128
|
+
#undef M
|
|
129
|
+
#undef U
|
|
130
|
+
#undef R
|
|
131
|
+
#undef Y
|
|
132
|
+
|
|
133
|
+
/* Each sprite gets its own GRAM row band (gy step 16) so a 16-wide sprite can't
|
|
134
|
+
* read into the next one's columns. */
|
|
135
|
+
static GtSprite SPR_SHIP, SPR_ENEMY, SPR_SHOT, SPR_BOOM;
|
|
136
|
+
static void load_art(void) {
|
|
137
|
+
SPR_SHIP.gx = 0; SPR_SHIP.gy = 0; SPR_SHIP.w = SHIP_W; SPR_SHIP.h = SHIP_H;
|
|
138
|
+
SPR_ENEMY.gx = 0; SPR_ENEMY.gy = 16; SPR_ENEMY.w = ENEMY_W; SPR_ENEMY.h = ENEMY_H;
|
|
139
|
+
SPR_SHOT.gx = 0; SPR_SHOT.gy = 32; SPR_SHOT.w = SHOT_W; SPR_SHOT.h = SHOT_H;
|
|
140
|
+
SPR_BOOM.gx = 0; SPR_BOOM.gy = 48; SPR_BOOM.w = BOOM_W; SPR_BOOM.h = BOOM_H;
|
|
141
|
+
gt_load_sprite(ART_SHIP, SPR_SHIP.gx, SPR_SHIP.gy, SHIP_W, SHIP_H);
|
|
142
|
+
gt_load_sprite(ART_ENEMY, SPR_ENEMY.gx, SPR_ENEMY.gy, ENEMY_W, ENEMY_H);
|
|
143
|
+
gt_load_sprite(ART_SHOT, SPR_SHOT.gx, SPR_SHOT.gy, SHOT_W, SHOT_H);
|
|
144
|
+
gt_load_sprite(ART_BOOM, SPR_BOOM.gx, SPR_BOOM.gy, BOOM_W, BOOM_H);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ── game state ── */
|
|
148
|
+
#define N_ENEMY 4
|
|
149
|
+
#define N_SHOT 3
|
|
150
|
+
#define N_BOOM 4
|
|
151
|
+
|
|
152
|
+
static unsigned char ship_x;
|
|
153
|
+
static unsigned char ex[N_ENEMY], ey[N_ENEMY], e_on[N_ENEMY]; /* enemies: x, y, active */
|
|
154
|
+
static unsigned char sx[N_SHOT], sy[N_SHOT], s_on[N_SHOT]; /* shots: x, y, active */
|
|
155
|
+
static unsigned char bx[N_BOOM], by[N_BOOM], bt[N_BOOM]; /* explosions: x, y, timer */
|
|
156
|
+
static unsigned char fire_cool, spawn_t;
|
|
157
|
+
static unsigned int score;
|
|
158
|
+
static unsigned char lives;
|
|
159
|
+
|
|
160
|
+
static void reset_game(void) {
|
|
161
|
+
unsigned char i;
|
|
162
|
+
ship_x = 56; score = 0; lives = 3; fire_cool = 0; spawn_t = 0;
|
|
163
|
+
for (i = 0; i < N_ENEMY; i++) e_on[i] = 0;
|
|
164
|
+
for (i = 0; i < N_SHOT; i++) s_on[i] = 0;
|
|
165
|
+
for (i = 0; i < N_BOOM; i++) bt[i] = 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* spawn an explosion at (x,y). */
|
|
169
|
+
static void make_boom(unsigned char x, unsigned char y) {
|
|
170
|
+
unsigned char i;
|
|
171
|
+
for (i = 0; i < N_BOOM; i++) {
|
|
172
|
+
if (!bt[i]) { bx[i] = x; by[i] = y; bt[i] = 10; return; }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* spawn one enemy at the top in a free slot. */
|
|
177
|
+
static void spawn_enemy(void) {
|
|
178
|
+
unsigned char i;
|
|
179
|
+
for (i = 0; i < N_ENEMY; i++) {
|
|
180
|
+
if (!e_on[i]) {
|
|
181
|
+
ex[i] = 8 + (unsigned char)(rnd8() % 100);
|
|
182
|
+
ey[i] = 0;
|
|
183
|
+
e_on[i] = 1;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* boxes-overlap test (sprites are ~16 wide). */
|
|
190
|
+
static unsigned char near16(unsigned char ax, unsigned char ay, unsigned char bx, unsigned char by) {
|
|
191
|
+
unsigned char dx = ax > bx ? ax - bx : bx - ax;
|
|
192
|
+
unsigned char dy = ay > by ? ay - by : by - ay;
|
|
193
|
+
return dx < 14 && dy < 14;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
void main(void) {
|
|
197
|
+
unsigned char i, j;
|
|
198
|
+
load_art();
|
|
199
|
+
reset_game();
|
|
200
|
+
|
|
201
|
+
for (;;) {
|
|
202
|
+
/* ── input ── */
|
|
203
|
+
if ((player1_buttons & INPUT_MASK_LEFT) && ship_x > 2) ship_x -= 2;
|
|
204
|
+
if ((player1_buttons & INPUT_MASK_RIGHT) && ship_x < 110) ship_x += 2;
|
|
205
|
+
if (fire_cool) fire_cool--;
|
|
206
|
+
if ((player1_buttons & (INPUT_MASK_A | INPUT_MASK_START)) && !fire_cool) {
|
|
207
|
+
for (i = 0; i < N_SHOT; i++) {
|
|
208
|
+
if (!s_on[i]) { s_on[i] = 1; sx[i] = ship_x + 5; sy[i] = 100; fire_cool = 10; gt_sfx(GT_SFX_SHOOT); break; }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ── shots move up ── */
|
|
213
|
+
for (i = 0; i < N_SHOT; i++) {
|
|
214
|
+
if (s_on[i]) {
|
|
215
|
+
if (sy[i] < 6) s_on[i] = 0;
|
|
216
|
+
else sy[i] -= 6;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/* ── spawn enemies on a timer ── */
|
|
221
|
+
spawn_t++;
|
|
222
|
+
if (spawn_t >= 48) { spawn_t = 0; spawn_enemy(); }
|
|
223
|
+
|
|
224
|
+
/* ── enemies fall ── */
|
|
225
|
+
for (i = 0; i < N_ENEMY; i++) {
|
|
226
|
+
if (e_on[i]) {
|
|
227
|
+
ey[i]++;
|
|
228
|
+
if (ey[i] > 120) e_on[i] = 0; /* off the bottom: just despawn */
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* ── shot vs enemy ── */
|
|
233
|
+
for (i = 0; i < N_ENEMY; i++) {
|
|
234
|
+
if (e_on[i]) {
|
|
235
|
+
for (j = 0; j < N_SHOT; j++) {
|
|
236
|
+
if (s_on[j] && near16(ex[i] + 8, ey[i] + 8, sx[j] + 3, sy[j] + 4)) {
|
|
237
|
+
make_boom(ex[i], ey[i]);
|
|
238
|
+
e_on[i] = 0; s_on[j] = 0; score += 10; gt_sfx(GT_SFX_EXPLODE);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* ── enemy vs ship ── */
|
|
246
|
+
for (i = 0; i < N_ENEMY; i++) {
|
|
247
|
+
if (e_on[i] && near16(ex[i] + 8, ey[i] + 8, ship_x + 8, 116)) {
|
|
248
|
+
make_boom(ex[i], ey[i]);
|
|
249
|
+
e_on[i] = 0;
|
|
250
|
+
gt_sfx(GT_SFX_EXPLODE);
|
|
251
|
+
if (lives) lives--;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* ── age explosions ── */
|
|
256
|
+
for (i = 0; i < N_BOOM; i++) if (bt[i]) bt[i]--;
|
|
257
|
+
|
|
258
|
+
/* ── draw (scalar copies: indexing inside the gt_blit macro is fragile in cc65) ── */
|
|
259
|
+
gt_clear(C_SPACE);
|
|
260
|
+
for (i = 0; i < N_ENEMY; i++) if (e_on[i]) { unsigned char dx = ex[i], dy = ey[i]; gt_blit(dx, dy, SPR_ENEMY); }
|
|
261
|
+
for (i = 0; i < N_SHOT; i++) if (s_on[i]) { unsigned char dx = sx[i], dy = sy[i]; gt_blit(dx, dy, SPR_SHOT); }
|
|
262
|
+
for (i = 0; i < N_BOOM; i++) if (bt[i]) { unsigned char dx = bx[i], dy = by[i]; gt_blit(dx, dy, SPR_BOOM); }
|
|
263
|
+
gt_blit(ship_x, 108, SPR_SHIP);
|
|
264
|
+
/* HUD: bar in the play area (y>=7), text inside it. */
|
|
265
|
+
queue_draw_box(1, 7, 126, 7, C_HUDBAR);
|
|
266
|
+
hud_number(score, 30, 8, 2, C_HUD);
|
|
267
|
+
hud_pips(lives, 96, 8, 4, C_LIFE);
|
|
268
|
+
/* Re-clean the top BORDER (rows 0-6) LAST. Blitting sprites leaves a stray light
|
|
269
|
+
* line on the framebuffer's top scanline; only the border-clear path can rewrite
|
|
270
|
+
* rows 0-6, and doing it AFTER the sprite blits removes the flickering 1px seam. */
|
|
271
|
+
queue_clear_border(C_HUDBAR);
|
|
272
|
+
gt_present();
|
|
273
|
+
gt_sfx_tick();
|
|
274
|
+
|
|
275
|
+
update_inputs();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* ── sports.c — GameTank 2-player paddle game (complete example) ─────────────
|
|
2
|
+
*
|
|
3
|
+
* A COMPLETE, working game on the bundled GameTank SDK draw-queue runtime — and
|
|
4
|
+
* a genuine 2-PLAYER one (the GameTank has two gamepad ports, unlike the
|
|
5
|
+
* handhelds). Two paddles, a bouncing ball that speeds up on each hit, first to
|
|
6
|
+
* 7 wins. Title → play → match-point → restart. The framebuffer makes a clean
|
|
7
|
+
* paddle game trivial — paddles + ball are just rects redrawn each frame.
|
|
8
|
+
*
|
|
9
|
+
* FORK THIS. Markers:
|
|
10
|
+
* HARDWARE IDIOM (load-bearing) — paddles/ball/court = rects redrawn each
|
|
11
|
+
* frame; the ball's sub-pixel motion is integer x/y with a velocity. No
|
|
12
|
+
* sprites, no flicker.
|
|
13
|
+
* GAME LOGIC (clay) — paddle speed, ball speed-up, win score: tune freely.
|
|
14
|
+
*
|
|
15
|
+
* SFX (gt_sound.h, ACP synth): a blip on wall bounces, a "pong" on paddle hits,
|
|
16
|
+
* and a ding on each point scored. Box-drawn HUD (gt_hud.h).
|
|
17
|
+
* SCREEN: 128x128. PLAYERS: 2 (P1 left pad = $2008, P2 right pad = $2009).
|
|
18
|
+
*/
|
|
19
|
+
#include "gametank.h"
|
|
20
|
+
#include "draw_queue.h"
|
|
21
|
+
#include "input.h"
|
|
22
|
+
#include "gt_palette.h"
|
|
23
|
+
#include "gt_draw.h"
|
|
24
|
+
#include "gt_hud.h"
|
|
25
|
+
#include "gt_sound.h"
|
|
26
|
+
|
|
27
|
+
#define C_COURT GT_DKGRN /* court: dark green */
|
|
28
|
+
#define C_NET GT_WHITE
|
|
29
|
+
#define C_P1 GT_CYAN /* left paddle */
|
|
30
|
+
#define C_P2 GT_GOLD /* right paddle */
|
|
31
|
+
#define C_BALL GT_WHITE
|
|
32
|
+
#define C_HUD GT_WHITE
|
|
33
|
+
|
|
34
|
+
#define PAD_H 26
|
|
35
|
+
#define PAD_W 5
|
|
36
|
+
#define WIN 7
|
|
37
|
+
|
|
38
|
+
static unsigned char p1y, p2y; /* paddle tops */
|
|
39
|
+
static signed char bx, by; /* ball pos (signed for clamp math) */
|
|
40
|
+
static signed char vx, vy; /* ball velocity */
|
|
41
|
+
static unsigned char s1, s2;
|
|
42
|
+
|
|
43
|
+
static void serve(unsigned char toLeft) {
|
|
44
|
+
bx = 62; by = 60;
|
|
45
|
+
vx = toLeft ? -2 : 2; vy = (by & 1) ? 1 : -1;
|
|
46
|
+
}
|
|
47
|
+
static void reset_game(void) { p1y = 51; p2y = 51; s1 = 0; s2 = 0; serve(1); }
|
|
48
|
+
|
|
49
|
+
static unsigned char title_or_win(unsigned char win) { /* win: 0 title, 1 P1, 2 P2 */
|
|
50
|
+
gt_start_reset();
|
|
51
|
+
while (1) {
|
|
52
|
+
gt_clear(C_COURT);
|
|
53
|
+
if (!win) {
|
|
54
|
+
gt_rect(28, 36, 72, 10, C_BALL);
|
|
55
|
+
gt_rect(20, 64, PAD_W, PAD_H, C_P1);
|
|
56
|
+
gt_rect(103, 64, PAD_W, PAD_H, C_P2);
|
|
57
|
+
gt_rect(62, 72, 5, 5, C_BALL);
|
|
58
|
+
} else {
|
|
59
|
+
gt_rect(30, 52, 68, 24, C_NET);
|
|
60
|
+
hud_digit(win, 60, 56, 4, win == 1 ? C_P1 : C_P2); /* "1" or "2" wins */
|
|
61
|
+
}
|
|
62
|
+
gt_present(); gt_music_tick();
|
|
63
|
+
update_inputs();
|
|
64
|
+
if (gt_start_pressed()) return 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
void main(void) {
|
|
69
|
+
unsigned char i;
|
|
70
|
+
reset_game();
|
|
71
|
+
|
|
72
|
+
for (;;) {
|
|
73
|
+
title_or_win(0);
|
|
74
|
+
reset_game();
|
|
75
|
+
|
|
76
|
+
for (;;) {
|
|
77
|
+
update_inputs();
|
|
78
|
+
|
|
79
|
+
/* P1 (left pad) + P2 (right pad) — update_inputs fills player1/2_buttons */
|
|
80
|
+
if ((player1_buttons & INPUT_MASK_UP) && p1y > 2) p1y -= 3;
|
|
81
|
+
if ((player1_buttons & INPUT_MASK_DOWN) && p1y < 127 - PAD_H) p1y += 3;
|
|
82
|
+
if ((player2_buttons & INPUT_MASK_UP) && p2y > 2) p2y -= 3;
|
|
83
|
+
if ((player2_buttons & INPUT_MASK_DOWN) && p2y < 127 - PAD_H) p2y += 3;
|
|
84
|
+
|
|
85
|
+
/* ball move */
|
|
86
|
+
bx += vx; by += vy;
|
|
87
|
+
if (by <= 2) { by = 2; vy = -vy; gt_sfx(GT_SFX_SHOOT); } /* wall blip */
|
|
88
|
+
if (by >= 122) { by = 122; vy = -vy; gt_sfx(GT_SFX_SHOOT); }
|
|
89
|
+
|
|
90
|
+
/* paddle collisions (speed up on hit) — the satisfying "pong" */
|
|
91
|
+
if (vx < 0 && bx <= 25 && bx >= 18 && by + 5 >= (signed char)p1y && by <= (signed char)(p1y + PAD_H)) {
|
|
92
|
+
bx = 25; vx = (signed char)(-vx + 1); vy += (signed char)((by - (signed char)(p1y + PAD_H / 2)) >> 3);
|
|
93
|
+
gt_sfx(GT_SFX_HIT);
|
|
94
|
+
}
|
|
95
|
+
if (vx > 0 && bx >= 98 && bx <= 105 && by + 5 >= (signed char)p2y && by <= (signed char)(p2y + PAD_H)) {
|
|
96
|
+
bx = 98; vx = (signed char)(-vx - 1); vy += (signed char)((by - (signed char)(p2y + PAD_H / 2)) >> 3);
|
|
97
|
+
gt_sfx(GT_SFX_HIT);
|
|
98
|
+
}
|
|
99
|
+
if (vx > 4) vx = 4; if (vx < -4) vx = -4;
|
|
100
|
+
|
|
101
|
+
/* score — a clear ding for the point */
|
|
102
|
+
if (bx < 0) { s2++; gt_sfx(GT_SFX_COIN); if (s2 >= WIN) goto win2; serve(0); }
|
|
103
|
+
if (bx > 126) { s1++; gt_sfx(GT_SFX_COIN); if (s1 >= WIN) goto win1; serve(1); }
|
|
104
|
+
|
|
105
|
+
/* ── HARDWARE IDIOM: court + net + paddles + ball, all rects ── */
|
|
106
|
+
gt_clear(C_COURT);
|
|
107
|
+
for (i = 4; i < 124; i += 10) gt_rect(63, i, 2, 5, C_NET); /* dashed net */
|
|
108
|
+
gt_rect(20, p1y, PAD_W, PAD_H, C_P1);
|
|
109
|
+
gt_rect(103, p2y, PAD_W, PAD_H, C_P2);
|
|
110
|
+
gt_rect((unsigned char)bx, (unsigned char)by, 5, 5, C_BALL);
|
|
111
|
+
hud_number(s1, 44, 4, 2, C_P1);
|
|
112
|
+
hud_number(s2, 84, 4, 2, C_P2);
|
|
113
|
+
|
|
114
|
+
gt_present(); gt_music_tick();
|
|
115
|
+
}
|
|
116
|
+
win1: title_or_win(1); continue;
|
|
117
|
+
win2: title_or_win(2); continue;
|
|
118
|
+
}
|
|
119
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "romdevtools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.84.0",
|
|
4
4
|
"description": "Tool server giving coding agents full control of homebrew ROM development AND reverse-engineering/romhacking across 17 retro platforms (NES, SNES, GB, Genesis, Atari, C64, PC Engine, MSX, PlayStation, N64, Dreamcast, ...) via WASM toolchains + emulator cores. Use over plain HTTP, as an Agent Skill, or as an MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/mcp/server.js",
|
|
@@ -52,29 +52,30 @@
|
|
|
52
52
|
"pngjs": "^7.0.0",
|
|
53
53
|
"romdev-analysis": "0.1.0",
|
|
54
54
|
"romdev-analysis-decompiler": "0.1.0",
|
|
55
|
-
"romdev-core-bluemsx": "0.
|
|
56
|
-
"romdev-core-fceumm": "0.
|
|
57
|
-
"romdev-core-gambatte": "0.
|
|
58
|
-
"romdev-core-geargrafx": "0.
|
|
59
|
-
"romdev-core-gpgx": "0.
|
|
60
|
-
"romdev-core-handy": "0.
|
|
61
|
-
"romdev-core-
|
|
62
|
-
"romdev-core-
|
|
63
|
-
"romdev-core-
|
|
64
|
-
"romdev-core-
|
|
65
|
-
"romdev-core-
|
|
55
|
+
"romdev-core-bluemsx": "0.7.0",
|
|
56
|
+
"romdev-core-fceumm": "0.11.0",
|
|
57
|
+
"romdev-core-gambatte": "0.10.0",
|
|
58
|
+
"romdev-core-geargrafx": "0.8.0",
|
|
59
|
+
"romdev-core-gpgx": "0.13.0",
|
|
60
|
+
"romdev-core-handy": "0.8.0",
|
|
61
|
+
"romdev-core-gametank": "0.1.0",
|
|
62
|
+
"romdev-core-flycast": "0.2.0",
|
|
63
|
+
"romdev-core-parallel-n64": "0.2.0",
|
|
64
|
+
"romdev-core-beetle-psx-hw": "0.2.0",
|
|
65
|
+
"romdev-core-prosystem": "0.9.0",
|
|
66
|
+
"romdev-core-vice": "0.10.0",
|
|
66
67
|
"romdev-famitone": "0.1.0",
|
|
67
68
|
"romdev-maxmod": "0.1.0",
|
|
68
|
-
"romdev-platform-atari2600": "0.
|
|
69
|
-
"romdev-platform-gba": "0.
|
|
70
|
-
"romdev-platform-snes": "0.
|
|
71
|
-
"romdev-toolchain-cc65": "0.1.
|
|
72
|
-
"romdev-toolchain-m68k-gcc": "0.2.
|
|
73
|
-
"romdev-toolchain-mips-gcc": "0.1.
|
|
74
|
-
"romdev-toolchain-rgbds": "0.1.
|
|
75
|
-
"romdev-toolchain-sdcc": "0.2.
|
|
76
|
-
"romdev-toolchain-sh-gcc": "0.1.
|
|
77
|
-
"romdev-toolchain-vasm": "0.1.
|
|
69
|
+
"romdev-platform-atari2600": "0.9.0",
|
|
70
|
+
"romdev-platform-gba": "0.9.0",
|
|
71
|
+
"romdev-platform-snes": "0.9.0",
|
|
72
|
+
"romdev-toolchain-cc65": "0.1.2",
|
|
73
|
+
"romdev-toolchain-m68k-gcc": "0.2.1",
|
|
74
|
+
"romdev-toolchain-mips-gcc": "0.1.1",
|
|
75
|
+
"romdev-toolchain-rgbds": "0.1.1",
|
|
76
|
+
"romdev-toolchain-sdcc": "0.2.1",
|
|
77
|
+
"romdev-toolchain-sh-gcc": "0.1.1",
|
|
78
|
+
"romdev-toolchain-vasm": "0.1.1",
|
|
78
79
|
"romdev-xgm2": "0.1.0",
|
|
79
80
|
"romdev_game_codes": "0.1.0",
|
|
80
81
|
"socket.io": "^4.8.3",
|
|
@@ -174,6 +174,30 @@ export const CAPABILITIES = {
|
|
|
174
174
|
cart: false, disasm: true, decompile: true,
|
|
175
175
|
},
|
|
176
176
|
},
|
|
177
|
+
gametank: {
|
|
178
|
+
// Clyde Shaffer's open W65C02S console: a 128x128 framebuffer drawn by a
|
|
179
|
+
// hardware blitter (no tilemap / no fixed OAM), + a second 65C02 audio
|
|
180
|
+
// coprocessor. Closest cousin is the Lynx (also 65C02 + blitter). The core
|
|
181
|
+
// is patched with the romdev_* debug hooks (6502 regsnap + MemoryWrite/Read
|
|
182
|
+
// watchpoints + the mos6502 dispatch freeze) — so cpuState + write/read
|
|
183
|
+
// watchpoints + pc-break + watchdog + coverage are LIVE, alongside build/run/
|
|
184
|
+
// screenshot/disasm/decompile. inspectSprites is N/A (the blitter has no OAM,
|
|
185
|
+
// like Dreamcast); inspectBackground N/A (framebuffer, not a tilemap).
|
|
186
|
+
cpuFamily: "6502", decompileQuality: "rough",
|
|
187
|
+
cpus: { main: "6502", secondary: ["acp-65c02"] }, // ACP = the audio coprocessor (2nd 65C02)
|
|
188
|
+
// audioDebug(chip:'acp') reports the ACP's STATE (DAC output, IRQ/sample rate,
|
|
189
|
+
// run/mute, audio-CPU PC) via the core's romdev_acp_get export — it's a second
|
|
190
|
+
// 65C02 driving a DAC, not a fixed-register synth.
|
|
191
|
+
audioChips: ["acp"],
|
|
192
|
+
memoryRegions: [...GENERIC_REGIONS],
|
|
193
|
+
renderingKind: "framebuffer", introspection: "shallow",
|
|
194
|
+
ops: {
|
|
195
|
+
build: true, run: true, screenshot: true,
|
|
196
|
+
inspectSprites: false, inspectPalette: true, inspectBackground: false,
|
|
197
|
+
renderingContext: true, cpuState: true, audioDebug: true,
|
|
198
|
+
cart: true, disasm: true, decompile: true,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
177
201
|
pce: {
|
|
178
202
|
cpuFamily: "huc6280", decompileQuality: "medium",
|
|
179
203
|
cpus: { main: "", secondary: [] }, // getCPUState main NOT wired for pce
|
package/src/cores/registry.js
CHANGED
|
@@ -53,6 +53,7 @@ export const CORES = {
|
|
|
53
53
|
c64: { platform: "c64", coreName: "vice_x64", pkg: "romdev-core-vice", displayName: "Commodore 64 (VICE x64)" },
|
|
54
54
|
pce: { platform: "pce", coreName: "geargrafx", pkg: "romdev-core-geargrafx", displayName: "PC Engine / TurboGrafx-16 (Geargrafx)", aka: "turbografx,tg16,pcengine" },
|
|
55
55
|
msx: { platform: "msx", coreName: "bluemsx", pkg: "romdev-core-bluemsx", displayName: "MSX / MSX2 (blueMSX)", aka: "msx2" },
|
|
56
|
+
gametank: { platform: "gametank", coreName: "gametank", pkg: "romdev-core-gametank", displayName: "GameTank (Clyde Shaffer)", aka: "gtr" },
|
|
56
57
|
// 32-bit MIPS tier. These cores HW-render (GL): the host lazy-loads the OPTIONAL
|
|
57
58
|
// webgl-node bridge only when one of these boots (hwRender:true). The other 14 are
|
|
58
59
|
// software-rendered and never touch GL, so a headless user without the GPU module
|
|
@@ -69,7 +70,12 @@ export const CORES = {
|
|
|
69
70
|
// Flycast = full Dreamcast emulator, GLES3/WebGL2 HW-render (PowerVR2 is GPU-first,
|
|
70
71
|
// no software framebuffer path) → driven through the native-gles/webgl-node bridge
|
|
71
72
|
// like the GL N64 build. HLE BIOS (reios) on by default — no firmware to ship.
|
|
72
|
-
|
|
73
|
+
// noderawfs: the flycast WASM is built with -s NODERAWFS=1, so its filesystem IS
|
|
74
|
+
// Node's real fs — libchdr fopens/seeks the disc image off DISK on demand instead
|
|
75
|
+
// of the host loading the whole (up to ~1GB) CHD into the WASM heap (which OOM'd a
|
|
76
|
+
// 1GB max-heap on big discs like Sonic Adventure). The host passes the REAL path
|
|
77
|
+
// and skips the malloc+FS.writeFile for these cores. See LibretroHost.loadMedia.
|
|
78
|
+
dreamcast: { platform: "dreamcast", coreName: "flycast", pkg: "romdev-core-flycast", displayName: "Sega Dreamcast (Flycast)", aka: "dc", hwRender: true, noderawfs: true },
|
|
73
79
|
};
|
|
74
80
|
|
|
75
81
|
/** Try to get {jsPath,wasmPath} for a core from its binary package. */
|