romdevtools 0.14.0 → 0.16.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 +18 -11
- package/CHANGELOG.md +94 -0
- package/README.md +1 -1
- 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/http/skill-doc.js +1 -1
- package/src/http/tool-registry.js +1 -1
- package/src/mcp/tools/index.js +4 -4
- package/src/mcp/tools/project.js +33 -22
- package/src/mcp/tools/toolchain.js +196 -20
- 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/cc65.js +8 -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/gba-c/gba-c.js +6 -1
- package/src/toolchains/genesis-c/README.md +1 -1
- package/src/toolchains/genesis-c/genesis-c.js +10 -2
- package/src/toolchains/parse-errors.js +67 -5
- package/src/toolchains/sdcc/preflight-lint.js +47 -7
- package/src/toolchains/snes-c/snes-c.js +3 -7
package/AGENTS.md
CHANGED
|
@@ -43,7 +43,7 @@ Skip playtest only when there's clearly no human in the loop: CI runs, automated
|
|
|
43
43
|
|
|
44
44
|
## Tool surface: everything is loaded — just call the tool
|
|
45
45
|
|
|
46
|
-
**All ~
|
|
46
|
+
**All ~32 tools are registered and callable from session init — there is no loading step.** If you see a tool name anywhere in this doc or via `catalog({op:'categories'})`, you can call it right now. Each tool is a small VERB with an operation axis — `memory({op})`, `build({output})`, `sprites({op})`, `breakpoint({on})`, `cpu({op})` — so the whole surface is a few dozen names, not a few hundred.
|
|
47
47
|
|
|
48
48
|
(We used to lazy-load tools behind a `loadCategory` call. It caused more harm than good — agents burned round-trips re-loading categories, and dynamic registration never propagated reliably to clients anyway. The consolidation shrank the surface enough that the entire thing loads up front; the old `loadCategory`/`describeTool` discovery tools are gone.)
|
|
49
49
|
|
|
@@ -242,14 +242,21 @@ your project dir, it lands at `./read_joystick.asm` (alongside
|
|
|
242
242
|
`main.asm`), NOT under `./include/` or `./lib/`. Every platform
|
|
243
243
|
follows the same flat layout.
|
|
244
244
|
|
|
245
|
-
Because the layout is flat,
|
|
246
|
-
|
|
247
|
-
(
|
|
248
|
-
|
|
249
|
-
(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
245
|
+
Because the layout is flat, **the simplest loop is `build({output:'run', path, platform})`
|
|
246
|
+
(build + load + run + screenshot in one call) or `build({output:'project', path, platform})`
|
|
247
|
+
(build the dir to a ROM) — no per-iteration file manifest, on EVERY platform.** Point it at
|
|
248
|
+
a scaffolded directory and it does the right per-platform thing automatically: finds the
|
|
249
|
+
entry (`main.c` for C / SGDK Genesis / GBA / cc65-C / SDCC-C, or `main.s` / `main.asm` for
|
|
250
|
+
asm), routes the platform's crt0 correctly (e.g. GB/GBC `gb_crt0.s` via the cart-header path,
|
|
251
|
+
not as a plain source — so no `gsinit` collision), applies the right linker preset
|
|
252
|
+
(e.g. NES `chr-ram-runtime`, which supplies the OAM/CHARS segments), skips SDK intermediates
|
|
253
|
+
(e.g. Genesis `sega.preprocessed.s`, the SNES SPC700 driver, any `*.upstream.*`), wires the
|
|
254
|
+
runtime (GBA libtonc/libgba/maxmod by what the source includes), routes `#include`d C/asm
|
|
255
|
+
siblings as includes, treats `.h`/`.inc` as includes, and folds binary assets
|
|
256
|
+
(`.bin/.chr/.pcm/.brr/.vgm/.xgc/...`) in as `binaryIncludes`. So iterating an on-disk project
|
|
257
|
+
is just `build({output:'run', path:'/my/proj', platform})` every time — you do **not** need to
|
|
258
|
+
enumerate `sources`/`includes`/`crt0Path`/`linkerConfig` by hand. (Use `build({output:'rom'})`
|
|
259
|
+
with explicit `sources` only when the files aren't on disk, e.g. generated in-context.)
|
|
253
260
|
|
|
254
261
|
## Supported platforms
|
|
255
262
|
|
|
@@ -881,7 +888,7 @@ OAM format: bytes per sprite are `[y, tileIndex, attributes, x]`.
|
|
|
881
888
|
|
|
882
889
|
Three shapes, pick the one that matches what you're doing:
|
|
883
890
|
|
|
884
|
-
- **`scaffold({op:'project', platform, name, path, template?})`** — writes a starter directory: `main.{c,asm,s}` (from `examples/<platform>/templates/`) + every runtime file the template depends on (headers, crt0, linker .cfg) + README + `.gitignore`. Self-contained: take it elsewhere and rebuild with stock cc65/sdcc, no romdev install needed. Defaults to `template:"default"` (smallest visible-and-runnable program); most tier-1 platforms also have `hello_sprite` + `tile_engine` + the 5 genre templates.
|
|
891
|
+
- **`scaffold({op:'project', platform, name, path, template?})`** — writes a starter directory: `main.{c,asm,s}` (from `examples/<platform>/templates/`) + every runtime file the template depends on (headers, crt0, linker .cfg) + README + `.gitignore`. Self-contained: take it elsewhere and rebuild with stock cc65/sdcc, no romdev install needed. Defaults to `template:"default"` (smallest visible-and-runnable program); most tier-1 platforms also have `hello_sprite` + `tile_engine` + the 5 genre templates. **Then build it in ONE call: `build({output:'run', path:<that dir>, platform})`** — the dir build applies the platform's recipe (crt0/linker-preset/runtime/intermediate-skip) automatically, so you never hand-wire `crt0Path`/`codeLoc`/`linkerConfig`. This scaffold→build path is verified to build + render on every platform/template.
|
|
885
892
|
|
|
886
893
|
- **`scaffold({op:'project', ..., withSnippets: true})`** — same as above, **plus** drops every vetted starter snippet for the platform alongside main.c. Use when you want "main.c + every helper file ready to edit" in one shot, without picking a genre. Snippets that overlap with the template's runtime are skipped (no double-writes). Response includes `snippetsCopied: string[]`.
|
|
887
894
|
|
|
@@ -1044,7 +1051,7 @@ A few platform-tool quirks worth knowing up front:
|
|
|
1044
1051
|
- **asar bank-border-crossed** can happen if your `org` + `dw` runs past $00FFFF. Native vectors are at $FFE4-$FFEE; emulation vectors at $FFF4-$FFFF. Use `scaffold({op:'snippets', platform: "snes", mode: "get", name: "lorom_header.asm"})` for the layout.
|
|
1045
1052
|
- **cc65 (NES, C64, etc.) zero page** starts at $02. cc65 reserves $00-$01 for its runtime. Your first `.res 1` lands at $02, not $00. Use `symbols({op:'map'})` after `build({output:'romWithDebug'})` to confirm.
|
|
1046
1053
|
- **NES pattern table cap = 256 tiles per nametable**. The tilemap index is 8-bit, so per-frame BG can use at most 256 unique tiles per pattern table. Auto-converting a busy illustration usually overflows. `encodeArt({stage:'tilemap'})` warns; the only workaround is mid-frame CHR bank switching (MMC3-class mapper).
|
|
1047
|
-
- **NES + GB/GBC turnkey** (R9/R10 self-contained + sound, 2026-05-25): use `scaffold({op:'project', platform, template, name, path})` to scaffold a project. The pipeline copies every file the template depends on — `{nes,gb}_runtime.{h,c}`, `gb_hardware.h`, custom `crt0.s`, linker `.cfg`, `patch-header.js` (GB) — into the project directory alongside `main.c`. **No auto-injection at build time.**
|
|
1054
|
+
- **NES + GB/GBC turnkey** (R9/R10 self-contained + sound, 2026-05-25): use `scaffold({op:'project', platform, template, name, path})` to scaffold a project. The pipeline copies every file the template depends on — `{nes,gb}_runtime.{h,c}`, `gb_hardware.h`, custom `crt0.s`, linker `.cfg`, `patch-header.js` (GB) — into the project directory alongside `main.c`. **No auto-injection at build time.** Iterate the whole dir in one call with `build({output:'run', path, platform})` (or supply `sources` / `sourcesPaths` / `includes` / `includePaths` / `crt0` / `crt0Path` / `linkerConfig` / `codeLoc` explicitly when the files aren't on disk). Take the project elsewhere with stock cc65/sdcc and it builds the same way. The runtime APIs include sprites, BG, input, AND **sound** — `sound_init` / `sound_play_tone(channel, period, vol, length)` / `sound_play_noise` / `sound_off`. NES drives pulse1+pulse2+triangle+noise via $4000-$400F + $4015; GB drives the 4-channel APU via NR10-NR52. SFX-grade, fire-and-forget — for full music tracks, drop in famitone2 (NES) or your own driver. Templates: `default` (palette cycle), `hello_sprite` (sprite + d-pad + **beep on A press**), `tile_engine` (multi-room tile map). Docs: [`src/platforms/nes/MENTAL_MODEL.md`](src/platforms/nes/MENTAL_MODEL.md) + [`TROUBLESHOOTING.md`](src/platforms/nes/TROUBLESHOOTING.md); [`src/platforms/gb/MENTAL_MODEL.md`](src/platforms/gb/MENTAL_MODEL.md) + [`TROUBLESHOOTING.md`](src/platforms/gb/TROUBLESHOOTING.md). **NES sprite staging:** the bundled `nes_runtime` now handles the OAM-DMA race for you (oam_clear just resets the index; ppu_wait_nmi hides unused slots after you stage), so the old "must stage before ppu_wait_nmi" flicker trap is gone — the standard `oam_clear → oam_spr → ppu_wait_nmi` loop renders cleanly. **GB ROM header:** both asm and C builds auto-run `rgbfix` inside `build({output:'rom'/'run'})`, so the Nintendo logo + checksums + CGB flag are correct out of the box — no manual header-patch step needed (use `romPatch({op:'gbHeader'})` only to fix up an external ROM).
|
|
1048
1055
|
- **Game Boy / GBC silent-failure footguns** (R54 cleanup, full detail in `platform({op:'doc', platform:"gb"|"gbc", name:"mental_model"})`):
|
|
1049
1056
|
- **The bundled `gb_crt0.s` is now actually linked.** Pre-r54 a fundamental bug in `buildZ80C` was shipping the raw .s text to sdld as if pre-assembled — sdld silently rejected it and fell back to SDCC's stock sm83 crt0 (no GB cart boot, no IRQ vectors). Map showed no `init` symbol, $0000 was $FF, $0100 was $FF. Every GB ROM ran on stock crt0 invisibly. Fixed by auto-detecting .s source vs .rel object and running it through sdasgb first. Post-fix: `init` at $0150, entry $0100 = `00 c3 50 01` (nop; jp $0150), reset vector $0000 = $C9. **This was the root cause for #14 audio AND part of why every previous "runtime should work OOTB" round still felt friction-heavy.**
|
|
1050
1057
|
- **GB/GBC C builds now auto-fix the header at build time** (rgbfix runs inside `build({output:'rom'})`): Nintendo logo at $0104, header checksum at $014D, global checksum, and the CGB flag at $0143 ($00 for `.gb`, $C0 for `.gbc`). You no longer need to patch the header manually — the ROM `build({output:'rom'})` hands back boots on real hardware as-is. `romPatch({op:'gbHeader', path})` still exists if you want to override title / cart type / RAM size / etc. on an existing file.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,100 @@ 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.15.0
|
|
8
|
+
|
|
9
|
+
**Scaffold audit: every scaffold on every platform now builds AND renders.** Two
|
|
10
|
+
independent multi-agent audits found the documented "scaffold then build the dir"
|
|
11
|
+
happy path was broken on most platforms, and that many scaffolds built but
|
|
12
|
+
rendered blank. Both are fixed — verified 115/115 templates build via the dir
|
|
13
|
+
path, and every genre game renders recognizable content. This matters most for
|
|
14
|
+
weaker agents: the first build now succeeds, so they build on a working example
|
|
15
|
+
instead of assuming the server is broken and installing their own tools.
|
|
16
|
+
|
|
17
|
+
### Fixed — project-directory build (the linchpin)
|
|
18
|
+
- **`build({output:'run'|'project', path})` now works on EVERY platform.** It was
|
|
19
|
+
0/8 on GB/GBC/NES/Genesis and 0/5 on Atari 2600 via the natural path. The
|
|
20
|
+
dir-builder used to glob every file as a source; it now applies a per-platform
|
|
21
|
+
recipe that matches a hand-written build: routes the crt0 correctly (GB/GBC
|
|
22
|
+
`gb_crt0.s` via the cart-header path — no more `Multiple definition of gsinit`),
|
|
23
|
+
applies the linker preset (NES `chr-ram-runtime` — no more `Missing memory area
|
|
24
|
+
'OAM'`), skips SDK intermediates (Genesis `sega.preprocessed.s`, the SNES SPC700
|
|
25
|
+
driver, any `*.upstream.*`), wires the GBA runtime (libtonc/libgba/maxmod by
|
|
26
|
+
what the source includes), routes `#include`d C/asm siblings as includes, and
|
|
27
|
+
bundles every incbin asset (`.xgc`/`.vgz`/…). `output:'run'` accepts `path` too
|
|
28
|
+
(build+load+run+screenshot a dir in one call). One shared code path so the two
|
|
29
|
+
build routes can't drift. Locked in by `scaffold-build-happypath.test.js`.
|
|
30
|
+
- **SNES multi-`.c` builds** (genre scaffolds ship `main.c` + `snes_sfx.c`) — the
|
|
31
|
+
stale single-`.c` guard in `buildSnesC` is removed (the link path already
|
|
32
|
+
handled multiple TUs).
|
|
33
|
+
|
|
34
|
+
### Fixed — scaffolds rendered blank/wrong (now show content)
|
|
35
|
+
- **SMS/GG**: `vdp_init` R6 default 0xFB→0xFF — sprite tiles now read from $2000
|
|
36
|
+
where scaffolds upload them (were reading the empty $0000 bank → invisible). GG
|
|
37
|
+
also: 64-byte GG palette format (scaffolds shipped 32) + visible-window coords.
|
|
38
|
+
- **Genesis**: genre scaffolds now call `VDP_linkSprites` (the SAT link bytes were
|
|
39
|
+
0 = end-of-list → only slot 0 drew); shmup enemies spread across the field.
|
|
40
|
+
- **SNES**: `sports`/`racing` `oamSet` now uses byte offsets (`slot<<2`); a real
|
|
41
|
+
4bpp console font is embedded (the stub made all text black) + the BG-base /
|
|
42
|
+
text-offset / `consoleVblank` wiring fixed → c_hello/puzzle/platformer show text.
|
|
43
|
+
- **Lynx**: `platformer`/`puzzle`/`sports`/`racing` use the `while(tgi_busy()){}` +
|
|
44
|
+
full-screen `tgi_bar` clear (were black via bare `tgi_clear()`).
|
|
45
|
+
- **C64**: genre sprite data moved $0800→$2000 (it collided with the $0801 `.prg`
|
|
46
|
+
load → `sports` crashed to BASIC, sprites corrupt).
|
|
47
|
+
- **NES**: the bundled runtime no longer races the OAM-DMA — `oam_clear` resets the
|
|
48
|
+
index and `ppu_wait_nmi` hides unused slots after staging, so sprite-light
|
|
49
|
+
scaffolds no longer flicker to black; `default` backdrop no longer cycles to black.
|
|
50
|
+
- **PCE**: `pce_video.c` now programs the VDC display timing (NTSC 256×224) — fixes
|
|
51
|
+
the vertically-doubled picture.
|
|
52
|
+
- **GBA**: per-frame `tte_printf("%05d")` (broken in the bundled libtonc — garbles +
|
|
53
|
+
wedges the loop) replaced with a hand-built score string + `tte_write`.
|
|
54
|
+
- **Atari 2600**: `paddle` kernel rebuilt to a 2-line kernel (the per-line work
|
|
55
|
+
overflowed a 76-cycle scanline → ~250 lines, no vsync lock); now stable at 210.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
- **Doc-drift swept**: every agent-facing `runSource`/`buildSource` → the current
|
|
59
|
+
`build({output:'run'|'rom'})`, dead tool names (`loadCategory`, `inspectSprites`,
|
|
60
|
+
…) → the consolidated forms, across the scaffold README emitter, `nextStep`,
|
|
61
|
+
examples, platform docs, and `.cfg` comments. Emitted scaffold-README `frames`
|
|
62
|
+
60→240 (60 caught the boot logo on some platforms → false "blank").
|
|
63
|
+
- **Thin genres** given a BG world (Genesis sports court / racing road, NES racing
|
|
64
|
+
road, SNES sports/racing) so they read as the genre, not objects on black.
|
|
65
|
+
- **Missing-genre error** for msx/pce/atari2600 now names the working project
|
|
66
|
+
templates instead of a bare "default".
|
|
67
|
+
- The `uint8-loop-bound` preflight lint is scope-aware (no longer false-flags a
|
|
68
|
+
`uint16_t` loop counter that shares a name with a `uint8_t` in another function).
|
|
69
|
+
|
|
70
|
+
## 0.16.0
|
|
71
|
+
|
|
72
|
+
**Build diagnostics: agents were building blind — errors AND warnings now reach
|
|
73
|
+
the response as structured `issues[]`.** An agent can only fix what the toolchain
|
|
74
|
+
tells it, where it tells it. Audited the whole build surface and closed the gaps
|
|
75
|
+
so diagnostics (file/line/message/stage) come back in the tool result, not buried
|
|
76
|
+
in the raw log. (Also bumps a doc count: the surface is 32 tools after 0.15.0's
|
|
77
|
+
dmaTrace→watch / patchGbHeader→romPatch consolidation; stale "34" references in
|
|
78
|
+
the docs + source comments updated.)
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
- **Warnings were OFF.** No C compiler was being asked for them. gcc (GBA/Genesis)
|
|
82
|
+
now compiles USER source with `-Wall -Wextra -Wno-unused-parameter` (the bundled
|
|
83
|
+
SDK stays warning-free so its noise doesn't bury the agent's); cc65 enables its
|
|
84
|
+
valid high-value `-W` set. So unused vars, implicit declarations, etc. are now
|
|
85
|
+
emitted and surfaced.
|
|
86
|
+
- **Swallowed errors now structured:** SDCC's keyword-less `file:line: syntax
|
|
87
|
+
error: …` and `warning NNN: …` (GB/GBC/SMS/MSX previously returned an empty
|
|
88
|
+
`issues[]` on a syntax error); the sdld/ASlink `Undefined Global '_x'` link
|
|
89
|
+
error; vasm errors (Genesis asm emits no stage marker, so they hit the
|
|
90
|
+
fallback, which had skipped the vasm parser); and a **missing `incbin` asset**
|
|
91
|
+
— the #1 thing an agent forgets to pass — now reports `could not open <x.bin>`
|
|
92
|
+
with the exact filename.
|
|
93
|
+
- **Fixed a build crash that ate the real error:** `build({output:'rom', path})`
|
|
94
|
+
fell into the source builder with no source and threw "Cannot read properties
|
|
95
|
+
of undefined (reading 'split')" instead of the compiler error; it now routes to
|
|
96
|
+
the project-dir builder like `output:'run'`/`'project'`.
|
|
97
|
+
- Verified live across all 14 platforms; a `parse-errors-coverage` test locks the
|
|
98
|
+
formats in. (Known limit: asar/SNES-asm only yields a wrapper "aborted"
|
|
99
|
+
message — its WASM build aborts without printing line info.)
|
|
100
|
+
|
|
7
101
|
## 0.14.0
|
|
8
102
|
|
|
9
103
|
**Two platform-specific top-level tools folded into their domain verbs, a
|
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Agents: the server delivers [`AGENTS.md`](./AGENTS.md) as connection-time instru
|
|
|
52
52
|
Most agents support MCP, but you don't have to use it. Run the server
|
|
53
53
|
(`npx romdevtools`) and **skip wiring it into your agent's MCP
|
|
54
54
|
config** — no `claude mcp add`, no `mcp.json` entry, no MCP client at all. The
|
|
55
|
-
same
|
|
55
|
+
same 32 tools are reachable over plain HTTP / as an Agent Skill against the
|
|
56
56
|
running server:
|
|
57
57
|
|
|
58
58
|
- **Plain HTTP:** `POST http://127.0.0.1:7331/tool/{name}` with the args as a JSON
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
; standard NTSC frame timing (3 vsync + 37 vblank + 192 visible + 30
|
|
4
4
|
; overscan). Read joystick to move the sprite.
|
|
5
5
|
;
|
|
6
|
-
; Build:
|
|
6
|
+
; Build: build({ output: "rom", platform: "atari2600", source: <this file> })
|
|
7
7
|
|
|
8
8
|
processor 6502
|
|
9
9
|
org $F000
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
; standard NTSC frame timing (3 vsync + 37 vblank + 192 visible + 30
|
|
4
4
|
; overscan). Read joystick to move the sprite.
|
|
5
5
|
;
|
|
6
|
-
; Build:
|
|
6
|
+
; Build: build({ output: "rom", platform: "atari2600", source: <this file> })
|
|
7
7
|
|
|
8
8
|
processor 6502
|
|
9
9
|
org $F000
|
|
@@ -111,9 +111,14 @@ MAIN:
|
|
|
111
111
|
STA VSYNC
|
|
112
112
|
|
|
113
113
|
; ── VBLANK (37 lines) — game logic ────────────────────────────────
|
|
114
|
+
; 34 here + the 3 STA WSYNC in the P0/P1 positioning block below = 37 VBLANK
|
|
115
|
+
; lines total. (Bug fix: this loop used to be 37 AND the positioning added 3
|
|
116
|
+
; more → 265 scanlines/frame → the TV/emulator can't lock vsync → rolling /
|
|
117
|
+
; black picture. Exactly 262 lines = 3 VSYNC + 37 VBLANK + 192 visible + 30
|
|
118
|
+
; overscan; the positioning WSYNCs MUST be counted against the 37.)
|
|
114
119
|
LDA #2
|
|
115
120
|
STA VBLANK
|
|
116
|
-
LDX #
|
|
121
|
+
LDX #34
|
|
117
122
|
.vb:
|
|
118
123
|
STA WSYNC
|
|
119
124
|
DEX
|
|
@@ -212,93 +217,100 @@ MAIN:
|
|
|
212
217
|
STA AUDV0
|
|
213
218
|
.sfx_done:
|
|
214
219
|
|
|
215
|
-
; Position P0
|
|
216
|
-
STA
|
|
217
|
-
|
|
220
|
+
; ── Position P0 / P1 / HMOVE — exactly 3 WSYNC-bounded lines ───────
|
|
221
|
+
; CRITICAL: every RESPx write AND the STA HMOVE must complete inside
|
|
222
|
+
; the 76-cycle scanline that began with its STA WSYNC. A DEX/BNE delay
|
|
223
|
+
; loop costs 5 cycles/iteration, so the loop count must be small enough
|
|
224
|
+
; that RESPx still lands before the line ends. The old code used
|
|
225
|
+
; LDX #38 (~189 cycles = 2.5 scanlines!) with no WSYNC before RESP1/
|
|
226
|
+
; HMOVE, so it emitted ~2-3 UNCOUNTED scanlines past the 262 budget →
|
|
227
|
+
; ~265 lines/frame → vsync never locks (rolling magenta band). HMOVE
|
|
228
|
+
; was also issued mid-line; it must follow a fresh WSYNC.
|
|
229
|
+
|
|
230
|
+
; Line 1 of 3: coarse-position P0 (left, ~column 16)
|
|
218
231
|
STA WSYNC
|
|
219
|
-
LDX #
|
|
232
|
+
LDX #5
|
|
220
233
|
.p0d:
|
|
221
234
|
DEX
|
|
222
|
-
BNE .p0d
|
|
235
|
+
BNE .p0d ; ~24 cycles in → P0 lands near the left edge
|
|
223
236
|
STA RESP0
|
|
224
|
-
;
|
|
237
|
+
; Line 2 of 3: coarse-position P1 (right, ~column 132)
|
|
225
238
|
STA WSYNC
|
|
226
|
-
LDX #
|
|
239
|
+
LDX #13
|
|
227
240
|
.p1d:
|
|
228
241
|
DEX
|
|
229
|
-
BNE .p1d
|
|
242
|
+
BNE .p1d ; ~64 cycles in (< 76) → P1 lands near the right
|
|
230
243
|
STA RESP1
|
|
244
|
+
; Line 3 of 3: apply HMOVE on a FRESH line, right after WSYNC
|
|
245
|
+
STA WSYNC
|
|
231
246
|
STA HMOVE
|
|
232
247
|
|
|
233
248
|
LDA #0
|
|
234
249
|
STA VBLANK
|
|
235
250
|
|
|
236
|
-
; ── Visible (192 lines)
|
|
251
|
+
; ── Visible (192 lines) — TWO-LINE KERNEL ─────────────────────────
|
|
252
|
+
; CRITICAL: a single scanline is only 76 CPU cycles. The full per-line
|
|
253
|
+
; render here (playfield walls + P0 + P1 + ball, each a SEC/SBC/CMP +
|
|
254
|
+
; conditional store) is ~88 cycles — it does NOT fit in one line. In a
|
|
255
|
+
; 1-line kernel each WSYNC iteration then spills past the line boundary,
|
|
256
|
+
; so 192 iterations stretch to ~232 emitted lines → ~250-line frame →
|
|
257
|
+
; vsync never locks (rolling magenta band — THE bug).
|
|
258
|
+
;
|
|
259
|
+
; The fix is the standard 2600 "2-line kernel": each loop pass renders
|
|
260
|
+
; TWO scanlines and splits the work across two WSYNCs, doubling the
|
|
261
|
+
; budget to ~152 cycles. 96 passes × 2 lines = 192 visible lines.
|
|
262
|
+
; Y counts 192→2 in steps of 2; paddles/ball move in 2px steps (fine
|
|
263
|
+
; for Pong). The branchless "LDA #off / CMP / BCS skip / LDA #on" form
|
|
264
|
+
; also drops the JMPs the old code paid every line.
|
|
237
265
|
LDY #192
|
|
238
266
|
.draw:
|
|
267
|
+
; ---- first line of the pair: playfield walls + left paddle ----
|
|
239
268
|
STA WSYNC
|
|
240
|
-
|
|
241
|
-
; Top-bottom walls: lines 0..3 and 188..191 draw a full-width PF
|
|
242
|
-
CMP #4
|
|
243
|
-
BCS .nottop
|
|
244
|
-
LDA #$FF
|
|
245
|
-
STA PF0
|
|
246
|
-
STA PF1
|
|
247
|
-
STA PF2
|
|
248
|
-
JMP .pf_done
|
|
249
|
-
.nottop:
|
|
250
|
-
CMP #188
|
|
251
|
-
BCC .notbot
|
|
252
|
-
LDA #$FF
|
|
253
|
-
STA PF0
|
|
254
|
-
STA PF1
|
|
255
|
-
STA PF2
|
|
256
|
-
JMP .pf_done
|
|
257
|
-
.notbot:
|
|
269
|
+
; Top + bottom walls: full-width PF on the outer rows (Y>=189 / Y<5)
|
|
258
270
|
LDA #0
|
|
271
|
+
CPY #189
|
|
272
|
+
BCS .wall
|
|
273
|
+
CPY #5
|
|
274
|
+
BCS .nowall
|
|
275
|
+
.wall:
|
|
276
|
+
LDA #$FF
|
|
277
|
+
.nowall:
|
|
259
278
|
STA PF0
|
|
260
279
|
STA PF1
|
|
261
280
|
STA PF2
|
|
262
|
-
.pf_done:
|
|
263
281
|
; Left paddle: 8 lines starting at P0_Y
|
|
264
282
|
TYA
|
|
265
283
|
SEC
|
|
266
284
|
SBC P0_Y
|
|
267
285
|
CMP #8
|
|
268
|
-
BCS .p0blank
|
|
269
|
-
LDA #$FF
|
|
270
|
-
STA GRP0
|
|
271
|
-
JMP .p0done
|
|
272
|
-
.p0blank:
|
|
273
286
|
LDA #0
|
|
287
|
+
BCS .p0off
|
|
288
|
+
LDA #$FF
|
|
289
|
+
.p0off:
|
|
274
290
|
STA GRP0
|
|
275
|
-
|
|
291
|
+
; ---- second line of the pair: right paddle + ball ----
|
|
292
|
+
STA WSYNC
|
|
276
293
|
; Right paddle: 8 lines starting at P1_Y
|
|
277
294
|
TYA
|
|
278
295
|
SEC
|
|
279
296
|
SBC P1_Y
|
|
280
297
|
CMP #8
|
|
281
|
-
BCS .p1blank
|
|
282
|
-
LDA #$FF
|
|
283
|
-
STA GRP1
|
|
284
|
-
JMP .p1done
|
|
285
|
-
.p1blank:
|
|
286
298
|
LDA #0
|
|
299
|
+
BCS .p1off
|
|
300
|
+
LDA #$FF
|
|
301
|
+
.p1off:
|
|
287
302
|
STA GRP1
|
|
288
|
-
.p1done:
|
|
289
303
|
; Ball: 2 lines starting at BALL_Y
|
|
290
304
|
TYA
|
|
291
305
|
SEC
|
|
292
306
|
SBC BALL_Y
|
|
293
307
|
CMP #2
|
|
294
|
-
BCS .blblank
|
|
295
|
-
LDA #2
|
|
296
|
-
STA ENABL
|
|
297
|
-
JMP .bldone
|
|
298
|
-
.blblank:
|
|
299
308
|
LDA #0
|
|
309
|
+
BCS .bloff
|
|
310
|
+
LDA #2
|
|
311
|
+
.bloff:
|
|
300
312
|
STA ENABL
|
|
301
|
-
|
|
313
|
+
DEY
|
|
302
314
|
DEY
|
|
303
315
|
BNE .draw
|
|
304
316
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ── Hello, Atari 7800 — MARIA bring-up + single sprite ──────────────
|
|
2
2
|
//
|
|
3
|
-
// Build:
|
|
3
|
+
// Build: build({ output: "rom", platform: "atari7800", source: <this file>,
|
|
4
4
|
// language: "c" })
|
|
5
5
|
//
|
|
6
6
|
// Sets up a minimal display list, paints the background blue and renders
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* the 4KB of internal RAM.
|
|
35
35
|
*
|
|
36
36
|
* ── Build ───────────────────────────────────────────────────────
|
|
37
|
-
*
|
|
37
|
+
* build({ output: "rom", platform: "atari7800", sources: { "main.c": ... } })
|
|
38
38
|
*/
|
|
39
39
|
#include <stdint.h>
|
|
40
40
|
#include "atari7800_sfx.h"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* The note tables live in atari7800_music.c — they ARE the song.
|
|
12
12
|
*
|
|
13
|
-
* Build:
|
|
13
|
+
* Build: build({ output: "rom", platform: "atari7800", template: "music_demo" })
|
|
14
14
|
*/
|
|
15
15
|
#include <stdint.h>
|
|
16
16
|
#include "atari7800_music.h"
|
package/examples/c64/main.c
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// directly: writes screen codes into screen RAM, sets per-cell colors
|
|
5
5
|
// in color RAM, and cycles the border color in a main loop.
|
|
6
6
|
//
|
|
7
|
-
// Build:
|
|
7
|
+
// Build: build({ output: "rom", platform: "c64", source: <this file>, language: "c" })
|
|
8
8
|
|
|
9
9
|
#include <stdint.h>
|
|
10
10
|
|
|
@@ -80,7 +80,7 @@ static void wait_vblank(void) {
|
|
|
80
80
|
|
|
81
81
|
static void copy_sprite(uint8_t slot, const uint8_t *data) {
|
|
82
82
|
uint8_t i;
|
|
83
|
-
volatile uint8_t *dst = (volatile uint8_t*)(
|
|
83
|
+
volatile uint8_t *dst = (volatile uint8_t*)(0x2000 + slot * 64); /* $2000, not $0800 (collides w/ $0801 .prg) */
|
|
84
84
|
for (i = 0; i < 64; i++) dst[i] = data[i];
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -131,7 +131,7 @@ void main(void) {
|
|
|
131
131
|
|
|
132
132
|
POKE(VIC_SPR_ENA, 0);
|
|
133
133
|
copy_sprite(0, player_sprite);
|
|
134
|
-
SPRITE_POINTERS[0] =
|
|
134
|
+
SPRITE_POINTERS[0] = 0x80; /* $2000/64 */
|
|
135
135
|
POKE(VIC_SPR_COL(0), 0x07); /* yellow player */
|
|
136
136
|
POKE(VIC_BORDER, 0x06); /* dark blue */
|
|
137
137
|
POKE(VIC_BG0, 0x06);
|
|
@@ -49,7 +49,7 @@ static void wait_vblank(void) {
|
|
|
49
49
|
|
|
50
50
|
static void copy_sprite(uint8_t slot, const uint8_t *data) {
|
|
51
51
|
uint8_t i;
|
|
52
|
-
volatile uint8_t *dst = (volatile uint8_t*)(
|
|
52
|
+
volatile uint8_t *dst = (volatile uint8_t*)(0x2000 + slot * 64); /* $2000, not $0800 (collides w/ $0801 .prg) */
|
|
53
53
|
for (i = 0; i < 64; i++) dst[i] = data[i];
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -75,8 +75,8 @@ void main(void) {
|
|
|
75
75
|
uint8_t i, pad;
|
|
76
76
|
POKE(VIC_SPR_ENA, 0);
|
|
77
77
|
copy_sprite(0, car_sprite);
|
|
78
|
-
SPRITE_POINTERS[0] =
|
|
79
|
-
for (i = 0; i < MAX_OBS; i++) SPRITE_POINTERS[1 + i] =
|
|
78
|
+
SPRITE_POINTERS[0] = 0x80; /* $2000/64 */
|
|
79
|
+
for (i = 0; i < MAX_OBS; i++) SPRITE_POINTERS[1 + i] = 0x80;
|
|
80
80
|
POKE(VIC_SPR_COL(0), 0x07); /* yellow player */
|
|
81
81
|
for (i = 0; i < MAX_OBS; i++) POKE(VIC_SPR_COL(1 + i), 0x02); /* red obstacles */
|
|
82
82
|
POKE(VIC_BORDER, 0x00);
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
#define PEEK(addr) (*(volatile uint8_t*)(addr))
|
|
21
21
|
|
|
22
22
|
#define SPRITE_POINTERS ((volatile uint8_t*)0x07F8)
|
|
23
|
-
#define SPRITE_DATA_BASE
|
|
23
|
+
#define SPRITE_DATA_BASE 0x2000 /* sprite N data at $2000 + N*64 — NOT $0800,
|
|
24
|
+
* which collides with the $0801 .prg load (C64-1) */
|
|
24
25
|
|
|
25
26
|
#define JOY_UP 0x01
|
|
26
27
|
#define JOY_DOWN 0x02
|
|
@@ -121,9 +122,9 @@ void main(void) {
|
|
|
121
122
|
|
|
122
123
|
/* Sprite pointers: slot N points at /64 index into the VIC bank.
|
|
123
124
|
* Default bank = $0000-$3FFF. $0800 / 64 = 32 = $20. */
|
|
124
|
-
SPRITE_POINTERS[SLOT_PLAYER] =
|
|
125
|
-
for (i = 0; i < MAX_BULLETS; i++) SPRITE_POINTERS[SLOT_BULLET0 + i] =
|
|
126
|
-
for (i = 0; i < MAX_ENEMIES; i++) SPRITE_POINTERS[SLOT_ENEMY0 + i] =
|
|
125
|
+
SPRITE_POINTERS[SLOT_PLAYER] = 0x80; /* $2000/64 */
|
|
126
|
+
for (i = 0; i < MAX_BULLETS; i++) SPRITE_POINTERS[SLOT_BULLET0 + i] = 0x81;
|
|
127
|
+
for (i = 0; i < MAX_ENEMIES; i++) SPRITE_POINTERS[SLOT_ENEMY0 + i] = 0x82;
|
|
127
128
|
|
|
128
129
|
POKE(VIC_SPR_COL(SLOT_PLAYER), 0x07); /* yellow */
|
|
129
130
|
for (i = 0; i < MAX_BULLETS; i++) POKE(VIC_SPR_COL(SLOT_BULLET0 + i), 0x01); /* white */
|
|
@@ -175,7 +176,7 @@ void main(void) {
|
|
|
175
176
|
if (aabb(&bullets[i], &enemies[j])) {
|
|
176
177
|
bullets[i].alive = 0;
|
|
177
178
|
enemies[j].alive = 0;
|
|
178
|
-
if (score <
|
|
179
|
+
if (score < 65500u) score += 10;
|
|
179
180
|
sfx_noise(8);
|
|
180
181
|
break;
|
|
181
182
|
}
|
|
@@ -41,7 +41,7 @@ static void wait_vblank(void) {
|
|
|
41
41
|
|
|
42
42
|
static void copy_sprite(uint8_t slot, const uint8_t *data) {
|
|
43
43
|
uint8_t i;
|
|
44
|
-
volatile uint8_t *dst = (volatile uint8_t*)(
|
|
44
|
+
volatile uint8_t *dst = (volatile uint8_t*)(0x2000 + slot * 64); /* $2000, not $0800 (collides w/ $0801 .prg) */
|
|
45
45
|
for (i = 0; i < 64; i++) dst[i] = data[i];
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -56,9 +56,9 @@ void main(void) {
|
|
|
56
56
|
copy_sprite(0, paddle_sprite);
|
|
57
57
|
copy_sprite(1, paddle_sprite);
|
|
58
58
|
copy_sprite(2, ball_sprite);
|
|
59
|
-
SPRITE_POINTERS[0] =
|
|
60
|
-
SPRITE_POINTERS[1] =
|
|
61
|
-
SPRITE_POINTERS[2] =
|
|
59
|
+
SPRITE_POINTERS[0] = 0x80; /* $2000/64 */
|
|
60
|
+
SPRITE_POINTERS[1] = 0x81;
|
|
61
|
+
SPRITE_POINTERS[2] = 0x82;
|
|
62
62
|
POKE(VIC_SPR_COL(0), 0x01); /* white */
|
|
63
63
|
POKE(VIC_SPR_COL(1), 0x01);
|
|
64
64
|
POKE(VIC_SPR_COL(2), 0x07); /* yellow ball */
|
package/examples/gb/main.asm
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
; it to the BG map, enables the LCD, then loops reading joypad and
|
|
5
5
|
; scrolling the BG. Build with:
|
|
6
6
|
;
|
|
7
|
-
;
|
|
7
|
+
; build({ output: "rom", platform:"gb", source: <this>})
|
|
8
8
|
;
|
|
9
9
|
; rgbfix gets applied automatically by buildForPlatform so the header
|
|
10
10
|
; checksum is valid.
|
package/examples/gb/main.c
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* ── Hello, Game Boy in C — SDCC sm83 port ─────────────────────────
|
|
2
2
|
* Minimal: cycle the BG palette on every vblank.
|
|
3
3
|
*
|
|
4
|
-
* Build:
|
|
4
|
+
* Build: build({ output: "rom", platform: "gb", source: <this file>, language: "c" })
|
|
5
5
|
*
|
|
6
6
|
* SDCC 4.4.0 codegen quirks to avoid in `__sfr __at` register-heavy
|
|
7
7
|
* code:
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* pixel that moves left/right via the d-pad.
|
|
6
6
|
*
|
|
7
7
|
* Build via romdev:
|
|
8
|
-
*
|
|
8
|
+
* build({ output: "rom", platform:"gba", language:"c", runtime:"libgba",
|
|
9
9
|
* source: <this file>})
|
|
10
10
|
*
|
|
11
11
|
* NOTE: the DEFAULT GBA runtime is libtonc (use `tonc_hello.c` instead
|
|
@@ -22,6 +22,20 @@
|
|
|
22
22
|
#include <tonc.h>
|
|
23
23
|
#include "gba_sfx.h"
|
|
24
24
|
|
|
25
|
+
/* draw a 5-digit score WITHOUT tte_printf (broken in this libtonc — GBA-1). */
|
|
26
|
+
static void draw_score(int x, unsigned v) {
|
|
27
|
+
char buf[24];
|
|
28
|
+
int i, n = 0;
|
|
29
|
+
buf[n++]='#'; buf[n++]='{'; buf[n++]='P'; buf[n++]=':';
|
|
30
|
+
if (x >= 100) buf[n++] = '0' + (x/100)%10;
|
|
31
|
+
if (x >= 10) buf[n++] = '0' + (x/10)%10;
|
|
32
|
+
buf[n++] = '0' + x%10;
|
|
33
|
+
buf[n++]=','; buf[n++]='8'; buf[n++]='}';
|
|
34
|
+
for (i = 4; i >= 0; i--) { buf[n+i] = '0' + (v % 10); v /= 10; }
|
|
35
|
+
n += 5; buf[n] = 0;
|
|
36
|
+
tte_write(buf);
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
#define COLS 6
|
|
26
40
|
#define ROWS 12
|
|
27
41
|
|
|
@@ -134,7 +148,7 @@ static void lock_piece(void) {
|
|
|
134
148
|
grid[r][c] = 0;
|
|
135
149
|
grid[r][c + 1] = 0;
|
|
136
150
|
grid[r][c + 2] = 0;
|
|
137
|
-
if (score <
|
|
151
|
+
if (score < 65500u) score += 30;
|
|
138
152
|
sfx_tone(1, 1700, 10); /* triple-clear chime */
|
|
139
153
|
}
|
|
140
154
|
}
|
|
@@ -211,7 +225,7 @@ int main(void) {
|
|
|
211
225
|
new_piece();
|
|
212
226
|
prev = now;
|
|
213
227
|
tte_erase_rect(88 + 6*8, 8, 88 + 11*8, 16);
|
|
214
|
-
|
|
228
|
+
draw_score(88 + 6*8, score);
|
|
215
229
|
continue;
|
|
216
230
|
}
|
|
217
231
|
prev = now;
|
|
@@ -231,7 +245,7 @@ int main(void) {
|
|
|
231
245
|
draw_piece(piece_x, piece_y, 0);
|
|
232
246
|
|
|
233
247
|
tte_erase_rect(88 + 6*8, 8, 88 + 11*8, 16);
|
|
234
|
-
|
|
248
|
+
draw_score(88 + 6*8, score);
|
|
235
249
|
}
|
|
236
250
|
return 0;
|
|
237
251
|
}
|