Sega 32X game development & porting
This skill turns a game — an existing port target or a new idea — into a
playable, verified .32x cartridge ROM. It encodes the toolchain, hardware
model, project layout, build pipeline, optimization playbook, and the
automated PicoDrive test methodology that catches the number-one 32X failure:
a ROM that compiles cleanly but boots to a black screen.
The reference implementation for everything here is Victor Luchits' DOOM 32X:
Resurrection (d32xr) built with Chilly Willy's Sega devkit (32XDK). When
in doubt about how to do something on real hardware, look at how d32xr does it.
Pick a rendering family for the game:
- Software polygon 3D (fixed-point transform, reciprocal-divide projection,
flat-triangle rasterizer — no GPU/FPU) →
references/software-3d.md and the
ready-made engine in assets/3d/. Games shipped: a rally racer, a rail
shooter, kart racers (polygon and Mode-7).
- Voxel landscape (Comanche-style scrolling heightmap of perspective-scaled
cells; camera above a near plane; per-slice hoisted divide) →
references/voxel-landscape.md. Game shipped: a voxel shmup (Zepton).
- 2D sprites (packed 8bpp framebuffer + scanline shape fills; menus, shmups,
arcade ports) →
references/2d-and-shmup.md and assets/2d/gfx_shapes.c.
Game shipped: a faithful vertical shooter with a full menu flow.
- First-person raycaster (grid dungeon crawler: quarter-res cast + 2×2
expand, per-column DDA, depth-sorted billboards, shade-bank palette fog) →
references/software-3d.md. Game shipped: a dungeon crawler at 30 fps.
- Strategy / grid (top-down RTS or tactics: A* pathfinding, three-state fog
of war, deterministic grid logic with interpolated rendering, worker economy)
→
references/strategy-and-grid.md. Game shipped: a Warcraft-style RTS with
battery-backed SRAM saves.
- Pseudo-3D from sprites (pre-baked view angles or sprite-stacking; racers,
driving games) →
references/software-3d.md.
For worked examples of complete 32X ports (a DOS software-3D racer, HTML5
games, a voxel PICO-8 shmup, and a DOS action-adventure with a 68000 side and an
asset pipeline), see references/examples.md.
For sound, see references/audio.md (PWM stereo FIFO, a software voice
mixer, the framebuffer-redraw-starves-the-FIFO gotcha, and how to actually
verify audio via PCM capture + a spectral fingerprint).
Definition of done (do not stop early)
A 32X task is complete only when all of these hold. Treat them as a
checklist and report each one:
- It compiles —
make produces a .32x ROM with no errors.
- It links within RAM —
.data + .bss fit in SDRAM and BSS does not
collide with the SH-2 stacks (see the memory map below). This is checked
mechanically; a link that "succeeds" can still overflow RAM.
- It is not a black screen — a headless PicoDrive test boots the real ROM
and asserts that rendered frames are lit, colourful, and change over time.
- It is playable — scripted controller inputs drive the game through its
real states (title → menu → gameplay) and each checkpoint frame passes.
- The ROM is reachable by the user — the final
.32x is copied to a
known output path and presented (never left only in a scratch build dir).
Never declare success on "it compiled." A compiling black screen is the
default failure mode of a naive 32X port, and the whole point of this skill is
to get past it.
Step 0 — Install the toolchain
Chilly Willy's devkit provides both cross-compilers: sh-elf-gcc (the two
SH-2 CPUs) and m68k-elf-gcc (the Genesis 68000). Install release
20220418 into /opt/toolchains/sega (override with GENDEV=<path>):
curl -LO https://github.com/viciious/32XDK/releases/download/20220418/chillys-sega-devkit-20220418-opt.tar.zst
sudo tar --zstd -xf chillys-sega-devkit-20220418-opt.tar.zst -C /
Verify: /opt/toolchains/sega/sh-elf/bin/sh-elf-gcc --version (GCC 12.1).
Full details, exact flags, and CI-cache tricks: references/toolchain-and-build.md.
Pick the workflow
- Porting an existing game (DOS, Genesis, PICO-8, HTML5, a C/Pascal
codebase, an emulator core, etc.) → read
references/porting-workflow.md.
This is the most common request. The core idea: split the game into a
platform-clean core and a thin 32X shell, get it running on desktop first
as an oracle, then bring it up on hardware incrementally. PICO-8 carts are
a recurring target with a reusable compatibility layer — see
references/pico8-porting.md. For interpreter-driven games (RPG Maker, VN engines),
don't port the player — compile the content to bytecode + a tiny VM; for a
large one, run a content audit first to bound the work, and decide ROM
banking / 32X-CD before freezing the asset format (see
references/porting-workflow.md and references/architecture.md).
- Creating a new native 32X game from scratch → start from the project
layout below and
references/architecture.md; the porting doc's "bring-up
order" still applies.
- Optimizing / improving an existing 32X project → read
references/optimization.md. Mine d32xr for patterns (fixed-point,
bitshifting, hoisting work out of loops, offloading to the second SH-2,
cache alignment).
- Debugging a black screen / crash → jump to "Black-screen triage" below
and
references/testing.md.
Whatever the workflow, wire up the tests from references/testing.md early.
They are how you know you are done rather than hoping.
Canonical project layout
Keep game logic strictly separate from 32X hardware code. This is what makes a
port verifiable (you can run the same core on desktop) and what keeps the SH-2
side small.
game-32x/
├── Makefile # SH-2 + 68000 build → .32x (template in assets/)
├── src/
│ ├── core/ # portable C11: game logic, physics, rendering
│ │ # NO OS calls, NO float in hot paths, endian-clean
│ └── platform/
│ ├── 32x/ # SH-2 shell: main, hw/VDP, palette, audio, input
│ │ ├── mars.ld # SH-2 linker script (template in assets/)
│ │ ├── mars_start.s # SH-2 startup / ROM+Mars header + embedded 68000 bin
│ │ └── md_src/ # 68000 resident: controller + VBlank + music service
│ └── sdl/ # desktop reference shell (test oracle; optional but
│ # strongly recommended for ports)
├── tools/ # build-time asset converters, romfix (assets/)
├── tests/ # PicoDrive harness + scripts + verify_rom (assets/)
│ ├── harness.c # headless libretro host
│ ├── run_tests.py # runner + black-screen / playability assertions
│ ├── verify_rom.py # static ROM/ELF structural checks
│ └── scripts/ # point-to-point input scripts (boot, menu, play…)
└── rom/ (or release/) # OUTPUT: the final .32x lands here
Two-CPU rule of thumb: put the game on the master SH-2, dedicate the
slave SH-2 to a heavy parallel job (PWM audio mixing, or a rendering phase),
and use the 68000 for controller polling, VBlank timing, and native
YM2612/PSG music. See references/architecture.md.
Build → verify → emulate loop
make -j # → rom/<game>.32x (+ .elf + .map)
python3 tests/verify_rom.py rom/<game>.32x build/<game>.elf # static checks
python3 tests/run_tests.py # headless PicoDrive point-to-point
The Makefile template ends with a romfix step (writes the Genesis header
checksum and pads the ROM) and a check target that runs verify_rom.py. Wire
run_tests.py into CI. Build PicoDrive's libretro core once (instructions in
references/testing.md).
Work in small verified milestones. Build the game one feature at a time: a
HAL-free module (feature.c/.h, pure C, no mars.h) + a host test_feature.c
run with the system cc, then wire it into main and confirm with a PicoDrive
script + screenshot. Each slice ends green (host tests + verify_rom +
PicoDrive) before the next starts, so a regression can only be the last slice,
and "done" always means a passing host test and an on-screen capture — not "it
compiled." For content too long to watch frame-by-frame (a full level), build an
in-ROM verification accelerator (a tick-multiplier + protection behind an
unused button) and publish COMM-register telemetry so a headless run can
assert exact end-state — see references/testing.md. Full rhythm in
references/porting-workflow.md.
Hard constraints cheat-sheet
Memory map the SH-2 linker (mars.ld) must honor:
0x02000000 ROM (.text + .rodata; cartridge, read-only, ~4 MiB window)
0x06000000 SDRAM (256 KiB total, shared by both SH-2s):
.data (initialized, copied from ROM by startup)
.bss (zeroed by startup; heap grows up from its end)
...
0x0603FC00 top of master SH-2 stack (grows down) ← single-CPU layout
0x0603F800 / 0x06040000 split stacks if you use the slave SH-2
- SDRAM is only 256 KiB.
.data + .bss plus stacks must fit. Verify that
__bss_end stays well below the stack base (CI in d32xr asserts
bss_end < 0x603C000). Overflowing RAM is a top black-screen cause.
- Large/immutable data lives in ROM, not RAM. Decode assets at build time
and read them from the cartridge; do not
malloc big buffers.
- The ROM needs a valid Genesis + Mars header.
SEGA 32X at 0x100, the
Mars module header, correct SH-2 entry points/vector bases, ROM-end at
0x1A4, and the 16-bit word checksum at 0x18E. Always run the romfix step.
- Everything is big-endian. Byte-swap when reading little-endian source
assets (DOS files) at build time or load time.
- Mask the controller to the reliable 3-button subset (U/D/L/R, A/B/C,
Start). Several emulators mirror d-pad bits into the 6-button extended
nibble, making every direction read as a Jump/Back press.
Black-screen triage
When a ROM compiles but shows black, check in this order (details in
references/testing.md and references/architecture.md):
- RAM overflow —
.data + .bss exceeds SDRAM / collides with stacks.
--gc-sections stripped live code — the linker kept only the header and
discarded the game. verify_rom.py guards this by asserting known code
markers are present and .text is large.
- Palette never loaded — nonzero pixels all map to palette entry 0
(black). Seed the palette before the first frame.
- VDP / framebuffer not initialized, or frame buffers never flipped.
- 68000 handshake stall — startup released the slave/68000 through a stale
register, or a blocking audio/VGM wait wedged VBlank service.
- Asset blob placed beyond the fixed low-ROM window the 68000 copies from
at boot.
- A corrupt build/tree — if a minimal boot ROM is also black and a
known-good ROM isn't, the build itself is producing bad output (differences in
the SH-2 vector table/code from identical sources). The reliable fix is to
rebuild from a
cp -r of a booting tree. When the cause isn't obvious, work
the empirical black-screen ladder in references/testing.md (read the real
frame → palette-0 tell → cycle-colour hang test → minimal boot → isolate
render vs logic → cmp the ROMs → rebuild from known-good) rather than
guessing.
If instead the ROM draws once and then hangs, it's a hang, not a black
screen: build a per-frame heartbeat square (behind a debug flag) — if it
freezes the SH-2 crashed; if it keeps animating you have a logic deadlock — plus
an interpreter/state overlay. See references/testing.md.
For a large content port (a multi-map RPG), audit the whole project first
(inventory every map/opcode and what your VM already covers) and settle the
ROM-banking / 32X-CD question before freezing the asset address format — see
references/porting-workflow.md and references/architecture.md.
Optimization quick rules (full playbook in references/optimization.md)
When asked to "optimize the code":
- Hoist invariant work out of loops; precompute tables.
- Use bit-shifts and masks instead of
*, /, % by powers of two; the
SH-2 has no fast hardware divide.
- Use fixed-point (e.g. 16.16), never floating point, in hot paths.
- Offload a parallel workload to the slave SH-2 via the COMM registers.
- Mark hot, DMA-touched routines with the cache-aligned section attribute (see
ATTR_DATA_CACHE_ALIGN in d32xr) and clear cache lines deliberately.
- Build
release with -Os -flto -fomit-frame-pointer -ffunction-sections -fdata-sections -Wl,--gc-sections.
- Beware the GCC 12.1 SH-2 miscompile traps (12-byte struct returns, 64-bit
multiply chains, dropped stores, calls across mixed
-O levels): if a
correct program misbehaves only on hardware, see the workarounds in
references/toolchain-and-build.md before doubting your logic.
- Frame time is quantised to 60/n (flip waits for vblank), so optimize to get
under the next vblank boundary, not for raw pixel counts. For mostly-static
scenes use dirty-rectangle rendering over a cached background (per-framebuffer
dirty lists; HUD cached by content hash) — full playbook in
references/optimization.md.
- Look at d32xr's
r_phase*.c, sh2_*.s, and marsnew.c for concrete idioms.
Bundled resources
references/toolchain-and-build.md — devkit install, both compilers, exact
flags, linker map, header/romfix, CI.
references/architecture.md — dual SH-2, 68000 role, SDRAM budget, VDP
framebuffer & palette, PWM audio, VGM music, controllers, inter-CPU COMM.
references/porting-workflow.md — the step-by-step port method (core/shell
split, desktop oracle, asset conversion, timing model, incremental bring-up).
references/software-3d.md — the flat-polygon 3D pipeline + assets/3d/.
references/voxel-landscape.md — Comanche-style voxel terrain (cell-billboard
vs per-column raycaster, projection, the compute-vs-fillrate trade), plus the
into-the-screen projectile/enemy model.
references/2d-and-shmup.md — 2D sprite games: shape primitives, menu/flow
state machines, faithful-graphics reconstruction from source, event-driven
sound.
references/pico8-porting.md — porting PICO-8 carts: the pico8_api compat
layer (palette→CRAM, sspr/pal/print, btn/atan2 conventions), data extraction,
resolution doubling, and the PICO-8/Mode-7 scanline hot-path idioms.
references/strategy-and-grid.md — RTS/tactics/grid-crawler games: host-tested
A* pathfinding, three-state fog of war, deterministic grid logic with
interpolated rendering, RTS AI/economy/construction, and turn-based grid rules.
references/audio.md — PWM FIFO, software voice mixer, PCM-capture verification. Also a MIDI→VGM pipeline (YM2612/PSG) to
generate Genesis-side music from a game's MIDI score.
references/optimization.md — SH-2 optimization patterns from d32xr, plus
how to measure effective framerate through the video harness and the
fillrate-vs-compute playbook.
references/testing.md — build PicoDrive, the harness + script DSL + runner,
black-screen assertions, static ROM verification, and the hard-won debugging
lessons (rebuild-from-known-good-tree, render-vs-logic isolation, the
"run N ≠ N iterations" caveat, pixel-detection false-positives, button-map
diagnosis).
assets/ — ready-to-adapt Makefile, mars.ld, romfix.py,
verify_rom.py, harness.c, run_tests.py, test scripts, the 3d/ engine,
and 2d/gfx_shapes.c shape fills.
1---2name: sega-32x-gamedev3description: Create, improve, optimize, and port games for the Sega 32X (Sega Mars) using Chilly Willy's 32XDK toolchain and the DOOM 32X Resurrection (d32xr) codebase as reference. Use whenever the user wants to port a game (DOS, Genesis, PICO-8, HTML5, or any platform) to the 32X, build or debug a homebrew 32X ROM, or make a 32X game of any kind: software-3D / polygon (fixed-point transform, flat-triangle rasterizer), a Comanche-style voxel landscape / voxel shmup, or a 2D sprite game / shooter / arcade port with menus. Also for the second SH-2 / dual-core, optimizing SH-2 code, measuring framerate, fixing a black-screen ROM, or PicoDrive tests. Trigger even when the user only says "port X to 32X", "make a 32X game", "3D/voxel on 32X", "compile this for 32X", mentions a `.32x` ROM, SH-2 / dual-SH2 / 68000 Mars hardware, the mars.ld linker, VDP framebuffer/palette issues, or PWM audio. Prefer this skill over general knowledge for the 32X: its toolchain, memory map, and test methodology are easy to get subtly wrong.4---56# Sega 32X game development & porting78This skill turns a game — an existing port target or a new idea — into a9**playable, verified `.32x` cartridge ROM**. It encodes the toolchain, hardware10model, project layout, build pipeline, optimization playbook, and the11automated PicoDrive test methodology that catches the number-one 32X failure:12a ROM that compiles cleanly but boots to a **black screen**.1314The reference implementation for everything here is Victor Luchits' **DOOM 32X:15Resurrection** (`d32xr`) built with **Chilly Willy's Sega devkit (32XDK)**. When16in doubt about how to do something on real hardware, look at how d32xr does it.1718- d32xr source: https://github.com/viciious/d32xr19- 32XDK releases: https://github.com/viciious/32XDK/releases202122Pick a **rendering family** for the game:2324- **Software polygon 3D** (fixed-point transform, reciprocal-divide projection,25 flat-triangle rasterizer — no GPU/FPU) → `references/software-3d.md` and the26 ready-made engine in `assets/3d/`. Games shipped: a rally racer, a rail27 shooter, kart racers (polygon and Mode-7).28- **Voxel landscape** (Comanche-style scrolling heightmap of perspective-scaled29 cells; camera above a near plane; per-slice hoisted divide) →30 `references/voxel-landscape.md`. Game shipped: a voxel shmup (Zepton).31- **2D sprites** (packed 8bpp framebuffer + scanline shape fills; menus, shmups,32 arcade ports) → `references/2d-and-shmup.md` and `assets/2d/gfx_shapes.c`.33 Game shipped: a faithful vertical shooter with a full menu flow.34- **First-person raycaster** (grid dungeon crawler: quarter-res cast + 2×235 expand, per-column DDA, depth-sorted billboards, shade-bank palette fog) →36 `references/software-3d.md`. Game shipped: a dungeon crawler at 30 fps.37- **Strategy / grid** (top-down RTS or tactics: A* pathfinding, three-state fog38 of war, deterministic grid logic with interpolated rendering, worker economy)39 → `references/strategy-and-grid.md`. Game shipped: a Warcraft-style RTS with40 battery-backed SRAM saves.41- **Pseudo-3D from sprites** (pre-baked view angles or sprite-stacking; racers,42 driving games) → `references/software-3d.md`.434445For **worked examples** of complete 32X ports (a DOS software-3D racer, HTML546games, a voxel PICO-8 shmup, and a DOS action-adventure with a 68000 side and an47asset pipeline), see `references/examples.md`.484950For **sound**, see `references/audio.md` (PWM stereo FIFO, a software voice51mixer, the framebuffer-redraw-starves-the-FIFO gotcha, and how to actually52verify audio via PCM capture + a spectral fingerprint).5354## Definition of done (do not stop early)5556A 32X task is complete only when **all** of these hold. Treat them as a57checklist and report each one:58591. **It compiles** — `make` produces a `.32x` ROM with no errors.602. **It links within RAM** — `.data + .bss` fit in SDRAM and BSS does not61 collide with the SH-2 stacks (see the memory map below). This is checked62 mechanically; a link that "succeeds" can still overflow RAM.633. **It is not a black screen** — a headless PicoDrive test boots the real ROM64 and asserts that rendered frames are lit, colourful, and change over time.654. **It is playable** — scripted controller inputs drive the game through its66 real states (title → menu → gameplay) and each checkpoint frame passes.675. **The ROM is reachable by the user** — the final `.32x` is copied to a68 known output path and presented (never left only in a scratch build dir).6970Never declare success on "it compiled." A compiling black screen is the71default failure mode of a naive 32X port, and the whole point of this skill is72to get past it.7374## Step 0 — Install the toolchain7576Chilly Willy's devkit provides both cross-compilers: **`sh-elf-gcc`** (the two77SH-2 CPUs) and **`m68k-elf-gcc`** (the Genesis 68000). Install release78`20220418` into `/opt/toolchains/sega` (override with `GENDEV=<path>`):7980```sh81curl -LO https://github.com/viciious/32XDK/releases/download/20220418/chillys-sega-devkit-20220418-opt.tar.zst82sudo tar --zstd -xf chillys-sega-devkit-20220418-opt.tar.zst -C /83```8485Verify: `/opt/toolchains/sega/sh-elf/bin/sh-elf-gcc --version` (GCC 12.1).86Full details, exact flags, and CI-cache tricks: **`references/toolchain-and-build.md`**.8788## Pick the workflow8990- **Porting an existing game** (DOS, Genesis, PICO-8, HTML5, a C/Pascal91 codebase, an emulator core, etc.) → read **`references/porting-workflow.md`**.92 This is the most common request. The core idea: split the game into a93 *platform-clean core* and a *thin 32X shell*, get it running on desktop first94 as an oracle, then bring it up on hardware incrementally. **PICO-8 carts** are95 a recurring target with a reusable compatibility layer — see96 **`references/pico8-porting.md`**. For **interpreter-driven games** (RPG Maker, VN engines),97 don't port the player — compile the content to bytecode + a tiny VM; for a98 **large** one, run a content audit first to bound the work, and decide ROM99 banking / 32X-CD before freezing the asset format (see100 `references/porting-workflow.md` and `references/architecture.md`).101- **Creating a new native 32X game from scratch** → start from the project102 layout below and `references/architecture.md`; the porting doc's "bring-up103 order" still applies.104- **Optimizing / improving an existing 32X project** → read105 **`references/optimization.md`**. Mine d32xr for patterns (fixed-point,106 bitshifting, hoisting work out of loops, offloading to the second SH-2,107 cache alignment).108- **Debugging a black screen / crash** → jump to "Black-screen triage" below109 and `references/testing.md`.110111Whatever the workflow, wire up the tests from **`references/testing.md`** early.112They are how you *know* you are done rather than hoping.113114## Canonical project layout115116Keep game logic strictly separate from 32X hardware code. This is what makes a117port verifiable (you can run the same core on desktop) and what keeps the SH-2118side small.119120```121game-32x/122├── Makefile # SH-2 + 68000 build → .32x (template in assets/)123├── src/124│ ├── core/ # portable C11: game logic, physics, rendering125│ │ # NO OS calls, NO float in hot paths, endian-clean126│ └── platform/127│ ├── 32x/ # SH-2 shell: main, hw/VDP, palette, audio, input128│ │ ├── mars.ld # SH-2 linker script (template in assets/)129│ │ ├── mars_start.s # SH-2 startup / ROM+Mars header + embedded 68000 bin130│ │ └── md_src/ # 68000 resident: controller + VBlank + music service131│ └── sdl/ # desktop reference shell (test oracle; optional but132│ # strongly recommended for ports)133├── tools/ # build-time asset converters, romfix (assets/)134├── tests/ # PicoDrive harness + scripts + verify_rom (assets/)135│ ├── harness.c # headless libretro host136│ ├── run_tests.py # runner + black-screen / playability assertions137│ ├── verify_rom.py # static ROM/ELF structural checks138│ └── scripts/ # point-to-point input scripts (boot, menu, play…)139└── rom/ (or release/) # OUTPUT: the final .32x lands here140```141142Two-CPU rule of thumb: put the **game** on the master SH-2, dedicate the143**slave SH-2** to a heavy parallel job (PWM audio mixing, or a rendering phase),144and use the **68000** for controller polling, VBlank timing, and native145YM2612/PSG music. See `references/architecture.md`.146147## Build → verify → emulate loop148149```sh150make -j # → rom/<game>.32x (+ .elf + .map)151python3 tests/verify_rom.py rom/<game>.32x build/<game>.elf # static checks152python3 tests/run_tests.py # headless PicoDrive point-to-point153```154155The Makefile template ends with a `romfix` step (writes the Genesis header156checksum and pads the ROM) and a `check` target that runs `verify_rom.py`. Wire157`run_tests.py` into CI. Build PicoDrive's libretro core once (instructions in158`references/testing.md`).159160**Work in small verified milestones.** Build the game one feature at a time: a161HAL-free module (`feature.c/.h`, pure C, no `mars.h`) + a host `test_feature.c`162run with the system `cc`, *then* wire it into `main` and confirm with a PicoDrive163script + screenshot. Each slice ends green (host tests + `verify_rom` +164PicoDrive) before the next starts, so a regression can only be the last slice,165and "done" always means a passing host test *and* an on-screen capture — not "it166compiled." For content too long to watch frame-by-frame (a full level), build an167**in-ROM verification accelerator** (a tick-multiplier + protection behind an168unused button) and publish **COMM-register telemetry** so a headless run can169assert exact end-state — see `references/testing.md`. Full rhythm in170`references/porting-workflow.md`.171172## Hard constraints cheat-sheet173174Memory map the SH-2 linker (`mars.ld`) must honor:175176```1770x02000000 ROM (.text + .rodata; cartridge, read-only, ~4 MiB window)1780x06000000 SDRAM (256 KiB total, shared by both SH-2s):179 .data (initialized, copied from ROM by startup)180 .bss (zeroed by startup; heap grows up from its end)181 ...1820x0603FC00 top of master SH-2 stack (grows down) ← single-CPU layout1830x0603F800 / 0x06040000 split stacks if you use the slave SH-2184```185186- **SDRAM is only 256 KiB.** `.data + .bss` plus stacks must fit. Verify that187 `__bss_end` stays well below the stack base (CI in d32xr asserts188 `bss_end < 0x603C000`). Overflowing RAM is a top black-screen cause.189- **Large/immutable data lives in ROM, not RAM.** Decode assets at build time190 and read them from the cartridge; do not `malloc` big buffers.191- **The ROM needs a valid Genesis + Mars header.** `SEGA 32X` at 0x100, the192 Mars module header, correct SH-2 entry points/vector bases, ROM-end at193 0x1A4, and the 16-bit word checksum at 0x18E. Always run the `romfix` step.194- **Everything is big-endian.** Byte-swap when reading little-endian source195 assets (DOS files) at build time or load time.196- **Mask the controller to the reliable 3-button subset** (U/D/L/R, A/B/C,197 Start). Several emulators mirror d-pad bits into the 6-button extended198 nibble, making every direction read as a Jump/Back press.199200## Black-screen triage201202When a ROM compiles but shows black, check in this order (details in203`references/testing.md` and `references/architecture.md`):2042051. **RAM overflow** — `.data + .bss` exceeds SDRAM / collides with stacks.2062. **`--gc-sections` stripped live code** — the linker kept only the header and207 discarded the game. `verify_rom.py` guards this by asserting known code208 markers are present and `.text` is large.2093. **Palette never loaded** — nonzero pixels all map to palette entry 0210 (black). Seed the palette before the first frame.2114. **VDP / framebuffer not initialized**, or frame buffers never flipped.2125. **68000 handshake stall** — startup released the slave/68000 through a stale213 register, or a blocking audio/VGM wait wedged VBlank service.2146. **Asset blob placed beyond the fixed low-ROM window** the 68000 copies from215 at boot.2167. **A corrupt build/tree** — if a *minimal* boot ROM is also black and a217 known-good ROM isn't, the build itself is producing bad output (differences in218 the SH-2 vector table/code from identical sources). The reliable fix is to219 rebuild from a `cp -r` of a booting tree. When the cause isn't obvious, work220 the **empirical black-screen ladder** in `references/testing.md` (read the real221 frame → palette-0 tell → cycle-colour hang test → minimal boot → isolate222 render vs logic → `cmp` the ROMs → rebuild from known-good) rather than223 guessing.224225If instead the ROM **draws once and then hangs**, it's a *hang*, not a black226screen: build a per-frame **heartbeat square** (behind a debug flag) — if it227freezes the SH-2 crashed; if it keeps animating you have a logic deadlock — plus228an interpreter/state overlay. See `references/testing.md`.229230For a **large content port** (a multi-map RPG), audit the whole project first231(inventory every map/opcode and what your VM already covers) and settle the232ROM-banking / 32X-CD question before freezing the asset address format — see233`references/porting-workflow.md` and `references/architecture.md`.234235## Optimization quick rules (full playbook in references/optimization.md)236237When asked to "optimize the code":238239- Hoist invariant work **out of loops**; precompute tables.240- Use **bit-shifts and masks** instead of `*`, `/`, `%` by powers of two; the241 SH-2 has no fast hardware divide.242- Use **fixed-point** (e.g. 16.16), never floating point, in hot paths.243- **Offload** a parallel workload to the slave SH-2 via the COMM registers.244- Mark hot, DMA-touched routines with the cache-aligned section attribute (see245 `ATTR_DATA_CACHE_ALIGN` in d32xr) and clear cache lines deliberately.246- Build `release` with `-Os -flto -fomit-frame-pointer -ffunction-sections247 -fdata-sections -Wl,--gc-sections`.248- **Beware the GCC 12.1 SH-2 miscompile traps** (12-byte struct returns, 64-bit249 multiply chains, dropped stores, calls across mixed `-O` levels): if a250 *correct* program misbehaves only on hardware, see the workarounds in251 `references/toolchain-and-build.md` before doubting your logic.252- **Frame time is quantised to 60/n** (flip waits for vblank), so optimize to get253 *under the next vblank boundary*, not for raw pixel counts. For mostly-static254 scenes use **dirty-rectangle rendering over a cached background** (per-framebuffer255 dirty lists; HUD cached by content hash) — full playbook in256 `references/optimization.md`.257- Look at d32xr's `r_phase*.c`, `sh2_*.s`, and `marsnew.c` for concrete idioms.258259## Bundled resources260261- `references/toolchain-and-build.md` — devkit install, both compilers, exact262 flags, linker map, header/romfix, CI.263- `references/architecture.md` — dual SH-2, 68000 role, SDRAM budget, VDP264 framebuffer & palette, PWM audio, VGM music, controllers, inter-CPU COMM.265- `references/porting-workflow.md` — the step-by-step port method (core/shell266 split, desktop oracle, asset conversion, timing model, incremental bring-up).267- `references/software-3d.md` — the flat-polygon 3D pipeline + `assets/3d/`.268- `references/voxel-landscape.md` — Comanche-style voxel terrain (cell-billboard269 vs per-column raycaster, projection, the compute-vs-fillrate trade), plus the270 into-the-screen projectile/enemy model.271- `references/2d-and-shmup.md` — 2D sprite games: shape primitives, menu/flow272 state machines, faithful-graphics reconstruction from source, event-driven273 sound.274- `references/pico8-porting.md` — porting PICO-8 carts: the `pico8_api` compat275 layer (palette→CRAM, sspr/pal/print, btn/atan2 conventions), data extraction,276 resolution doubling, and the PICO-8/Mode-7 scanline hot-path idioms.277- `references/strategy-and-grid.md` — RTS/tactics/grid-crawler games: host-tested278 A* pathfinding, three-state fog of war, deterministic grid logic with279 interpolated rendering, RTS AI/economy/construction, and turn-based grid rules.280- `references/audio.md` — PWM FIFO, software voice mixer, PCM-capture verification. Also a **MIDI→VGM** pipeline (YM2612/PSG) to281 generate Genesis-side music from a game's MIDI score.282- `references/optimization.md` — SH-2 optimization patterns from d32xr, **plus283 how to measure effective framerate through the video harness** and the284 fillrate-vs-compute playbook.285- `references/testing.md` — build PicoDrive, the harness + script DSL + runner,286 black-screen assertions, static ROM verification, **and the hard-won debugging287 lessons** (rebuild-from-known-good-tree, render-vs-logic isolation, the288 "run N ≠ N iterations" caveat, pixel-detection false-positives, button-map289 diagnosis).290- `assets/` — ready-to-adapt `Makefile`, `mars.ld`, `romfix.py`,291 `verify_rom.py`, `harness.c`, `run_tests.py`, test scripts, the `3d/` engine,292 and `2d/gfx_shapes.c` shape fills.