[AI] Cannonball for Sega 32X
- physical : no
- rom : yes
- licensing : unlicensed
- code : cannonballforsega32xbyharoldook
- Date(s) : 2026
- Tag(s) : Pixel Art, sega-32x, Singleplayer, Generative AI used
- By(s) : haroldo-ok
Data retrieved from haroldo-ok.itch.io
Cannonball for Sega 32X by haroldo-ok
- title : Cannonball for Sega 32X by haroldo-ok
- status : Released
- tags : Pixel Art,
sega-32x,
Singleplayer
- ai : AI Assisted, Code
- last updated : 2026
- by : haroldo-ok
- code : cannonballforsega32xbyharoldook
Github repo: https://github.com/haroldo-ok/cannonball-outrun-32x
OutRun 32X — Cannonball-style arcade racer engine for the Sega 32X
An original-work, from-scratch OutRun-style racing engine and 32X platform port, using djyt's Cannonball (https://github.com/djyt/cannonball) as the gameplay reference and viciious's Doom 32X resurrection (https://github.com/viciious/d32xr) as the 32X architectural reference. All code is original; all art/audio assets are procedurally generated (tools/gen_assets.py). No Sega or Cannonball code/ROM content is included.
Track: branching pyramid with VISIBLE forks
OutRun-style fork pyramid with 15 generated placeholder stages (src/engine/trackdata.c): 5 depth levels on a diamond lattice. The last 16 segments before every checkpoint (except the final level) render as a real road split: two full roads diverge 8 world-units/segment up to +-128 around a grass wedge and continue parallel to the horizon (FORK_SEGS / FORK_SEP_MAX in engine.h). A flashing "FORK AHEAD" HUD warning fires as the split comes into view; the branch you take is the side your car is on at the checkpoint. On the far side the camera recenters onto the chosen road (pos_x folded by -=/+128 u with a render-side ease, so there is no screen snap), the stage banner flashes the new stage's name, and play continues.
Per-stage visual identity (src/sh2/theme.c): every stage has its own road color set, landscape color set, sky ramp (blue / sunset / glacier), and decor sprite type (round trees, palms, pines, cacti, rocks - all procedural, generated by tools/gen_assets.py). In the fork, each branch renders with ITS destination stage's theme (road palette + environs grass), and fork signage points into its branch (left/right arrow sprites), so the player literally reads the choice off the road.
Stages are generated once at Track_Build() into a flat materialized segment array with hand-varied personalities (COCONUT COAST opener, PALM MARINA, GOLDEN DUNES, ALPINE PASS hairpins, REDWOOD forest walls, METROPOLIS gantries, MOJAVE straights, ...). At runtime the engine remaps driven z by a per-stage delta, updated exactly once per checkpoint - never inside the render z-walk.
Seam hygiene (regression-tested): per-stage end elevation is captured into stage_climb[] at build time (the raw slot is destroyed by the next stage's generation, which silently zeroed the old continuity fold -> instant cliffs), and ALL track queries clamp to the active stage window, so the far view ahead of a checkpoint shows the flat fork continuation rather than peeking at the wrong sibling branch (phantom vertical drops). The renderer also fills any scanline gap between sky and the highest road row with far grass - hilly stages whose vanishing point dips below the eye line used to show a black band across the horizon.
Optimization notes (d32xr idioms)
Modeled on Doom 32X XR (refs/d32xr): precomputed per-depth 1/z tables like r_main.c's yslope (render_init), all divides evicted from scanline/pixel loops (SEG_LEN=64 -> >>6/&63), per-step colors/edges hoisted out of row loops, divisions replaced by exact reciprocal multiply+shift (hud numbers, kmh, tsec), 2 divides per sprite instead of per blit (1:1 path has zero), fixed-point only (SH2 has no FPU).
Layout
src/engine/ portable fixed-point (16.16) racing engine (race state machine, physics, road model, traffic, track) src/sh2/ 32X platform layer: crt0, video/palette/framebuffer, renderer, HUD, sprites, telemetry, diagnostics src/m68k/ Mega Drive side: 0x3F0 security/startup module (binary data, standard on every 32X cart), 68k pad publisher ld/ SH2 link script (text ROM @0x02000000, data -> SDRAM) tests/host/ engine unit tests (43 checks) + render_snap: runs the real SH2 renderer on x86 against a memory framebuffer, writes BMP/PNG evidence frames and machine-checks the fork split, themed branch palettes, stage identity colors, and "no black band below the horizon" tests/mame/ MAME emulator harness: boot smoke test, input point-to- point test (START button!), screenshot capture, and the debugger probe scripts used during bring-up tests/mame/bios self-authored SH2/68k BIOS stubs (replace Sega's copyrighted boot ROMs; only used by the test harness) tools/ asset generator, ROM header fixer, checksum patcher, toolchain build/restore scripts
Build
make # produces outrun32x.32x (512 KiB)
Requires an SH2 cross toolchain (sh-elf-gcc, big-endian -m2) and m68k-linux-gnu binutils/gcc. If the prebuilt toolchain under tc/ is missing, make self-heals it from toolchain_sh.tar.zst via tools/restore_toolchain.sh.
Diagnostic build flags: make DIAGCRT0=1 (boot-step liveness byte at SDRAM 0x0603FFFC), make DIAG=<n> (bisect harness, src/sh2/diag.c).
Controls
START start game / leave title screen A or C gas B brake Left/Right steer
The title screen runs an attract-mode road demo; the bottom-left of the HUD shows live input as an IN:xx hex byte (80=START, 40=A, 20=C, 10=B, 08=right, 04=left) — useful to verify your emulator routes the pad through.
Tests
tests/run_tests.sh # full suite gcc -DENGINE_TEST_HOOKS ... # host engine tests (see tests/host) python3 tests/test_rom_header.py outrun32x.32x
MAME harness (Debian mame 0.276; always use -nodrc — the SH2 DRC backend segfaults on this host even with an empty guest):
tests/mame/bios/make # rebuild BIOS stub zip (32x.zip) mame 32x -cart outrun32x.32x -rompath tests/mame/roms -nodrc \ -autoboot_script tests/mame/input_steer.lua \ -video soft -sound none -nothrottle -skip_gameinfo -str 20
The MAME test that guards the "press START to start" regression is tests/mame/input_steer.lua: it boots the cart, injects START through the emulated joypad port, and asserts the engine state machine leaves ST_TITLE for ST_RACE (via the telemetry block at SDRAM 0x0603E000), then drives and steers and asserts speed/car position respond.
Hard-won MAME/32X bring-up notes (read before touching crt0/BIOS stubs!)
SH2 reset vectors are PC first ([0]=PC, [4]=SP) — the opposite of the 68000. The security module jumps to the 68k entry only after COMM8 == the ROM checksum word (@0x18E); the master SH2 BIOS stub provides it.
MAME's SH2 interpreter implements only a subset of system instructions; an unimplemented one (or a wild jump) throws an invisible illegal- instruction exception that cascades. The BIOS stubs therefore install a fault handler (vbr+0x10/0x18/0x24) that parks the faulting PC/SR in SDRAM for CI.
Lua address_space reads do NOT apply the SH2 m_am address mask: use fully masked addresses in probes (SDRAM 0x060xxxxx, sysregs 0x00004xxx). MMIO mirror reads through Lua can be misleading; trust raw SDRAM, screenshots and gdb hardware watchpoints.
The 32X device in the "32x" driver is driven by the cartridge's own 0x3F0-module; the g_bios file in the stub zip is a formality.
Screenshots