romdevtools 0.22.1 → 0.24.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 +169 -494
- package/CHANGELOG.md +103 -0
- package/examples/genesis/templates/platformer.c +5 -1
- package/examples/genesis/templates/two_plane_parallax.c +166 -0
- package/package.json +2 -2
- 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 +225 -2
- package/src/host/framebuffer.js +37 -0
- package/src/http/skill-doc.js +1 -1
- package/src/mcp/tools/audio.js +2 -2
- package/src/mcp/tools/frame.js +13 -34
- package/src/mcp/tools/index.js +2 -2
- package/src/mcp/tools/input-layout.js +10 -0
- package/src/mcp/tools/input.js +26 -2
- package/src/mcp/tools/metasprite-tools.js +1 -1
- package/src/mcp/tools/platform-tools.js +18 -11
- package/src/mcp/tools/playtest.js +17 -2
- package/src/mcp/tools/project.js +9 -1
- package/src/mcp/tools/rendering-context.js +1 -1
- package/src/mcp/tools/symbols.js +130 -39
- package/src/mcp/tools/tile-inspect.js +1 -1
- package/src/mcp/tools/toolchain.js +3 -2
- package/src/mcp/tools/watch-memory.js +58 -6
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +155 -6
- package/src/platforms/atari2600/MENTAL_MODEL.md +37 -0
- package/src/platforms/atari7800/MENTAL_MODEL.md +36 -0
- package/src/platforms/c64/MENTAL_MODEL.md +83 -6
- package/src/platforms/gb/MENTAL_MODEL.md +74 -0
- package/src/platforms/gb/lib/c/SDCC_GOTCHAS.md +91 -0
- package/src/platforms/gba/MENTAL_MODEL.md +57 -3
- package/src/platforms/gba/lib/arm-archives/libc.a +0 -0
- package/src/platforms/gba/lib/arm-archives/libgcc.a +0 -0
- package/src/platforms/gba/lib/arm-archives/libnosys.a +0 -0
- package/src/platforms/gbc/MENTAL_MODEL.md +34 -0
- package/src/platforms/gbc/lib/c/SDCC_GOTCHAS.md +91 -0
- package/src/platforms/genesis/MENTAL_MODEL.md +180 -0
- package/src/platforms/genesis/TROUBLESHOOTING.md +32 -0
- package/src/platforms/genesis/lib/c/libc.a +0 -0
- package/src/platforms/genesis/lib/c/libgcc.a +0 -0
- package/src/platforms/genesis/lib/c/libm.a +0 -0
- package/src/platforms/gg/MENTAL_MODEL.md +24 -0
- package/src/platforms/gg/lib/c/gg_crt0.s +30 -0
- package/src/platforms/lynx/MENTAL_MODEL.md +33 -7
- package/src/platforms/msx/MENTAL_MODEL.md +27 -0
- package/src/platforms/nes/MENTAL_MODEL.md +35 -0
- package/src/platforms/sms/MENTAL_MODEL.md +51 -0
- package/src/platforms/sms/lib/c/sms_crt0.s +40 -0
- package/src/platforms/snes/MENTAL_MODEL.md +21 -0
- package/src/platforms/snes/TROUBLESHOOTING.md +43 -0
- package/src/playtest/playtest.js +48 -0
- package/src/toolchains/sdcc/preflight-lint.js +164 -8
- package/examples/msx/catch_game/_verify.mjs +0 -93
- package/examples/pce/catch_game/_verify.mjs +0 -75
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,109 @@ All notable changes to `romdevtools`. Dates are release dates.
|
|
|
4
4
|
(Published as `romdev-mcp` through 0.11.0; renamed to `romdevtools` in 0.13.0 —
|
|
5
5
|
the `romdev-mcp` bin is kept as an alias.)
|
|
6
6
|
|
|
7
|
+
## 0.24.0
|
|
8
|
+
|
|
9
|
+
### Added — C64 keyboard + joyport input (VICE core patch)
|
|
10
|
+
An agent RE'ing C64 Uridium could reach the intro via joystick but couldn't ENTER
|
|
11
|
+
gameplay — the game needs **F1** (1 player) + fire on **port 2**, and romdev's
|
|
12
|
+
input was joypad-mask-only. Many C64 games gate gameplay behind KEYBOARD setup
|
|
13
|
+
screens that joystick can't reach. The VICE core now exports
|
|
14
|
+
`romdev_key_matrix`/`romdev_kbdbuf_feed`/`romdev_joyport_*`, surfaced as:
|
|
15
|
+
- **`input({op:'pressKey', key})`** — press a C64 keyboard key (F1/F3/F5/F7,
|
|
16
|
+
Return, Space, Run/Stop, a–z, 0–9, …) by driving the C64 8×8 key matrix.
|
|
17
|
+
- **`input({op:'typeText', text})`** — feed a string into the kernal keyboard
|
|
18
|
+
buffer (LOAD/RUN/filenames); `\r` → RETURN.
|
|
19
|
+
- **`input({op:'joyport', joyport?})`** — read/set the active C64 joystick port
|
|
20
|
+
(1 or 2; default 2, most games).
|
|
21
|
+
- `input({op:'layout', platform:'c64'})` now reports the keyboard keys + joyport.
|
|
22
|
+
|
|
23
|
+
**A controller alone plays C64** (the Batocera/RetroDeck model — no physical
|
|
24
|
+
keyboard needed). Spare pad buttons/stick map to the C64 keyboard keys games need
|
|
25
|
+
to start: X/Space=Space, L2=Run/Stop, R2=Return, top face / right-stick =
|
|
26
|
+
F1/F3/F5/F7; d-pad + Fire stay the joystick. Unified in the host so it works the
|
|
27
|
+
same in the playtest window AND for the agent's headless `setInput`. No-controller
|
|
28
|
+
keyboard fallback maps PC F1–F4/Space/Enter to the same C64 keys.
|
|
29
|
+
`playtest({op:'open'})` on a C64 game relays the controls. (romdev-core-vice 0.7.0.)
|
|
30
|
+
|
|
31
|
+
### Changed — leaner, less-confusing agent docs (AGENTS.md / SKILL.md)
|
|
32
|
+
AGENTS.md was loaded in full every session for every platform — ~30k tokens, of
|
|
33
|
+
which ~13k was platform-specific or duplicated detail an agent on one platform
|
|
34
|
+
never needed (and could misapply across platforms). Cut **~31% (~9.5k tokens)**:
|
|
35
|
+
- Per-platform debug-tooling detail → each platform's `MENTAL_MODEL.md`.
|
|
36
|
+
- The ROM-hacking workflow → folded into the `ROMHACKING_PLAYBOOK.md` guide.
|
|
37
|
+
- Toolchain landmines → per-platform `TROUBLESHOOTING.md`/`SDCC_GOTCHAS.md`.
|
|
38
|
+
- Disassembler flag reference → it's already in the disasm tool's own schema.
|
|
39
|
+
All reachable on demand via `platform({op:'doc'})`; AGENTS.md keeps generic
|
|
40
|
+
guidance + symptom→doc pointers. **"Read your target platform's
|
|
41
|
+
`platform({op:'doc', name:'mental_model'})` BEFORE you write code for it"** is now
|
|
42
|
+
a top-level rule (the footguns live there) — so the on-demand docs actually get
|
|
43
|
+
read. The dynamic SKILL.md inherits all of this.
|
|
44
|
+
|
|
45
|
+
## 0.23.0
|
|
46
|
+
|
|
47
|
+
Response to real build-session feedback. Theme: bugs found, false alarms
|
|
48
|
+
removed, and — the recurring finding — **tools that already existed but agents
|
|
49
|
+
couldn't find them**.
|
|
50
|
+
|
|
51
|
+
### Fixed — multi-tenant host cross-talk (a whole class of bugs)
|
|
52
|
+
`sprites({op:'inspect', platform:'genesis'})` returned a *GBC*-flavored error while
|
|
53
|
+
Genesis was loaded. Root cause: `inspectSpritesCore` / `getCPUStateCore` /
|
|
54
|
+
`getAudioStateCore` / `inspectBackgroundMapCore` / `inspectPatternTilesCore` were
|
|
55
|
+
module-global `export let` bindings reassigned per session, each closing over THAT
|
|
56
|
+
registration's `sessionKey` — so the last session to register stole the host for
|
|
57
|
+
everyone. Now the caller's `sessionKey` is threaded through. Verified on all 7
|
|
58
|
+
tile-based sprite platforms with sessions live simultaneously.
|
|
59
|
+
|
|
60
|
+
### Fixed — SMS/GG C crt0: `static x = N;` initializers booted to 0
|
|
61
|
+
Investigating two reported "silent sm83 miscompiles" (32-bit xorshift, indexed
|
|
62
|
+
loop): NEITHER reproduces on GB/GBC — both produce byte-exact output. The real bug
|
|
63
|
+
was a genuine **z80 crt0 defect**: `sms_crt0.s`/`gg_crt0.s` placed `_INITIALIZER`
|
|
64
|
+
in RAM not ROM, so the gsinit `ldir` copied RAM onto itself → every value-static
|
|
65
|
+
booted 0 and BSS wasn't zeroed. Fixed both (mirrors the correct `gb_crt0.s`).
|
|
66
|
+
Re-verified all SMS/GG scaffolds still build clean + render.
|
|
67
|
+
|
|
68
|
+
### Changed — lint stops crying wolf on WRAM copies
|
|
69
|
+
The SDCC `xdata-copy-miscompile` warning fired on EVERY `dst[i]=src[i]` loop,
|
|
70
|
+
including plain WRAM arrays (the message even said "ignore if plain WRAM") —
|
|
71
|
+
training agents to ignore lint. Now: provably-VRAM dest → warning, plain RAM array
|
|
72
|
+
→ suppressed, unknown → info. (Deliberately did NOT add the requested 32-bit-shift
|
|
73
|
+
/ short-loop lint heuristics — they'd be false positives for non-bugs.)
|
|
74
|
+
|
|
75
|
+
### Added — feedback ergonomics
|
|
76
|
+
- **`frame({op:'screenshot', scale})` up-scales** (integer ≥2, nearest-neighbor)
|
|
77
|
+
for legible handheld shots (GB 160×144 → 640×576 at `scale:4`), plus the
|
|
78
|
+
existing `0<scale<1` downscale. All platforms.
|
|
79
|
+
- **Cheap symbol→address:** `symbols({op:'resolve', dbgPath|mapPath, name})` reads
|
|
80
|
+
the map FILE on disk (no 63 KB round-trip through context); `build({output:
|
|
81
|
+
'romWithDebug', resolveSymbols:[...]})` folds just those addresses into the
|
|
82
|
+
result. cc65 `.dbg` / sdld `.map` / GNU ld `.map`.
|
|
83
|
+
|
|
84
|
+
### Added — Genesis feel/perf diagnostics
|
|
85
|
+
- **`watch({on:'dma', perFrame:true})`** — per-frame DMA-bytes timeline + spike
|
|
86
|
+
detection (catches "rewriting tilemaps in the frame loop", the #1 Genesis feel
|
|
87
|
+
bug). A hardware-scroll scaffold settles to a flat ~8 bytes/frame.
|
|
88
|
+
- **`scaffold` template `two_plane_parallax`** — plane-A foreground + plane-B
|
|
89
|
+
repeated starfield + player sprite, hardware scroll only, zero loop-time tilemap
|
|
90
|
+
writes. Builds clean, renders, scrolls (verified).
|
|
91
|
+
- Genesis MENTAL_MODEL/TROUBLESHOOTING: "do NOT rewrite tilemaps in the frame
|
|
92
|
+
loop", logical-vs-hardware plane size, the correct parallax loop, Sonic-style
|
|
93
|
+
column streaming, and a "why does movement feel choppy?" recipe.
|
|
94
|
+
|
|
95
|
+
### Changed — discoverability (the recurring root cause)
|
|
96
|
+
Several feedback "feature asks" were tools that already existed. `recordSession`
|
|
97
|
+
(motion/telemetry timeline) was mislabeled in the catalog as "capture inputs for
|
|
98
|
+
replay"; relabeled. New AGENTS.md "Diagnosing behavior over time (game-feel)"
|
|
99
|
+
section maps symptom → existing tool (choppy movement → `recordSession`/`watch`
|
|
100
|
+
series on scroll+sprite; wrong-but-clean value → `resolveSymbols` + `memory` read;
|
|
101
|
+
can't-read-the-BG → `background({view:'map'})` + `screenshot scale:4`).
|
|
102
|
+
|
|
103
|
+
### Other
|
|
104
|
+
- `build({output:'project', path})` already defaults the gb/gbc/sms/gg/msx crt0 +
|
|
105
|
+
codeLoc — documented (the GBC agent was hand-passing them to `output:'rom'`).
|
|
106
|
+
- P4 doc + a new info-level `wram-static-overlap` lint advisory (hardcoded
|
|
107
|
+
`$C000–$C0FF` pointer overlapping SDCC's static-data segment — the actual cause
|
|
108
|
+
of the reporter's "monochrome RNG").
|
|
109
|
+
|
|
7
110
|
## 0.22.1
|
|
8
111
|
|
|
9
112
|
Doc-only follow-up to 0.22.0's movement-analysis feedback: the `pressDuring`
|
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
* level fits in the scroll plane and plain VDP_setHorizontalScroll moves
|
|
14
14
|
* it — a real working scroller with no streaming. For a world WIDER than
|
|
15
15
|
* 512 px you additionally stream the column entering view each time camX
|
|
16
|
-
* crosses an 8-px boundary (see MENTAL_MODEL.md "
|
|
16
|
+
* crosses an 8-px boundary (see MENTAL_MODEL.md "How Sonic-style large
|
|
17
|
+
* maps REALLY work"). For a from-scratch smooth-scroll + parallax
|
|
18
|
+
* starting point with NO per-frame tilemap writes, see the
|
|
19
|
+
* two_plane_parallax template + MENTAL_MODEL.md "Scrolling, parallax &
|
|
20
|
+
* the feel trap".
|
|
17
21
|
*/
|
|
18
22
|
|
|
19
23
|
#include <genesis.h>
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/* ── two_plane_parallax.c — Genesis SGDK two-plane parallax scaffold ──
|
|
2
|
+
*
|
|
3
|
+
* The "Uridium / Sonic-feel" starting point: a side-scrolling world that
|
|
4
|
+
* moves SMOOTHLY because the frame loop does HARDWARE SCROLL ONLY. There
|
|
5
|
+
* are ZERO tilemap writes inside the loop — the two planes are painted
|
|
6
|
+
* ONCE at setup, and every frame we just nudge two scroll registers and
|
|
7
|
+
* re-stage one sprite. This is the single most important thing to copy:
|
|
8
|
+
*
|
|
9
|
+
* ★ Do NOT rewrite tilemaps in the frame loop. ★
|
|
10
|
+
*
|
|
11
|
+
* Rewriting a plane each frame is a big VDP/DMA burst that overruns
|
|
12
|
+
* vblank and makes movement feel choppy/juddery. Hardware scroll is free.
|
|
13
|
+
*
|
|
14
|
+
* Layout:
|
|
15
|
+
* - Plane A (foreground): a painted "world" — a ground strip + scattered
|
|
16
|
+
* platform blocks across a 512-px (64-cell) plane. Scrolls 1:1 with
|
|
17
|
+
* the camera.
|
|
18
|
+
* - Plane B (background): a repeated starfield filling the whole plane.
|
|
19
|
+
* Scrolls at 1/4 the camera speed for parallax depth (far things
|
|
20
|
+
* move slower). Because Plane B is filled edge-to-edge and the VDP
|
|
21
|
+
* scroll wraps within the plane, it tiles forever with no redraw.
|
|
22
|
+
* - One hardware sprite = the player, drawn in SCREEN space.
|
|
23
|
+
*
|
|
24
|
+
* Drive it: D-pad LEFT/RIGHT scrolls the camera (the player walks within
|
|
25
|
+
* the world). The camera clamps to the world edges.
|
|
26
|
+
*
|
|
27
|
+
* IMPORTANT (logical vs hardware plane size): the Genesis has ONE shared
|
|
28
|
+
* plane-size setting for BOTH planes. We use the default 64x32 cells
|
|
29
|
+
* (512x256 px). A "32-wide level" still lives inside a 64-wide PHYSICAL
|
|
30
|
+
* plane — you don't get an independent per-plane size. See the Genesis
|
|
31
|
+
* MENTAL_MODEL.md "Scrolling, parallax & the feel trap".
|
|
32
|
+
*
|
|
33
|
+
* To go WIDER than 512 px (a true Sonic-size level) you keep this exact
|
|
34
|
+
* loop and add ONE thing: stream the single offscreen column that's about
|
|
35
|
+
* to scroll into view each time the camera crosses an 8-px tile boundary
|
|
36
|
+
* (a circular buffer in the 64-cell plane) — NOT a whole-plane redraw.
|
|
37
|
+
* See MENTAL_MODEL.md "How Sonic-style large maps really work".
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
#include <genesis.h>
|
|
41
|
+
|
|
42
|
+
/* ── Tiles (4bpp, 8 rows x 4 bytes). Indices live at/above TILE_USER_INDEX
|
|
43
|
+
* so they don't clobber SGDK's font + system tiles below it. ── */
|
|
44
|
+
#define T_BLANK (TILE_USER_INDEX + 0)
|
|
45
|
+
#define T_GROUND (TILE_USER_INDEX + 1)
|
|
46
|
+
#define T_BLOCK (TILE_USER_INDEX + 2)
|
|
47
|
+
#define T_PLAYER (TILE_USER_INDEX + 3)
|
|
48
|
+
#define T_STAR (TILE_USER_INDEX + 4)
|
|
49
|
+
|
|
50
|
+
static const u32 tile_blank[8] = { 0,0,0,0,0,0,0,0 };
|
|
51
|
+
/* Solid ground: lit top edge, darker body. */
|
|
52
|
+
static const u32 tile_ground[8] = {
|
|
53
|
+
0x11111111, 0x12222221, 0x12222221, 0x12222221,
|
|
54
|
+
0x12222221, 0x12222221, 0x12222221, 0x12222221,
|
|
55
|
+
};
|
|
56
|
+
/* A floating platform block. */
|
|
57
|
+
static const u32 tile_block[8] = {
|
|
58
|
+
0x33333333, 0x34444443, 0x34444443, 0x34444443,
|
|
59
|
+
0x34444443, 0x34444443, 0x34444443, 0x33333333,
|
|
60
|
+
};
|
|
61
|
+
/* Player: a chunky diamond so it reads on screen. */
|
|
62
|
+
static const u32 tile_player[8] = {
|
|
63
|
+
0x00055000, 0x00555500, 0x05555550, 0x55555555,
|
|
64
|
+
0x55555555, 0x05555550, 0x00555500, 0x00055000,
|
|
65
|
+
};
|
|
66
|
+
/* A single off-center star pixel on a dark field (background twinkle). */
|
|
67
|
+
static const u32 tile_star[8] = {
|
|
68
|
+
0x00000000, 0x00060000, 0x00000000, 0x00000600,
|
|
69
|
+
0x00000000, 0x06000000, 0x00000000, 0x00000060,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
#define PLANE_W_CELLS 64 /* default plane is 64x32 cells */
|
|
73
|
+
#define WORLD_W 512 /* = 64 cells * 8 px (one plane wide) */
|
|
74
|
+
#define SCREEN_W 320 /* H40 */
|
|
75
|
+
#define GROUND_Y 192 /* px — top of the ground strip */
|
|
76
|
+
|
|
77
|
+
/* Static foreground platforms in WORLD PIXEL coords. Painted ONCE. */
|
|
78
|
+
typedef struct { s16 x, y, wcells; } Block;
|
|
79
|
+
static const Block blocks[] = {
|
|
80
|
+
{ 48, 152, 5 }, { 160, 128, 4 }, { 256, 160, 6 },
|
|
81
|
+
{ 352, 112, 4 }, { 432, 144, 5 },
|
|
82
|
+
};
|
|
83
|
+
#define N_BLOCKS (sizeof(blocks) / sizeof(blocks[0]))
|
|
84
|
+
|
|
85
|
+
/* Player WORLD x exposed for headless motion-trace inspection:
|
|
86
|
+
* symbols({op:'resolve', name:'g_player_x'}) → read it over frames while
|
|
87
|
+
* holding RIGHT and compare against the camera scroll (see MENTAL_MODEL
|
|
88
|
+
* "Why does horizontal movement feel choppy?"). `volatile` keeps it from
|
|
89
|
+
* being optimised into a register so the read resolves. */
|
|
90
|
+
volatile s16 g_player_x = 32; /* world px */
|
|
91
|
+
volatile s16 g_cam_x = 0; /* camera scroll (world px) */
|
|
92
|
+
|
|
93
|
+
int main(bool hard) {
|
|
94
|
+
(void)hard;
|
|
95
|
+
|
|
96
|
+
/* Palettes (BGR, 3 bits/chan). PAL0 = sprite, PAL1 = planes. */
|
|
97
|
+
PAL_setColor(0 + 5, 0x008E); /* player orange (Uridium-ish) */
|
|
98
|
+
PAL_setColor(16 + 1, 0x0A86); /* ground top (light) */
|
|
99
|
+
PAL_setColor(16 + 2, 0x0530); /* ground body (dark) */
|
|
100
|
+
PAL_setColor(16 + 3, 0x0CCC); /* block edge (white) */
|
|
101
|
+
PAL_setColor(16 + 4, 0x0844); /* block body (steel) */
|
|
102
|
+
PAL_setColor(16 + 6, 0x0EEE); /* star (white) */
|
|
103
|
+
|
|
104
|
+
/* Upload all tiles once. */
|
|
105
|
+
VDP_loadTileData(tile_blank, T_BLANK, 1, DMA);
|
|
106
|
+
VDP_loadTileData(tile_ground, T_GROUND, 1, DMA);
|
|
107
|
+
VDP_loadTileData(tile_block, T_BLOCK, 1, DMA);
|
|
108
|
+
VDP_loadTileData(tile_player, T_PLAYER, 1, DMA);
|
|
109
|
+
VDP_loadTileData(tile_star, T_STAR, 1, DMA);
|
|
110
|
+
|
|
111
|
+
/* ── PAINT PLANE B (starfield) ONCE — fills the whole 64x32 plane so
|
|
112
|
+
* it tiles forever as the scroll wraps. NEVER touched again. ── */
|
|
113
|
+
VDP_fillTileMapRect(BG_B, TILE_ATTR_FULL(PAL1, 0, 0, 0, T_STAR),
|
|
114
|
+
0, 0, PLANE_W_CELLS, 32);
|
|
115
|
+
|
|
116
|
+
/* ── PAINT PLANE A (foreground world) ONCE. ──
|
|
117
|
+
* Ground strip across the whole world width, then the platform blocks
|
|
118
|
+
* on top. After this, the loop does NOT write the tilemap again. */
|
|
119
|
+
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL1, 0, 0, 0, T_GROUND),
|
|
120
|
+
0, GROUND_Y >> 3, PLANE_W_CELLS, (256 - GROUND_Y) >> 3);
|
|
121
|
+
for (u16 i = 0; i < N_BLOCKS; i++) {
|
|
122
|
+
const Block* b = &blocks[i];
|
|
123
|
+
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL1, 0, 0, 0, T_BLOCK),
|
|
124
|
+
b->x >> 3, b->y >> 3, b->wcells, 1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
VDP_drawText("PARALLAX D-PAD = SCROLL", 7, 1);
|
|
128
|
+
|
|
129
|
+
const s16 PLAYER_SPEED = 2;
|
|
130
|
+
s16 player_screen_x = SCREEN_W / 2 - 4; /* player stays mid-screen */
|
|
131
|
+
|
|
132
|
+
while (TRUE) {
|
|
133
|
+
u16 pad = JOY_readJoypad(JOY_1);
|
|
134
|
+
|
|
135
|
+
/* Move the player through the WORLD. */
|
|
136
|
+
if (pad & BUTTON_LEFT) g_player_x -= PLAYER_SPEED;
|
|
137
|
+
if (pad & BUTTON_RIGHT) g_player_x += PLAYER_SPEED;
|
|
138
|
+
if (g_player_x < 0) g_player_x = 0;
|
|
139
|
+
if (g_player_x > WORLD_W - 8) g_player_x = WORLD_W - 8;
|
|
140
|
+
|
|
141
|
+
/* Camera centers on the player, clamped to the world edges. */
|
|
142
|
+
s16 cam = g_player_x - (SCREEN_W / 2 - 4);
|
|
143
|
+
if (cam < 0) cam = 0;
|
|
144
|
+
if (cam > WORLD_W - SCREEN_W) cam = WORLD_W - SCREEN_W;
|
|
145
|
+
g_cam_x = cam;
|
|
146
|
+
|
|
147
|
+
/* When the camera is hard against an edge, let the player walk to
|
|
148
|
+
* the screen edge instead of staying pinned to the centre. */
|
|
149
|
+
player_screen_x = g_player_x - cam;
|
|
150
|
+
|
|
151
|
+
/* ★ THE ENTIRE PER-FRAME RENDER COST: two scroll-register writes.
|
|
152
|
+
* Positive cam scrolls the plane LEFT, so we write the negative.
|
|
153
|
+
* Plane A: 1:1 with the world. Plane B: 1/4 speed = parallax. */
|
|
154
|
+
VDP_setHorizontalScroll(BG_A, -cam);
|
|
155
|
+
VDP_setHorizontalScroll(BG_B, -(cam >> 2));
|
|
156
|
+
|
|
157
|
+
/* Re-stage the one player sprite (screen space) + flush the SAT.
|
|
158
|
+
* SPRITE_SIZE(1,1) = 8x8. */
|
|
159
|
+
VDP_setSprite(0, player_screen_x, GROUND_Y - 8, SPRITE_SIZE(1, 1),
|
|
160
|
+
TILE_ATTR_FULL(PAL0, 1, 0, 0, T_PLAYER));
|
|
161
|
+
VDP_updateSprites(1, DMA);
|
|
162
|
+
|
|
163
|
+
SYS_doVBlankProcess();
|
|
164
|
+
}
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "romdevtools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Tool server giving coding agents full control of homebrew ROM development AND reverse-engineering/romhacking across 14 retro platforms (NES, SNES, GB, Genesis, Atari, C64, PC Engine, MSX, ...) via WASM toolchains + emulator cores. Use over plain HTTP, as an Agent Skill, or as an MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/mcp/server.js",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"romdev-core-gpgx": "0.10.0",
|
|
58
58
|
"romdev-core-handy": "0.5.0",
|
|
59
59
|
"romdev-core-prosystem": "0.6.0",
|
|
60
|
-
"romdev-core-vice": "0.
|
|
60
|
+
"romdev-core-vice": "0.7.0",
|
|
61
61
|
"romdev-famitone": "0.1.0",
|
|
62
62
|
"romdev-maxmod": "0.1.0",
|
|
63
63
|
"romdev-platform-atari2600": "0.6.0",
|