romdevtools 0.14.0 → 0.15.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 +17 -10
- package/CHANGELOG.md +63 -0
- package/examples/atari2600/main.asm +1 -1
- package/examples/atari2600/templates/default.asm +1 -1
- package/examples/atari2600/templates/paddle.asm +59 -47
- package/examples/atari7800/main.c +1 -1
- package/examples/atari7800/templates/default.c +1 -1
- package/examples/atari7800/templates/music_demo.c +1 -1
- package/examples/c64/main.c +1 -1
- package/examples/c64/templates/platformer.c +2 -2
- package/examples/c64/templates/puzzle.c +1 -1
- package/examples/c64/templates/racing.c +3 -3
- package/examples/c64/templates/shmup.c +6 -5
- package/examples/c64/templates/sports.c +4 -4
- package/examples/gb/main.asm +1 -1
- package/examples/gb/main.c +1 -1
- package/examples/gb/templates/puzzle.c +1 -1
- package/examples/gb/templates/racing.c +1 -1
- package/examples/gb/templates/shmup.c +1 -1
- package/examples/gba/templates/gba_hello.c +1 -1
- package/examples/gba/templates/maxmod_demo.c +1 -1
- package/examples/gba/templates/puzzle.c +17 -3
- package/examples/gba/templates/racing.c +16 -2
- package/examples/gba/templates/shmup.c +23 -4
- package/examples/gba/templates/tonc_hello.c +6 -4
- package/examples/gbc/main.asm +1 -1
- package/examples/gbc/templates/puzzle.c +1 -1
- package/examples/gbc/templates/racing.c +1 -1
- package/examples/gbc/templates/shmup.c +1 -1
- package/examples/genesis/main.s +1 -1
- package/examples/genesis/templates/puzzle.c +1 -1
- package/examples/genesis/templates/racing.c +45 -1
- package/examples/genesis/templates/shmup.c +12 -3
- package/examples/genesis/templates/shmup_2p.c +2 -2
- package/examples/genesis/templates/sports.c +39 -0
- package/examples/gg/templates/hello_sprite.c +38 -23
- package/examples/gg/templates/music_demo.c +11 -8
- package/examples/gg/templates/platformer.c +37 -15
- package/examples/gg/templates/racing.c +25 -12
- package/examples/gg/templates/shmup.c +12 -6
- package/examples/gg/templates/sports.c +30 -16
- package/examples/gg/templates/tile_engine.c +24 -10
- package/examples/lynx/templates/platformer.c +7 -1
- package/examples/lynx/templates/puzzle.c +8 -2
- package/examples/lynx/templates/racing.c +7 -1
- package/examples/lynx/templates/sports.c +7 -1
- package/examples/nes/main.c +2 -2
- package/examples/nes/space-shooter/nes_runtime.h +1 -1
- package/examples/nes/templates/default.c +4 -1
- package/examples/nes/templates/racing.c +50 -1
- package/examples/pce/main.c +1 -1
- package/examples/sms/templates/hello_sprite.c +1 -1
- package/examples/sms/templates/music_demo.c +1 -1
- package/examples/sms/templates/puzzle.c +1 -1
- package/examples/sms/templates/racing.c +1 -1
- package/examples/sms/templates/shmup.c +1 -1
- package/examples/sms/templates/shmup_2p.c +2 -2
- package/examples/snes/main.asm +1 -1
- package/examples/snes/templates/c-hello-data.asm +309 -14
- package/examples/snes/templates/c-hello.c +13 -2
- package/examples/snes/templates/default.c +1 -1
- package/examples/snes/templates/hello_sprite-data.asm +300 -2
- package/examples/snes/templates/hello_sprite.c +10 -1
- package/examples/snes/templates/music_demo-data.asm +300 -2
- package/examples/snes/templates/music_demo.c +10 -1
- package/examples/snes/templates/platformer-data.asm +300 -2
- package/examples/snes/templates/platformer.c +10 -1
- package/examples/snes/templates/puzzle-data.asm +300 -2
- package/examples/snes/templates/puzzle.c +11 -1
- package/examples/snes/templates/racing-data.asm +300 -2
- package/examples/snes/templates/racing.c +40 -4
- package/examples/snes/templates/shmup-data.asm +299 -6
- package/examples/snes/templates/shmup.c +11 -7
- package/examples/snes/templates/sports-data.asm +300 -2
- package/examples/snes/templates/sports.c +40 -5
- package/package.json +1 -1
- package/src/mcp/tools/project.js +33 -22
- package/src/mcp/tools/toolchain.js +183 -19
- package/src/observer/livestream.html +34 -4
- package/src/platforms/gg/MENTAL_MODEL.md +14 -13
- package/src/platforms/gg/lib/c/vdp_init.c +10 -8
- package/src/platforms/msx/MENTAL_MODEL.md +1 -1
- package/src/platforms/nes/TROUBLESHOOTING.md +1 -1
- package/src/platforms/nes/lib/c/nes_runtime.c +28 -6
- package/src/platforms/pce/MENTAL_MODEL.md +1 -1
- package/src/platforms/pce/lib/c/pce_hw.h +1 -0
- package/src/platforms/pce/lib/c/pce_video.c +26 -0
- package/src/platforms/sms/MENTAL_MODEL.md +12 -12
- package/src/platforms/sms/lib/c/vdp_init.c +10 -8
- package/src/platforms/sms/lib/vdp_init.s +1 -1
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +1 -1
- package/src/toolchains/cc65/presets/nes/chr-ram.cfg +1 -1
- package/src/toolchains/cc65/presets/nes/chr-ram.crt0.s +1 -1
- package/src/toolchains/genesis-c/README.md +1 -1
- package/src/toolchains/sdcc/preflight-lint.js +47 -7
- package/src/toolchains/snes-c/snes-c.js +3 -7
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
* Writes the 11 mode-4 registers to a sane baseline:
|
|
4
4
|
* display OFF, vblank IRQ off, 192-line mode 4, name table at $3800,
|
|
5
5
|
* BG tile data at $0000, sprite attr table at $3F00, sprite tile data
|
|
6
|
-
* at $
|
|
6
|
+
* at $2000 (R6=0xFF → SA13 set → tiles read from $2000-$3FFF). Call
|
|
7
7
|
* once after reset before uploading palette/tiles/map.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* sprite tiles
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Sprite-tile base: R6 bit 2 (SA13) selects $0000 (clear) vs $2000 (set).
|
|
10
|
+
* We default to R6=0xFF ($2000) because EVERY bundled scaffold uploads its
|
|
11
|
+
* sprite tiles to $2000 (sms_load_tiles(0x2000, ...)) — so the baseline must
|
|
12
|
+
* match what consumers do, or sprites read from the empty/BG bank and render
|
|
13
|
+
* invisible. (Many SMS references say "R6=0xFB → $2000", which is backwards:
|
|
14
|
+
* 0xFB has SA13 CLEAR = $0000.) If you instead keep sprite tiles in the BG
|
|
15
|
+
* bank at $0000, set R6=0xFB. sprites({op:'inspect'}) → spriteTileDataBase
|
|
16
|
+
* shows the address the VDP is actually reading from.
|
|
15
17
|
*
|
|
16
18
|
* After loading assets, enable display by re-writing R1 with bit 6 set:
|
|
17
19
|
* sms_vdp_display_on();
|
|
@@ -33,7 +35,7 @@ void sms_vdp_init(void) {
|
|
|
33
35
|
0xFF, /* R3: color table (ignored in M4) */
|
|
34
36
|
0xFF, /* R4: BG tile data at $0000 */
|
|
35
37
|
0xFF, /* R5: sprite attr table at $3F00 */
|
|
36
|
-
|
|
38
|
+
0xFF, /* R6: sprite tile data at $2000 (SA13 set; scaffolds upload here) */
|
|
37
39
|
0x00, /* R7: border = sprite palette entry 0 */
|
|
38
40
|
0x00, /* R8: BG X scroll */
|
|
39
41
|
0x00, /* R9: BG Y scroll */
|
|
@@ -40,7 +40,7 @@ _vdp_init_regs:
|
|
|
40
40
|
.db $FF ; R3: color table — M4 ignores
|
|
41
41
|
.db $FF ; R4: BG tile data — M4: bit 2 selects $0000 vs $2000
|
|
42
42
|
.db $FF ; R5: sprite attr table base ($3F00 = $7E << 7)
|
|
43
|
-
.db $
|
|
43
|
+
.db $FF ; R6: sprite tile data at $2000 (SA13 set; scaffolds upload here)
|
|
44
44
|
.db $00 ; R7: border color = sprite palette entry 0
|
|
45
45
|
.db $00 ; R8: BG X scroll
|
|
46
46
|
.db $00 ; R9: BG Y scroll
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# runtime. There is no CHARS segment.
|
|
13
13
|
#
|
|
14
14
|
# To use this preset:
|
|
15
|
-
# 1. linkerConfig: "chr-ram" on
|
|
15
|
+
# 1. linkerConfig: "chr-ram" on build({output:'rom'})
|
|
16
16
|
# 2. Supply your own crt0/header source that writes the 16-byte iNES
|
|
17
17
|
# header with byte 5 = 0. Easiest: paste the snippet
|
|
18
18
|
# getStarterSnippet({platform:"nes", name:"chr_ram_header", language:"asm"}).
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# runtime. There is no CHARS segment.
|
|
13
13
|
#
|
|
14
14
|
# To use this preset:
|
|
15
|
-
# 1. linkerConfig: "chr-ram" on
|
|
15
|
+
# 1. linkerConfig: "chr-ram" on build({output:'rom'})
|
|
16
16
|
# 2. Supply your own crt0/header source that writes the 16-byte iNES
|
|
17
17
|
# header with byte 5 = 0. Easiest: paste the snippet
|
|
18
18
|
# getStarterSnippet({platform:"nes", name:"chr_ram_header", language:"asm"}).
|
|
@@ -91,7 +91,7 @@ _exit: jsr donelib
|
|
|
91
91
|
; rti
|
|
92
92
|
;
|
|
93
93
|
; ; then DELETE the `nmi: rti` line below from this crt0 (or load a
|
|
94
|
-
; ; modified crt0 via your own
|
|
94
|
+
; ; modified crt0 via your own build({output:'rom'}) sources entry).
|
|
95
95
|
|
|
96
96
|
.segment "STARTUP"
|
|
97
97
|
|
|
@@ -23,7 +23,7 @@ once the WASM artifacts ship. Pipeline shape:
|
|
|
23
23
|
- **WASM port of cc1/as/ld (stage 2)**: pending.
|
|
24
24
|
- **SGDK native build against the cross-toolchain (stage 3)**: pending.
|
|
25
25
|
- **JS driver `buildGenesisC()`**: pending.
|
|
26
|
-
- **
|
|
26
|
+
- **build({output:'rom'}) wiring**: pending.
|
|
27
27
|
|
|
28
28
|
## Why a 3-stage build
|
|
29
29
|
|
|
@@ -68,13 +68,53 @@ export function lintSdccSource(source, file = "main.c", opts = {}) {
|
|
|
68
68
|
// CONSERVATIVE: only flag when we can SEE the counter declared u8 and
|
|
69
69
|
// the bound is a constant we can evaluate — never guess.
|
|
70
70
|
{
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
// Build a SCOPE-AWARE map of where each name is declared and at what
|
|
72
|
+
// width. A name like `i` is commonly re-declared in several functions
|
|
73
|
+
// — some as uint8_t, some as uint16_t. A flat "is this name ever u8"
|
|
74
|
+
// set wrongly flags the uint16_t loop just because a DIFFERENT
|
|
75
|
+
// function declared its own `i` as uint8_t (the SMS/GG default
|
|
76
|
+
// scaffold false-positive). Instead we record EVERY declaration's
|
|
77
|
+
// line + width, then for each loop consult the nearest declaration of
|
|
78
|
+
// the counter that appears ABOVE the loop — i.e. the one actually in
|
|
79
|
+
// scope — and only flag it when that declaration is 8-bit.
|
|
80
|
+
//
|
|
81
|
+
// decls: Map<name, Array<{line:number, u8:boolean}>> (line is 1-based)
|
|
82
|
+
const decls = new Map();
|
|
83
|
+
const addDecl = (names, line, u8) => {
|
|
84
|
+
for (const raw of names.split(",")) {
|
|
85
|
+
const n = raw.trim();
|
|
86
|
+
if (!n) continue;
|
|
87
|
+
if (!decls.has(n)) decls.set(n, []);
|
|
88
|
+
decls.get(n).push({ line, u8 });
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const u8re = /\b(?:unsigned\s+char|char|u8|uint8_t|uint8|int8_t|int8|signed\s+char)\s+([A-Za-z_]\w*(?:\s*,\s*[A-Za-z_]\w*)*)\s*;/;
|
|
92
|
+
// 16-bit-or-wider integer declarations (and float/double, which also
|
|
93
|
+
// can't overflow at 255). Anything not 8-bit is "wide" for our purpose.
|
|
94
|
+
const wideRe = /\b(?:unsigned\s+(?:short|int|long)|signed\s+(?:short|int|long)|short|int|long|u16|u32|u64|uint16_t|uint16|int16_t|int16|uint32_t|uint32|int32_t|int32|uint64_t|uint64|int64_t|int64|size_t|ptrdiff_t)\s+([A-Za-z_]\w*(?:\s*,\s*[A-Za-z_]\w*)*)\s*;/;
|
|
95
|
+
for (let i = 0; i < lines.length; i++) {
|
|
96
|
+
const code = lines[i].replace(/\/\/.*$/, "").replace(/\/\*.*?\*\//g, "");
|
|
97
|
+
const m8 = code.match(u8re);
|
|
98
|
+
if (m8) { addDecl(m8[1], i + 1, true); continue; }
|
|
99
|
+
const mw = code.match(wideRe);
|
|
100
|
+
if (mw) addDecl(mw[1], i + 1, false);
|
|
77
101
|
}
|
|
102
|
+
// Is the counter declared 8-bit in the scope visible at `loopLine`?
|
|
103
|
+
// Use the NEAREST declaration above the loop. If the nearest visible
|
|
104
|
+
// declaration is wide (uint16_t etc.), the loop is fine.
|
|
105
|
+
const counterIsU8AtLine = (counter, loopLine) => {
|
|
106
|
+
const ds = decls.get(counter);
|
|
107
|
+
if (!ds) return false;
|
|
108
|
+
let best = null;
|
|
109
|
+
for (const d of ds) {
|
|
110
|
+
if (d.line <= loopLine && (best === null || d.line > best.line)) best = d;
|
|
111
|
+
}
|
|
112
|
+
// No declaration above the loop (e.g. param/global declared after, or
|
|
113
|
+
// out-of-order) — fall back to "flag only if EVERY decl is u8" so we
|
|
114
|
+
// never wolf-cry on a name that is also declared wide somewhere.
|
|
115
|
+
if (best === null) return ds.every((d) => d.u8);
|
|
116
|
+
return best.u8;
|
|
117
|
+
};
|
|
78
118
|
const evalConst = (expr) => {
|
|
79
119
|
// Only literals and pure `A * B [* C]` products of decimal/hex ints.
|
|
80
120
|
const t = expr.trim();
|
|
@@ -91,7 +131,7 @@ export function lintSdccSource(source, file = "main.c", opts = {}) {
|
|
|
91
131
|
const m = code.match(/\bfor\s*\([^;]*;\s*([A-Za-z_]\w*)\s*<\s*([^;]+?)\s*;/);
|
|
92
132
|
if (!m) continue;
|
|
93
133
|
const [, counter, boundExpr] = m;
|
|
94
|
-
if (!
|
|
134
|
+
if (!counterIsU8AtLine(counter, i + 1)) continue;
|
|
95
135
|
const bound = evalConst(boundExpr);
|
|
96
136
|
if (bound !== null && bound > 255) {
|
|
97
137
|
issues.push({
|
|
@@ -148,13 +148,9 @@ function normalizeSnesSources(args) {
|
|
|
148
148
|
if (cFiles.length === 0) {
|
|
149
149
|
throw new Error("buildSnesC: `sources` must include at least one .c file.");
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
`Today only one .c file is supported per build — combine via #include or wait for ` +
|
|
155
|
-
`multi-TU support. .asm/.s siblings work fine.`,
|
|
156
|
-
);
|
|
157
|
-
}
|
|
151
|
+
// Multiple .c files ARE supported: buildWithPvSnesLib compiles each to its
|
|
152
|
+
// own .obj (tcc→wla) and links them all (Stage 1 + Stage 3). The genre
|
|
153
|
+
// scaffolds ship main.c + snes_sfx.c and rely on this.
|
|
158
154
|
return args.sources;
|
|
159
155
|
}
|
|
160
156
|
if (typeof args.source === "string") {
|