romdevtools 0.28.0 → 0.30.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 +53 -43
- package/CHANGELOG.md +91 -0
- package/README.md +3 -3
- package/examples/README.md +7 -7
- package/examples/atari2600/templates/platformer.asm +1225 -332
- package/examples/atari2600/templates/puzzle.asm +1056 -0
- package/examples/atari2600/templates/racing.asm +906 -275
- package/examples/atari2600/templates/shmup.asm +1031 -239
- package/examples/atari2600/templates/sports.asm +1135 -253
- package/examples/atari7800/templates/platformer.c +991 -156
- package/examples/atari7800/templates/puzzle.c +1091 -148
- package/examples/atari7800/templates/racing.c +952 -124
- package/examples/atari7800/templates/shmup.c +812 -134
- package/examples/atari7800/templates/sports.c +820 -184
- package/examples/c64/templates/platformer.c +879 -164
- package/examples/c64/templates/puzzle.c +855 -178
- package/examples/c64/templates/racing.c +873 -97
- package/examples/c64/templates/shmup.c +757 -161
- package/examples/c64/templates/sports.c +755 -100
- package/examples/gb/templates/platformer.c +841 -179
- package/examples/gb/templates/puzzle.c +986 -246
- package/examples/gb/templates/racing.c +754 -174
- package/examples/gb/templates/shmup.c +673 -175
- package/examples/gb/templates/sports.c +790 -159
- package/examples/gba/templates/platformer.c +626 -165
- package/examples/gba/templates/puzzle.c +519 -269
- package/examples/gba/templates/racing.c +511 -206
- package/examples/gba/templates/shmup.c +564 -179
- package/examples/gba/templates/sports.c +454 -174
- package/examples/gbc/templates/platformer.c +944 -180
- package/examples/gbc/templates/puzzle.c +363 -109
- package/examples/gbc/templates/racing.c +884 -180
- package/examples/gbc/templates/shmup.c +821 -185
- package/examples/gbc/templates/sports.c +870 -162
- package/examples/genesis/templates/platformer.c +747 -129
- package/examples/genesis/templates/puzzle.c +694 -261
- package/examples/genesis/templates/racing.c +726 -203
- package/examples/genesis/templates/shmup.c +535 -142
- package/examples/genesis/templates/sports.c +495 -158
- package/examples/gg/templates/platformer.c +880 -215
- package/examples/gg/templates/puzzle.c +875 -216
- package/examples/gg/templates/racing.c +915 -172
- package/examples/gg/templates/shmup.c +714 -191
- package/examples/gg/templates/sports.c +732 -129
- package/examples/lynx/templates/platformer.c +604 -69
- package/examples/lynx/templates/puzzle.c +498 -158
- package/examples/lynx/templates/racing.c +538 -102
- package/examples/lynx/templates/shmup.c +458 -131
- package/examples/lynx/templates/sports.c +496 -72
- package/examples/msx/platformer/main.c +649 -162
- package/examples/msx/puzzle/main.c +742 -240
- package/examples/msx/racing/main.c +669 -178
- package/examples/msx/shmup/main.c +460 -178
- package/examples/msx/sports/main.c +592 -126
- package/examples/nes/templates/platformer.c +589 -171
- package/examples/nes/templates/puzzle.c +563 -242
- package/examples/nes/templates/racing.c +502 -208
- package/examples/nes/templates/shmup.c +339 -145
- package/examples/nes/templates/sports.c +341 -183
- package/examples/pce/platformer/main.c +874 -205
- package/examples/pce/puzzle/main.c +802 -287
- package/examples/pce/racing/main.c +783 -208
- package/examples/pce/shmup/main.c +638 -212
- package/examples/pce/sports/main.c +586 -169
- package/examples/porting-across-platforms/README.md +1 -1
- package/examples/sms/templates/platformer.c +762 -177
- package/examples/sms/templates/puzzle.c +752 -212
- package/examples/sms/templates/racing.c +808 -145
- package/examples/sms/templates/shmup.c +599 -162
- package/examples/sms/templates/sports.c +630 -122
- package/examples/snes/templates/music_demo.c +7 -0
- package/examples/snes/templates/platformer-data.asm +123 -24
- package/examples/snes/templates/platformer-hdr.asm +57 -0
- package/examples/snes/templates/platformer.c +586 -165
- package/examples/snes/templates/puzzle-data.asm +116 -21
- package/examples/snes/templates/puzzle-hdr.asm +57 -0
- package/examples/snes/templates/puzzle.c +614 -235
- package/examples/snes/templates/racing-data.asm +390 -32
- package/examples/snes/templates/racing-hdr.asm +57 -0
- package/examples/snes/templates/racing.c +807 -196
- package/examples/snes/templates/shmup-data.asm +87 -29
- package/examples/snes/templates/shmup-hdr.asm +57 -0
- package/examples/snes/templates/shmup.c +459 -198
- package/examples/snes/templates/sports-data.asm +48 -2
- package/examples/snes/templates/sports-hdr.asm +57 -0
- package/examples/snes/templates/sports.c +414 -163
- package/package.json +12 -12
- package/src/cores/wasm/bluemsx_libretro.js +1 -1
- package/src/cores/wasm/bluemsx_libretro.wasm +0 -0
- package/src/cores/wasm/fceumm_libretro.js +1 -1
- package/src/cores/wasm/fceumm_libretro.wasm +0 -0
- package/src/cores/wasm/gambatte_libretro.js +1 -1
- package/src/cores/wasm/gambatte_libretro.wasm +0 -0
- package/src/cores/wasm/geargrafx_libretro.js +1 -1
- package/src/cores/wasm/geargrafx_libretro.wasm +0 -0
- package/src/cores/wasm/genesis_plus_gx_libretro.js +1 -1
- package/src/cores/wasm/genesis_plus_gx_libretro.wasm +0 -0
- package/src/cores/wasm/handy_libretro.js +1 -1
- package/src/cores/wasm/handy_libretro.wasm +0 -0
- package/src/cores/wasm/mgba_libretro.js +1 -1
- package/src/cores/wasm/mgba_libretro.wasm +0 -0
- package/src/cores/wasm/prosystem_libretro.js +1 -1
- package/src/cores/wasm/prosystem_libretro.wasm +0 -0
- package/src/cores/wasm/snes9x_libretro.js +1 -1
- package/src/cores/wasm/snes9x_libretro.wasm +0 -0
- package/src/cores/wasm/stella2014_libretro.js +1 -1
- package/src/cores/wasm/stella2014_libretro.wasm +0 -0
- 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 +84 -8
- package/src/http/tool-registry.js +11 -11
- package/src/mcp/tools/cheats.js +2 -1
- package/src/mcp/tools/frame.js +3 -2
- package/src/mcp/tools/index.js +3 -3
- package/src/mcp/tools/input.js +5 -4
- package/src/mcp/tools/lifecycle.js +6 -4
- package/src/mcp/tools/memory.js +131 -24
- package/src/mcp/tools/platform-docs.js +1 -1
- package/src/mcp/tools/preview-tile.js +6 -2
- package/src/mcp/tools/project.js +1098 -130
- package/src/mcp/tools/record.js +6 -7
- package/src/mcp/tools/rom-id.js +5 -1
- package/src/mcp/tools/run-until.js +12 -4
- package/src/mcp/tools/snippets.js +6 -6
- package/src/mcp/tools/sprite-pipeline.js +14 -2
- package/src/mcp/tools/state.js +2 -1
- package/src/mcp/tools/tile-inspect.js +8 -1
- package/src/mcp/tools/toolchain.js +12 -1
- package/src/mcp/tools/watch-memory.js +53 -10
- package/src/observer/bus.js +73 -0
- package/src/observer/livestream.html +4 -2
- package/src/observer/tool-wrap.js +17 -14
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +32 -3
- package/src/platforms/atari7800/MENTAL_MODEL.md +5 -5
- package/src/platforms/atari7800/TROUBLESHOOTING.md +5 -5
- package/src/platforms/c64/MENTAL_MODEL.md +11 -4
- package/src/platforms/c64/TROUBLESHOOTING.md +13 -0
- package/src/platforms/gb/MENTAL_MODEL.md +3 -3
- package/src/platforms/gb/TROUBLESHOOTING.md +61 -8
- package/src/platforms/gb/lib/c/README.md +10 -11
- package/src/platforms/gb/lib/c/gb_crt0.s +27 -3
- package/src/platforms/gb/lib/c/patch-header.js +13 -3
- package/src/platforms/gba/MENTAL_MODEL.md +4 -4
- package/src/platforms/gba/TROUBLESHOOTING.md +3 -3
- package/src/platforms/gba/lib/c/gba_sfx.c +40 -0
- package/src/platforms/gba/lib/c/gba_sfx.h +10 -0
- package/src/platforms/gbc/MENTAL_MODEL.md +4 -4
- package/src/platforms/gbc/TROUBLESHOOTING.md +4 -4
- package/src/platforms/gbc/UPSTREAM_SOURCES.md +1 -1
- package/src/platforms/gbc/lib/c/README.md +10 -11
- package/src/platforms/gbc/lib/c/gb_crt0.s +26 -3
- package/src/platforms/gbc/lib/c/patch-header.js +13 -3
- package/src/platforms/genesis/MENTAL_MODEL.md +3 -3
- package/src/platforms/genesis/TROUBLESHOOTING.md +2 -2
- package/src/platforms/gg/MENTAL_MODEL.md +4 -4
- package/src/platforms/gg/TROUBLESHOOTING.md +3 -3
- package/src/platforms/gg/UPSTREAM_SOURCES.md +1 -1
- package/src/platforms/gg/lib/c/joypad_read.c +29 -0
- package/src/platforms/lynx/MENTAL_MODEL.md +1 -1
- package/src/platforms/lynx/TROUBLESHOOTING.md +3 -3
- package/src/platforms/msx/MENTAL_MODEL.md +5 -5
- package/src/platforms/msx/TROUBLESHOOTING.md +2 -2
- package/src/platforms/msx/lib/c/msx_hw.h +1 -0
- package/src/platforms/msx/lib/c/msx_vdp.c +25 -0
- package/src/platforms/nes/MENTAL_MODEL.md +2 -2
- package/src/platforms/nes/lib/c/nes_runtime.c +149 -34
- package/src/platforms/nes/lib/c/nes_runtime.h +34 -1
- package/src/platforms/pce/MENTAL_MODEL.md +5 -5
- package/src/platforms/pce/TROUBLESHOOTING.md +1 -1
- package/src/platforms/pce/lib/c/pce_hw.h +11 -0
- package/src/platforms/pce/lib/c/pce_video.c +32 -0
- package/src/platforms/sms/MENTAL_MODEL.md +6 -6
- package/src/platforms/snes/MENTAL_MODEL.md +2 -2
- package/src/platforms/snes/TROUBLESHOOTING.md +40 -1
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +13 -8
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.crt0.s +58 -5
- package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +52 -3
- package/src/toolchains/cc65/presets/pce/rom32k.cfg +52 -0
- package/src/toolchains/index.js +27 -11
|
@@ -12,13 +12,13 @@ romdev ships a **hardware helper library** (`src/platforms/pce/lib/c/`:
|
|
|
12
12
|
`psg_tone()` instead of poking VDC/VCE registers by hand. cc65 has **no** sprite
|
|
13
13
|
library, so this lib is how you get pixels on screen.
|
|
14
14
|
|
|
15
|
-
The fastest way to a working game:
|
|
16
|
-
"shmup"})`** — or any
|
|
17
|
-
genre set. For a smaller
|
|
18
|
-
|
|
15
|
+
The fastest way to a working game: **fork the example game whose core loop is
|
|
16
|
+
nearest yours — `examples({op:'fork', example:"pce/shmup", name, path})`** — or any
|
|
17
|
+
of `platformer` / `puzzle` / `sports` / `racing`, the full genre set. For a smaller
|
|
18
|
+
starting point fork `pce/sprite_move` (also `music_sfx`, `catch_game`). Either drops
|
|
19
19
|
a complete, *building* project — a verified playable example + the helper lib +
|
|
20
20
|
docs. Read the example's `main.c`, then change it. The examples live in
|
|
21
|
-
`examples/pce/`. The genre
|
|
21
|
+
`examples/pce/`. The genre examples fill the BAT (32×32 virtual screen); the
|
|
22
22
|
`platformer` smooth-scrolls the background via the VDC BXR (R7) register.
|
|
23
23
|
**Gotcha:** `#include <stdint.h>` for int8/16/32_t — `pce.h` only typedefs u8/u16.
|
|
24
24
|
|
|
@@ -66,4 +66,4 @@ The 5-bit channel volume (`PSG_CHAN_CTRL` low bits, 0-31) is roughly an
|
|
|
66
66
|
ATTENUATOR: each step below 31 costs ~1.5 dB. A "middle" value like 13 is
|
|
67
67
|
about -27 dB — effectively silence on real hardware and most cores. Use
|
|
68
68
|
**29-31 for SFX/music** and treat anything under ~20 as a deliberate whisper.
|
|
69
|
-
(The bundled `psg_tone`
|
|
69
|
+
(The bundled `psg_tone` example helper and the music ticker default loud.)
|
|
@@ -103,8 +103,19 @@ void disp_enable(void); /* VDC R5: BG + SPR + VBlank IRQ on at once
|
|
|
103
103
|
void vblank_irq_enable(void); /* just the VBlank IRQ bit (waitvsync needs it) */
|
|
104
104
|
void load_tiles(u16 vram, const u16 *src, u16 n); /* alias of vram_write (tiles) */
|
|
105
105
|
void set_sprite(u8 slot, u16 x, u16 y, u16 pattern, u8 palette); /* fill shadow SATB */
|
|
106
|
+
void set_sprite_ex(u8 slot, u16 x, u16 y, u16 pattern, u8 palette, u16 attr_ex);
|
|
106
107
|
void satb_dma(void); /* DMA shadow SATB -> VDC (R19) */
|
|
107
108
|
|
|
109
|
+
/* attr_ex bits for set_sprite_ex() — the HuC6270 large-sprite size + flip
|
|
110
|
+
* bits in SATB word3. A 32-wide sprite needs a 2-aligned pattern code, 32x32
|
|
111
|
+
* needs 4-aligned, 32x64 needs 8-aligned; the data is consecutive 16x16 cells
|
|
112
|
+
* (left-to-right, then down). See the set_sprite_ex() comment in pce_video.c. */
|
|
113
|
+
#define SPR_CGX_32 0x0100 /* width 32px (two cells side by side) */
|
|
114
|
+
#define SPR_CGY_32 0x1000 /* height 32px (two cell rows) */
|
|
115
|
+
#define SPR_CGY_64 0x3000 /* height 64px (four cell rows) */
|
|
116
|
+
#define SPR_XFLIP 0x0800 /* mirror horizontally */
|
|
117
|
+
#define SPR_YFLIP 0x8000 /* mirror vertically */
|
|
118
|
+
|
|
108
119
|
/* The shadow SATB lives in VRAM at this word address; satb_dma() points the VDC
|
|
109
120
|
* SATB-DMA source (R19) here. Pattern base for tiles is your choice; sprites
|
|
110
121
|
* default to using the same VRAM you load_tiles() into. */
|
|
@@ -131,6 +131,38 @@ void set_sprite(u8 slot, u16 x, u16 y, u16 pattern, u8 palette) {
|
|
|
131
131
|
e[3] = (u16)(0x0080 | (palette & 0x0F)); /* word3: SPBG-front + pal */
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/* set_sprite() with the HuC6270's LARGE-SPRITE size bits — the PCE's signature
|
|
135
|
+
* trick (sprites up to 32x64 from ONE SATB entry, where the NES needs 8+).
|
|
136
|
+
*
|
|
137
|
+
* SATB word3 (the attribute word) layout:
|
|
138
|
+
* bit 15 Y-flip
|
|
139
|
+
* bits13:12 CGY — sprite HEIGHT: 00=16px, 01=32px, 11=64px (10 is invalid)
|
|
140
|
+
* bit 11 X-flip
|
|
141
|
+
* bit 8 CGX — sprite WIDTH: 0=16px, 1=32px
|
|
142
|
+
* bit 7 SPBG — 1 = sprite in front of background
|
|
143
|
+
* bits 3:0 sprite sub-palette (0-15)
|
|
144
|
+
*
|
|
145
|
+
* `attr_ex` is OR'd into word3 — pass the SPR_* constants from pce_hw.h
|
|
146
|
+
* (e.g. SPR_CGX_32 | SPR_CGY_32 for a 32x32 sprite). SPBG-front is still set
|
|
147
|
+
* for you, same as set_sprite().
|
|
148
|
+
*
|
|
149
|
+
* PATTERN LAYOUT for large sprites: the hardware ignores the low bit(s) of the
|
|
150
|
+
* pattern code and fetches consecutive 16x16 cells (64 words each) instead:
|
|
151
|
+
* 32 wide: cell N = left, N+1 = right (N multiple of 2)
|
|
152
|
+
* 32 tall: row r adds 2*r: N, N+1 / N+2, N+3 (N multiple of 4)
|
|
153
|
+
* 64 tall: rows 0-3 add 2*r: N .. N+7 (N multiple of 8)
|
|
154
|
+
* So a 32x32 sprite's VRAM data is FOUR cells in TL, TR, BL, BR order, and
|
|
155
|
+
* its pattern code must be 4-aligned (pattern = VRAM>>6 like set_sprite). */
|
|
156
|
+
void set_sprite_ex(u8 slot, u16 x, u16 y, u16 pattern, u8 palette, u16 attr_ex) {
|
|
157
|
+
u16 *e;
|
|
158
|
+
if (slot >= 64) return;
|
|
159
|
+
e = &_pce_satb[slot * 4];
|
|
160
|
+
e[0] = (u16)(y + 64); /* word0: Y (biased) */
|
|
161
|
+
e[1] = (u16)(x + 32); /* word1: X (biased) */
|
|
162
|
+
e[2] = (u16)((pattern & 0x3FF) << 1); /* word2: pattern (cell<<1) */
|
|
163
|
+
e[3] = (u16)(0x0080 | (palette & 0x0F) | attr_ex); /* word3: size/flip + SPBG + pal */
|
|
164
|
+
}
|
|
165
|
+
|
|
134
166
|
/* Copy the shadow SATB into VRAM at PCE_SATB_VRAM, then tell the VDC to DMA it
|
|
135
167
|
* into its internal sprite table (R19 = SATB source). */
|
|
136
168
|
void satb_dma(void) {
|
|
@@ -78,7 +78,7 @@ R1 = 0x80 display OFF, vblank IRQ off, 192-line
|
|
|
78
78
|
R2 = 0xFF name table at $3800
|
|
79
79
|
R4 = 0xFF BG tile data at $0000
|
|
80
80
|
R5 = 0xFF sprite attr table at $3F00
|
|
81
|
-
R6 = 0xFF sprite tile data at $2000 (own bank;
|
|
81
|
+
R6 = 0xFF sprite tile data at $2000 (own bank; the example games upload here)
|
|
82
82
|
R7 = 0x00 border colour
|
|
83
83
|
```
|
|
84
84
|
|
|
@@ -114,7 +114,7 @@ So Y bytes and X/tile pairs are split into TWO regions of the SAT.
|
|
|
114
114
|
`src/platforms/sms/lib/c/sprite_table.c` keeps a 256-byte shadow
|
|
115
115
|
buffer in WRAM and uploads it to the SAT each vblank.
|
|
116
116
|
|
|
117
|
-
### Two footguns the bundled
|
|
117
|
+
### Two footguns the bundled example games keep hitting
|
|
118
118
|
|
|
119
119
|
1. **8 sprites per scanline limit.** The VDP draws up to 8 sprites per
|
|
120
120
|
scanline; the 9th+ are silently dropped. If you draw a "CATCH THE
|
|
@@ -147,9 +147,9 @@ buffer in WRAM and uploads it to the SAT each vblank.
|
|
|
147
147
|
`sms_vdp_init()` sets R6 = 0xFF. R6 bit 2 is the SA13 select for
|
|
148
148
|
sprite tile data — bit 2 is **SET** in 0xFF, so sprite tiles read
|
|
149
149
|
from `$2000-$3FFF`, their **own bank** separate from BG tiles at
|
|
150
|
-
$0000. This matches every bundled
|
|
150
|
+
$0000. This matches every bundled example, which uploads sprite
|
|
151
151
|
tiles to `$2000` (`sms_load_tiles(0x2000, …)`) — default and
|
|
152
|
-
|
|
152
|
+
examples agree, so sprites render.
|
|
153
153
|
|
|
154
154
|
Watch the bit: 0xFB has SA13 **CLEAR** = sprite tiles at $0000
|
|
155
155
|
(shared with the BG bank). If you set R6=0xFB you MUST upload your
|
|
@@ -222,7 +222,7 @@ PSG (SN76489) on port $7F. 4 channels: 3 square waves + 1 noise.
|
|
|
222
222
|
Writes are byte-wise; the high bit selects "latch register" vs
|
|
223
223
|
"continue previous register".
|
|
224
224
|
|
|
225
|
-
A full driver is beyond the scope of these
|
|
225
|
+
A full driver is beyond the scope of these example games. For
|
|
226
226
|
playable SFX, manually pulse $7F with the latch-register byte
|
|
227
227
|
followed by data bytes. Real games ship a music driver in WRAM.
|
|
228
228
|
|
|
@@ -325,7 +325,7 @@ region per bank with a bank-by-bank native rebuild recipe in `BUILD.md`.
|
|
|
325
325
|
|
|
326
326
|
## Horizontal scrolling (for side-scrollers)
|
|
327
327
|
|
|
328
|
-
The `platformer`
|
|
328
|
+
The `platformer` example is single-screen. To make it a side-scroller:
|
|
329
329
|
|
|
330
330
|
- **Hardware scroll:** write VDP register 8 (horizontal scroll) each frame =
|
|
331
331
|
`-camX & 0xFF` (the reg scrolls the screen; the name table is 32×28 and
|
|
@@ -253,7 +253,7 @@ PVSnesLib's `hdr.asm` fills these in.
|
|
|
253
253
|
|
|
254
254
|
## Where the SDK lives (and how to read it)
|
|
255
255
|
|
|
256
|
-
`
|
|
256
|
+
`examples({op:'fork'})` (any SNES example) ships the FULL PVSnesLib source +
|
|
257
257
|
header tree into the new project at `vendor/pvsneslib/`. So when
|
|
258
258
|
your code does `#include <snes.h>`, those headers come from
|
|
259
259
|
`vendor/pvsneslib/include/`:
|
|
@@ -296,7 +296,7 @@ Loadable via snes9x (`loadMedia`).
|
|
|
296
296
|
|
|
297
297
|
## Horizontal scrolling (for side-scrollers)
|
|
298
298
|
|
|
299
|
-
The `platformer`
|
|
299
|
+
The `platformer` example is single-screen. SNES scrolling is the easiest of
|
|
300
300
|
the tile platforms because each BG layer has its own hardware scroll register
|
|
301
301
|
and parallax is nearly free.
|
|
302
302
|
|
|
@@ -160,6 +160,45 @@ Three layers:
|
|
|
160
160
|
PVSnesLib's API is the path of least resistance. Roll your own SPC
|
|
161
161
|
driver only when you really need the control.
|
|
162
162
|
|
|
163
|
+
## "Music never starts (sfx works, sfx_init returned 0)"
|
|
164
|
+
|
|
165
|
+
A command sent to the bundled snes_sfx driver IMMEDIATELY after
|
|
166
|
+
`sfx_init()` returns can be silently swallowed. `sfx_init` returns the
|
|
167
|
+
instant the SPC echoes the jump command, but the driver then spends
|
|
168
|
+
~50 DSP port writes initialising before it seeds its command
|
|
169
|
+
edge-detector from $2140. A `sfx_music_play()` issued inside that
|
|
170
|
+
window becomes the SEED — no edge, no dispatch, music never starts.
|
|
171
|
+
|
|
172
|
+
Symptoms via the debug tools: `getAudioState({chip:'dsp'})` shows
|
|
173
|
+
voice 1 with pitch 0 / env 0; ARAM $00 (prev_cmd) already equals your
|
|
174
|
+
command byte while ARAM $01 (music_on) is 0.
|
|
175
|
+
|
|
176
|
+
Fix: put one `WaitForVBlank()` between `sfx_init()` and the first
|
|
177
|
+
`sfx_play`/`sfx_music_play` — a frame is thousands of SPC cycles, the
|
|
178
|
+
driver is guaranteed to be in its command loop. The racing example does
|
|
179
|
+
exactly this (see its `sfx_init` call site).
|
|
180
|
+
|
|
181
|
+
## "My HDMA table stops landing / OAM gets corrupted" (HDMA channel fights the OAM DMA)
|
|
182
|
+
|
|
183
|
+
A DMA channel cannot serve general-purpose DMA and HDMA in the same
|
|
184
|
+
frame — and PVSnesLib's runtime OWNS two channels for GP-DMA:
|
|
185
|
+
|
|
186
|
+
- **channel 0** — `dmaCopyVram` and friends (console text upload,
|
|
187
|
+
`oamInitGfxSet`, `consoleVblank`)
|
|
188
|
+
- **channel 7** — the VBlank ISR's OAM upload (vblank.asm rewrites
|
|
189
|
+
$4370-$4375 EVERY NMI)
|
|
190
|
+
|
|
191
|
+
Park an HDMA effect on channel 7 and it works for exactly zero frames:
|
|
192
|
+
each NMI silently rewrites the channel's DMAP/BBAD/A1T with OAM-DMA
|
|
193
|
+
parameters, so your per-scanline writes stop landing — and worse, the
|
|
194
|
+
HDMA unit then feeds your table bytes into $2104 (OAM data). The
|
|
195
|
+
failure is maddeningly partial: channels 1-6 keep working, so a
|
|
196
|
+
multi-channel effect (e.g. a Mode 7 split) comes up ALMOST right with
|
|
197
|
+
one register mysteriously stuck at a stale value.
|
|
198
|
+
|
|
199
|
+
Fix: keep HDMA on channels 1-6. The Mode 7 racing example uses 2-6 and
|
|
200
|
+
documents the assignment at its `road_hdma_on()`.
|
|
201
|
+
|
|
163
202
|
## "consoleDrawText output is corrupt / shifted"
|
|
164
203
|
|
|
165
204
|
`consoleInitText(palnum, palsize, tilfont, palfont)` configures the
|
|
@@ -220,7 +259,7 @@ synthesizes a fallback `issues[]` entry with a hint. The idioms to avoid:
|
|
|
220
259
|
crossed a bank boundary and the layout is wrong. Native interrupt vectors live
|
|
221
260
|
at `$FFE4-$FFEE`, emulation vectors at `$FFF4-$FFFF` — keep your header/vector
|
|
222
261
|
block where the layout expects it. Use
|
|
223
|
-
`
|
|
262
|
+
`examples({op:'snippets', platform:"snes", mode:"get", snippetName:"lorom_header.asm"})`
|
|
224
263
|
for the canonical layout (and `lorom_multibank.asm` for multi-bank).
|
|
225
264
|
|
|
226
265
|
(This is the asar/asm path. The default PVSnesLib **C** path goes through
|
|
@@ -19,13 +19,15 @@
|
|
|
19
19
|
# 3. Write CHR data from C at runtime: PPUADDR = 0x00; PPUDATA = byte; etc.
|
|
20
20
|
|
|
21
21
|
SYMBOLS {
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
|
|
22
|
+
# C parameter stack is ONE page ($0600-$06FF, grows down from $0700).
|
|
23
|
+
# NROM-sized C games use far less than 256 B of it (shallow call
|
|
24
|
+
# depth, mostly static data), and shrinking it frees $0500-$05FF as
|
|
25
|
+
# the USER SCRATCH PAGE: game code may place absolute-addressed
|
|
26
|
+
# arrays there (e.g. `#define BOARD ((unsigned char*)0x0500)`) when
|
|
27
|
+
# BSS ($0300-$04FF) is full — the puzzle example game does exactly
|
|
28
|
+
# this. The top page ($0700-$07FF) stays reserved for a music
|
|
29
|
+
# driver's scratch RAM (FamiTone2 et al.).
|
|
30
|
+
__STACKSIZE__: type = weak, value = $0100;
|
|
29
31
|
}
|
|
30
32
|
MEMORY {
|
|
31
33
|
ZP: file = "", start = $0002, size = $001A, type = rw, define = yes;
|
|
@@ -41,7 +43,10 @@ MEMORY {
|
|
|
41
43
|
|
|
42
44
|
# NO ROM2 / CHARS — this is the whole point of the CHR-RAM preset.
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
# $0500-$05FF: user scratch page (see SYMBOLS note) — NOT a segment;
|
|
47
|
+
# game code addresses it absolutely so the linker never places
|
|
48
|
+
# anything here.
|
|
49
|
+
SRAM: file = "", start = $0600, size = __STACKSIZE__, define = yes;
|
|
45
50
|
|
|
46
51
|
# Reserved page for a sound-driver's RAM scratch ($0700-$07FF). The
|
|
47
52
|
# bundled FamiTone2 engine (music_demo scaffold) pins FT_BASE_ADR here
|
|
@@ -28,7 +28,12 @@
|
|
|
28
28
|
.import _main, zerobss, copydata
|
|
29
29
|
.import __RAM_START__, __RAM_SIZE__
|
|
30
30
|
.import __SRAM_START__, __SRAM_SIZE__
|
|
31
|
-
.import
|
|
31
|
+
.import _vram_q_hi, _vram_q_lo, _vram_q_val
|
|
32
|
+
.import _vram_queue_head, _vram_queue_len, _vram_queue_lock
|
|
33
|
+
|
|
34
|
+
; Must match nes_runtime.c (QUEUE_MAX 32 ring buffer).
|
|
35
|
+
QUEUE_MASK = 31
|
|
36
|
+
FLUSH_BUDGET = 16
|
|
32
37
|
.import _scroll_x, _scroll_y, _ppuctrl_value, _nmi_counter
|
|
33
38
|
.importzp c_sp
|
|
34
39
|
|
|
@@ -39,7 +44,12 @@
|
|
|
39
44
|
.byte $4e, $45, $53, $1a ; "NES" + EOF
|
|
40
45
|
.byte 2 ; PRG-ROM banks (16K each) → 32K
|
|
41
46
|
.byte 0 ; CHR-ROM banks (8K each) → 0 = CHR-RAM
|
|
42
|
-
.byte %
|
|
47
|
+
.byte %00000011 ; flags6 — vertical mirroring + BATTERY.
|
|
48
|
+
; The battery bit maps persistent 8KB
|
|
49
|
+
; PRG-RAM at $6000 (the save_ram region)
|
|
50
|
+
; — hiscore_load/save in nes_runtime use
|
|
51
|
+
; it. Benign when unused; without it,
|
|
52
|
+
; $6000-$7FFF is OPEN BUS on NROM.
|
|
43
53
|
.byte %00000000 ; flags7 — mapper hi nybble
|
|
44
54
|
.byte 0, 0, 0, 0, 0, 0, 0, 0
|
|
45
55
|
|
|
@@ -129,9 +139,46 @@ nmi:
|
|
|
129
139
|
lda #$02 ; high byte of $0200
|
|
130
140
|
sta $4014 ; PPU OAMDMA — kicks off the copy
|
|
131
141
|
|
|
132
|
-
;
|
|
133
|
-
;
|
|
134
|
-
|
|
142
|
+
; ── Drain the VRAM queue — IN ASSEMBLY, on purpose ──────────────
|
|
143
|
+
; Vblank is ~2273 CPU cycles and the OAM DMA above just spent 513.
|
|
144
|
+
; Compiled C costs 200+ cycles per queue entry, so a C flush blows
|
|
145
|
+
; past the end of vblank — and PPUDATA writes during ACTIVE
|
|
146
|
+
; RENDERING land at corrupted addresses (the PPU's internal v
|
|
147
|
+
; register is busy fetching tiles; its coarse-X/fine-Y counters
|
|
148
|
+
; shear every late write). This loop costs ~40 cycles per entry,
|
|
149
|
+
; so FLUSH_BUDGET entries always finish safely inside vblank.
|
|
150
|
+
; QUEUE_MASK/FLUSH_BUDGET must match nes_runtime.c's ring buffer.
|
|
151
|
+
lda _vram_queue_lock
|
|
152
|
+
bne @flush_done ; a push is mid-flight — skip this vblank
|
|
153
|
+
lda _vram_queue_len
|
|
154
|
+
beq @flush_done
|
|
155
|
+
cmp #FLUSH_BUDGET
|
|
156
|
+
bcc @flush_n_ok
|
|
157
|
+
lda #FLUSH_BUDGET
|
|
158
|
+
@flush_n_ok:
|
|
159
|
+
sta nmi_drain ; loop counter
|
|
160
|
+
sta nmi_drained ; remembered for the length update
|
|
161
|
+
bit $2002 ; reset the PPUADDR write latch
|
|
162
|
+
ldx _vram_queue_head
|
|
163
|
+
@flush_loop:
|
|
164
|
+
lda _vram_q_hi,x
|
|
165
|
+
sta $2006
|
|
166
|
+
lda _vram_q_lo,x
|
|
167
|
+
sta $2006
|
|
168
|
+
lda _vram_q_val,x
|
|
169
|
+
sta $2007
|
|
170
|
+
inx
|
|
171
|
+
txa
|
|
172
|
+
and #QUEUE_MASK ; ring wrap
|
|
173
|
+
tax
|
|
174
|
+
dec nmi_drain
|
|
175
|
+
bne @flush_loop
|
|
176
|
+
stx _vram_queue_head
|
|
177
|
+
lda _vram_queue_len
|
|
178
|
+
sec
|
|
179
|
+
sbc nmi_drained
|
|
180
|
+
sta _vram_queue_len
|
|
181
|
+
@flush_done:
|
|
135
182
|
|
|
136
183
|
; Reset PPUADDR to $2000 (otherwise the queue's last $2006 write
|
|
137
184
|
; leaves it dangling and the PPU samples random VRAM as the BG).
|
|
@@ -172,6 +219,12 @@ irq: rti
|
|
|
172
219
|
_shadow_oam: .res 256
|
|
173
220
|
|
|
174
221
|
; ------------------------------------------------------------------------
|
|
222
|
+
; NMI-private temporaries — deliberately NOT cc65's zp tmp1-4 (the NMI
|
|
223
|
+
; would corrupt them under interrupted C code).
|
|
224
|
+
.segment "BSS"
|
|
225
|
+
nmi_drain: .res 1
|
|
226
|
+
nmi_drained: .res 1
|
|
227
|
+
|
|
175
228
|
.segment "VECTORS"
|
|
176
229
|
.word nmi ; $FFFA
|
|
177
230
|
.word start ; $FFFC
|
|
@@ -22,7 +22,12 @@
|
|
|
22
22
|
.import _main, zerobss, copydata
|
|
23
23
|
.import __RAM_START__, __RAM_SIZE__
|
|
24
24
|
.import __SRAM_START__, __SRAM_SIZE__
|
|
25
|
-
.import
|
|
25
|
+
.import _vram_q_hi, _vram_q_lo, _vram_q_val
|
|
26
|
+
.import _vram_queue_head, _vram_queue_len, _vram_queue_lock
|
|
27
|
+
|
|
28
|
+
; Must match nes_runtime.c (QUEUE_MAX 32 ring buffer).
|
|
29
|
+
QUEUE_MASK = 31
|
|
30
|
+
FLUSH_BUDGET = 16
|
|
26
31
|
.import _scroll_x, _scroll_y, _ppuctrl_value, _nmi_counter
|
|
27
32
|
.importzp c_sp
|
|
28
33
|
|
|
@@ -109,8 +114,46 @@ nmi:
|
|
|
109
114
|
lda #$02 ; high byte of $0200
|
|
110
115
|
sta $4014 ; PPU OAMDMA — kicks off the copy
|
|
111
116
|
|
|
112
|
-
;
|
|
113
|
-
|
|
117
|
+
; ── Drain the VRAM queue — IN ASSEMBLY, on purpose ──────────────
|
|
118
|
+
; Vblank is ~2273 CPU cycles and the OAM DMA above just spent 513.
|
|
119
|
+
; Compiled C costs 200+ cycles per queue entry, so a C flush blows
|
|
120
|
+
; past the end of vblank — and PPUDATA writes during ACTIVE
|
|
121
|
+
; RENDERING land at corrupted addresses (the PPU's internal v
|
|
122
|
+
; register is busy fetching tiles; its coarse-X/fine-Y counters
|
|
123
|
+
; shear every late write). This loop costs ~40 cycles per entry,
|
|
124
|
+
; so FLUSH_BUDGET entries always finish safely inside vblank.
|
|
125
|
+
; QUEUE_MASK/FLUSH_BUDGET must match nes_runtime.c's ring buffer.
|
|
126
|
+
lda _vram_queue_lock
|
|
127
|
+
bne @flush_done ; a push is mid-flight — skip this vblank
|
|
128
|
+
lda _vram_queue_len
|
|
129
|
+
beq @flush_done
|
|
130
|
+
cmp #FLUSH_BUDGET
|
|
131
|
+
bcc @flush_n_ok
|
|
132
|
+
lda #FLUSH_BUDGET
|
|
133
|
+
@flush_n_ok:
|
|
134
|
+
sta nmi_drain ; loop counter
|
|
135
|
+
sta nmi_drained ; remembered for the length update
|
|
136
|
+
bit $2002 ; reset the PPUADDR write latch
|
|
137
|
+
ldx _vram_queue_head
|
|
138
|
+
@flush_loop:
|
|
139
|
+
lda _vram_q_hi,x
|
|
140
|
+
sta $2006
|
|
141
|
+
lda _vram_q_lo,x
|
|
142
|
+
sta $2006
|
|
143
|
+
lda _vram_q_val,x
|
|
144
|
+
sta $2007
|
|
145
|
+
inx
|
|
146
|
+
txa
|
|
147
|
+
and #QUEUE_MASK ; ring wrap
|
|
148
|
+
tax
|
|
149
|
+
dec nmi_drain
|
|
150
|
+
bne @flush_loop
|
|
151
|
+
stx _vram_queue_head
|
|
152
|
+
lda _vram_queue_len
|
|
153
|
+
sec
|
|
154
|
+
sbc nmi_drained
|
|
155
|
+
sta _vram_queue_len
|
|
156
|
+
@flush_done:
|
|
114
157
|
|
|
115
158
|
; Reset PPUADDR to $2000 so the PPU doesn't sample random VRAM as BG.
|
|
116
159
|
bit $2002
|
|
@@ -147,6 +190,12 @@ irq: rti
|
|
|
147
190
|
_shadow_oam: .res 256
|
|
148
191
|
|
|
149
192
|
; ------------------------------------------------------------------------
|
|
193
|
+
; NMI-private temporaries — deliberately NOT cc65's zp tmp1-4 (the NMI
|
|
194
|
+
; would corrupt them under interrupted C code).
|
|
195
|
+
.segment "BSS"
|
|
196
|
+
nmi_drain: .res 1
|
|
197
|
+
nmi_drained: .res 1
|
|
198
|
+
|
|
150
199
|
.segment "VECTORS"
|
|
151
200
|
.word nmi ; $FFFA
|
|
152
201
|
.word start ; $FFFC
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# PC Engine 32KB HuCard ld65 config (romdev 'rom32k' preset).
|
|
2
|
+
#
|
|
3
|
+
# WHY THIS EXISTS: cc65's stock pce.cfg defaults to an 8KB image, and its
|
|
4
|
+
# documented 16K/32K option ($CARTSIZE) places STARTUP/VECTORS at the END of
|
|
5
|
+
# the file — but a HuCard maps file offset 0 as bank 0, and the HuC6280 reset
|
|
6
|
+
# maps MPR7 (=$E000-$FFFF, where the vectors live) to BANK 0. So a stock-cfg
|
|
7
|
+
# 32K image boots to a black screen (verified on geargrafx). This config puts
|
|
8
|
+
# bank 0 (STARTUP/VECTORS + hot code) FIRST in the file, at $E000, and the
|
|
9
|
+
# remaining 24KB of banks 1-3 at $8000-$DFFF — exactly where cc65's pce crt0
|
|
10
|
+
# TAMs them (MPR4=bank1, MPR5=bank2, MPR6=bank3) before calling main().
|
|
11
|
+
SYMBOLS {
|
|
12
|
+
__CARTSIZE__: type = weak, value = $8000; # crt0 compares >$8000 vs this
|
|
13
|
+
__STACKSIZE__: type = weak, value = $0300; # 3 pages stack
|
|
14
|
+
}
|
|
15
|
+
MEMORY {
|
|
16
|
+
ZP: file = "", start = $0000, define = yes, size = $0100;
|
|
17
|
+
# RAM bank ($F8 at MPR1)
|
|
18
|
+
MAIN: file = "", start = $2200, define = yes, size = $1E00 - __STACKSIZE__;
|
|
19
|
+
# HuCard bank 0 — hardware maps it at $E000 (MPR7) at reset. File offset 0.
|
|
20
|
+
ROM0: file = %O, start = $E000, size = $2000, fill = yes, fillval = $FF;
|
|
21
|
+
# HuCard banks 1-3 — crt0 maps them at $8000/$A000/$C000 (MPR4/5/6).
|
|
22
|
+
ROM: file = %O, start = $8000, size = $6000, fill = yes, fillval = $FF;
|
|
23
|
+
}
|
|
24
|
+
SEGMENTS {
|
|
25
|
+
ZEROPAGE: load = ZP, type = zp;
|
|
26
|
+
EXTZP: load = ZP, type = zp, optional = yes;
|
|
27
|
+
APPZP: load = ZP, type = zp, optional = yes;
|
|
28
|
+
DATA: load = ROM0, run = MAIN, type = rw, define = yes;
|
|
29
|
+
INIT: load = MAIN, type = bss, optional = yes;
|
|
30
|
+
BSS: load = MAIN, type = bss, define = yes;
|
|
31
|
+
LOWCODE: load = ROM0, type = ro, optional = yes;
|
|
32
|
+
ONCE: load = ROM0, type = ro, optional = yes;
|
|
33
|
+
CODE: load = ROM, type = ro;
|
|
34
|
+
RODATA: load = ROM, type = ro;
|
|
35
|
+
STARTUP: load = ROM0, type = ro, start = $FFF6 - $0066;
|
|
36
|
+
VECTORS: load = ROM0, type = ro, start = $FFF6;
|
|
37
|
+
}
|
|
38
|
+
FEATURES {
|
|
39
|
+
CONDES: type = constructor,
|
|
40
|
+
label = __CONSTRUCTOR_TABLE__,
|
|
41
|
+
count = __CONSTRUCTOR_COUNT__,
|
|
42
|
+
segment = ONCE;
|
|
43
|
+
CONDES: type = destructor,
|
|
44
|
+
label = __DESTRUCTOR_TABLE__,
|
|
45
|
+
count = __DESTRUCTOR_COUNT__,
|
|
46
|
+
segment = RODATA;
|
|
47
|
+
CONDES: type = interruptor,
|
|
48
|
+
label = __INTERRUPTOR_TABLE__,
|
|
49
|
+
count = __INTERRUPTOR_COUNT__,
|
|
50
|
+
segment = RODATA,
|
|
51
|
+
import = __CALLIRQ__;
|
|
52
|
+
}
|
package/src/toolchains/index.js
CHANGED
|
@@ -37,7 +37,7 @@ const CC65_TARGET = {
|
|
|
37
37
|
const LANGUAGE_TOOLCHAIN = {
|
|
38
38
|
atari2600: {
|
|
39
39
|
asm: { toolchain: "dasm", available: true },
|
|
40
|
-
basic: { toolchain: "batariBasic", available: false, note: "BASIC for 2600 via batariBasic — not bundled. bB's transpiler is written in Perl, which we don't ship as WASM. A port to C or JS would be a multi-day project. For now, write 2600 games in 6507 asm via dasm — the bundled
|
|
40
|
+
basic: { toolchain: "batariBasic", available: false, note: "BASIC for 2600 via batariBasic — not bundled. bB's transpiler is written in Perl, which we don't ship as WASM. A port to C or JS would be a multi-day project. For now, write 2600 games in 6507 asm via dasm — the bundled example games (default, paddle, single_screen) show the canonical race-the-beam pattern, and an LLM agent writes 2600 asm fluently." },
|
|
41
41
|
},
|
|
42
42
|
nes: {
|
|
43
43
|
asm: { toolchain: "cc65", available: true },
|
|
@@ -65,11 +65,11 @@ const LANGUAGE_TOOLCHAIN = {
|
|
|
65
65
|
},
|
|
66
66
|
snes: {
|
|
67
67
|
asm: { toolchain: "asar", available: true },
|
|
68
|
-
c: { toolchain: "tcc816+wladx", available: true, note: "C for SNES via tcc-65816 + wla-65816 + wlalink. The PVSnesLib runtime IS bundled (built from source) and auto-linked — #include <snes.h> gives you consoleDrawText, setMode, oamSet, WaitForVBlank, etc. out of the box. `
|
|
68
|
+
c: { toolchain: "tcc816+wladx", available: true, note: "C for SNES via tcc-65816 + wla-65816 + wlalink. The PVSnesLib runtime IS bundled (built from source) and auto-linked — #include <snes.h> gives you consoleDrawText, setMode, oamSet, WaitForVBlank, etc. out of the box. `examples({op:'fork'})` gives you a complete working PVSnesLib C project. Pass options.pvsneslib:false for the bare-main minimum-viable path." },
|
|
69
69
|
},
|
|
70
70
|
genesis: {
|
|
71
71
|
asm: { toolchain: "vasm68k", available: true },
|
|
72
|
-
c: { toolchain: "m68k-elf-gcc", available: true, note: "C for Genesis via gcc 14.2.0 + binutils + newlib, all compiled to WASM. The SGDK runtime IS bundled (built from source) and auto-linked — sprite engine, VDP, controller, PSG/Z80 sound, resource helpers all work; #include <genesis.h>. `
|
|
72
|
+
c: { toolchain: "m68k-elf-gcc", available: true, note: "C for Genesis via gcc 14.2.0 + binutils + newlib, all compiled to WASM. The SGDK runtime IS bundled (built from source) and auto-linked — sprite engine, VDP, controller, PSG/Z80 sound, resource helpers all work; #include <genesis.h>. `examples({op:'fork'})` gives you a complete working SGDK C project (the recommended path). Pass options.sgdk:false for the bare-gcc minimum-viable path." },
|
|
73
73
|
},
|
|
74
74
|
gba: {
|
|
75
75
|
c: { toolchain: "arm-none-eabi-gcc", available: true, note: "C for GBA via gcc 14.2.0 + binutils + newlib + libtonc 1.4.5 (default) OR libgba 0.5.4 (opt-in via runtime:\"libgba\"), all compiled to WASM (R24 + R28). #include <tonc.h> + tte_write/tte_printf works out of the box — that's the canonical Tonc-tutorial API every published GBA C resource uses. Caveat: tte_iohook (libtonc) and console.c (libgba) — the libsysbase-backed iprintf bridges — are NOT bundled. Use tte_printf directly, which is what the Tonc tutorial actually does." },
|
|
@@ -91,7 +91,7 @@ const LANGUAGE_TOOLCHAIN = {
|
|
|
91
91
|
* Default language per platform. The choice reflects what's fastest /
|
|
92
92
|
* smallest / best-matched to LLM fluency. Every platform that has a bundled
|
|
93
93
|
* C compiler + runtime defaults to C — that's the canonical, productive path
|
|
94
|
-
* and what `
|
|
94
|
+
* and what `examples({op:'fork'})` projects use (cc65 for NES/C64/Atari7800/
|
|
95
95
|
* Lynx, SDCC for GB/GBC/SMS/GG, gcc+SGDK for Genesis, tcc+PVSnesLib for SNES,
|
|
96
96
|
* gcc+libtonc for GBA). Platforms whose only bundled toolchain is an assembler
|
|
97
97
|
* default to asm (Atari 2600 → dasm; SNES/Genesis keep an asm option too, but
|
|
@@ -743,10 +743,9 @@ export async function buildForPlatform(args) {
|
|
|
743
743
|
// crt0 + headers + sources come straight from the caller. The build
|
|
744
744
|
// pipeline does NOT auto-inject platform runtimes, custom crt0s,
|
|
745
745
|
// or post-link header patches. Every byte that compiles is visible
|
|
746
|
-
// to the caller's repo. Use `
|
|
747
|
-
//
|
|
748
|
-
//
|
|
749
|
-
// to fetch individual pieces.
|
|
746
|
+
// to the caller's repo. Use `examples({op:'fork'})` to get a
|
|
747
|
+
// self-contained project with the runtime files copied in, or
|
|
748
|
+
// `examples({op:'snippets'/'copySnippets'})` to fetch individual pieces.
|
|
750
749
|
const crt0 = args.crt0;
|
|
751
750
|
|
|
752
751
|
// Pre-flight lint: scan the C sources for known SDCC C89 violations
|
|
@@ -787,10 +786,27 @@ export async function buildForPlatform(args) {
|
|
|
787
786
|
// and RAM-size ($0149) bytes — without -m/-r, -v leaves them at the
|
|
788
787
|
// linker's garbage pad (e.g. type $3C), and emulators/hardware reject
|
|
789
788
|
// an unknown MBC type with "retro_load_game failed". -m 0x00 = ROM ONLY
|
|
790
|
-
// (no mapper), -r 0x00 = no cart RAM — correct for
|
|
789
|
+
// (no mapper), -r 0x00 = no cart RAM — correct for plain 32KB builds.
|
|
790
|
+
//
|
|
791
|
+
// Battery-cart passthrough (0.29.0 examples): a crt0 may DECLARE the
|
|
792
|
+
// cart in the header window (the GB equivalent of the NES crt0's iNES
|
|
793
|
+
// BATTERY bit — see the gbc lib gb_crt0.s, which emits $0147=$03 /
|
|
794
|
+
// $0149=$02 for MBC1+RAM+BATTERY so hi-scores persist in SAVE_RAM).
|
|
795
|
+
// If the linked image carries a KNOWN battery-MBC type byte with a
|
|
796
|
+
// sane RAM size, pass those through to rgbfix instead of stomping
|
|
797
|
+
// them to ROM-only; anything unrecognized (linker pad garbage) still
|
|
798
|
+
// falls back to the safe ROM-only default, so crt0s that don't
|
|
799
|
+
// declare a cart behave exactly as before.
|
|
800
|
+
const BATTERY_CART_TYPES = new Set([0x03, 0x06, 0x0F, 0x10, 0x13, 0x1B, 0x1E]); // MBC1/2/3/5 +BATTERY variants
|
|
801
|
+
const declType = binary.length > 0x149 ? binary[0x147] : 0x00;
|
|
802
|
+
const declRam = binary.length > 0x149 ? binary[0x149] : 0x00;
|
|
803
|
+
const cartByte = BATTERY_CART_TYPES.has(declType) ? declType : 0x00;
|
|
804
|
+
const ramByte = cartByte !== 0x00 && declRam >= 0x01 && declRam <= 0x05 ? declRam : 0x00;
|
|
805
|
+
const mArg = "0x" + cartByte.toString(16).padStart(2, "0").toUpperCase();
|
|
806
|
+
const rArg = "0x" + ramByte.toString(16).padStart(2, "0").toUpperCase();
|
|
791
807
|
const fixOpts = args.platform === "gbc"
|
|
792
|
-
? ["-v", "-p", "0xFF", "-C", "-m",
|
|
793
|
-
: ["-v", "-p", "0xFF", "-m",
|
|
808
|
+
? ["-v", "-p", "0xFF", "-C", "-m", mArg, "-r", rArg]
|
|
809
|
+
: ["-v", "-p", "0xFF", "-m", mArg, "-r", rArg];
|
|
794
810
|
const fix = await runRgbfix({ rom: binary, options: fixOpts });
|
|
795
811
|
if (fix.exitCode === 0 && fix.binary) {
|
|
796
812
|
binary = fix.binary;
|