romdevtools 0.16.0 → 0.22.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 +75 -16
- package/CHANGELOG.md +316 -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/hello_sprite.c +48 -4
- package/examples/atari7800/templates/music_demo.c +47 -2
- 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/c64/templates/tile_engine.c +77 -27
- package/examples/gb/templates/default.c +110 -16
- package/examples/gb/templates/hello_sprite.c +15 -6
- package/examples/gb/templates/music_demo.c +36 -0
- package/examples/gb/templates/platformer.c +28 -6
- package/examples/gb/templates/puzzle.c +35 -4
- package/examples/gb/templates/racing.c +75 -10
- package/examples/gb/templates/shmup.c +41 -3
- package/examples/gb/templates/sports.c +51 -3
- package/examples/gb/templates/tile_engine.c +3 -2
- package/examples/gba/templates/gba_hello.c +29 -11
- package/examples/gba/templates/maxmod_demo.c +36 -2
- package/examples/gba/templates/platformer.c +3 -1
- 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/gba/templates/tonc_hello_sprite.c +35 -1
- package/examples/gbc/templates/default.c +103 -26
- package/examples/gbc/templates/hello_sprite.c +12 -3
- package/examples/gbc/templates/music_demo.c +56 -12
- package/examples/gbc/templates/platformer.c +28 -6
- package/examples/gbc/templates/puzzle.c +35 -4
- package/examples/gbc/templates/racing.c +88 -21
- package/examples/gbc/templates/shmup.c +37 -3
- package/examples/gbc/templates/sports.c +48 -3
- package/examples/gbc/templates/tile_engine.c +3 -2
- package/examples/genesis/main.s +53 -1
- package/examples/genesis/templates/hello_sprite.c +25 -3
- 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/genesis/templates/shmup_2p.c +31 -0
- package/examples/genesis/templates/xgm2_demo.c +20 -0
- package/examples/gg/templates/default.c +56 -18
- package/examples/gg/templates/hello_sprite.c +25 -2
- package/examples/gg/templates/music_demo.c +24 -2
- package/examples/gg/templates/platformer.c +18 -12
- package/examples/gg/templates/puzzle.c +38 -7
- package/examples/gg/templates/racing.c +58 -9
- package/examples/gg/templates/shmup.c +47 -3
- package/examples/gg/templates/sports.c +57 -16
- package/examples/gg/templates/tile_engine.c +12 -6
- package/examples/lynx/templates/default.c +39 -8
- package/examples/lynx/templates/hello_sprite.c +15 -1
- package/examples/lynx/templates/music_demo.c +13 -1
- 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/hello_sprite.c +35 -0
- package/examples/nes/templates/music_demo.c +40 -0
- 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/catch_game/main.c +22 -3
- package/examples/pce/music_sfx/main.c +28 -1
- 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/pce/sprite_move/main.c +7 -2
- package/examples/sms/main.c +35 -6
- package/examples/sms/templates/hello_sprite.c +29 -3
- package/examples/sms/templates/music_demo.c +18 -4
- 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/shmup_2p.c +24 -1
- package/examples/sms/templates/sports.c +47 -4
- package/examples/snes/main.asm +108 -17
- package/examples/snes/templates/c-hello-data.asm +23 -0
- package/examples/snes/templates/c-hello.c +18 -1
- package/examples/snes/templates/default.c +50 -28
- package/examples/snes/templates/hello_sprite-data.asm +23 -0
- package/examples/snes/templates/hello_sprite.c +17 -1
- package/examples/snes/templates/music_demo-data.asm +23 -0
- package/examples/snes/templates/music_demo.c +22 -4
- package/examples/snes/templates/platformer-data.asm +22 -0
- package/examples/snes/templates/platformer.c +20 -2
- package/examples/snes/templates/puzzle-data.asm +22 -0
- package/examples/snes/templates/puzzle.c +21 -2
- 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/cheats/gamegenie.js +0 -1
- package/src/cli/smoke.js +1 -3
- 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 +191 -16
- package/src/host/callbacks.js +9 -1
- package/src/host/chafa-render.js +2 -0
- package/src/host/dsp-state.js +2 -2
- package/src/host/gpgx-state.js +4 -0
- package/src/host/types.js +15 -8
- package/src/http/routes.js +1 -1
- package/src/http/tool-registry.js +26 -1
- package/src/mcp/server.js +1 -1
- package/src/mcp/state.js +36 -0
- package/src/mcp/tools/address-to-symbol.js +0 -1
- package/src/mcp/tools/art-loaders.js +1 -1
- package/src/mcp/tools/cart-parts.js +75 -4
- package/src/mcp/tools/classify-region.js +1 -1
- package/src/mcp/tools/diff-roms.js +1 -1
- package/src/mcp/tools/disasm-rebuild.js +507 -0
- package/src/mcp/tools/disasm.js +97 -9
- package/src/mcp/tools/find-references.js +1 -2
- package/src/mcp/tools/font-map.js +1 -1
- package/src/mcp/tools/frame.js +168 -3
- package/src/mcp/tools/index.js +0 -49
- package/src/mcp/tools/input-layout.js +0 -1
- package/src/mcp/tools/input.js +33 -3
- package/src/mcp/tools/lifecycle.js +18 -4
- package/src/mcp/tools/lospec.js +0 -19
- package/src/mcp/tools/platform-docs.js +1 -1
- package/src/mcp/tools/platform-tools.js +4 -4
- package/src/mcp/tools/project.js +54 -11
- package/src/mcp/tools/reinject.js +0 -1
- package/src/mcp/tools/rom-id.js +2 -2
- package/src/mcp/tools/snippets.js +2 -2
- package/src/mcp/tools/sprite-pipeline.js +1 -2
- package/src/mcp/tools/state.js +201 -14
- package/src/mcp/tools/tile-inspect.js +1 -1
- package/src/mcp/tools/toolchain.js +105 -12
- package/src/mcp/tools/watch-memory.js +137 -16
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +34 -0
- package/src/platforms/atari2600/TROUBLESHOOTING.md +6 -0
- package/src/platforms/atari7800/TROUBLESHOOTING.md +6 -0
- package/src/platforms/c64/MENTAL_MODEL.md +45 -1
- package/src/platforms/c64/TROUBLESHOOTING.md +6 -0
- package/src/platforms/c64/d64.js +280 -0
- package/src/platforms/c64/sid.js +0 -2
- package/src/platforms/common/metasprite-adapters.js +1 -1
- package/src/platforms/common/metasprite-codegen.js +3 -3
- package/src/platforms/common/registers.js +5 -3
- package/src/platforms/gb/MENTAL_MODEL.md +10 -0
- package/src/platforms/gb/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gb/lib/c/gb_runtime.c +4 -4
- package/src/platforms/gba/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gbc/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gbc/lib/c/gb_runtime.c +4 -4
- package/src/platforms/genesis/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gg/TROUBLESHOOTING.md +6 -0
- package/src/platforms/lynx/TROUBLESHOOTING.md +6 -0
- package/src/platforms/msx/MENTAL_MODEL.md +10 -6
- package/src/platforms/msx/TROUBLESHOOTING.md +6 -0
- package/src/platforms/nes/MENTAL_MODEL.md +63 -2
- package/src/platforms/nes/TROUBLESHOOTING.md +6 -0
- package/src/platforms/nes/image-to-tilemap.js +3 -0
- package/src/platforms/nes/lib/asm/famitone2.s +5 -1
- package/src/platforms/pce/MENTAL_MODEL.md +9 -4
- package/src/platforms/pce/TROUBLESHOOTING.md +6 -0
- package/src/platforms/pce/lib/c/pce_video.c +1 -1
- package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
- package/src/platforms/snes/TROUBLESHOOTING.md +6 -0
- package/src/platforms/snes/brr.js +0 -2
- package/src/playtest/playtest.js +0 -7
- package/src/rom-id/identifier.js +15 -0
- package/src/toolchains/asar/asar.js +0 -9
- package/src/toolchains/assemble-snippet.js +30 -12
- package/src/toolchains/cc65/ines.js +145 -0
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +14 -1
- 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 -3
- package/src/toolchains/common/sdk-cache.js +1 -1
- package/src/toolchains/genesis-c/genesis-c.js +5 -3
- package/src/toolchains/index.js +27 -3
- package/src/toolchains/parse-errors.js +78 -1
- package/src/toolchains/sdcc/preflight-lint.js +5 -1
- package/src/toolchains/sdcc/sdcc.js +1 -1
- package/src/toolchains/sjasm/sjasm.js +1 -1
- package/src/toolchains/snes-c/snes-c.js +2 -2
- package/src/toolchains/vasm68k/vasm68k.js +2 -4
- package/src/toolchains/wladx/wladx.js +1 -1
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/* ── shmup/main.c — MSX vertical-shooter scaffold (screen 2) ─────────
|
|
2
|
+
*
|
|
3
|
+
* Mirrors the NES/Genesis/SNES/GB/SMS shmup scaffolds, translated to the
|
|
4
|
+
* MSX VDP via the romdev MSX helper lib (msx_hw.h + msx_vdp.c).
|
|
5
|
+
*
|
|
6
|
+
* Player ship (sprite plane 0) + 4 bullet slots (planes 1-4) + 4 enemy
|
|
7
|
+
* slots (planes 5-8), a wave spawner, and AABB collisions. Score is drawn
|
|
8
|
+
* as on-screen tiles ("SCORE 000") along the top row. The whole 32x24
|
|
9
|
+
* screen-2 name table is painted with a banded starfield so the display is
|
|
10
|
+
* clearly space, not a flat backdrop.
|
|
11
|
+
*
|
|
12
|
+
* Controls: joystick LEFT/RIGHT/UP/DOWN moves the ship, trigger A fires.
|
|
13
|
+
* We read joystick PORT 1 (and the keyboard cursor on stick 0 as a
|
|
14
|
+
* fallback), and GTTRIG for the fire button.
|
|
15
|
+
*
|
|
16
|
+
* Cartridge rule: INIT must never return, so main() ends in for(;;).
|
|
17
|
+
*
|
|
18
|
+
* Hardware path (all through the MSX helper lib):
|
|
19
|
+
* - msx_set_screen2() screen 2 (GRAPHIC II), 256x192, display ON
|
|
20
|
+
* - msx_vram_write() upload tile font + sprite patterns to VRAM
|
|
21
|
+
* - msx_set_sprite() position the ship/bullets/enemies each frame
|
|
22
|
+
* - msx_read_joystick() BIOS GTSTCK — 0=center, 1-8 = direction CW
|
|
23
|
+
* - msx_psg_tone/off() fire blip + explosion noise
|
|
24
|
+
* - vsync() one game step per VDP frame (interrupt-free)
|
|
25
|
+
*/
|
|
26
|
+
#include "msx_hw.h"
|
|
27
|
+
|
|
28
|
+
/* ── interrupt-free vblank sync (poll VDP status S#0 bit 7) ────────────── */
|
|
29
|
+
__sfr __at 0x99 VDPSTATUS;
|
|
30
|
+
static void vsync(void) {
|
|
31
|
+
(void)VDPSTATUS;
|
|
32
|
+
while (!(VDPSTATUS & 0x80)) {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* fire-button trigger uses the BIOS GTTRIG wrapper (gttrig) from msx_hw.h:
|
|
37
|
+
* gttrig(0)=space/any, gttrig(1)/gttrig(2)=port-A/B triggers. */
|
|
38
|
+
|
|
39
|
+
#define MAX_BULLETS 4
|
|
40
|
+
#define MAX_ENEMIES 4
|
|
41
|
+
|
|
42
|
+
/* ── tile font: SPACE, S C O R E, digits, plus a couple starfield tiles ── */
|
|
43
|
+
#define T_SPACE 0
|
|
44
|
+
#define T_S 1
|
|
45
|
+
#define T_C 2
|
|
46
|
+
#define T_O 3
|
|
47
|
+
#define T_R 4
|
|
48
|
+
#define T_E 5
|
|
49
|
+
#define T_0 6 /* digits 0..9 are consecutive: T_0 + n */
|
|
50
|
+
#define T_DEEP 16 /* solid deep-space band (dark blue) */
|
|
51
|
+
#define T_BAND 17 /* solid lighter space band (medium blue) */
|
|
52
|
+
#define T_STAR1 18 /* deep-space cell with a faint star */
|
|
53
|
+
#define T_STAR2 19 /* lighter-band cell with a bright star */
|
|
54
|
+
|
|
55
|
+
static const uint8_t font[20][8] = {
|
|
56
|
+
/* 0 SPACE */ {0,0,0,0,0,0,0,0},
|
|
57
|
+
/* 1 S */ {0x7C,0xC0,0xC0,0x78,0x0C,0x0C,0xF8,0x00},
|
|
58
|
+
/* 2 C */ {0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00},
|
|
59
|
+
/* 3 O */ {0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00},
|
|
60
|
+
/* 4 R */ {0xFC,0xC6,0xC6,0xFC,0xD8,0xCC,0xC6,0x00},
|
|
61
|
+
/* 5 E */ {0xFE,0xC0,0xC0,0xF8,0xC0,0xC0,0xFE,0x00},
|
|
62
|
+
/* 6 0 */ {0x7C,0xCE,0xDE,0xF6,0xE6,0xC6,0x7C,0x00},
|
|
63
|
+
/* 7 1 */ {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00},
|
|
64
|
+
/* 8 2 */ {0x7C,0xC6,0x06,0x1C,0x70,0xC0,0xFE,0x00},
|
|
65
|
+
/* 9 3 */ {0x7C,0xC6,0x06,0x3C,0x06,0xC6,0x7C,0x00},
|
|
66
|
+
/* 10 4 */ {0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x00},
|
|
67
|
+
/* 11 5 */ {0xFE,0xC0,0xFC,0x06,0x06,0xC6,0x7C,0x00},
|
|
68
|
+
/* 12 6 */ {0x3C,0x60,0xC0,0xFC,0xC6,0xC6,0x7C,0x00},
|
|
69
|
+
/* 13 7 */ {0xFE,0x06,0x0C,0x18,0x30,0x30,0x30,0x00},
|
|
70
|
+
/* 14 8 */ {0x7C,0xC6,0xC6,0x7C,0xC6,0xC6,0x7C,0x00},
|
|
71
|
+
/* 15 9 */ {0x7C,0xC6,0xC6,0x7E,0x06,0x0C,0x78,0x00},
|
|
72
|
+
/* 16 DEEP (solid fill) */ {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF},
|
|
73
|
+
/* 17 BAND (solid fill) */ {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF},
|
|
74
|
+
/* 18 STAR1 (deep + dot) */ {0xFF,0xFF,0xFF,0xEF,0xFF,0xFF,0xFF,0xFF},
|
|
75
|
+
/* 19 STAR2 (band + dot) */ {0xFF,0xEF,0xEF,0x83,0xEF,0xEF,0xFF,0xFF}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/* colour bytes per glyph: high nibble fg, low nibble bg.
|
|
79
|
+
* TMS9918 fixed palette: 1=black, 4=dark blue, 5=med blue, 14=grey, 15=white.
|
|
80
|
+
* The solid space tiles fill entirely with their fg colour. The star tiles
|
|
81
|
+
* use a white star (fg) over the band colour (bg). */
|
|
82
|
+
#define COL_TEXT 0xF1 /* white text on black */
|
|
83
|
+
#define COL_DEEP 0x44 /* solid dark-blue deep space */
|
|
84
|
+
#define COL_BAND 0x55 /* solid medium-blue band */
|
|
85
|
+
#define COL_STAR1 0xF4 /* white star pixel over dark-blue field */
|
|
86
|
+
#define COL_STAR2 0xF5 /* white star over medium-blue band */
|
|
87
|
+
|
|
88
|
+
/* ── sprite patterns (8x8) ─────────────────────────────────────────────── */
|
|
89
|
+
static const uint8_t spr_ship[8] = {0x18,0x3C,0x7E,0x7E,0xFF,0xFF,0xDB,0x81};
|
|
90
|
+
static const uint8_t spr_bullet[8] = {0x18,0x3C,0x3C,0x3C,0x3C,0x3C,0x18,0x00};
|
|
91
|
+
static const uint8_t spr_enemy[8] = {0x81,0x42,0x24,0x18,0x18,0x24,0x42,0x81};
|
|
92
|
+
|
|
93
|
+
#define PAT_SHIP 0
|
|
94
|
+
#define PAT_BULLET 1
|
|
95
|
+
#define PAT_ENEMY 2
|
|
96
|
+
|
|
97
|
+
/* TMS9918 fixed sprite palette: 15=white, 10=yellow, 9=red(ish) */
|
|
98
|
+
#define COL_SHIP 15
|
|
99
|
+
#define COL_BULLET 10
|
|
100
|
+
#define COL_ENEMY 9
|
|
101
|
+
|
|
102
|
+
typedef struct { uint8_t x, y, alive; } Obj;
|
|
103
|
+
|
|
104
|
+
static Obj player;
|
|
105
|
+
static Obj bullets[MAX_BULLETS];
|
|
106
|
+
static Obj enemies[MAX_ENEMIES];
|
|
107
|
+
static uint16_t score;
|
|
108
|
+
static uint8_t spawn_timer;
|
|
109
|
+
static uint16_t rng;
|
|
110
|
+
static uint8_t blip;
|
|
111
|
+
|
|
112
|
+
static uint8_t next_rand(void) {
|
|
113
|
+
rng ^= (uint16_t)(rng << 7);
|
|
114
|
+
rng ^= (uint16_t)(rng >> 9);
|
|
115
|
+
rng ^= (uint16_t)(rng << 8);
|
|
116
|
+
return (uint8_t)(rng & 0xFF);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* upload the glyph patterns into ALL THREE screen-2 pattern thirds */
|
|
120
|
+
static void load_font(void) {
|
|
121
|
+
uint8_t third, i;
|
|
122
|
+
uint16_t patbase, colbase;
|
|
123
|
+
for (third = 0; third < 3; third++) {
|
|
124
|
+
patbase = (uint16_t)(VRAM_PATTERN + ((uint16_t)third << 11));
|
|
125
|
+
colbase = (uint16_t)(VRAM_COLOR + ((uint16_t)third << 11));
|
|
126
|
+
for (i = 0; i < 20; i++) {
|
|
127
|
+
uint8_t col = COL_TEXT;
|
|
128
|
+
if (i == T_DEEP) col = COL_DEEP;
|
|
129
|
+
else if (i == T_BAND) col = COL_BAND;
|
|
130
|
+
else if (i == T_STAR1) col = COL_STAR1;
|
|
131
|
+
else if (i == T_STAR2) col = COL_STAR2;
|
|
132
|
+
msx_vram_write((uint16_t)(patbase + ((uint16_t)i << 3)), font[i], 8);
|
|
133
|
+
msx_fill_vram((uint16_t)(colbase + ((uint16_t)i << 3)), 8, col);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static void put_tile(uint8_t col, uint8_t row, uint8_t tile) {
|
|
139
|
+
msx_vram_write((uint16_t)(VRAM_NAME + (uint16_t)row * 32 + col), &tile, 1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* paint a banded starfield across the WHOLE 32x24 name table: alternating
|
|
143
|
+
* 3-row bands of deep/medium blue (so neither colour dominates) with a sparse
|
|
144
|
+
* scattering of white stars. Row 0 stays blank for the SCORE line. */
|
|
145
|
+
static void draw_starfield(void) {
|
|
146
|
+
uint8_t row, col, band, tile, h;
|
|
147
|
+
msx_fill_vram(VRAM_NAME, 32, T_SPACE); /* blank row 0 (HUD) */
|
|
148
|
+
for (row = 1; row < 24; row++) {
|
|
149
|
+
band = (uint8_t)(((row / 3) & 1)); /* alternate every 3 rows */
|
|
150
|
+
for (col = 0; col < 32; col++) {
|
|
151
|
+
h = (uint8_t)((row * 7 + col * 5) & 15);
|
|
152
|
+
if (h == 0) tile = band ? T_STAR2 : T_STAR1; /* a star */
|
|
153
|
+
else tile = band ? T_BAND : T_DEEP; /* solid band */
|
|
154
|
+
put_tile(col, row, tile);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
static void draw_label(void) {
|
|
160
|
+
put_tile(1, 0, T_S); put_tile(2, 0, T_C); put_tile(3, 0, T_O);
|
|
161
|
+
put_tile(4, 0, T_R); put_tile(5, 0, T_E);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
static void draw_score(void) {
|
|
165
|
+
uint16_t s = score;
|
|
166
|
+
put_tile(7, 0, (uint8_t)(T_0 + (s / 100) % 10));
|
|
167
|
+
put_tile(8, 0, (uint8_t)(T_0 + (s / 10) % 10));
|
|
168
|
+
put_tile(9, 0, (uint8_t)(T_0 + s % 10));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static uint8_t aabb(Obj *a, Obj *b) {
|
|
172
|
+
return a->x < b->x + 8 && a->x + 8 > b->x
|
|
173
|
+
&& a->y < b->y + 8 && a->y + 8 > b->y;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static void fire(void) {
|
|
177
|
+
uint8_t i;
|
|
178
|
+
for (i = 0; i < MAX_BULLETS; i++) {
|
|
179
|
+
if (!bullets[i].alive) {
|
|
180
|
+
bullets[i].x = player.x;
|
|
181
|
+
bullets[i].y = (uint8_t)(player.y - 8);
|
|
182
|
+
bullets[i].alive = 1;
|
|
183
|
+
msx_psg_tone(0, 0x100, 12);
|
|
184
|
+
blip = 4;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
static void spawn(void) {
|
|
191
|
+
uint8_t i;
|
|
192
|
+
for (i = 0; i < MAX_ENEMIES; i++) {
|
|
193
|
+
if (!enemies[i].alive) {
|
|
194
|
+
enemies[i].x = (uint8_t)(8 + (next_rand() % 232));
|
|
195
|
+
enemies[i].y = 16;
|
|
196
|
+
enemies[i].alive = 1;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
void main(void) {
|
|
203
|
+
uint8_t i, j, dir, trig, prev_trig;
|
|
204
|
+
|
|
205
|
+
msx_set_screen2();
|
|
206
|
+
msx_clear_sprites();
|
|
207
|
+
load_font();
|
|
208
|
+
draw_starfield();
|
|
209
|
+
draw_label();
|
|
210
|
+
|
|
211
|
+
msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_SHIP * 8), spr_ship, 8);
|
|
212
|
+
msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_BULLET * 8), spr_bullet, 8);
|
|
213
|
+
msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_ENEMY * 8), spr_enemy, 8);
|
|
214
|
+
|
|
215
|
+
player.x = 120; player.y = 160; player.alive = 1;
|
|
216
|
+
for (i = 0; i < MAX_BULLETS; i++) bullets[i].alive = 0;
|
|
217
|
+
for (i = 0; i < MAX_ENEMIES; i++) enemies[i].alive = 0;
|
|
218
|
+
score = 0;
|
|
219
|
+
spawn_timer = 0;
|
|
220
|
+
rng = 0xACE1;
|
|
221
|
+
blip = 0;
|
|
222
|
+
prev_trig = 0;
|
|
223
|
+
draw_score();
|
|
224
|
+
|
|
225
|
+
for (;;) {
|
|
226
|
+
vsync();
|
|
227
|
+
|
|
228
|
+
dir = msx_read_joystick(1);
|
|
229
|
+
if (dir == STICK_CENTER) dir = msx_read_joystick(0);
|
|
230
|
+
trig = (uint8_t)(gttrig(1) || gttrig(2));
|
|
231
|
+
|
|
232
|
+
if ((dir == STICK_LEFT || dir == STICK_UL || dir == STICK_DL)
|
|
233
|
+
&& player.x > 4) player.x = (uint8_t)(player.x - 2);
|
|
234
|
+
if ((dir == STICK_RIGHT || dir == STICK_UR || dir == STICK_DR)
|
|
235
|
+
&& player.x < 248) player.x = (uint8_t)(player.x + 2);
|
|
236
|
+
if ((dir == STICK_UP || dir == STICK_UL || dir == STICK_UR)
|
|
237
|
+
&& player.y > 16) player.y = (uint8_t)(player.y - 2);
|
|
238
|
+
if ((dir == STICK_DOWN || dir == STICK_DL || dir == STICK_DR)
|
|
239
|
+
&& player.y < 168) player.y = (uint8_t)(player.y + 2);
|
|
240
|
+
|
|
241
|
+
if (trig && !prev_trig) fire();
|
|
242
|
+
prev_trig = trig;
|
|
243
|
+
|
|
244
|
+
/* advance bullets */
|
|
245
|
+
for (i = 0; i < MAX_BULLETS; i++) {
|
|
246
|
+
if (!bullets[i].alive) continue;
|
|
247
|
+
if (bullets[i].y < 18) { bullets[i].alive = 0; continue; }
|
|
248
|
+
bullets[i].y = (uint8_t)(bullets[i].y - 4);
|
|
249
|
+
}
|
|
250
|
+
/* advance enemies */
|
|
251
|
+
for (i = 0; i < MAX_ENEMIES; i++) {
|
|
252
|
+
if (!enemies[i].alive) continue;
|
|
253
|
+
enemies[i].y = (uint8_t)(enemies[i].y + 1);
|
|
254
|
+
if (enemies[i].y >= 184) enemies[i].alive = 0;
|
|
255
|
+
}
|
|
256
|
+
spawn_timer = (uint8_t)(spawn_timer + 1);
|
|
257
|
+
if (spawn_timer >= 28) { spawn_timer = 0; spawn(); }
|
|
258
|
+
|
|
259
|
+
/* bullet vs enemy */
|
|
260
|
+
for (i = 0; i < MAX_BULLETS; i++) {
|
|
261
|
+
if (!bullets[i].alive) continue;
|
|
262
|
+
for (j = 0; j < MAX_ENEMIES; j++) {
|
|
263
|
+
if (!enemies[j].alive) continue;
|
|
264
|
+
if (aabb(&bullets[i], &enemies[j])) {
|
|
265
|
+
bullets[i].alive = 0;
|
|
266
|
+
enemies[j].alive = 0;
|
|
267
|
+
if (score < 999) { score++; draw_score(); }
|
|
268
|
+
msx_psg_tone(1, 0x400, 14);
|
|
269
|
+
blip = 6;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (blip) { blip--; if (!blip) { msx_psg_off(0); msx_psg_off(1); } }
|
|
276
|
+
|
|
277
|
+
/* push sprites */
|
|
278
|
+
msx_set_sprite(0, player.x, player.y, PAT_SHIP, COL_SHIP);
|
|
279
|
+
for (i = 0; i < MAX_BULLETS; i++)
|
|
280
|
+
msx_set_sprite((uint8_t)(1 + i), bullets[i].x,
|
|
281
|
+
bullets[i].alive ? bullets[i].y : SPRITE_END_Y,
|
|
282
|
+
PAT_BULLET, COL_BULLET);
|
|
283
|
+
for (i = 0; i < MAX_ENEMIES; i++)
|
|
284
|
+
msx_set_sprite((uint8_t)(5 + i), enemies[i].x,
|
|
285
|
+
enemies[i].alive ? enemies[i].y : SPRITE_END_Y,
|
|
286
|
+
PAT_ENEMY, COL_ENEMY);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/* ── sports/main.c — MSX two-player Pong scaffold (screen 2) ─────────
|
|
2
|
+
*
|
|
3
|
+
* Mirrors the SMS/GB/etc Pong scaffolds, translated to the MSX VDP via the
|
|
4
|
+
* romdev helper lib (msx_hw.h + msx_vdp.c).
|
|
5
|
+
*
|
|
6
|
+
* The court (green field + white top/bottom + sidelines + dashed centre
|
|
7
|
+
* net) fills the whole 32x24 screen-2 name table. Two paddles (each three
|
|
8
|
+
* stacked 8x8 sprites) and a ball are sprites.
|
|
9
|
+
*
|
|
10
|
+
* Controls:
|
|
11
|
+
* Player 1 — joystick PORT 1 UP/DOWN moves the left paddle.
|
|
12
|
+
* Player 2 — joystick PORT 2 UP/DOWN moves the right paddle; when no
|
|
13
|
+
* second pad is present (stick 2 reads centre) the right paddle falls
|
|
14
|
+
* back to chase-the-ball AI, so the game is playable solo. Plug a
|
|
15
|
+
* second pad in mid-session and player 2 just starts working.
|
|
16
|
+
*
|
|
17
|
+
* Cartridge rule: INIT must never return — main() ends in for(;;).
|
|
18
|
+
*/
|
|
19
|
+
#include "msx_hw.h"
|
|
20
|
+
|
|
21
|
+
/* ── interrupt-free vblank sync (poll VDP status S#0 bit 7) ────────────── */
|
|
22
|
+
__sfr __at 0x99 VDPSTATUS;
|
|
23
|
+
static void vsync(void) {
|
|
24
|
+
(void)VDPSTATUS;
|
|
25
|
+
while (!(VDPSTATUS & 0x80)) {
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#define COURT_TOP 16
|
|
30
|
+
#define COURT_BOT 184
|
|
31
|
+
#define PADDLE_H 24
|
|
32
|
+
#define BALL_SIZE 8
|
|
33
|
+
#define PADDLE_X1 16
|
|
34
|
+
#define PADDLE_X2 232
|
|
35
|
+
|
|
36
|
+
/* tile patterns (8x8) for the court */
|
|
37
|
+
#define T_FIELD 0
|
|
38
|
+
#define T_LINE 1
|
|
39
|
+
#define T_NET 2
|
|
40
|
+
|
|
41
|
+
static const uint8_t TILE_FIELD[8] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
|
|
42
|
+
static const uint8_t TILE_LINE[8] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
|
|
43
|
+
static const uint8_t TILE_NET[8] = {0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00};
|
|
44
|
+
|
|
45
|
+
/* colour bytes (hi fg, lo bg). 3=green(dark), 12=green(light), 15=white */
|
|
46
|
+
#define COL_FIELD 0x21 /* dark green field on black */
|
|
47
|
+
#define COL_LINE 0xF1 /* white line on black */
|
|
48
|
+
#define COL_NET 0xF2 /* white net dashes on dark-green field */
|
|
49
|
+
|
|
50
|
+
/* paddle/ball sprite pattern (8x8 solid block) */
|
|
51
|
+
static const uint8_t SPR_BLOCK[8] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
|
|
52
|
+
#define COL_SPR 15 /* white */
|
|
53
|
+
|
|
54
|
+
static int16_t p1y, p2y, bx, by;
|
|
55
|
+
static int8_t bdx, bdy;
|
|
56
|
+
static uint8_t score_p1, score_p2;
|
|
57
|
+
static uint8_t serve_timer;
|
|
58
|
+
static uint8_t blip;
|
|
59
|
+
|
|
60
|
+
static void load_tiles(void) {
|
|
61
|
+
uint8_t third;
|
|
62
|
+
uint16_t pat, col;
|
|
63
|
+
for (third = 0; third < 3; third++) {
|
|
64
|
+
pat = (uint16_t)(VRAM_PATTERN + ((uint16_t)third << 11));
|
|
65
|
+
col = (uint16_t)(VRAM_COLOR + ((uint16_t)third << 11));
|
|
66
|
+
msx_vram_write((uint16_t)(pat + T_FIELD * 8), TILE_FIELD, 8);
|
|
67
|
+
msx_vram_write((uint16_t)(pat + T_LINE * 8), TILE_LINE, 8);
|
|
68
|
+
msx_vram_write((uint16_t)(pat + T_NET * 8), TILE_NET, 8);
|
|
69
|
+
msx_fill_vram((uint16_t)(col + T_FIELD * 8), 8, COL_FIELD);
|
|
70
|
+
msx_fill_vram((uint16_t)(col + T_LINE * 8), 8, COL_LINE);
|
|
71
|
+
msx_fill_vram((uint16_t)(col + T_NET * 8), 8, COL_NET);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static void set_cell(uint8_t row, uint8_t col, uint8_t tile) {
|
|
76
|
+
msx_vram_write((uint16_t)(VRAM_NAME + (uint16_t)row * 32 + col), &tile, 1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static void draw_court(void) {
|
|
80
|
+
uint8_t row, col, t;
|
|
81
|
+
for (row = 0; row < 24; row++) {
|
|
82
|
+
for (col = 0; col < 32; col++) {
|
|
83
|
+
t = T_FIELD;
|
|
84
|
+
if (row <= 1 || row >= 22) t = T_LINE;
|
|
85
|
+
else if (col == 1 || col == 30) t = T_LINE;
|
|
86
|
+
else if (col == 16) t = T_NET;
|
|
87
|
+
set_cell(row, col, t);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static void serve_ball(uint8_t to_left) {
|
|
93
|
+
bx = 124;
|
|
94
|
+
by = 90;
|
|
95
|
+
bdx = to_left ? -2 : 2;
|
|
96
|
+
bdy = ((score_p1 + score_p2) & 1) ? -1 : 1;
|
|
97
|
+
serve_timer = 30;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static void reset_match(void) {
|
|
101
|
+
p1y = 84; p2y = 84;
|
|
102
|
+
score_p1 = 0; score_p2 = 0;
|
|
103
|
+
serve_ball(0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
void main(void) {
|
|
107
|
+
uint8_t i, slot, p1, p2;
|
|
108
|
+
int16_t target;
|
|
109
|
+
|
|
110
|
+
msx_set_screen2();
|
|
111
|
+
msx_clear_sprites();
|
|
112
|
+
load_tiles();
|
|
113
|
+
draw_court();
|
|
114
|
+
msx_vram_write((uint16_t)(VRAM_SPRPAT + 0), SPR_BLOCK, 8);
|
|
115
|
+
|
|
116
|
+
blip = 0;
|
|
117
|
+
reset_match();
|
|
118
|
+
|
|
119
|
+
for (;;) {
|
|
120
|
+
vsync();
|
|
121
|
+
|
|
122
|
+
/* push sprites: left paddle (3 cells), right paddle (3), ball */
|
|
123
|
+
slot = 0;
|
|
124
|
+
for (i = 0; i < PADDLE_H / 8; i++)
|
|
125
|
+
msx_set_sprite(slot++, PADDLE_X1, (uint8_t)(p1y + i * 8), 0, COL_SPR);
|
|
126
|
+
for (i = 0; i < PADDLE_H / 8; i++)
|
|
127
|
+
msx_set_sprite(slot++, PADDLE_X2, (uint8_t)(p2y + i * 8), 0, COL_SPR);
|
|
128
|
+
msx_set_sprite(slot++, (uint8_t)bx, (uint8_t)by, 0, COL_SPR);
|
|
129
|
+
|
|
130
|
+
p1 = msx_read_joystick(1);
|
|
131
|
+
p2 = msx_read_joystick(2);
|
|
132
|
+
|
|
133
|
+
if ((p1 == STICK_UP || p1 == STICK_UL || p1 == STICK_UR)
|
|
134
|
+
&& p1y > COURT_TOP) p1y -= 3;
|
|
135
|
+
if ((p1 == STICK_DOWN || p1 == STICK_DL || p1 == STICK_DR)
|
|
136
|
+
&& p1y < COURT_BOT - PADDLE_H) p1y += 3;
|
|
137
|
+
|
|
138
|
+
if (p2 != STICK_CENTER) {
|
|
139
|
+
if ((p2 == STICK_UP || p2 == STICK_UL || p2 == STICK_UR)
|
|
140
|
+
&& p2y > COURT_TOP) p2y -= 3;
|
|
141
|
+
if ((p2 == STICK_DOWN || p2 == STICK_DL || p2 == STICK_DR)
|
|
142
|
+
&& p2y < COURT_BOT - PADDLE_H) p2y += 3;
|
|
143
|
+
} else {
|
|
144
|
+
target = (int16_t)(by - PADDLE_H / 2);
|
|
145
|
+
if (p2y < target && p2y < COURT_BOT - PADDLE_H) p2y += 2;
|
|
146
|
+
else if (p2y > target && p2y > COURT_TOP) p2y -= 2;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (serve_timer > 0) {
|
|
150
|
+
serve_timer--;
|
|
151
|
+
} else {
|
|
152
|
+
bx = (int16_t)(bx + bdx);
|
|
153
|
+
by = (int16_t)(by + bdy);
|
|
154
|
+
if (by < COURT_TOP) { by = COURT_TOP; bdy = (int8_t)(-bdy); msx_psg_tone(1, 0x300, 8); blip = 3; }
|
|
155
|
+
if (by + BALL_SIZE > COURT_BOT) { by = (int16_t)(COURT_BOT - BALL_SIZE); bdy = (int8_t)(-bdy); msx_psg_tone(1, 0x300, 8); blip = 3; }
|
|
156
|
+
|
|
157
|
+
if (bdx < 0
|
|
158
|
+
&& bx <= PADDLE_X1 + 8
|
|
159
|
+
&& bx + BALL_SIZE >= PADDLE_X1
|
|
160
|
+
&& by + BALL_SIZE > p1y
|
|
161
|
+
&& by < p1y + PADDLE_H) {
|
|
162
|
+
bdx = (int8_t)(-bdx);
|
|
163
|
+
bx = PADDLE_X1 + 8;
|
|
164
|
+
msx_psg_tone(0, 0x200, 10); blip = 4;
|
|
165
|
+
}
|
|
166
|
+
if (bdx > 0
|
|
167
|
+
&& bx + BALL_SIZE >= PADDLE_X2
|
|
168
|
+
&& bx <= PADDLE_X2 + 8
|
|
169
|
+
&& by + BALL_SIZE > p2y
|
|
170
|
+
&& by < p2y + PADDLE_H) {
|
|
171
|
+
bdx = (int8_t)(-bdx);
|
|
172
|
+
bx = (int16_t)(PADDLE_X2 - BALL_SIZE);
|
|
173
|
+
msx_psg_tone(0, 0x200, 10); blip = 4;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (bx < 4) { if (score_p2 < 9) score_p2++; msx_psg_tone(0, 0x500, 14); blip = 8; serve_ball(0); }
|
|
177
|
+
if (bx > 252) { if (score_p1 < 9) score_p1++; msx_psg_tone(0, 0x180, 14); blip = 8; serve_ball(1); }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (blip) { blip--; if (!blip) { msx_psg_off(0); msx_psg_off(1); } }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -1,44 +1,92 @@
|
|
|
1
1
|
/* ── default.c — minimal NES starter ────────────────────────────
|
|
2
2
|
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* A "hello, it works!" screen: a tiled background band + a bouncing
|
|
4
|
+
* sprite, so the very first build shows recognizable content (not a
|
|
5
|
+
* flat color). Smallest starting point when you're not yet sure what
|
|
6
|
+
* you want to build — edit from here.
|
|
6
7
|
*
|
|
7
8
|
* For something more game-shaped:
|
|
8
9
|
* - hello_sprite — sprite + d-pad movement
|
|
9
10
|
* - tile_engine — 32×30 nametable + rooms + door transitions
|
|
11
|
+
* - scaffold({op:'game', genre:'shmup'|'platformer'|...}) — full genres
|
|
10
12
|
*
|
|
11
|
-
*
|
|
13
|
+
* NES uses CHR-RAM here, so we upload our tile graphics at boot before
|
|
14
|
+
* turning rendering on (an empty CHR-RAM = a blank screen — the #1 NES
|
|
15
|
+
* "why is it black" footgun; see TROUBLESHOOTING.md).
|
|
12
16
|
*/
|
|
13
17
|
|
|
14
18
|
#include "nes_runtime.h"
|
|
15
19
|
|
|
16
|
-
/*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
static const uint8_t
|
|
20
|
+
/* Two 8×8 tiles in 2bpp NES format (plane0 = low bit, plane1 = high bit;
|
|
21
|
+
* 8 bytes each). Tile 1 = a solid filled block (color 1). Tile 2 = a simple
|
|
22
|
+
* face/box so the sprite reads as an object. */
|
|
23
|
+
static const uint8_t tiles[32] = {
|
|
24
|
+
/* tile 1 — solid block: plane0 all-on, plane1 all-off → every pixel color 1 */
|
|
25
|
+
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
|
26
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
27
|
+
/* tile 2 — sprite: a filled diamond/box (color 1 outline + fill) */
|
|
28
|
+
0x18,0x3C,0x7E,0xFF,0xFF,0x7E,0x3C,0x18,
|
|
29
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/* 32-byte palette: BG palette 0 gives the block a bright color on a blue
|
|
33
|
+
* backdrop; sprite palette 0 colour 1 = yellow so the sprite pops. */
|
|
34
|
+
static const uint8_t palette[32] = {
|
|
35
|
+
0x01, 0x30, 0x21, 0x0F, /* BG0: backdrop blue, block white, ... */
|
|
36
|
+
0x01, 0x30, 0x21, 0x0F,
|
|
37
|
+
0x01, 0x30, 0x21, 0x0F,
|
|
38
|
+
0x01, 0x30, 0x21, 0x0F,
|
|
39
|
+
0x0F, 0x28, 0x16, 0x30, /* SPR0: colour1 yellow */
|
|
40
|
+
0x0F, 0x28, 0x16, 0x30,
|
|
41
|
+
0x0F, 0x28, 0x16, 0x30,
|
|
42
|
+
0x0F, 0x28, 0x16, 0x30,
|
|
43
|
+
};
|
|
20
44
|
|
|
21
45
|
void main(void) {
|
|
22
|
-
uint8_t
|
|
23
|
-
uint8_t
|
|
24
|
-
|
|
46
|
+
uint8_t x, y;
|
|
47
|
+
uint8_t sx = 120, sy = 96; /* sprite position */
|
|
48
|
+
int8_t dx = 1, dy = 1; /* sprite velocity */
|
|
25
49
|
uint8_t frame = 0;
|
|
26
50
|
|
|
27
|
-
for (i = 0; i < 32; i++) palette[i] = bg_colors[0];
|
|
28
|
-
|
|
29
51
|
ppu_off();
|
|
52
|
+
/* The runtime's default PPUCTRL puts SPRITES at pattern table $0000 and the
|
|
53
|
+
* BACKGROUND at $1000 — two separate 4KB tables. So upload the block tile to
|
|
54
|
+
* the BG table ($1000, slot 1 → $1010) AND the sprite tile to the sprite
|
|
55
|
+
* table ($0000, slot 2 → $0020). (Uploading only to $0000 = invisible BG, the
|
|
56
|
+
* classic "my tiles are in the wrong pattern table" NES gotcha.) */
|
|
57
|
+
chr_ram_upload(0x1010, tiles, 16); /* BG block → BG tile index 1 */
|
|
58
|
+
chr_ram_upload(0x0020, tiles + 16, 16); /* SPR object → sprite tile index 2 */
|
|
30
59
|
palette_load(palette);
|
|
31
60
|
oam_clear();
|
|
61
|
+
|
|
62
|
+
/* Populate the nametable DIRECTLY while rendering is off. We use
|
|
63
|
+
* vram_unsafe_set (a raw PPU write) here on purpose: the friendly tile_set()
|
|
64
|
+
* goes through the NMI-flushed queue (24 entries), which would deadlock if you
|
|
65
|
+
* push hundreds of tiles before turning the PPU on. The "write VRAM directly
|
|
66
|
+
* while off, then turn on" idiom is the standard NES way to lay out a screen.
|
|
67
|
+
* Tile index 1 = the solid block. */
|
|
68
|
+
for (x = 0; x < 32; x++) {
|
|
69
|
+
vram_unsafe_set((uint16_t)(0x2000 + 4 * 32 + x), 1); /* top band (row 4) */
|
|
70
|
+
vram_unsafe_set((uint16_t)(0x2000 + 25 * 32 + x), 1); /* bottom band (row 25) */
|
|
71
|
+
}
|
|
72
|
+
for (y = 10; y < 18; y++) {
|
|
73
|
+
for (x = 8; x < 24; x++)
|
|
74
|
+
vram_unsafe_set((uint16_t)(0x2000 + (uint16_t)y * 32 + x), 1); /* center box */
|
|
75
|
+
}
|
|
76
|
+
|
|
32
77
|
ppu_on_all();
|
|
33
78
|
|
|
34
79
|
for (;;) {
|
|
80
|
+
/* stage the sprite BEFORE waiting (NMI DMAs shadow_oam at vblank). */
|
|
81
|
+
oam_clear();
|
|
82
|
+
oam_spr(sx, sy, 2, 0); /* tile 2, sprite palette 0 */
|
|
35
83
|
ppu_wait_nmi();
|
|
84
|
+
|
|
36
85
|
++frame;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
86
|
+
/* bounce the sprite around the visible area */
|
|
87
|
+
sx = (uint8_t)(sx + dx);
|
|
88
|
+
sy = (uint8_t)(sy + dy);
|
|
89
|
+
if (sx < 8 || sx > 240) dx = (int8_t)-dx;
|
|
90
|
+
if (sy < 16 || sy > 208) dy = (int8_t)-dy;
|
|
43
91
|
}
|
|
44
92
|
}
|
|
@@ -32,6 +32,21 @@ static const uint8_t tile_data[16] = {
|
|
|
32
32
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
/* Two BG tiles so the backdrop isn't a single flat colour (a uniform
|
|
36
|
+
* screen reads >=92% one colour and fails the blank-screen check):
|
|
37
|
+
* tile 1 — solid colour 1
|
|
38
|
+
* tile 2 — solid colour 2
|
|
39
|
+
* Checkerboarded across the nametable below. BG fetches from $1000-$1FFF
|
|
40
|
+
* under the default PPUCTRL, so these upload to the BG pattern table. */
|
|
41
|
+
static const uint8_t bg_tiles[2 * 16] = {
|
|
42
|
+
/* tile 1: solid colour 1 */
|
|
43
|
+
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
|
44
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
45
|
+
/* tile 2: solid colour 2 */
|
|
46
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
47
|
+
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
|
48
|
+
};
|
|
49
|
+
|
|
35
50
|
/* 32-byte palette: 4 BG palettes + 4 sprite palettes.
|
|
36
51
|
* BG index 0 ($3F00) is the universal backdrop.
|
|
37
52
|
* SPR index 0 ($3F10) is transparent — always; the value is written
|
|
@@ -49,6 +64,21 @@ static const uint8_t palette[32] = {
|
|
|
49
64
|
0x0F, 0x2A, 0x1A, 0x0A,
|
|
50
65
|
};
|
|
51
66
|
|
|
67
|
+
/* Fill nametable 0 ($2000) with a checkerboard of BG tiles 1 and 2 so the
|
|
68
|
+
* screen behind the sprite is visibly NOT blank. Attribute table stays at
|
|
69
|
+
* palette 0. Caller must have the PPU off. */
|
|
70
|
+
static void fill_bg(void) {
|
|
71
|
+
uint16_t addr;
|
|
72
|
+
uint8_t y, x;
|
|
73
|
+
for (y = 0; y < 30; y++) {
|
|
74
|
+
addr = (uint16_t)(0x2000 + (uint16_t)y * 32);
|
|
75
|
+
for (x = 0; x < 32; x++) {
|
|
76
|
+
vram_unsafe_set(addr, (uint8_t)(((x ^ y) & 1) + 1));
|
|
77
|
+
++addr;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
52
82
|
void main(void) {
|
|
53
83
|
uint8_t px = 124; /* mid-screen X */
|
|
54
84
|
uint8_t py = 110; /* mid-screen Y */
|
|
@@ -64,6 +94,11 @@ void main(void) {
|
|
|
64
94
|
* at $0000-$0FFF since the default PPUCTRL has sprite_pattern=0. */
|
|
65
95
|
chr_ram_upload(0x0010, tile_data, 16);
|
|
66
96
|
|
|
97
|
+
/* ── 2b. Upload BG tiles to the BG pattern table at $1000 and paint
|
|
98
|
+
* a checkerboard backdrop so the sprite isn't alone on a blank field. */
|
|
99
|
+
chr_ram_upload(0x1000, bg_tiles, sizeof(bg_tiles));
|
|
100
|
+
fill_bg();
|
|
101
|
+
|
|
67
102
|
/* ── 3. Load palette ─────────────────────────────────────────── */
|
|
68
103
|
palette_load(palette);
|
|
69
104
|
|