| name | sega-master-system-devkitsms |
| description | Program, build, debug, and port Sega Master System (and Game Gear / SG-1000) homebrew games in C with devkitSMS (SMSlib + PSGlib) and the SDCC Z80 compiler. Use whenever the user writes, compiles, fixes, optimizes, or ports an SMS/Master System or Game Gear game or demo: the SDCC + devkitSMS toolchain; producing a `.sms`/`.gg` ROM; VDP tiles, the tilemap, hardware sprites, palettes, PSG music/SFX, joypad input, VBlank/line interrupts, ROM bank switching, SRAM saves; converting assets (`.psgcompr` tiles, `vgm2psg` music, `folder2c`/`assets2banks`); or debugging the classic "compiles but boots to a black screen" failure. Trigger on mentions of devkitSMS, SMSlib, PSGlib, SDCC `-mz80`, ihx2sms, crt0_sms, `SMS_addSprite`/`SMS_waitForVBlank`, a `.sms` ROM, Master System / Game Gear homebrew, or Haroldo-OK's SMS games (Jill of the Jungle, Dangerous Dave, Food Fighter, Sub Rescue, Commander Keen). Prefer it over general knowledge for the SMS toolchain, VDP limits, or ROM format — easily gotten wrong from memory. |
Sega Master System game development & porting (devkitSMS)
This skill turns an idea — or an existing game to port — into a playable,
verified .sms ROM, built with SDCC (Z80 C compiler) and devkitSMS
(sverx's SMSlib + PSGlib). It bundles a consistent toolchain snapshot, project
templates, a headless test emulator, and references distilled from Haroldo-OK's
open-source SMS games.
The SMS is a Z80 with a tile/sprite VDP (256×192, two 16-colour palettes,
≤64 sprites / 8-per-line), a 4-channel PSG, and 8 KiB of RAM. There is no
framebuffer and no float unit. Success means respecting those limits, not
fighting them.
Do not rely on memory for SMSlib signatures, VDP addresses, build flags, or
the ROM header — they are precise and version-sensitive. Read the references and
verify by building and by running the test harness.
Definition of done (do not stop at "it compiled")
A ROM that links tells you almost nothing; the default failure is a clean build
that boots to a black screen. A task is done only when all hold — report each:
- Compiles and links — no SDCC errors, no
?ASlink undefined-symbol warnings.
- Becomes a ROM —
ihx2sms reports SEGA header found, checksum updated.
- Not a black screen —
smstest boots the real ROM; a screenshot frame has
the display on and several distinct colours.
- Reaches its states — scripted input drives title → play (→ game over) and
named game variables take expected values.
- Reachable by the user — the
.sms is copied to a known output path and
presented, not left in a scratch dir.
Details and the harness DSL: references/testing.md.
Step 0 — set up the toolchain (once)
bash scripts/setup_toolchain.sh
export PATH="$HOME/.devkitsms/bin:$PATH"
This installs SDCC (≥ 4.2, required), builds ihx2sms/folder2c from the
bundled public-domain sources, installs a mutually-consistent SMSlib/PSGlib/
crt0/header set into ~/.devkitsms, builds the smstest emulator, and runs a
smoke build. Full details + flag meanings: references/toolchain-and-build.md.
Pick the workflow
- New game from scratch → copy
assets/template/ (Makefile + src/main.c
skeleton), then read references/game-structure.md for the frame loop, actor
pattern, fixed-point, and collision model. For a game like an existing one,
clone that reference repo instead (references/examples.md). For a
tile-grid / maze game (Pac-Man, Sokoban, Bomberman) also read
references/grid-and-maze-games.md, and for an RPG / adventure / roguelike
(scrolling world, menus, dialogue, turns, battles, saves) read
references/rpg-adventure-roguelike.md — lane movement, turn-queuing, tunnel wrap,
target-cell chaser AI, procedural art, and maze validation.
- Porting an existing game (DOS/SDL/Allegro/C, another console) →
references/porting.md (and, if a view is too expensive to compute live but is a
pure function of a little state, references/prerendered-video.md). Core idea: keep the platform-clean game logic, rewrite
the platform layer against SMSlib, down-res to 256×192, turn runtime bitmaps
into compiled-in tiles, replace framebuffer collision with a RAM tile-grid, and
kill floating point.
- Building/fixing an existing devkitSMS project → use the project's own
vendored SMSlib (header+lib together — see the version rule below). Regenerate
data with
folder2c, then build.
- Debugging → jump to "Black-screen triage" below and
references/testing.md.
- Optimizing → fixed-point everywhere, precomputed LUTs, avoid
*///% by
non-powers-of-two, --max-allocs-per-node 100000, keep hot data in RAM.
Whatever the path, wire up a smstest playtest early — it is how you know you
are done instead of hoping.
The build pipeline (four steps)
folder2c data data
sdcc -c -mz80 -I$DK/include \
--peep-file $DK/include/peep-rules.txt file.c -o file.rel
sdcc -o game.ihx -mz80 --no-std-crt0 --data-loc 0xC000 \
$DK/lib/crt0_sms.rel *.rel $DK/lib/SMSlib.lib $DK/lib/PSGlib.lib
ihx2sms game.ihx game.sms
$DK = ~/.devkitsms. Prefer assets/template/Makefile (single ROM) or
assets/template/Makefile.banked (>48 KiB, bank-switched); both add a playtest
target. Linking also emits game.noi (symbols the test harness reads) — keep it.
The canonical frame loop
for (;;) {
keys = SMS_getKeysStatus();
update_world();
SMS_initSprites();
draw_actors();
SMS_finalizeSprites();
SMS_waitForVBlank();
SMS_copySpritestoSAT();
update_hud();
}
The one hard rule: touch VRAM / CRAM / the sprite SAT only during VBlank. All
game logic is RAM work before the wait; every VRAM write happens after it. Music
ticks from the line interrupt (PSGFrame() in the handler) so it survives slow
frames. See assets/template/src/main.c and references/game-structure.md.
Hard constraints cheat-sheet
- RAM is 8 KiB at
0xC000 (--data-loc 0xC000). No real heap — use static
fixed-size pools, keep large/immutable data in ROM.
- VRAM is 16 KiB: 448 tile slots + tilemap + SAT. Plan the tile budget up
front. Reach VRAM only via SMSlib VDP calls, only in VBlank.
- Colour is 2 bits/channel (
RGB(r,g,b), 0–3), two 16-entry palettes (BG +
sprite); sprite entry 0 is transparent.
- Sprites: ≤64 total, 8 per scanline. Wide rows flicker — stagger or use
8×16 tall mode.
- No float, slow divide. Use 8.8 fixed-point and precomputed LUTs.
- Code lives in the fixed first 32 KiB; page data banks into
0x8000–0xBFFF
with SMS_mapROMBank(n).
- Embed the ROM header once at file scope:
SMS_EMBED_SEGA_ROM_HEADER(9999,0)
(+ optional SMS_EMBED_SDSC_HEADER_AUTO_DATE(...)), else ihx2sms can't fix a
checksum and real hardware/emulators may reject it.
Version consistency (the #1 build breaker)
SMSlib.h and SMSlib.lib must come from the same devkitSMS version. Across
releases, SMS_addSprite changed from a function to a macro over
SMS_addSprite_f and the calling convention moved to __sdcccall(1) (needs SDCC
≥ 4.2). Symptoms and fixes:
?ASlink-Warning-Undefined Global '_SMS_addSprite' → mismatched header/lib.
Use one consistent set (the bundle, or the project's own vendored pair).
error 98: conflict with previous declaration of 'putchar' → old code vs SDCC
4.2 <stdio.h>; drop the local prototype, use SMSlib's text renderer.
More in references/toolchain-and-build.md.
Black-screen triage
When a ROM compiles but shows black, check in this order (see
references/testing.md, references/game-structure.md):
- Display left off — no
SMS_displayOn() after setup.
- Palette never loaded — every pixel maps to a black entry. Load BG/sprite
palettes before the first frame.
- Tiles/tilemap never uploaded — nothing to show. Load with display off.
- VRAM written outside VBlank — setup raced the beam; do bulk loads with the
display off, per-frame writes after
SMS_waitForVBlank().
- Crash in setup — a bad pointer/among the
UNSAFE_* calls wedged main
before the loop. Add a smstest expectvdp display 1 right after setup.
- Missing/!bad ROM header — checksum not fixed; re-check the embed macro.
Run smstest game.sms tests/playtest.txt (start from the smoke test in
references/testing.md) to see exactly which frame goes wrong and to grab .ppm
screenshots.
Bundled resources
scripts/setup_toolchain.sh — one-shot toolchain install + harness build + smoke test.
assets/devkitSMS/ — pinned, consistent SMSlib/PSGlib/crt0/headers + tool sources (VERSION.txt explains the version rule).
assets/template/ — Makefile, Makefile.banked, and a commented src/main.c game-loop skeleton.
assets/smstest/ — the headless SMS test emulator (smstest.c + superzazu's MIT Z80 core) and its build; ATTRIBUTION.md credits sources.
assets/smsvideo/ — reusable pre-rendered-video toolkit: smsvideo.py (delta/snapshot tile-stream encoder + verifier), smsvideo.{c,h} (runtime player), mkrom.py (bank splicer), banked Makefile, example.c. See references/prerendered-video.md.
assets/playtest.example.txt — a real, worked playtest script.
references/toolchain-and-build.md — install, pipeline, flags, memory model, version gotchas, GG/SG notes.
references/smslib-api.md — task-oriented SMSlib + PSGlib API map.
references/game-structure.md — frame loop, fixed-point, actor pattern, collision, HUD, state machine.
references/rpg-adventure-roguelike.md — scrolling OR flip-screen tilemap worlds, menus & dialogue boxes, turn-based/roguelike loops (procgen, FOV) AND real-time action combat (i-frames, knockback), party HUD/minimap, SRAM battery save (+ how to test it). For RPGs/adventures/roguelikes.
references/grid-and-maze-games.md — tile-grid/maze games: lane movement, turn-queuing, tunnel wrap, ghost-style target-cell AI, per-actor speed, procedural sprite/tile generation, maze flood-fill validation, clearing the nametable. Also metatiles (16×16 blocks) for large maps.
references/prerendered-video.md — effects the Z80 can't compute live (pseudo-3D road, rotations): pre-render offline and stream from ROM as simulation-indexed tile deltas + snapshots, inside the VBlank budget. Keeps it a game (the sim seeks the video), not a movie.
references/assets.md — images→tiles, music→PSG, maps, compression, banking.
references/testing.md — the harness, the playtest DSL, making variables visible, definition of done.