romdevtools 0.15.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +61 -13
- package/CHANGELOG.md +289 -0
- package/README.md +1 -1
- package/examples/README.md +2 -0
- package/examples/atari2600/templates/platformer.asm +460 -0
- package/examples/atari2600/templates/racing.asm +463 -0
- package/examples/atari2600/templates/shmup.asm +386 -0
- package/examples/atari2600/templates/sports.asm +362 -0
- package/examples/atari7800/templates/default.c +49 -5
- package/examples/atari7800/templates/platformer.c +43 -4
- package/examples/atari7800/templates/puzzle.c +39 -4
- package/examples/atari7800/templates/racing.c +39 -4
- package/examples/atari7800/templates/shmup.c +40 -2
- package/examples/atari7800/templates/sports.c +36 -5
- package/examples/c64/templates/platformer.c +19 -5
- package/examples/c64/templates/puzzle.c +32 -2
- package/examples/c64/templates/shmup.c +28 -2
- package/examples/c64/templates/sports.c +30 -2
- package/examples/gb/templates/default.c +110 -16
- package/examples/gb/templates/platformer.c +25 -4
- package/examples/gb/templates/puzzle.c +32 -2
- package/examples/gb/templates/racing.c +72 -8
- package/examples/gb/templates/shmup.c +38 -1
- package/examples/gb/templates/sports.c +48 -1
- package/examples/gba/templates/gba_hello.c +29 -11
- package/examples/gba/templates/puzzle.c +15 -3
- package/examples/gba/templates/racing.c +65 -3
- package/examples/gba/templates/shmup.c +41 -4
- package/examples/gba/templates/sports.c +36 -2
- package/examples/gba/templates/tonc_hello.c +41 -5
- package/examples/gbc/templates/default.c +103 -26
- package/examples/gbc/templates/platformer.c +25 -4
- package/examples/gbc/templates/puzzle.c +32 -2
- package/examples/gbc/templates/racing.c +85 -19
- package/examples/gbc/templates/shmup.c +34 -1
- package/examples/gbc/templates/sports.c +45 -1
- package/examples/genesis/templates/puzzle.c +37 -3
- package/examples/genesis/templates/racing.c +44 -11
- package/examples/genesis/templates/sgdk_hello.c +34 -1
- package/examples/genesis/templates/shmup.c +31 -1
- package/examples/gg/templates/default.c +56 -18
- package/examples/gg/templates/platformer.c +18 -12
- package/examples/gg/templates/puzzle.c +38 -7
- package/examples/gg/templates/racing.c +51 -5
- package/examples/gg/templates/shmup.c +47 -3
- package/examples/gg/templates/sports.c +46 -3
- package/examples/lynx/templates/default.c +39 -8
- package/examples/lynx/templates/puzzle.c +28 -1
- package/examples/lynx/templates/racing.c +34 -7
- package/examples/lynx/templates/shmup.c +42 -3
- package/examples/lynx/templates/sports.c +29 -2
- package/examples/msx/platformer/main.c +213 -0
- package/examples/msx/puzzle/main.c +250 -0
- package/examples/msx/racing/main.c +249 -0
- package/examples/msx/shmup/main.c +288 -0
- package/examples/msx/sports/main.c +182 -0
- package/examples/nes/templates/default.c +67 -19
- package/examples/nes/templates/platformer.c +65 -6
- package/examples/nes/templates/puzzle.c +67 -6
- package/examples/nes/templates/racing.c +45 -13
- package/examples/nes/templates/shmup.c +51 -2
- package/examples/nes/templates/sports.c +51 -6
- package/examples/pce/platformer/main.c +283 -0
- package/examples/pce/puzzle/main.c +304 -0
- package/examples/pce/racing/main.c +304 -0
- package/examples/pce/shmup/main.c +346 -0
- package/examples/pce/sports/main.c +254 -0
- package/examples/sms/main.c +35 -6
- package/examples/sms/templates/puzzle.c +34 -5
- package/examples/sms/templates/racing.c +39 -2
- package/examples/sms/templates/shmup.c +41 -2
- package/examples/sms/templates/sports.c +43 -2
- package/examples/snes/templates/default.c +50 -28
- package/examples/snes/templates/platformer-data.asm +22 -0
- package/examples/snes/templates/platformer.c +16 -1
- package/examples/snes/templates/puzzle-data.asm +22 -0
- package/examples/snes/templates/puzzle.c +17 -1
- package/examples/snes/templates/racing-data.asm +22 -0
- package/examples/snes/templates/racing.c +17 -1
- package/examples/snes/templates/shmup-data.asm +22 -0
- package/examples/snes/templates/shmup.c +20 -1
- package/examples/snes/templates/sports-data.asm +22 -0
- package/examples/snes/templates/sports.c +16 -1
- package/package.json +1 -1
- package/src/cores/wasm/vice_x64_libretro.js +1 -1
- package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
- package/src/host/LibretroHost.js +122 -1
- package/src/host/callbacks.js +9 -1
- package/src/host/types.js +15 -8
- package/src/http/skill-doc.js +1 -1
- package/src/http/tool-registry.js +27 -2
- package/src/mcp/tools/cart-parts.js +75 -3
- package/src/mcp/tools/disasm-rebuild.js +507 -0
- package/src/mcp/tools/disasm.js +95 -6
- package/src/mcp/tools/frame.js +168 -3
- package/src/mcp/tools/index.js +4 -4
- package/src/mcp/tools/lifecycle.js +4 -2
- package/src/mcp/tools/project.js +54 -9
- package/src/mcp/tools/state.js +201 -14
- package/src/mcp/tools/toolchain.js +89 -4
- package/src/mcp/tools/watch-memory.js +125 -14
- package/src/platforms/c64/MENTAL_MODEL.md +45 -1
- package/src/platforms/c64/d64.js +281 -0
- package/src/platforms/gb/MENTAL_MODEL.md +10 -0
- package/src/platforms/msx/MENTAL_MODEL.md +10 -6
- package/src/platforms/nes/MENTAL_MODEL.md +63 -2
- package/src/platforms/pce/MENTAL_MODEL.md +9 -4
- package/src/platforms/pce/lib/c/pce_video.c +1 -1
- package/src/rom-id/identifier.js +15 -0
- package/src/toolchains/cc65/cc65.js +8 -1
- package/src/toolchains/cc65/ines.js +145 -0
- package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
- package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
- package/src/toolchains/common/reassemble.js +10 -2
- package/src/toolchains/gba-c/gba-c.js +6 -1
- package/src/toolchains/genesis-c/genesis-c.js +10 -2
- package/src/toolchains/parse-errors.js +67 -5
|
@@ -53,12 +53,39 @@ static const uint8_t tile_block_b[16] = {
|
|
|
53
53
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
54
54
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
55
55
|
};
|
|
56
|
+
/* BG scenery tiles — painted into the nametable so the playfield reads as a
|
|
57
|
+
* real machine on boot (without them the screen is just sprites on flat
|
|
58
|
+
* black). All live in the BACKGROUND pattern table ($1000); the block tiles
|
|
59
|
+
* above are sprites ($0000).
|
|
60
|
+
*
|
|
61
|
+
* BG_WALL — solid bordered block (idx 1, steel blue): the well frame.
|
|
62
|
+
* BG_BRICK — a brick/dither pattern (idx 2) tiling the cabinet wall that
|
|
63
|
+
* surrounds the well, so the whole screen is covered.
|
|
64
|
+
* BG_INNER — a faint grid speck (idx 3) lining the inside of the well so
|
|
65
|
+
* empty cells read as a recessed playfield, not a black hole. */
|
|
66
|
+
static const uint8_t tile_wall[16] = {
|
|
67
|
+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
68
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
69
|
+
};
|
|
70
|
+
static const uint8_t tile_brick[16] = {
|
|
71
|
+
0, 0, 0, 0, 0, 0, 0, 0, /* plane 0 clear */
|
|
72
|
+
0xFF, 0x80, 0x80, 0x80, 0xFF, 0x08, 0x08, 0x08, /* plane 1 → colour 2 brick */
|
|
73
|
+
};
|
|
74
|
+
static const uint8_t tile_inner[16] = {
|
|
75
|
+
0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, /* plane 0 specks */
|
|
76
|
+
0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, /* plane 1 too → colour 3 */
|
|
77
|
+
};
|
|
78
|
+
#define BG_WALL 1 /* BG tile index 1 → uploaded to $1010 */
|
|
79
|
+
#define BG_BRICK 2 /* BG tile index 2 → uploaded to $1020 */
|
|
80
|
+
#define BG_INNER 3 /* BG tile index 3 → uploaded to $1030 */
|
|
56
81
|
|
|
57
82
|
static const uint8_t palette[32] = {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
0x0F,
|
|
61
|
-
0x0F,
|
|
83
|
+
/* BG0: backdrop near-black, wall = steel blue (idx1), brick = brown
|
|
84
|
+
* (idx2), inner grid = dark grey (idx3). */
|
|
85
|
+
0x0F, 0x11, 0x17, 0x00,
|
|
86
|
+
0x0F, 0x11, 0x17, 0x00,
|
|
87
|
+
0x0F, 0x11, 0x17, 0x00,
|
|
88
|
+
0x0F, 0x11, 0x17, 0x00,
|
|
62
89
|
/* Sprite palette 0: idx1=red, idx2=green, idx3=blue */
|
|
63
90
|
0x0F, 0x16, 0x1A, 0x12,
|
|
64
91
|
0x0F, 0x16, 0x1A, 0x12,
|
|
@@ -156,11 +183,45 @@ void main(void) {
|
|
|
156
183
|
int8_t i;
|
|
157
184
|
|
|
158
185
|
ppu_off();
|
|
159
|
-
chr_ram_upload(0x0000, tile_blank, 16);
|
|
160
|
-
chr_ram_upload(0x0010, tile_block_r, 16);
|
|
186
|
+
chr_ram_upload(0x0000, tile_blank, 16); /* sprite slot 0 */
|
|
187
|
+
chr_ram_upload(0x0010, tile_block_r, 16); /* sprite slots 1..3 */
|
|
161
188
|
chr_ram_upload(0x0020, tile_block_g, 16);
|
|
162
189
|
chr_ram_upload(0x0030, tile_block_b, 16);
|
|
190
|
+
chr_ram_upload(0x1010, tile_wall, 16); /* BG slot 1 (background table) */
|
|
191
|
+
chr_ram_upload(0x1020, tile_brick, 16); /* BG slot 2 (cabinet wall) */
|
|
192
|
+
chr_ram_upload(0x1030, tile_inner, 16); /* BG slot 3 (well interior) */
|
|
163
193
|
palette_load(palette);
|
|
194
|
+
|
|
195
|
+
/* Draw the cabinet + well into the nametable while rendering is off
|
|
196
|
+
* (vram_unsafe_set = raw PPU write; the friendly tile_set queue would
|
|
197
|
+
* deadlock before ppu_on). The grid is 6 wide × 12 tall at pixel origin
|
|
198
|
+
* (80,16) → tile cols 10..15, rows 2..13; frame it one cell out. We first
|
|
199
|
+
* tile the WHOLE screen with brick (the machine cabinet), then carve the
|
|
200
|
+
* recessed well interior, then stamp the steel-blue frame on top — so the
|
|
201
|
+
* screen is fully covered instead of sprites floating on black. */
|
|
202
|
+
{
|
|
203
|
+
uint16_t gx0 = ORIGIN_X / 8, gy0 = ORIGIN_Y / 8; /* 10, 2 */
|
|
204
|
+
uint16_t gx1 = gx0 + GRID_W, gy1 = gy0 + GRID_H; /* 16, 14 (exclusive) */
|
|
205
|
+
uint16_t cc, rr;
|
|
206
|
+
/* whole-screen cabinet brick */
|
|
207
|
+
for (rr = 0; rr < 30; rr++)
|
|
208
|
+
for (cc = 0; cc < 32; cc++)
|
|
209
|
+
vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + cc), BG_BRICK);
|
|
210
|
+
/* recessed well interior (inside the frame) */
|
|
211
|
+
for (rr = gy0; rr < gy1; rr++)
|
|
212
|
+
for (cc = gx0; cc < gx1; cc++)
|
|
213
|
+
vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + cc), BG_INNER);
|
|
214
|
+
/* steel frame one cell out around the well */
|
|
215
|
+
for (cc = gx0 - 1; cc <= gx1; cc++) {
|
|
216
|
+
vram_unsafe_set((uint16_t)(0x2000 + (gy0 - 1) * 32 + cc), BG_WALL); /* top */
|
|
217
|
+
vram_unsafe_set((uint16_t)(0x2000 + gy1 * 32 + cc), BG_WALL); /* bottom */
|
|
218
|
+
}
|
|
219
|
+
for (rr = gy0 - 1; rr <= gy1; rr++) {
|
|
220
|
+
vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + (gx0 - 1)), BG_WALL); /* left */
|
|
221
|
+
vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + gx1), BG_WALL); /* right */
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
164
225
|
oam_clear();
|
|
165
226
|
ppu_on_all();
|
|
166
227
|
sound_init();
|
|
@@ -59,14 +59,22 @@ static const uint8_t tile_digits[10 * 16] = {
|
|
|
59
59
|
/* ── Background road tiles ───────────────────────────────────────────
|
|
60
60
|
* Default PPUCTRL ($90) reads BG patterns from pattern table 1 ($1000),
|
|
61
61
|
* so these go to CHR $1000+ and are indexed independently of the sprite
|
|
62
|
-
* tiles above.
|
|
63
|
-
*
|
|
62
|
+
* tiles above. The grey backdrop (colour 0) is the road surface; colour 1
|
|
63
|
+
* (white) draws the markings, colour 2 (green) the grass, colour 3 the
|
|
64
|
+
* dark seam in the tarmac.
|
|
64
65
|
*
|
|
65
|
-
* BG_T_EDGE:
|
|
66
|
-
* BG_T_LANE:
|
|
67
|
-
*
|
|
66
|
+
* BG_T_EDGE: a solid 2px vertical stripe — the road shoulder line.
|
|
67
|
+
* BG_T_LANE: a 2px vertical dash (on 4 rows / off 4) — the dashed centre
|
|
68
|
+
* lane marking when stacked down a column.
|
|
69
|
+
* BG_T_GRASS: a textured green roadside (colour 2 hatch) so the area
|
|
70
|
+
* outside the shoulders isn't flat — fills the screen sides.
|
|
71
|
+
* BG_T_ROAD: a faint tarmac texture (a couple of colour-3 specks) tiled
|
|
72
|
+
* across the driving surface so the road doesn't read as one
|
|
73
|
+
* solid grey block. */
|
|
68
74
|
#define BG_T_EDGE 1
|
|
69
75
|
#define BG_T_LANE 2
|
|
76
|
+
#define BG_T_GRASS 3
|
|
77
|
+
#define BG_T_ROAD 4
|
|
70
78
|
static const uint8_t bg_tile_edge[16] = {
|
|
71
79
|
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, /* plane 0 (colour bit 0) */
|
|
72
80
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
@@ -75,13 +83,21 @@ static const uint8_t bg_tile_lane[16] = {
|
|
|
75
83
|
0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, /* dash: 4 on, 4 off */
|
|
76
84
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
77
85
|
};
|
|
86
|
+
static const uint8_t bg_tile_grass[16] = {
|
|
87
|
+
0, 0, 0, 0, 0, 0, 0, 0, /* plane 0 clear */
|
|
88
|
+
0xEE, 0xBB, 0xEE, 0xBB, 0xEE, 0xBB, 0xEE, 0xBB, /* plane 1 → colour 2 hatch */
|
|
89
|
+
};
|
|
90
|
+
static const uint8_t bg_tile_road[16] = {
|
|
91
|
+
0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, /* plane 0 specks */
|
|
92
|
+
0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, /* plane 1 too → colour 3 */
|
|
93
|
+
};
|
|
78
94
|
|
|
79
95
|
static const uint8_t palette[32] = {
|
|
80
|
-
/* BG palettes — light grey backdrop simulates road */
|
|
81
|
-
0x10, 0x30,
|
|
82
|
-
0x10, 0x30,
|
|
83
|
-
0x10, 0x30,
|
|
84
|
-
0x10, 0x30,
|
|
96
|
+
/* BG palettes — light grey backdrop simulates road; idx2 = grass green */
|
|
97
|
+
0x10, 0x30, 0x1A, 0x00,
|
|
98
|
+
0x10, 0x30, 0x1A, 0x00,
|
|
99
|
+
0x10, 0x30, 0x1A, 0x00,
|
|
100
|
+
0x10, 0x30, 0x1A, 0x00,
|
|
85
101
|
/* Sprite palettes */
|
|
86
102
|
0x10, 0x21, 0x16, 0x30, /* sp0: blue car (P1) */
|
|
87
103
|
0x10, 0x16, 0x21, 0x30, /* sp1: red enemy */
|
|
@@ -125,8 +141,22 @@ static uint8_t aabb(Car *a, Car *b) {
|
|
|
125
141
|
#define ROAD_DIV_1 13
|
|
126
142
|
#define ROAD_DIV_2 17
|
|
127
143
|
static void draw_road(void) {
|
|
128
|
-
uint8_t row;
|
|
144
|
+
uint8_t row, col;
|
|
129
145
|
uint16_t base;
|
|
146
|
+
/* Fill the WHOLE nametable so nothing reads as flat colour: grass on the
|
|
147
|
+
* roadside (outside the shoulders) and a faint tarmac texture on the
|
|
148
|
+
* driving surface. Then stamp the shoulder + lane markings on top. */
|
|
149
|
+
for (row = 0; row < 30; row++) {
|
|
150
|
+
base = (uint16_t)(0x2000 + (uint16_t)row * 32);
|
|
151
|
+
for (col = 0; col < 32; col++) {
|
|
152
|
+
if (col < ROAD_EDGE_L || col > ROAD_EDGE_R) {
|
|
153
|
+
vram_unsafe_set((uint16_t)(base + col), BG_T_GRASS); /* roadside */
|
|
154
|
+
} else if (((row + col) & 3) == 0) {
|
|
155
|
+
vram_unsafe_set((uint16_t)(base + col), BG_T_ROAD); /* tarmac speck */
|
|
156
|
+
}
|
|
157
|
+
/* else leave colour-0 grey road surface */
|
|
158
|
+
}
|
|
159
|
+
}
|
|
130
160
|
for (row = ROAD_TOP_ROW; row <= ROAD_BOT_ROW; row++) {
|
|
131
161
|
base = (uint16_t)(0x2000 + (uint16_t)row * 32);
|
|
132
162
|
vram_unsafe_set((uint16_t)(base + ROAD_EDGE_L), BG_T_EDGE);
|
|
@@ -183,8 +213,10 @@ void main(void) {
|
|
|
183
213
|
|
|
184
214
|
/* BG road tiles live in pattern table 1 ($1000) — that's where the
|
|
185
215
|
* default PPUCTRL ($90) tells the PPU to read background patterns. */
|
|
186
|
-
chr_ram_upload((uint16_t)(0x1000 + BG_T_EDGE
|
|
187
|
-
chr_ram_upload((uint16_t)(0x1000 + BG_T_LANE
|
|
216
|
+
chr_ram_upload((uint16_t)(0x1000 + BG_T_EDGE * 16), bg_tile_edge, 16);
|
|
217
|
+
chr_ram_upload((uint16_t)(0x1000 + BG_T_LANE * 16), bg_tile_lane, 16);
|
|
218
|
+
chr_ram_upload((uint16_t)(0x1000 + BG_T_GRASS * 16), bg_tile_grass, 16);
|
|
219
|
+
chr_ram_upload((uint16_t)(0x1000 + BG_T_ROAD * 16), bg_tile_road, 16);
|
|
188
220
|
|
|
189
221
|
palette_load(palette);
|
|
190
222
|
draw_road(); /* paint the static road while the PPU is off */
|
|
@@ -38,10 +38,34 @@ static const uint8_t tile_enemy[16] = {
|
|
|
38
38
|
0x81, 0x42, 0x24, 0xFF, 0xFF, 0x24, 0x42, 0x81, /* plane 0 — spider-ish */
|
|
39
39
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
40
40
|
};
|
|
41
|
+
/* BG starfield tiles — painted across the whole nametable so the screen
|
|
42
|
+
* reads as a real "space" scene on boot instead of flat black. They live in
|
|
43
|
+
* the BACKGROUND pattern table ($1000), separate from the sprite tiles above
|
|
44
|
+
* (the runtime puts BG at $1000, sprites at $0000).
|
|
45
|
+
*
|
|
46
|
+
* BG_DUST — a faint checkerboard "space dust" that tiles seamlessly; the
|
|
47
|
+
* base layer that covers the whole field so it never reads blank.
|
|
48
|
+
* BG_STAR — three small stars (colour 1) sprinkled over the dust.
|
|
49
|
+
* BG_BRITE — a single bright + star (colour 2) for the rare close star. */
|
|
50
|
+
static const uint8_t tile_dust[16] = {
|
|
51
|
+
0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, /* plane 0: checker (idx 1) */
|
|
52
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
53
|
+
};
|
|
54
|
+
static const uint8_t tile_star[16] = {
|
|
55
|
+
0x00, 0x08, 0x00, 0x42, 0x00, 0x00, 0x20, 0x01, /* plane 0: four small stars */
|
|
56
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
57
|
+
};
|
|
58
|
+
static const uint8_t tile_brite[16] = {
|
|
59
|
+
0x00, 0x00, 0x10, 0x38, 0x10, 0x00, 0x00, 0x00, /* plane 0: + arms (idx ... ) */
|
|
60
|
+
0x00, 0x00, 0x10, 0x38, 0x10, 0x00, 0x00, 0x00, /* plane 1 set too → colour 3 */
|
|
61
|
+
};
|
|
62
|
+
#define BG_DUST 1 /* BG tile index 1 → uploaded to $1010 */
|
|
63
|
+
#define BG_STAR 2 /* BG tile index 2 → uploaded to $1020 */
|
|
64
|
+
#define BG_BRITE 3 /* BG tile index 3 → uploaded to $1030 */
|
|
41
65
|
|
|
42
66
|
/* ── Palette ─────────────────────────────────────────────────────── */
|
|
43
67
|
static const uint8_t palette[32] = {
|
|
44
|
-
/*
|
|
68
|
+
/* BG0: backdrop near-black, star colour = dim white */
|
|
45
69
|
0x0F, 0x10, 0x20, 0x30,
|
|
46
70
|
0x0F, 0x10, 0x20, 0x30,
|
|
47
71
|
0x0F, 0x10, 0x20, 0x30,
|
|
@@ -126,14 +150,39 @@ void main(void) {
|
|
|
126
150
|
|
|
127
151
|
ppu_off();
|
|
128
152
|
|
|
129
|
-
/* Upload tile data — blank in slot 0 + 3 sprite tiles in slots 1..3
|
|
153
|
+
/* Upload tile data — blank in slot 0 + 3 sprite tiles in slots 1..3,
|
|
154
|
+
* all in the SPRITE pattern table ($0000). */
|
|
130
155
|
chr_ram_upload(0x0000, tile_blank, 16);
|
|
131
156
|
chr_ram_upload(0x0010, tile_ship, 16);
|
|
132
157
|
chr_ram_upload(0x0020, tile_bullet, 16);
|
|
133
158
|
chr_ram_upload(0x0030, tile_enemy, 16);
|
|
159
|
+
/* Upload the starfield tiles to the BACKGROUND pattern table
|
|
160
|
+
* ($1010/$1020/$1030 = BG slots 1/2/3). */
|
|
161
|
+
chr_ram_upload(0x1010, tile_dust, 16);
|
|
162
|
+
chr_ram_upload(0x1020, tile_star, 16);
|
|
163
|
+
chr_ram_upload(0x1030, tile_brite, 16);
|
|
134
164
|
|
|
135
165
|
palette_load(palette);
|
|
136
166
|
|
|
167
|
+
/* Paint a full starfield directly into the nametable while the PPU is off
|
|
168
|
+
* (vram_unsafe_set = raw write; tile_set's NMI queue would deadlock
|
|
169
|
+
* before ppu_on). Every one of the 32×30 cells gets the faint "dust" base,
|
|
170
|
+
* with small stars sprinkled every few cells and the odd bright star — a
|
|
171
|
+
* deterministic scatter so the backdrop is unambiguously "space", densely
|
|
172
|
+
* filled rather than flat black. */
|
|
173
|
+
{
|
|
174
|
+
uint16_t r, cc;
|
|
175
|
+
uint8_t tile;
|
|
176
|
+
for (r = 0; r < 30; r++) {
|
|
177
|
+
for (cc = 0; cc < 32; cc++) {
|
|
178
|
+
tile = BG_DUST; /* base dust everywhere */
|
|
179
|
+
if (((r * 5 + cc * 3) % 7) == 0) tile = BG_STAR; /* sprinkle stars */
|
|
180
|
+
if (((r * 3 + cc * 7) % 23) == 0) tile = BG_BRITE; /* rare bright one */
|
|
181
|
+
vram_unsafe_set((uint16_t)(0x2000 + r * 32 + cc), tile);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
137
186
|
oam_clear();
|
|
138
187
|
ppu_on_all();
|
|
139
188
|
sound_init();
|
|
@@ -34,6 +34,27 @@ static const uint8_t tile_ball[16] = {
|
|
|
34
34
|
0x00, 0x3C, 0x7E, 0x7E, 0x7E, 0x7E, 0x3C, 0x00,
|
|
35
35
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
36
36
|
};
|
|
37
|
+
/* BG court tiles (background pattern table $1000), so the court reads as a
|
|
38
|
+
* real Pong arena on boot instead of sprites on flat black:
|
|
39
|
+
* BG_WALL — solid bar (idx1 white): the top/bottom rails.
|
|
40
|
+
* BG_NET — dashed vertical bar (idx1 white): the centre net.
|
|
41
|
+
* BG_FLOOR — a faint court-floor hatch (idx2 green) tiled across the whole
|
|
42
|
+
* playfield so the arena surface is covered, not black. */
|
|
43
|
+
static const uint8_t tile_wall[16] = {
|
|
44
|
+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
45
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
46
|
+
};
|
|
47
|
+
static const uint8_t tile_net[16] = {
|
|
48
|
+
0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, /* dashed vertical bar */
|
|
49
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
50
|
+
};
|
|
51
|
+
static const uint8_t tile_floor[16] = {
|
|
52
|
+
0, 0, 0, 0, 0, 0, 0, 0, /* plane 0 clear */
|
|
53
|
+
0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, /* plane 1 → colour 2 checker */
|
|
54
|
+
};
|
|
55
|
+
#define BG_WALL 1 /* BG slot 1 → $1010 */
|
|
56
|
+
#define BG_NET 2 /* BG slot 2 → $1020 */
|
|
57
|
+
#define BG_FLOOR 3 /* BG slot 3 → $1030 */
|
|
37
58
|
/* Digits 0-9 (3 wide × 5 tall, padded to 8×8). Used for the score HUD. */
|
|
38
59
|
static const uint8_t tile_digits[10 * 16] = {
|
|
39
60
|
/* 0 */ 0xE0,0xA0,0xA0,0xA0,0xE0,0x00,0x00,0x00, 0,0,0,0,0,0,0,0,
|
|
@@ -55,11 +76,12 @@ static const uint8_t tile_digits[10 * 16] = {
|
|
|
55
76
|
#define T_DIGIT0 3 /* digits live at slots 3..12 */
|
|
56
77
|
|
|
57
78
|
static const uint8_t palette[32] = {
|
|
58
|
-
/*
|
|
59
|
-
|
|
60
|
-
0x0F, 0x30,
|
|
61
|
-
0x0F, 0x30,
|
|
62
|
-
0x0F, 0x30,
|
|
79
|
+
/* BG0: backdrop near-black, court walls/net = white (idx1),
|
|
80
|
+
* court-floor hatch = dark green (idx2) */
|
|
81
|
+
0x0F, 0x30, 0x1A, 0x00,
|
|
82
|
+
0x0F, 0x30, 0x1A, 0x00,
|
|
83
|
+
0x0F, 0x30, 0x1A, 0x00,
|
|
84
|
+
0x0F, 0x30, 0x1A, 0x00,
|
|
63
85
|
/* Sprite palettes */
|
|
64
86
|
0x0F, 0x30, 0x16, 0x12, /* sp0: white paddle */
|
|
65
87
|
0x0F, 0x30, 0x16, 0x12, /* sp1: white ball */
|
|
@@ -103,13 +125,36 @@ void main(void) {
|
|
|
103
125
|
|
|
104
126
|
ppu_off();
|
|
105
127
|
|
|
106
|
-
/* Upload tiles. */
|
|
128
|
+
/* Upload sprite tiles (sprite pattern table $0000). */
|
|
107
129
|
chr_ram_upload(T_BLANK * 16, tile_blank, 16);
|
|
108
130
|
chr_ram_upload(T_PADDLE * 16, tile_paddle, 16);
|
|
109
131
|
chr_ram_upload(T_BALL * 16, tile_ball, 16);
|
|
110
132
|
chr_ram_upload(T_DIGIT0 * 16, tile_digits, sizeof(tile_digits));
|
|
133
|
+
/* Upload court tiles to the BACKGROUND pattern table ($1010..$1030). */
|
|
134
|
+
chr_ram_upload(0x1010, tile_wall, 16);
|
|
135
|
+
chr_ram_upload(0x1020, tile_net, 16);
|
|
136
|
+
chr_ram_upload(0x1030, tile_floor, 16);
|
|
111
137
|
|
|
112
138
|
palette_load(palette);
|
|
139
|
+
|
|
140
|
+
/* Paint the court into the nametable while rendering is off
|
|
141
|
+
* (vram_unsafe_set = raw write). First carpet the whole playfield with the
|
|
142
|
+
* green floor hatch so the arena surface is covered, then lay the top/
|
|
143
|
+
* bottom rails (rows 1 and 27) and the dashed centre net (column 15) on
|
|
144
|
+
* top. Without the floor the screen is just sprites on flat black. */
|
|
145
|
+
{
|
|
146
|
+
uint16_t cc, rr;
|
|
147
|
+
for (rr = 0; rr < 30; rr++)
|
|
148
|
+
for (cc = 0; cc < 32; cc++)
|
|
149
|
+
vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + cc), BG_FLOOR);
|
|
150
|
+
for (cc = 0; cc < 32; cc++) {
|
|
151
|
+
vram_unsafe_set((uint16_t)(0x2000 + 1 * 32 + cc), BG_WALL); /* top rail (y≈8) */
|
|
152
|
+
vram_unsafe_set((uint16_t)(0x2000 + 27 * 32 + cc), BG_WALL); /* bottom rail(y≈216)*/
|
|
153
|
+
}
|
|
154
|
+
for (rr = 2; rr < 27; rr++)
|
|
155
|
+
vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + 15), BG_NET); /* centre net */
|
|
156
|
+
}
|
|
157
|
+
|
|
113
158
|
oam_clear();
|
|
114
159
|
ppu_on_all();
|
|
115
160
|
sound_init();
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PC Engine "platformer" — a side-scrolling platformer scaffold.
|
|
3
|
+
*
|
|
4
|
+
* Run and jump across a world wider than one screen. The d-pad moves left/right,
|
|
5
|
+
* button I jumps; gravity pulls you down and you land on top of solid platforms.
|
|
6
|
+
* The camera follows the player and the background scrolls smoothly via the VDC
|
|
7
|
+
* background X-scroll register (BXR/R7).
|
|
8
|
+
*
|
|
9
|
+
* The PCE BAT (background map) is a 32x32 virtual screen (256px) that WRAPS, so
|
|
10
|
+
* a world wider than 256px needs COLUMN STREAMING: each time the camera crosses
|
|
11
|
+
* an 8px boundary we rewrite the BAT column that is about to scroll into view
|
|
12
|
+
* with the next world column's tiles. This mirrors the SMS platformer scaffold,
|
|
13
|
+
* using BXR instead of SMS R8.
|
|
14
|
+
*
|
|
15
|
+
* PCE notes (see pce_hw.h / MENTAL_MODEL.md):
|
|
16
|
+
* - disp_enable() turns on BG + sprites + the VBlank IRQ (waitvsync needs it).
|
|
17
|
+
* - .bss must be non-empty (pce_video.c's _pce_keep[] covers it).
|
|
18
|
+
* - we set BXR every frame via vdc_set_reg(VDC_BXR, camX) for smooth scroll.
|
|
19
|
+
*
|
|
20
|
+
* cc65 is C89 — declare locals at the top of a block.
|
|
21
|
+
*/
|
|
22
|
+
#include <pce.h>
|
|
23
|
+
#include <stdint.h> /* int8_t/int16_t/int32_t for sub-pixel physics + camera */
|
|
24
|
+
#include "pce_hw.h"
|
|
25
|
+
|
|
26
|
+
/* ---- VRAM layout (word addresses) --------------------------------------- */
|
|
27
|
+
#define BAT_VRAM 0x0000 /* 32x32 background map */
|
|
28
|
+
#define SKY_VRAM 0x1000 /* BG tile: sky (solid colour 1) */
|
|
29
|
+
#define WALL_VRAM 0x1010 /* BG tile: platform block (colour 2) */
|
|
30
|
+
#define WALLTOP_VRAM 0x1020 /* BG tile: platform top edge (colour 3 strip) */
|
|
31
|
+
#define PLAYER_VRAM 0x1800 /* 16x16 player */
|
|
32
|
+
|
|
33
|
+
#define BAT_ENTRY(pal, vram) ((u16)(((pal) << 12) | ((vram) >> 4)))
|
|
34
|
+
|
|
35
|
+
/* ---- world -------------------------------------------------------------- */
|
|
36
|
+
#define WORLD_COLS 96 /* 96 cells = 768 px world */
|
|
37
|
+
#define WORLD_W (WORLD_COLS * 8)
|
|
38
|
+
#define SCREEN_W 256
|
|
39
|
+
#define VIS_ROWS 28 /* 224-line display = 28 rows */
|
|
40
|
+
|
|
41
|
+
typedef struct { int16_t x, y, w, h; } Rect;
|
|
42
|
+
|
|
43
|
+
/* Platforms in WORLD pixel coords, spread across the 768px world. */
|
|
44
|
+
static const Rect platforms[] = {
|
|
45
|
+
{ 0, 200, 768, 24 }, /* floor spans the world */
|
|
46
|
+
{ 48, 168, 56, 8 },
|
|
47
|
+
{ 140, 152, 64, 8 },
|
|
48
|
+
{ 232, 128, 56, 8 },
|
|
49
|
+
{ 96, 112, 40, 8 },
|
|
50
|
+
{ 320, 160, 72, 8 },
|
|
51
|
+
{ 416, 128, 64, 8 },
|
|
52
|
+
{ 360, 88, 48, 8 },
|
|
53
|
+
{ 512, 152, 80, 8 },
|
|
54
|
+
{ 600, 120, 56, 8 },
|
|
55
|
+
{ 672, 168, 72, 8 },
|
|
56
|
+
{ 560, 80, 48, 8 }
|
|
57
|
+
};
|
|
58
|
+
#define N_PLATFORMS (sizeof(platforms) / sizeof(platforms[0]))
|
|
59
|
+
|
|
60
|
+
/* ---- state -------------------------------------------------------------- */
|
|
61
|
+
static int16_t px, py; /* player position in 1/16-px units */
|
|
62
|
+
static int16_t vx, vy;
|
|
63
|
+
static int16_t camX, lastCamCol;
|
|
64
|
+
static u8 pad, prev_pad;
|
|
65
|
+
static u16 spr_buf[64];
|
|
66
|
+
static u16 tile_buf[16];
|
|
67
|
+
|
|
68
|
+
static void make_solid_tile(u16 *t, u8 ci) {
|
|
69
|
+
u8 r;
|
|
70
|
+
u8 p0 = (ci & 1) ? 0xFF : 0x00;
|
|
71
|
+
u8 p1 = (ci & 2) ? 0xFF : 0x00;
|
|
72
|
+
u8 p2 = (ci & 4) ? 0xFF : 0x00;
|
|
73
|
+
u8 p3 = (ci & 8) ? 0xFF : 0x00;
|
|
74
|
+
for (r = 0; r < 8; ++r) {
|
|
75
|
+
t[r] = (u16)(p0 | (p1 << 8));
|
|
76
|
+
t[r + 8] = (u16)(p2 | (p3 << 8));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* platform-top tile: colour 2 body with a colour-3 highlight on the top 2 rows */
|
|
81
|
+
static void make_walltop_tile(u16 *t) {
|
|
82
|
+
make_solid_tile(t, 2);
|
|
83
|
+
/* rows 0,1: set plane0 too so those pixels read colour 3 (planes0+1) */
|
|
84
|
+
t[0] = (u16)(0x00FF | (t[0] & 0xFF00));
|
|
85
|
+
t[1] = (u16)(0x00FF | (t[1] & 0xFF00));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static void make_player_sprite(void) {
|
|
89
|
+
static const u16 body[16] = {
|
|
90
|
+
0x07E0, 0x0FF0, 0x1FF8, 0x1818, 0x1FF8, 0x1FF8, 0x3FFC, 0x7FFE,
|
|
91
|
+
0x7FFE, 0x7FFE, 0x3FFC, 0x1FF8, 0x0E70, 0x0C30, 0x0C30, 0x1818
|
|
92
|
+
};
|
|
93
|
+
static const u16 eyes[16] = {
|
|
94
|
+
0x0000, 0x0000, 0x0000, 0x0000, 0x0990, 0x0990, 0x0000, 0x0000,
|
|
95
|
+
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
|
|
96
|
+
};
|
|
97
|
+
u8 r;
|
|
98
|
+
for (r = 0; r < 64; ++r) spr_buf[r] = 0;
|
|
99
|
+
for (r = 0; r < 16; ++r) {
|
|
100
|
+
spr_buf[r] = (u16)(body[r] & ~eyes[r]); /* plane0 -> colour 1 */
|
|
101
|
+
spr_buf[r + 16] = eyes[r]; /* plane1 -> colour 2 */
|
|
102
|
+
}
|
|
103
|
+
load_tiles(PLAYER_VRAM, spr_buf, 64);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Is world cell (col,row) inside any platform? */
|
|
107
|
+
static u8 cell_is_wall(int16_t col, u8 row) {
|
|
108
|
+
int16_t cx = (int16_t)(col << 3);
|
|
109
|
+
int16_t cy = (int16_t)((int16_t)row << 3);
|
|
110
|
+
u8 i;
|
|
111
|
+
const Rect *p;
|
|
112
|
+
for (i = 0; i < N_PLATFORMS; ++i) {
|
|
113
|
+
p = &platforms[i];
|
|
114
|
+
if (cx + 8 > p->x && cx < p->x + p->w &&
|
|
115
|
+
cy + 8 > p->y && cy < p->y + p->h) return 1;
|
|
116
|
+
}
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Is world cell the TOP row of a platform (for the highlighted edge tile)? */
|
|
121
|
+
static u8 cell_is_top(int16_t col, u8 row) {
|
|
122
|
+
int16_t cy = (int16_t)((int16_t)row << 3);
|
|
123
|
+
int16_t cx = (int16_t)(col << 3);
|
|
124
|
+
u8 i;
|
|
125
|
+
const Rect *p;
|
|
126
|
+
for (i = 0; i < N_PLATFORMS; ++i) {
|
|
127
|
+
p = &platforms[i];
|
|
128
|
+
if (cx + 8 > p->x && cx < p->x + p->w && cy >= p->y && cy < p->y + 8) return 1;
|
|
129
|
+
}
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Write one world column into its wrapped BAT column. */
|
|
134
|
+
static void paint_column(int16_t worldCol) {
|
|
135
|
+
u8 ntCol, row;
|
|
136
|
+
u16 e;
|
|
137
|
+
if (worldCol < 0 || worldCol >= WORLD_COLS) return;
|
|
138
|
+
ntCol = (u8)(worldCol & 31);
|
|
139
|
+
for (row = 0; row < 32; ++row) {
|
|
140
|
+
if (row < VIS_ROWS && cell_is_wall(worldCol, row)) {
|
|
141
|
+
e = cell_is_top(worldCol, row)
|
|
142
|
+
? BAT_ENTRY(0, WALLTOP_VRAM)
|
|
143
|
+
: BAT_ENTRY(0, WALL_VRAM);
|
|
144
|
+
} else {
|
|
145
|
+
e = BAT_ENTRY(0, SKY_VRAM);
|
|
146
|
+
}
|
|
147
|
+
vram_set_write_addr((u16)(BAT_VRAM + row * 32 + ntCol));
|
|
148
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
149
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static void paint_initial(void) {
|
|
154
|
+
int16_t c;
|
|
155
|
+
for (c = 0; c < 32; ++c) paint_column(c);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
static u8 on_platform(int16_t ipx, int16_t ipy) {
|
|
159
|
+
u8 i;
|
|
160
|
+
const Rect *p;
|
|
161
|
+
for (i = 0; i < N_PLATFORMS; ++i) {
|
|
162
|
+
p = &platforms[i];
|
|
163
|
+
if (ipy + 16 == p->y && ipx + 12 > p->x && ipx + 4 < p->x + p->w) return 1;
|
|
164
|
+
}
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
void main(void) {
|
|
169
|
+
const int16_t GRAVITY = 10;
|
|
170
|
+
const int16_t MOVE = 22;
|
|
171
|
+
const int16_t JUMP = -200;
|
|
172
|
+
const int16_t MAXFALL = 300;
|
|
173
|
+
|
|
174
|
+
_pce_keep[0] = 0;
|
|
175
|
+
|
|
176
|
+
/* palette */
|
|
177
|
+
vce_set_color(0, PCE_RGB(1, 2, 5)); /* backdrop sky blue */
|
|
178
|
+
vce_set_color(1, PCE_RGB(2, 4, 7)); /* BG c1: sky */
|
|
179
|
+
vce_set_color(2, PCE_RGB(3, 2, 1)); /* BG c2: brown platform */
|
|
180
|
+
vce_set_color(3, PCE_RGB(1, 6, 1)); /* BG c3: green grassy top */
|
|
181
|
+
vce_set_color(256, PCE_RGB(0, 0, 0)); /* spr transparent */
|
|
182
|
+
vce_set_color(257, PCE_RGB(7, 1, 1)); /* spr c1: red body */
|
|
183
|
+
vce_set_color(258, PCE_RGB(7, 7, 7)); /* spr c2: white eyes */
|
|
184
|
+
|
|
185
|
+
make_solid_tile(tile_buf, 1); load_tiles(SKY_VRAM, tile_buf, 16);
|
|
186
|
+
make_solid_tile(tile_buf, 2); load_tiles(WALL_VRAM, tile_buf, 16);
|
|
187
|
+
make_walltop_tile(tile_buf); load_tiles(WALLTOP_VRAM, tile_buf, 16);
|
|
188
|
+
make_player_sprite();
|
|
189
|
+
|
|
190
|
+
paint_initial();
|
|
191
|
+
|
|
192
|
+
px = (int16_t)(24 << 4);
|
|
193
|
+
py = (int16_t)(160 << 4);
|
|
194
|
+
vx = 0; vy = 0;
|
|
195
|
+
camX = 0; lastCamCol = 0;
|
|
196
|
+
prev_pad = 0;
|
|
197
|
+
|
|
198
|
+
set_sprite(0, (u16)(px >> 4), (u16)(py >> 4), PLAYER_VRAM >> 6, 0);
|
|
199
|
+
satb_dma();
|
|
200
|
+
|
|
201
|
+
pce_joy_init();
|
|
202
|
+
disp_enable();
|
|
203
|
+
|
|
204
|
+
for (;;) {
|
|
205
|
+
int16_t ipx, ipy, npy, sx;
|
|
206
|
+
int16_t camCol;
|
|
207
|
+
int32_t np;
|
|
208
|
+
u8 grounded;
|
|
209
|
+
u8 i;
|
|
210
|
+
const Rect *p;
|
|
211
|
+
|
|
212
|
+
waitvsync();
|
|
213
|
+
pad = pce_joy_read();
|
|
214
|
+
|
|
215
|
+
ipx = px >> 4;
|
|
216
|
+
ipy = py >> 4;
|
|
217
|
+
|
|
218
|
+
/* camera follows player, clamped to world */
|
|
219
|
+
camX = (int16_t)(ipx - (SCREEN_W / 2 - 8));
|
|
220
|
+
if (camX < 0) camX = 0;
|
|
221
|
+
if (camX > WORLD_W - SCREEN_W) camX = (int16_t)(WORLD_W - SCREEN_W);
|
|
222
|
+
|
|
223
|
+
/* stream columns entering from the edges */
|
|
224
|
+
camCol = camX >> 3;
|
|
225
|
+
while (camCol > lastCamCol) { lastCamCol++; paint_column((int16_t)(lastCamCol + 31)); }
|
|
226
|
+
while (camCol < lastCamCol) { lastCamCol--; paint_column(lastCamCol); }
|
|
227
|
+
|
|
228
|
+
/* smooth pixel scroll via BG X register */
|
|
229
|
+
vdc_set_reg(VDC_BXR, (u16)camX);
|
|
230
|
+
|
|
231
|
+
/* horizontal move */
|
|
232
|
+
vx = 0;
|
|
233
|
+
if (pad & PCE_JOY_LEFT) vx = (int16_t)(-MOVE);
|
|
234
|
+
if (pad & PCE_JOY_RIGHT) vx = MOVE;
|
|
235
|
+
|
|
236
|
+
grounded = on_platform(ipx, ipy);
|
|
237
|
+
if ((pad & PCE_JOY_I) && !(prev_pad & PCE_JOY_I) && grounded) {
|
|
238
|
+
vy = JUMP;
|
|
239
|
+
psg_tone(0, 0x200, 24);
|
|
240
|
+
}
|
|
241
|
+
prev_pad = pad;
|
|
242
|
+
|
|
243
|
+
vy = (int16_t)(vy + GRAVITY);
|
|
244
|
+
if (vy > MAXFALL) vy = MAXFALL;
|
|
245
|
+
if (grounded && vy > 0) vy = 0;
|
|
246
|
+
|
|
247
|
+
/* horizontal integrate + clamp */
|
|
248
|
+
px = (int16_t)(px + vx);
|
|
249
|
+
if (px < 0) px = 0;
|
|
250
|
+
if (px > ((WORLD_W - 16) << 4)) px = (int16_t)((WORLD_W - 16) << 4);
|
|
251
|
+
|
|
252
|
+
/* vertical integrate with land-on-top */
|
|
253
|
+
np = (int32_t)py + (int32_t)vy;
|
|
254
|
+
npy = (int16_t)(np >> 4);
|
|
255
|
+
if (vy > 0) {
|
|
256
|
+
u8 landed = 0;
|
|
257
|
+
for (i = 0; i < N_PLATFORMS; ++i) {
|
|
258
|
+
p = &platforms[i];
|
|
259
|
+
if (ipy + 16 <= p->y && npy + 16 >= p->y &&
|
|
260
|
+
ipx + 12 > p->x && ipx + 4 < p->x + p->w) {
|
|
261
|
+
py = (int16_t)((p->y - 16) << 4);
|
|
262
|
+
vy = 0;
|
|
263
|
+
landed = 1;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (!landed) py = (int16_t)np;
|
|
268
|
+
} else {
|
|
269
|
+
py = (int16_t)np;
|
|
270
|
+
}
|
|
271
|
+
if (py > (224 << 4)) { px = (int16_t)(24 << 4); py = (int16_t)(160 << 4); vy = 0; }
|
|
272
|
+
|
|
273
|
+
/* free the jump SFX channel after it rings */
|
|
274
|
+
if (vy == 0) psg_off(0);
|
|
275
|
+
|
|
276
|
+
/* draw player in screen space */
|
|
277
|
+
sx = (int16_t)((px >> 4) - camX);
|
|
278
|
+
if (sx < 0) sx = 0;
|
|
279
|
+
if (sx > 240) sx = 240;
|
|
280
|
+
set_sprite(0, (u16)sx, (u16)(py >> 4), PLAYER_VRAM >> 6, 0);
|
|
281
|
+
satb_dma();
|
|
282
|
+
}
|
|
283
|
+
}
|