romdevtools 0.16.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 +60 -12
- package/CHANGELOG.md +258 -0
- 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/tool-registry.js +26 -1
- 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/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 +76 -3
- 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/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
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PC Engine "shmup" — a vertical shoot-'em-up scaffold.
|
|
3
|
+
*
|
|
4
|
+
* Fly a ship around the bottom of the screen with the d-pad and fire upward
|
|
5
|
+
* with button I. Enemies spawn at the top in waves and drift down; a bullet
|
|
6
|
+
* that overlaps an enemy destroys it and scores 10. The HUD shows the score
|
|
7
|
+
* with background digit tiles. A scrolling starfield BG keeps the screen full
|
|
8
|
+
* (so it clears the verify gate and the sprites read clearly).
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the NES/Genesis/SNES/GB/SMS shmup scaffolds, translated to the PCE
|
|
11
|
+
* helper API:
|
|
12
|
+
* - object pools (player + bullets + enemies) updated each frame
|
|
13
|
+
* - AABB collision
|
|
14
|
+
* - a wave spawner on a frame counter
|
|
15
|
+
* - 64-sprite shadow SATB + satb_dma()
|
|
16
|
+
*
|
|
17
|
+
* PCE notes (see pce_hw.h / MENTAL_MODEL.md):
|
|
18
|
+
* - disp_enable() turns on BG + sprites AND the VBlank IRQ so waitvsync()
|
|
19
|
+
* actually returns (without the IRQ bit the loop spins forever).
|
|
20
|
+
* - .bss must be non-empty; pce_video.c's _pce_keep[] covers that, and we
|
|
21
|
+
* touch _pce_keep[0] for clarity.
|
|
22
|
+
* - sprites get the SPBG-front bit from set_sprite(), so they draw over the
|
|
23
|
+
* opaque starfield BG.
|
|
24
|
+
*
|
|
25
|
+
* cc65 is C89 — declare locals at the top of a block.
|
|
26
|
+
*/
|
|
27
|
+
#include <pce.h>
|
|
28
|
+
#include "pce_hw.h"
|
|
29
|
+
|
|
30
|
+
/* ---- VRAM layout (word addresses) --------------------------------------- */
|
|
31
|
+
#define BAT_VRAM 0x0000 /* 32x32 background map */
|
|
32
|
+
#define FONT_VRAM 0x1000 /* digit/glyph tiles (8x8, 16 words each) */
|
|
33
|
+
#define STAR0_VRAM 0x1400 /* BG tile: empty space (solid colour 1) */
|
|
34
|
+
#define STAR1_VRAM 0x1410 /* BG tile: space band (solid colour 2) */
|
|
35
|
+
#define STAR2_VRAM 0x1420 /* BG tile: space + a star pixel */
|
|
36
|
+
#define SHIP_VRAM 0x1800 /* 16x16 player ship */
|
|
37
|
+
#define BULLET_VRAM 0x1840 /* 16x16 bullet */
|
|
38
|
+
#define ENEMY_VRAM 0x1880 /* 16x16 enemy */
|
|
39
|
+
|
|
40
|
+
#define BAT_ENTRY(pal, vram) ((u16)(((pal) << 12) | ((vram) >> 4)))
|
|
41
|
+
|
|
42
|
+
#define MAX_BULLETS 6
|
|
43
|
+
#define MAX_ENEMIES 6
|
|
44
|
+
|
|
45
|
+
/* ---- 5x7 glyph font (digits + a few letters for the HUD) ----------------- */
|
|
46
|
+
#define G_BLANK 0
|
|
47
|
+
#define G_0 1 /* digits 0..9 -> tiles 1..10 */
|
|
48
|
+
#define G_S 11
|
|
49
|
+
#define G_C 12
|
|
50
|
+
#define G_O 13
|
|
51
|
+
#define G_R 14
|
|
52
|
+
#define G_E 15
|
|
53
|
+
#define NUM_GLYPHS 16
|
|
54
|
+
|
|
55
|
+
static const u8 FONT5x7[NUM_GLYPHS][7] = {
|
|
56
|
+
/* BLANK */ {0,0,0,0,0,0,0},
|
|
57
|
+
/* 0 */ {0x0E,0x11,0x13,0x15,0x19,0x11,0x0E},
|
|
58
|
+
/* 1 */ {0x04,0x0C,0x04,0x04,0x04,0x04,0x0E},
|
|
59
|
+
/* 2 */ {0x0E,0x11,0x01,0x02,0x04,0x08,0x1F},
|
|
60
|
+
/* 3 */ {0x1F,0x02,0x04,0x02,0x01,0x11,0x0E},
|
|
61
|
+
/* 4 */ {0x02,0x06,0x0A,0x12,0x1F,0x02,0x02},
|
|
62
|
+
/* 5 */ {0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E},
|
|
63
|
+
/* 6 */ {0x06,0x08,0x10,0x1E,0x11,0x11,0x0E},
|
|
64
|
+
/* 7 */ {0x1F,0x01,0x02,0x04,0x08,0x08,0x08},
|
|
65
|
+
/* 8 */ {0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E},
|
|
66
|
+
/* 9 */ {0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C},
|
|
67
|
+
/* S */ {0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E},
|
|
68
|
+
/* C */ {0x0E,0x11,0x10,0x10,0x10,0x11,0x0E},
|
|
69
|
+
/* O */ {0x0E,0x11,0x11,0x11,0x11,0x11,0x0E},
|
|
70
|
+
/* R */ {0x1E,0x11,0x11,0x1E,0x14,0x12,0x11},
|
|
71
|
+
/* E */ {0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/* ---- game state --------------------------------------------------------- */
|
|
75
|
+
typedef struct { u16 x, y; u8 alive; } Obj;
|
|
76
|
+
|
|
77
|
+
static Obj player;
|
|
78
|
+
static Obj bullets[MAX_BULLETS];
|
|
79
|
+
static Obj enemies[MAX_ENEMIES];
|
|
80
|
+
static u16 score;
|
|
81
|
+
static u8 spawn_timer;
|
|
82
|
+
static u16 rng;
|
|
83
|
+
static u8 pad, prev_pad;
|
|
84
|
+
static u8 sfx_timer;
|
|
85
|
+
|
|
86
|
+
static u16 tile_buf[16]; /* scratch for one 8x8 tile */
|
|
87
|
+
static u16 spr_buf[64]; /* scratch for one 16x16 sprite */
|
|
88
|
+
|
|
89
|
+
/* ---- tile/sprite builders ----------------------------------------------- */
|
|
90
|
+
static void make_solid_tile(u16 *t, u8 ci) {
|
|
91
|
+
u8 r;
|
|
92
|
+
u8 p0 = (ci & 1) ? 0xFF : 0x00;
|
|
93
|
+
u8 p1 = (ci & 2) ? 0xFF : 0x00;
|
|
94
|
+
u8 p2 = (ci & 4) ? 0xFF : 0x00;
|
|
95
|
+
u8 p3 = (ci & 8) ? 0xFF : 0x00;
|
|
96
|
+
for (r = 0; r < 8; ++r) {
|
|
97
|
+
t[r] = (u16)(p0 | (p1 << 8));
|
|
98
|
+
t[r + 8] = (u16)(p2 | (p3 << 8));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* space tile with one star pixel in colour index 3 at (row 2, col 5) */
|
|
103
|
+
static void make_star_tile(u16 *t) {
|
|
104
|
+
u8 r;
|
|
105
|
+
for (r = 0; r < 8; ++r) { t[r] = 0x00FF; t[r + 8] = 0x0000; } /* base = colour 1 */
|
|
106
|
+
/* star = colour 3 (planes 0+1) at row 2: set plane1 bit too for that row */
|
|
107
|
+
t[2] = (u16)(0x00FF | (0x04 << 8)); /* plane0 row + plane1 single pixel */
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* upload one 16x16 sprite from a 16-row body mask in colour `ci` */
|
|
111
|
+
static void make_sprite(u16 vram, const u16 *body, u8 ci) {
|
|
112
|
+
u8 r;
|
|
113
|
+
for (r = 0; r < 64; ++r) spr_buf[r] = 0;
|
|
114
|
+
for (r = 0; r < 16; ++r) {
|
|
115
|
+
if (ci & 1) spr_buf[r] = body[r]; /* plane0 */
|
|
116
|
+
if (ci & 2) spr_buf[r + 16] = body[r]; /* plane1 */
|
|
117
|
+
if (ci & 4) spr_buf[r + 32] = body[r]; /* plane2 */
|
|
118
|
+
if (ci & 8) spr_buf[r + 48] = body[r]; /* plane3 */
|
|
119
|
+
}
|
|
120
|
+
load_tiles(vram, spr_buf, 64);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static void upload_font(void) {
|
|
124
|
+
u8 g, row, bits, plane0;
|
|
125
|
+
for (g = 0; g < NUM_GLYPHS; ++g) {
|
|
126
|
+
for (row = 0; row < 16; ++row) tile_buf[row] = 0;
|
|
127
|
+
for (row = 0; row < 7; ++row) {
|
|
128
|
+
bits = FONT5x7[g][row];
|
|
129
|
+
plane0 = 0;
|
|
130
|
+
if (bits & 0x10) plane0 |= 0x40;
|
|
131
|
+
if (bits & 0x08) plane0 |= 0x20;
|
|
132
|
+
if (bits & 0x04) plane0 |= 0x10;
|
|
133
|
+
if (bits & 0x02) plane0 |= 0x08;
|
|
134
|
+
if (bits & 0x01) plane0 |= 0x04;
|
|
135
|
+
tile_buf[row] = (u16)plane0;
|
|
136
|
+
}
|
|
137
|
+
load_tiles((u16)(FONT_VRAM + g * 16), tile_buf, 16);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
static void upload_art(void) {
|
|
142
|
+
/* ship: an upward-pointing arrow */
|
|
143
|
+
static const u16 ship[16] = {
|
|
144
|
+
0x0180, 0x0180, 0x03C0, 0x03C0, 0x07E0, 0x07E0, 0x0FF0, 0x0FF0,
|
|
145
|
+
0x1FF8, 0x1FF8, 0x3FFC, 0x7FFE, 0xFFFF, 0xE187, 0xC003, 0x8001
|
|
146
|
+
};
|
|
147
|
+
/* bullet: a small vertical pellet */
|
|
148
|
+
static const u16 bullet[16] = {
|
|
149
|
+
0x0000, 0x0180, 0x03C0, 0x03C0, 0x07E0, 0x07E0, 0x07E0, 0x07E0,
|
|
150
|
+
0x07E0, 0x07E0, 0x03C0, 0x03C0, 0x0180, 0x0000, 0x0000, 0x0000
|
|
151
|
+
};
|
|
152
|
+
/* enemy: a downward, blocky invader */
|
|
153
|
+
static const u16 enemy[16] = {
|
|
154
|
+
0x0000, 0x4002, 0x6006, 0x7FFE, 0x7FFE, 0xFDBF, 0xFFFF, 0xFFFF,
|
|
155
|
+
0xFFFF, 0x7FFE, 0x3FFC, 0x1FF8, 0x300C, 0x6006, 0x4002, 0x0000
|
|
156
|
+
};
|
|
157
|
+
upload_font();
|
|
158
|
+
make_solid_tile(tile_buf, 1); load_tiles(STAR0_VRAM, tile_buf, 16);
|
|
159
|
+
make_solid_tile(tile_buf, 2); load_tiles(STAR1_VRAM, tile_buf, 16);
|
|
160
|
+
make_star_tile(tile_buf); load_tiles(STAR2_VRAM, tile_buf, 16);
|
|
161
|
+
make_sprite(SHIP_VRAM, ship, 1); /* white */
|
|
162
|
+
make_sprite(BULLET_VRAM, bullet, 1); /* white (sub-pal 1 = yellow) */
|
|
163
|
+
make_sprite(ENEMY_VRAM, enemy, 1); /* white (sub-pal 2 = red) */
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* ---- BAT / HUD ---------------------------------------------------------- */
|
|
167
|
+
static void draw_starfield(void) {
|
|
168
|
+
u8 r, c;
|
|
169
|
+
u16 e0 = BAT_ENTRY(0, STAR0_VRAM);
|
|
170
|
+
u16 e1 = BAT_ENTRY(0, STAR1_VRAM);
|
|
171
|
+
u16 e2 = BAT_ENTRY(0, STAR2_VRAM);
|
|
172
|
+
u16 e;
|
|
173
|
+
for (r = 0; r < 32; ++r) {
|
|
174
|
+
vram_set_write_addr((u16)(BAT_VRAM + r * 32));
|
|
175
|
+
for (c = 0; c < 32; ++c) {
|
|
176
|
+
e = (r & 2) ? e1 : e0; /* depth bands */
|
|
177
|
+
if (((r * 7 + c * 5) & 7) == 0) e = e2; /* sparse stars */
|
|
178
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
179
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static void put_glyph(u8 col, u8 row, u8 glyph) {
|
|
185
|
+
u16 e = BAT_ENTRY(0, (u16)(FONT_VRAM + glyph * 16));
|
|
186
|
+
vram_set_write_addr((u16)(BAT_VRAM + row * 32 + col));
|
|
187
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
188
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static void draw_hud_label(void) {
|
|
192
|
+
static const u8 lbl[5] = { G_S, G_C, G_O, G_R, G_E };
|
|
193
|
+
u8 i;
|
|
194
|
+
for (i = 0; i < 5; ++i) put_glyph((u8)(1 + i), 1, lbl[i]);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
static void draw_score(void) {
|
|
198
|
+
u16 v = score;
|
|
199
|
+
u8 d0, d1, d2, d3;
|
|
200
|
+
d3 = (u8)(v % 10); v /= 10;
|
|
201
|
+
d2 = (u8)(v % 10); v /= 10;
|
|
202
|
+
d1 = (u8)(v % 10); v /= 10;
|
|
203
|
+
d0 = (u8)(v % 10);
|
|
204
|
+
put_glyph(7, 1, (u8)(G_0 + d0));
|
|
205
|
+
put_glyph(8, 1, (u8)(G_0 + d1));
|
|
206
|
+
put_glyph(9, 1, (u8)(G_0 + d2));
|
|
207
|
+
put_glyph(10, 1, (u8)(G_0 + d3));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* ---- gameplay helpers --------------------------------------------------- */
|
|
211
|
+
static u8 aabb(Obj *a, Obj *b) {
|
|
212
|
+
return (u8)(a->x < b->x + 14 && a->x + 14 > b->x &&
|
|
213
|
+
a->y < b->y + 14 && a->y + 14 > b->y);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
static u16 next_rand(void) {
|
|
217
|
+
rng = (u16)(rng * 25173u + 13849u);
|
|
218
|
+
return rng;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static void fire(void) {
|
|
222
|
+
u8 i;
|
|
223
|
+
for (i = 0; i < MAX_BULLETS; ++i) {
|
|
224
|
+
if (!bullets[i].alive) {
|
|
225
|
+
bullets[i].x = player.x;
|
|
226
|
+
bullets[i].y = (u16)(player.y - 10);
|
|
227
|
+
bullets[i].alive = 1;
|
|
228
|
+
psg_tone(2, 0x180, 26);
|
|
229
|
+
sfx_timer = 4;
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
static void spawn(void) {
|
|
236
|
+
u8 i;
|
|
237
|
+
for (i = 0; i < MAX_ENEMIES; ++i) {
|
|
238
|
+
if (!enemies[i].alive) {
|
|
239
|
+
enemies[i].x = (u16)(8 + (next_rand() >> 8) % 224);
|
|
240
|
+
enemies[i].y = 8;
|
|
241
|
+
enemies[i].alive = 1;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
void main(void) {
|
|
248
|
+
u8 i, j;
|
|
249
|
+
|
|
250
|
+
_pce_keep[0] = 0;
|
|
251
|
+
|
|
252
|
+
/* palette: BG sub-pal 0 + sprite sub-pals 0/1/2 */
|
|
253
|
+
vce_set_color(0, PCE_RGB(0, 0, 1)); /* backdrop dark blue */
|
|
254
|
+
vce_set_color(1, PCE_RGB(0, 0, 3)); /* BG c1: deep space blue */
|
|
255
|
+
vce_set_color(2, PCE_RGB(1, 1, 4)); /* BG c2: lighter space band */
|
|
256
|
+
vce_set_color(3, PCE_RGB(7, 7, 7)); /* BG c3: star white */
|
|
257
|
+
vce_set_color(256, PCE_RGB(0, 0, 0)); /* spr pal0 transparent */
|
|
258
|
+
vce_set_color(257, PCE_RGB(2, 6, 7)); /* spr pal0 c1: cyan ship */
|
|
259
|
+
vce_set_color(272, PCE_RGB(0, 0, 0)); /* spr pal1 transparent */
|
|
260
|
+
vce_set_color(273, PCE_RGB(7, 7, 0)); /* spr pal1 c1: yellow bullet */
|
|
261
|
+
vce_set_color(288, PCE_RGB(0, 0, 0)); /* spr pal2 transparent */
|
|
262
|
+
vce_set_color(289, PCE_RGB(7, 1, 1)); /* spr pal2 c1: red enemy */
|
|
263
|
+
|
|
264
|
+
upload_art();
|
|
265
|
+
draw_starfield();
|
|
266
|
+
draw_hud_label();
|
|
267
|
+
|
|
268
|
+
player.x = 120; player.y = 180; player.alive = 1;
|
|
269
|
+
for (i = 0; i < MAX_BULLETS; ++i) bullets[i].alive = 0;
|
|
270
|
+
for (i = 0; i < MAX_ENEMIES; ++i) enemies[i].alive = 0;
|
|
271
|
+
score = 0;
|
|
272
|
+
spawn_timer = 0;
|
|
273
|
+
rng = 0xC0DE;
|
|
274
|
+
prev_pad = 0;
|
|
275
|
+
sfx_timer = 0;
|
|
276
|
+
draw_score();
|
|
277
|
+
|
|
278
|
+
pce_joy_init();
|
|
279
|
+
disp_enable();
|
|
280
|
+
|
|
281
|
+
for (;;) {
|
|
282
|
+
waitvsync();
|
|
283
|
+
pad = pce_joy_read();
|
|
284
|
+
|
|
285
|
+
/* move ship */
|
|
286
|
+
if ((pad & PCE_JOY_LEFT) && player.x > 2) player.x -= 3;
|
|
287
|
+
if ((pad & PCE_JOY_RIGHT) && player.x < 238) player.x += 3;
|
|
288
|
+
if ((pad & PCE_JOY_UP) && player.y > 8) player.y -= 3;
|
|
289
|
+
if ((pad & PCE_JOY_DOWN) && player.y < 208) player.y += 3;
|
|
290
|
+
if ((pad & PCE_JOY_I) && !(prev_pad & PCE_JOY_I)) fire();
|
|
291
|
+
prev_pad = pad;
|
|
292
|
+
|
|
293
|
+
/* advance bullets */
|
|
294
|
+
for (i = 0; i < MAX_BULLETS; ++i) {
|
|
295
|
+
if (!bullets[i].alive) continue;
|
|
296
|
+
if (bullets[i].y < 6) { bullets[i].alive = 0; continue; }
|
|
297
|
+
bullets[i].y -= 6;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* advance enemies */
|
|
301
|
+
for (i = 0; i < MAX_ENEMIES; ++i) {
|
|
302
|
+
if (!enemies[i].alive) continue;
|
|
303
|
+
enemies[i].y += 1;
|
|
304
|
+
if (enemies[i].y >= 224) enemies[i].alive = 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* spawn waves */
|
|
308
|
+
spawn_timer++;
|
|
309
|
+
if (spawn_timer >= 36) { spawn_timer = 0; spawn(); }
|
|
310
|
+
|
|
311
|
+
/* bullet vs enemy */
|
|
312
|
+
for (i = 0; i < MAX_BULLETS; ++i) {
|
|
313
|
+
if (!bullets[i].alive) continue;
|
|
314
|
+
for (j = 0; j < MAX_ENEMIES; ++j) {
|
|
315
|
+
if (!enemies[j].alive) continue;
|
|
316
|
+
if (aabb(&bullets[i], &enemies[j])) {
|
|
317
|
+
bullets[i].alive = 0;
|
|
318
|
+
enemies[j].alive = 0;
|
|
319
|
+
if (score < 9999) score += 10;
|
|
320
|
+
draw_score();
|
|
321
|
+
psg_tone(3, 0x040, 28);
|
|
322
|
+
sfx_timer = 6;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* free the SFX channels so they're blips, not drones */
|
|
329
|
+
if (sfx_timer) {
|
|
330
|
+
--sfx_timer;
|
|
331
|
+
if (sfx_timer == 0) { psg_off(2); psg_off(3); }
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* push sprites: player(0), bullets(1..6), enemies(7..12) */
|
|
335
|
+
set_sprite(0, player.x, player.y, SHIP_VRAM >> 6, 0);
|
|
336
|
+
for (i = 0; i < MAX_BULLETS; ++i) {
|
|
337
|
+
u16 by = bullets[i].alive ? bullets[i].y : 0x1F0; /* park off-screen */
|
|
338
|
+
set_sprite((u8)(1 + i), bullets[i].x, by, BULLET_VRAM >> 6, 1);
|
|
339
|
+
}
|
|
340
|
+
for (i = 0; i < MAX_ENEMIES; ++i) {
|
|
341
|
+
u16 ey = enemies[i].alive ? enemies[i].y : 0x1F0;
|
|
342
|
+
set_sprite((u8)(7 + i), enemies[i].x, ey, ENEMY_VRAM >> 6, 2);
|
|
343
|
+
}
|
|
344
|
+
satb_dma();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PC Engine "sports" — a Pong-style two-paddle scaffold.
|
|
3
|
+
*
|
|
4
|
+
* Two paddles and a bouncing ball on a netted court. The d-pad moves player 1's
|
|
5
|
+
* (left) paddle up/down. Player 2's (right) paddle follows the ball with a
|
|
6
|
+
* chase-AI so the game is playable solo. The ball deflects off paddles and the
|
|
7
|
+
* top/bottom court lines; a ball past either edge scores for the other side and
|
|
8
|
+
* re-serves. Score is shown with background digit tiles. Mirrors the
|
|
9
|
+
* NES/Genesis/SNES/GB/SMS sports scaffolds.
|
|
10
|
+
*
|
|
11
|
+
* Paddles + ball are hardware sprites; the court (green field, white border
|
|
12
|
+
* lines, dashed centre net) is the BG tilemap, so the screen is clearly a
|
|
13
|
+
* sports court (clears the verify gate).
|
|
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
|
+
*
|
|
19
|
+
* cc65 is C89 — declare locals at the top of a block.
|
|
20
|
+
*/
|
|
21
|
+
#include <pce.h>
|
|
22
|
+
#include <stdint.h> /* int8_t/int16_t for ball velocity + positions */
|
|
23
|
+
#include "pce_hw.h"
|
|
24
|
+
|
|
25
|
+
/* ---- VRAM layout (word addresses) --------------------------------------- */
|
|
26
|
+
#define BAT_VRAM 0x0000
|
|
27
|
+
#define FONT_VRAM 0x1000 /* digit tiles */
|
|
28
|
+
#define GREEN_VRAM 0x1400 /* court field (colour 1) */
|
|
29
|
+
#define LINE_VRAM 0x1410 /* court line / border (colour 2) */
|
|
30
|
+
#define NET_VRAM 0x1420 /* dashed centre net */
|
|
31
|
+
#define PADDLE_VRAM 0x1800 /* 16x16 paddle segment */
|
|
32
|
+
#define BALL_VRAM 0x1840 /* 16x16 ball */
|
|
33
|
+
|
|
34
|
+
#define BAT_ENTRY(pal, vram) ((u16)(((pal) << 12) | ((vram) >> 4)))
|
|
35
|
+
|
|
36
|
+
#define COURT_TOP 24
|
|
37
|
+
#define COURT_BOT 216
|
|
38
|
+
#define PADDLE_H 48 /* 3 stacked 16px sprite segments */
|
|
39
|
+
#define BALL_SIZE 12
|
|
40
|
+
#define PADDLE_X1 16
|
|
41
|
+
#define PADDLE_X2 224
|
|
42
|
+
|
|
43
|
+
/* ---- font (digits only) ------------------------------------------------- */
|
|
44
|
+
#define NUM_GLYPHS 10
|
|
45
|
+
static const u8 FONT5x7[NUM_GLYPHS][7] = {
|
|
46
|
+
{0x0E,0x11,0x13,0x15,0x19,0x11,0x0E}, /* 0 */
|
|
47
|
+
{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E}, /* 1 */
|
|
48
|
+
{0x0E,0x11,0x01,0x02,0x04,0x08,0x1F}, /* 2 */
|
|
49
|
+
{0x1F,0x02,0x04,0x02,0x01,0x11,0x0E}, /* 3 */
|
|
50
|
+
{0x02,0x06,0x0A,0x12,0x1F,0x02,0x02}, /* 4 */
|
|
51
|
+
{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E}, /* 5 */
|
|
52
|
+
{0x06,0x08,0x10,0x1E,0x11,0x11,0x0E}, /* 6 */
|
|
53
|
+
{0x1F,0x01,0x02,0x04,0x08,0x08,0x08}, /* 7 */
|
|
54
|
+
{0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E}, /* 8 */
|
|
55
|
+
{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C} /* 9 */
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/* ---- state -------------------------------------------------------------- */
|
|
59
|
+
static int16_t p1y, p2y, bx, by;
|
|
60
|
+
static int8_t bdx, bdy;
|
|
61
|
+
static u8 score_p1, score_p2;
|
|
62
|
+
static u8 serve_timer;
|
|
63
|
+
static u8 pad;
|
|
64
|
+
static u16 tile_buf[16];
|
|
65
|
+
static u16 spr_buf[64];
|
|
66
|
+
static u8 sfx_timer;
|
|
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
|
+
/* net tile: green field (colour 1) with a colour-2 vertical dash centre column */
|
|
81
|
+
static void make_net_tile(u16 *t) {
|
|
82
|
+
u8 r;
|
|
83
|
+
for (r = 0; r < 8; ++r) {
|
|
84
|
+
u8 dash = (r < 5); /* dashed: top 5 rows of each tile are the dash */
|
|
85
|
+
u8 p1 = dash ? 0x18 : 0x00; /* centre 2 px -> colour 2 (plane1) */
|
|
86
|
+
t[r] = (u16)(0x00FF | (p1 << 8)); /* plane0 full (green) + dash */
|
|
87
|
+
t[r + 8] = 0x0000;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static void make_paddle_sprite(void) {
|
|
92
|
+
u8 r;
|
|
93
|
+
for (r = 0; r < 64; ++r) spr_buf[r] = 0;
|
|
94
|
+
/* a solid 8px-wide vertical bar centred in the 16px cell, colour 1 */
|
|
95
|
+
for (r = 0; r < 16; ++r) spr_buf[r] = 0x0FF0;
|
|
96
|
+
load_tiles(PADDLE_VRAM, spr_buf, 64);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static void make_ball_sprite(void) {
|
|
100
|
+
static const u16 ball[16] = {
|
|
101
|
+
0x0000, 0x0000, 0x07E0, 0x0FF0, 0x1FF8, 0x1FF8, 0x3FFC, 0x3FFC,
|
|
102
|
+
0x3FFC, 0x3FFC, 0x1FF8, 0x1FF8, 0x0FF0, 0x07E0, 0x0000, 0x0000
|
|
103
|
+
};
|
|
104
|
+
u8 r;
|
|
105
|
+
for (r = 0; r < 64; ++r) spr_buf[r] = 0;
|
|
106
|
+
for (r = 0; r < 16; ++r) spr_buf[r] = ball[r]; /* colour 1 */
|
|
107
|
+
load_tiles(BALL_VRAM, spr_buf, 64);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static void upload_font(void) {
|
|
111
|
+
u8 g, row, bits, plane0;
|
|
112
|
+
for (g = 0; g < NUM_GLYPHS; ++g) {
|
|
113
|
+
for (row = 0; row < 16; ++row) tile_buf[row] = 0;
|
|
114
|
+
for (row = 0; row < 7; ++row) {
|
|
115
|
+
bits = FONT5x7[g][row];
|
|
116
|
+
plane0 = 0;
|
|
117
|
+
if (bits & 0x10) plane0 |= 0x40;
|
|
118
|
+
if (bits & 0x08) plane0 |= 0x20;
|
|
119
|
+
if (bits & 0x04) plane0 |= 0x10;
|
|
120
|
+
if (bits & 0x02) plane0 |= 0x08;
|
|
121
|
+
if (bits & 0x01) plane0 |= 0x04;
|
|
122
|
+
tile_buf[row] = (u16)plane0;
|
|
123
|
+
}
|
|
124
|
+
load_tiles((u16)(FONT_VRAM + g * 16), tile_buf, 16);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static void draw_court(void) {
|
|
129
|
+
u8 r, c;
|
|
130
|
+
u16 g = BAT_ENTRY(0, GREEN_VRAM);
|
|
131
|
+
u16 ln = BAT_ENTRY(0, LINE_VRAM);
|
|
132
|
+
u16 nt = BAT_ENTRY(0, NET_VRAM);
|
|
133
|
+
u16 e;
|
|
134
|
+
for (r = 0; r < 32; ++r) {
|
|
135
|
+
vram_set_write_addr((u16)(BAT_VRAM + r * 32));
|
|
136
|
+
for (c = 0; c < 32; ++c) {
|
|
137
|
+
if (r <= 2 || r >= 27) e = ln; /* top/bottom border */
|
|
138
|
+
else if (c == 1 || c == 30) e = ln; /* sidelines */
|
|
139
|
+
else if (c == 16) e = nt; /* centre net */
|
|
140
|
+
else e = g; /* field */
|
|
141
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
142
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
static void put_glyph(u8 col, u8 row, u8 digit) {
|
|
148
|
+
u16 e = BAT_ENTRY(0, (u16)(FONT_VRAM + digit * 16));
|
|
149
|
+
vram_set_write_addr((u16)(BAT_VRAM + row * 32 + col));
|
|
150
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
151
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static void draw_scores(void) {
|
|
155
|
+
put_glyph(12, 1, (u8)(score_p1 % 10));
|
|
156
|
+
put_glyph(19, 1, (u8)(score_p2 % 10));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
static void serve_ball(u8 to_left) {
|
|
160
|
+
bx = 120; by = 110;
|
|
161
|
+
bdx = to_left ? -2 : 2;
|
|
162
|
+
bdy = ((score_p1 + score_p2) & 1) ? -1 : 1;
|
|
163
|
+
serve_timer = 40;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
void main(void) {
|
|
167
|
+
u8 i;
|
|
168
|
+
|
|
169
|
+
_pce_keep[0] = 0;
|
|
170
|
+
|
|
171
|
+
/* palette */
|
|
172
|
+
vce_set_color(0, PCE_RGB(0, 1, 0)); /* backdrop dark green */
|
|
173
|
+
vce_set_color(1, PCE_RGB(0, 4, 1)); /* BG c1: court green */
|
|
174
|
+
vce_set_color(2, PCE_RGB(7, 7, 7)); /* BG c2: white lines/net/digit */
|
|
175
|
+
vce_set_color(256, PCE_RGB(0, 0, 0)); /* spr pal0 transparent */
|
|
176
|
+
vce_set_color(257, PCE_RGB(7, 7, 7)); /* spr pal0 c1: white paddle */
|
|
177
|
+
vce_set_color(272, PCE_RGB(0, 0, 0)); /* spr pal1 transparent */
|
|
178
|
+
vce_set_color(273, PCE_RGB(7, 7, 0)); /* spr pal1 c1: yellow ball */
|
|
179
|
+
|
|
180
|
+
upload_font();
|
|
181
|
+
make_solid_tile(tile_buf, 1); load_tiles(GREEN_VRAM, tile_buf, 16);
|
|
182
|
+
make_solid_tile(tile_buf, 2); load_tiles(LINE_VRAM, tile_buf, 16);
|
|
183
|
+
make_net_tile(tile_buf); load_tiles(NET_VRAM, tile_buf, 16);
|
|
184
|
+
make_paddle_sprite();
|
|
185
|
+
make_ball_sprite();
|
|
186
|
+
|
|
187
|
+
draw_court();
|
|
188
|
+
|
|
189
|
+
p1y = 90; p2y = 90;
|
|
190
|
+
score_p1 = 0; score_p2 = 0;
|
|
191
|
+
sfx_timer = 0;
|
|
192
|
+
serve_ball(0);
|
|
193
|
+
draw_scores();
|
|
194
|
+
|
|
195
|
+
pce_joy_init();
|
|
196
|
+
disp_enable();
|
|
197
|
+
|
|
198
|
+
for (;;) {
|
|
199
|
+
u8 slot;
|
|
200
|
+
int16_t target;
|
|
201
|
+
waitvsync();
|
|
202
|
+
|
|
203
|
+
/* stage sprites: P1 paddle (3 segs), P2 paddle (3 segs), ball */
|
|
204
|
+
slot = 0;
|
|
205
|
+
for (i = 0; i < 3; ++i)
|
|
206
|
+
set_sprite(slot++, PADDLE_X1, (u16)(p1y + i * 16), PADDLE_VRAM >> 6, 0);
|
|
207
|
+
for (i = 0; i < 3; ++i)
|
|
208
|
+
set_sprite(slot++, PADDLE_X2, (u16)(p2y + i * 16), PADDLE_VRAM >> 6, 0);
|
|
209
|
+
set_sprite(slot++, (u16)bx, (u16)by, BALL_VRAM >> 6, 1);
|
|
210
|
+
satb_dma();
|
|
211
|
+
|
|
212
|
+
pad = pce_joy_read();
|
|
213
|
+
|
|
214
|
+
/* P1 control */
|
|
215
|
+
if ((pad & PCE_JOY_UP) && p1y > COURT_TOP) p1y -= 3;
|
|
216
|
+
if ((pad & PCE_JOY_DOWN) && p1y < COURT_BOT - PADDLE_H) p1y += 3;
|
|
217
|
+
|
|
218
|
+
/* P2 chase-AI */
|
|
219
|
+
target = (int16_t)(by - PADDLE_H / 2 + BALL_SIZE / 2);
|
|
220
|
+
if (p2y < target && p2y < COURT_BOT - PADDLE_H) p2y += 2;
|
|
221
|
+
else if (p2y > target && p2y > COURT_TOP) p2y -= 2;
|
|
222
|
+
|
|
223
|
+
if (serve_timer > 0) {
|
|
224
|
+
serve_timer--;
|
|
225
|
+
} else {
|
|
226
|
+
bx = (int16_t)(bx + bdx);
|
|
227
|
+
by = (int16_t)(by + bdy);
|
|
228
|
+
|
|
229
|
+
if (by < COURT_TOP) { by = COURT_TOP; bdy = (int8_t)(-bdy); psg_tone(1, 0x280, 18); sfx_timer = 4; }
|
|
230
|
+
if (by + BALL_SIZE > COURT_BOT) { by = (int16_t)(COURT_BOT - BALL_SIZE); bdy = (int8_t)(-bdy); psg_tone(1, 0x280, 18); sfx_timer = 4; }
|
|
231
|
+
|
|
232
|
+
/* left paddle */
|
|
233
|
+
if (bdx < 0 && bx <= PADDLE_X1 + 12 && bx + BALL_SIZE >= PADDLE_X1 &&
|
|
234
|
+
by + BALL_SIZE > p1y && by < p1y + PADDLE_H) {
|
|
235
|
+
bdx = (int8_t)(-bdx);
|
|
236
|
+
bx = PADDLE_X1 + 12;
|
|
237
|
+
psg_tone(0, 0x200, 22); sfx_timer = 4;
|
|
238
|
+
}
|
|
239
|
+
/* right paddle */
|
|
240
|
+
if (bdx > 0 && bx + BALL_SIZE >= PADDLE_X2 && bx <= PADDLE_X2 + 12 &&
|
|
241
|
+
by + BALL_SIZE > p2y && by < p2y + PADDLE_H) {
|
|
242
|
+
bdx = (int8_t)(-bdx);
|
|
243
|
+
bx = (int16_t)(PADDLE_X2 - BALL_SIZE);
|
|
244
|
+
psg_tone(0, 0x200, 22); sfx_timer = 4;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* scoring */
|
|
248
|
+
if (bx < 2) { if (score_p2 < 9) score_p2++; draw_scores(); psg_tone(0, 0x100, 24); sfx_timer = 8; serve_ball(0); }
|
|
249
|
+
if (bx > 246) { if (score_p1 < 9) score_p1++; draw_scores(); psg_tone(0, 0x100, 24); sfx_timer = 8; serve_ball(1); }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (sfx_timer) { --sfx_timer; if (sfx_timer == 0) { psg_off(0); psg_off(1); } }
|
|
253
|
+
}
|
|
254
|
+
}
|
package/examples/sms/main.c
CHANGED
|
@@ -47,9 +47,10 @@ static void vdp_init(void) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/* ─── Palette + tile data ─────────────────────────────────────────── */
|
|
50
|
-
/* SMS CRAM: 2-2-2 BGR. Entry 0 = backdrop.
|
|
50
|
+
/* SMS CRAM: 2-2-2 BGR. Entry 0 = backdrop. Entries: 1 = yellow 'H',
|
|
51
|
+
* 2 = blue panel, 3 = dark-blue panel (the checkerboard fill colours). */
|
|
51
52
|
static const uint8_t palette[32] = {
|
|
52
|
-
|
|
53
|
+
0x10, 0x0F, 0x30, 0x14, 0x00, 0x00, 0x00, 0x00, /* BG: backdrop, yellow, blue, dk-blue */
|
|
53
54
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
54
55
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* sprite palette unused */
|
|
55
56
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
@@ -68,6 +69,23 @@ static const uint8_t tile_h[32] = {
|
|
|
68
69
|
0x00, 0x00, 0x00, 0x00,
|
|
69
70
|
};
|
|
70
71
|
|
|
72
|
+
/* Tile 2 = solid colour 2 (blue), tile 3 = solid colour 3 (dark blue).
|
|
73
|
+
* The whole 32×24 screen is painted as a 2-colour checkerboard so the
|
|
74
|
+
* screen is obviously not blank and no single colour dominates. Plane 1
|
|
75
|
+
* set → colour 2; planes 0+1 set → colour 3. */
|
|
76
|
+
static const uint8_t tile_fill2[32] = {
|
|
77
|
+
0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
|
|
78
|
+
0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
|
|
79
|
+
0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
|
|
80
|
+
0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
|
|
81
|
+
};
|
|
82
|
+
static const uint8_t tile_fill3[32] = {
|
|
83
|
+
0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
|
|
84
|
+
0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
|
|
85
|
+
0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
|
|
86
|
+
0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
|
|
87
|
+
};
|
|
88
|
+
|
|
71
89
|
/* ─── Upload helpers ──────────────────────────────────────────────── */
|
|
72
90
|
static void load_palette(void) {
|
|
73
91
|
uint8_t i;
|
|
@@ -77,16 +95,27 @@ static void load_palette(void) {
|
|
|
77
95
|
|
|
78
96
|
static void load_tile(void) {
|
|
79
97
|
uint8_t i;
|
|
80
|
-
/* Tile 1 at VRAM offset 32 (= tile_idx * 32). Tile 0 left blank. */
|
|
98
|
+
/* Tile 1 = 'H' at VRAM offset 32 (= tile_idx * 32). Tile 0 left blank. */
|
|
81
99
|
vdp_set_addr(32, VDP_VRAM_WRITE);
|
|
82
100
|
for (i = 0; i < 32; i++) PORT_VDP_DATA = tile_h[i];
|
|
101
|
+
/* Tile 2 = blue fill (offset 64), tile 3 = dark-blue fill (offset 96). */
|
|
102
|
+
vdp_set_addr(64, VDP_VRAM_WRITE);
|
|
103
|
+
for (i = 0; i < 32; i++) PORT_VDP_DATA = tile_fill2[i];
|
|
104
|
+
vdp_set_addr(96, VDP_VRAM_WRITE);
|
|
105
|
+
for (i = 0; i < 32; i++) PORT_VDP_DATA = tile_fill3[i];
|
|
83
106
|
}
|
|
84
107
|
|
|
108
|
+
/* Paint the whole 32×24 visible screen as a blue/dark-blue checkerboard so
|
|
109
|
+
* the screen is obviously not blank and no single colour dominates. */
|
|
85
110
|
static void clear_name_table(void) {
|
|
86
|
-
|
|
111
|
+
uint8_t row, col;
|
|
87
112
|
vdp_set_addr(0x3800, VDP_VRAM_WRITE);
|
|
88
|
-
|
|
89
|
-
|
|
113
|
+
for (row = 0; row < 24; row++) {
|
|
114
|
+
for (col = 0; col < 32; col++) {
|
|
115
|
+
PORT_VDP_DATA = ((row ^ col) & 1) ? 2 : 3; /* checkerboard tiles 2/3 */
|
|
116
|
+
PORT_VDP_DATA = 0; /* attr: BG palette, no flip */
|
|
117
|
+
}
|
|
118
|
+
}
|
|
90
119
|
}
|
|
91
120
|
|
|
92
121
|
static void place_h(void) {
|