# # ### #### ##### # ###
## ## # # # # # # # #
# # # # # # # #### # #
# # # # # # # # #
# # ### #### ##### ##### #####
Sega Model 2 Hardware Runtime for Static Recompilation
Link a statically recompiled Model 2 game against native C hardware. No emulator, no interpreter — the i960 program becomes your executable, and this library is the arcade board it runs on.
Join the sp00nznet recomp Discord — the community hub for sp00nznet's recomp projects. Good place to ask questions, show a port you are working on, or find out what people are stuck on before you duplicate the effort.
Title-agnostic. Nothing here knows what game it is running. The memory map, geometry engine, rasterizer, coprocessor and I/O board all come from the board, not the title. Virtua Cop (1994) is the reference game it was built against, so the screenshots and some examples are its.
Alpha. No version has been tagged yet: the v0.1 to v0.3 milestones in the Changelog are untagged, and CHANGELOG.md tracks what has landed since. Virtua Cop boots, plays and draws, but its 3D is still visibly wrong in places.
Thirty-five Model 2 ROM sets now go through this library end to end. All of them lift, compile and boot; eleven draw; six run their attract sequence. See The Corpus Sweep.
Virtua Cop's attract mode, drawn by this library from the game's own display list: the geometry engine walks the command stream the recompiled i960 pushed into buffer RAM, the rasterizer textures and z-sorts it, and the System 24 tilemap engine puts the HUD on top. The matrices behind it were computed by the emulated MB86233 coprocessor running the game's own uploaded microcode.
This repo builds a library and its tests; a playable game comes from a port that links it (for example virtuacop). To build and check the library from a clean machine:
-
Install the tools.
- Windows: Visual Studio 2022 with Desktop development with C++, CMake
3.20 or newer, Git, and vcpkg
(cloned to
C:�cpkgand bootstrapped). ThenC:�cpkg�cpkg install sdl2:x64-windows. - Linux:
sudo apt install build-essential cmake git libsdl2-dev.
- Windows: Visual Studio 2022 with Desktop development with C++, CMake
3.20 or newer, Git, and vcpkg
(cloned to
-
Clone:
git clone https://github.com/sp00nznet/model2recomp.gitandcd model2recomp. -
Configure and build.
- Windows:
cmake -B build -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windowsthencmake --build build --config Release. - Linux:
cmake -B buildthencmake --build build -j.
- Windows:
-
Run the tests:
ctest --test-dir build -C Release. Expected:100% tests passed, 0 tests failed out of 3They need no ROMs. If CMake stops at
find_package(SDL2), the toolchain file is missing or SDL2 isn't installed for that triplet.
To lift and run a game of your own, follow docs/GETTING_STARTED.md. You supply your own ROM dump; none is distributed here, and neither is any code generated from one.
Static recompilation replaces a console or arcade CPU with native code: you
lift the machine code to C, compile it for the host, and it runs directly on
your processor. That gets you a CPU — it does not get you a machine. The
lifted code still expects to write a polygon to a register at 0x00804000 and
have something draw it.
model2recomp is that something. It is the rest of a Sega Model 2 board as a plain C library:
- a memory bus that routes the i960's 32-bit address space to the hardware behind it,
- an i960 CPU context — the register file, condition codes and call/return frame cache that lifted code operates on,
- the geometry engine and 3D rasterizer, a direct model of the hardware rather than a DSP emulator,
- the MB86233 "TGP" math coprocessor, which is a DSP emulator, because games upload their own microcode to it,
- System 24 tilemaps, palette, colour-translate and luma RAM,
- timers, the interrupt controller, the I/O board and backup SRAM,
- an SDL2 platform layer for window, input and presentation.
Your game project supplies the lifted i960 functions and a main(). This
library supplies everything they talk to.
MAME already runs Model 2, and runs it well — this library is built from MAME's research and could not exist without it. Static recomp is a different thing to want:
- Native speed — the game's own code is x86-64 machine code, not interpreted i960.
- Moddability — the game logic is C source you can read, patch and extend.
- Portability — the output targets anything with a C compiler.
- Preservation — a self-contained native binary with no emulator under it.
- Understanding — you cannot do this without learning the board properly.
Model 2 (1993) is the hardware behind Virtua Fighter 2, Daytona USA, Virtua Cop and Sega Rally. The original board:
| Part | Chip | Role |
|---|---|---|
| Main CPU | Intel i960KB @ 25 MHz | Game logic. This is what gets recompiled. |
| Math coprocessor | Fujitsu MB86233/86234 "TGP" | Matrix and vector maths, runs game-uploaded microcode |
| Geometry engine | Custom (TGP-based) | Transform, light, clip and z-sort the display list |
| Rasterizer | Sega/Lockheed-Martin custom | Textured, perspective-correct polygon fill |
| Tilemaps | Sega System 24 | Four 8×8 4bpp scroll layers, HUD and backgrounds |
| Sound | 68000 @ 10 MHz + YM3438 + 2× MultiPCM | Separate board, driven over a UART |
| I/O | Model 1 I/O Board 2 (837-11694) | Buttons, coins, lightgun, via dual-port RAM |
Honest status. "Done" means it does what the reference title needs and matches MAME's behaviour where it was ported from it.
| Subsystem | Status | Notes |
|---|---|---|
| i960 CPU context | Done | Registers, condition codes, call/return frame cache, IAC reinitialize |
| Memory bus | Done | Full 32-bit map: ROM, work RAM, buffer RAM, video, I/O, copro |
| Function dispatch | Done | Hash table from i960 address to native function, for indirect calls |
| Execution model | Done | Field sync at the guest's video-status busy-wait; interrupts dispatched there |
| TGP math coprocessor | Done | Full MB86233 core, math-table ROM, banked buffer-RAM window, FIFOs |
| Geometry engine | Done | Command stream parse, transform, lighting, clipping, z-sort |
| 3D rasterizer | Done | Perspective-correct texturing, luma RAM, 32-ramp colour translate, gamma |
| System 24 tilemaps | Done | Four layers, 8×8 4bpp, drawn in two passes around the 3D scene |
| Timers | Done | 4× countdown timers off the 25 MHz clock |
| Interrupt controller | Done | Request / acknowledge / enable, all lines serviced |
| EEPROM / backup SRAM | Done | 16 KB, saved and restored |
| I/O board | Partial | Model 1 board: command handshake, buttons and lightgun all reach the guest through DPRAM. CRX boards: the Sega 315-5649 register file and the 93C46 serial EEPROM hung off it |
| Interrupt frame | Done | The handler runs with the context snapshotted and restored around it, which is what the hardware guarantees. MODEL2_IRQMODE keeps the other models for A/B |
| Platform (SDL2) | Done | Window, scaling, presentation, keyboard and mouse |
| Sound | Stub | UART handshake only — no 68000, no MultiPCM, no audio |
| Copro data ROM | Done | The coprocessor's external data socket, masked to the region size in dwords. Empty on Virtua Cop; Daytona USA puts 4 MB of collision and height data there |
| Model 2A / 2B / 2C | Partial | The variant picks the memory map: 256 KB of program RAM, the Sega 315-5649 I/O chip with its serial EEPROM, the CRX texture and luma RAM windows, and 2B's relocated serial port. 2B still needs an ADSP-21062 SHARC and 2C an MB86235 for the math coprocessor, so neither can do 3D — but both can and do reach attract mode on tilemaps |
The rasterizer draws textured, perspective-correct, z-sorted polygons through the hardware's real colour path — palette entry to one of 32 ramps per channel in colour-translate RAM, indexed by luma, then gamma. Texels are filtered as MAME filters them: bilinear, with mipmaps, trilinear blending between levels, and the microtexture below level 0. See docs/technical/graphics-pipeline.md.
This trips everyone up, so it is worth being explicit. Model 2 has two things called "TGP" and they are not the same component:
recompiled i960 game code
| \
| 0x00880000/ \ 0x00800000 geo registers
| 0x00884000 \ 0x00804000 geo program port
v v
+----------------+ +---------------------+
| MATH COPRO | | GEOMETRY ENGINE |
| MB86233 DSP | | (also TGP silicon) |
| | | |
| src/copro.c | | src/geometry.c |
| EMULATED — | | MODELLED DIRECTLY — |
| runs the game's| | no DSP, the command |
| own microcode | | stream is parsed |
+----------------+ +---------------------+
| |
| matrices, | polygons
| vectors v
+--------------> +---------------------+
| RASTERIZER |
| src/geometry.c |
+---------------------+
The geometry engine is modelled directly — it parses the display list and produces polygons, exactly as MAME does, with no DSP involved. The math coprocessor cannot be, because the game uploads microcode to it at boot and then asks it to build every transformation matrix. Stub that and the game gets its own inputs back as a matrix; see docs/technical/tgp-coprocessor.md.
cmake -B build
cmake --build build --config ReleaseRequires CMake ≥ 3.20, a C17 compiler (MSVC 2022 and GCC tested), and
SDL2. On Windows, point CMake at vcpkg so find_package(SDL2) resolves:
cmake -B build -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake \
-DVCPKG_TARGET_TRIPLET=x64-windows
cmake --build build --config ReleaseThis repo builds a library. There is no game executable here and there
never will be — the .exe is built by your game project, which links this.
examples/minimal/main.c is the smallest thing that calls the API.
| Option | Default | Effect |
|---|---|---|
MODEL2RECOMP_BUILD_EXAMPLES |
ON |
Build examples/minimal |
MODEL2RECOMP_BUILD_TESTS |
ON |
Build tests/test_tilemap, tests/test_geometry |
Game projects normally turn both off:
set(MODEL2RECOMP_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(MODEL2RECOMP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
add_subdirectory(ext/model2recomp)
target_link_libraries(my_game PRIVATE model2recomp)Your recompiled game registers every lifted function by its i960 address, then hands control to the guest's own entry point. It never comes back — Model 2 games own their frame loop.
#include "model2recomp/model2recomp.h"
#include "model2recomp/func_table.h"
#include "model2recomp/i960.h"
#include "model2recomp/bus.h"
int main(int argc, char **argv)
{
model2recomp_init("My Model 2 Game", 2, MODEL2_ORIGINAL);
model2recomp_load_rom(argc > 1 ? argv[1] : "roms");
my_game_register_all(); /* generated: func_table_register(...) x N */
I960_FP = bus_read32(PRCB + 0x18);
I960_SP = I960_FP + 0x40;
func_table_call(RESET_IP); /* never returns; see Execution Model */
model2recomp_shutdown();
return 0;
}The frame boundary is inside the guest, not in your main(). See
docs/technical/execution-model.md — it is
the single most important thing to understand before you start.
model2recomp/
├── include/model2recomp/ Public API — one header per subsystem
│ ├── model2recomp.h Init, frame loop, field sync, shutdown
│ ├── i960.h CPU context, registers, condition codes
│ ├── bus.h Memory access + ROM loading
│ ├── func_table.h Address → native function dispatch
│ ├── video.h Geometry engine, rasterizer, tilemaps, palette
│ ├── copro.h MB86233 math coprocessor ports
│ ├── io.h, sound.h, timer.h, eeprom.h, platform.h
├── src/ Implementation
│ ├── copro.c MB86233 core + Model 2 board glue (956 lines)
│ ├── geometry.c Geometry engine + rasterizer (1374 lines)
│ ├── bus.c Address decode (748 lines)
│ ├── video.c Video registers, tilemaps, palette (511 lines)
│ ├── model2recomp.c Lifecycle, field sync, IRQ dispatch (372 lines)
│ ├── io.c, timer.c, i960.c, func_table.c, eeprom.c, sound.c
│ └── platform_sdl.c
├── examples/minimal/ Smallest program that calls the API
├── examples/corpus/ Generic launcher, one for every set in the sweep
├── tools/ i960 disassembler and lifter, ROM set reader,
│ and the corpus sweep
├── tests/ Standalone subsystem harnesses
├── docs/ Getting started + technical deep dives
└── ref/ Local scratch for MAME sources (git-ignored,
never redistributed)
- Getting Started — build a Model 2 port from a ROM dump, end to end.
- Execution Model — where the frame
boundary is, how interrupts land, why
main()never gets control back.
- Hardware Overview — the board, the full memory map, what each region does.
- Graphics Pipeline — display list, geometry engine, rasterizer, and the colour path.
- TGP Coprocessor — MB86233, the FIFO protocol, the math-table ROMs, and how it is scheduled.
- Debugging — environment variables, traces, screenshots, and how to find out why guest code never runs.
- Porting Targets — which Model 2 games are reachable from here, and what each one still needs.
- The Corpus Sweep — running every Model 2 set through the whole pipeline at once, and how the result is measured.
The original 1993 board is the variant implemented, and MAME's driver puts exactly three titles on it. That is the whole shortlist:
| Game | Year | Where it is |
|---|---|---|
| Virtua Cop | 1994 | The reference title — this library was built against it. Renders attract mode. |
| Daytona USA | 1993 | Boots and reaches the AM2 logo of its attract with a single-cabinet backup RAM, then holds, polling the drive board and the sound UART. |
| Desert Tank | 1994 | Boots and renders its attract sequence on the generic launcher, with no title-specific code at all. |
Everything else is a CRX board, and the memory map is now variant-aware: 256 KB of program RAM, the Sega 315-5649 I/O chip, and the CRX texture and luma windows. 2B and 2C still need an ADSP-21062 SHARC and an MB86235 for the math coprocessor — but that blocks 3D, not attract mode. Gunblade NY reaches attract on 2B with no coprocessor at all, drawing nothing but tilemaps, so a blank CRX set is stopped by something else. docs/technical/porting-targets.md works through every title and what each one needs.
There are only about thirty Model 2 titles, which is few enough to stop treating a port as a bespoke project and run the whole library as a corpus instead:
python tools/corpus.py allThat reads every ROM_START out of MAME's driver, builds each set's region
images, lifts its i960 program to C, builds it against this library on one
shared launcher, boots it headless and writes CORPUS.md — one row
per title, from "do we have the ROM" through to "does it reach attract mode".
Thirty-five sets on hand. All thirty-five extract, lift to C, compile and boot. Eleven put a picture on screen; six are running their attract sequence.
![]() |
![]() |
![]() |
| Virtua Cop — the reference title, Model 2 | Desert Tank — Model 2, and MACHINE_NOT_WORKING in MAME |
Virtua Cop 2 — 2A-CRX |
![]() |
![]() |
|
| Gunblade NY — 2B-CRX, with no coprocessor at all | Dynamite Baseball 97 — 2B-CRX |
Every one of those is the recompiled game drawing through this library at the board's native 496×384, captured headless by the sweep.
Two of them are worth a second look. Desert Tank is a title nobody had ported and MAME cannot run; it boots on the generic launcher with no title-specific code at all. Gunblade NY reaches attract on 2B-CRX, whose math coprocessor is an ADSP-21062 this library does not have — its attract sequence is tilemaps and text, and that path is shared across all four boards. So the missing DSPs block 3D, not attract, which is not what this project believed before the sweep ran.
The point of a corpus is that a fault found in one game is a fault fixed in all of them, and the clearest case is Virtua Cop itself — the title everything here was built against, and the one with the least left to find.
func_table's dispatch-depth guard was 500. Virtua Cop hit it three times a
run, which looked like nothing. In-game it was dropping a third of the picture:
| Depth 500 | Depth 20,000 |
|---|---|
![]() |
![]() |
Same frame, same input, same ROMs. 66,393 pixels — 34.9% of the screen — were black and are now drawn; the ground, and the panel on the WHARF AM2 sign. The guard was abandoning dispatches partway through the scene.
That was found by sweeping thirty-five titles, not by staring at this one. It went unnoticed for as long as Virtua Cop was the only game being looked at, because three cap hits in a run is not a number anybody investigates.
Stage 1, after taking a hit: the cracked-screen overlay over the 3D scene, the HUD, and the targeting reticle.
One game cannot exercise a board. Pointing this at a second title turned up eight bugs in shared code that Virtua Cop never tripped, and pointing it at thirty-five turned up more of the same kind — none of them missing hardware, all of them defaults that were quietly wrong:
- a dispatch-depth guard of 500, which turned a lifted loop into abandoned recursion in twenty of the thirty-five sets
- a UART read that returned one byte lane of four, so a 16-bit poll of the status register always read zero — a fault on the original board that neither reference title triggered
- the display list walked only when a game rewrote one register, rather than every vblank as the hardware does
- port A of the CRX I/O chip answering with the cabinet's inputs instead of its own latch
- a literal
0.0 / 0.0in four programs' ROMs, which is a runtime NaN on the i960 and a compile error in C
The sweep is what makes finding those cheap. docs/technical/corpus.md explains how it decides what it saw, and CORPUS.md carries a per-set blocker for everything diagnosed so far.
Nothing ROM-derived is committed — the corpus directory is local scratch, and the table records what happened on a machine that had the sets.
- virtuacop — Virtua Cop (Sega, 1994). Boots, runs its own frame loop, and renders attract mode.
The gaps are well defined, and none of them need permission to start:
- Sound.
src/sound.cis a UART handshake and nothing else. A 68000 core plus MultiPCM would make this the first Model 2 recomp with audio. - The I/O board EEPROM. Buttons work; the 93C46 holding coinage and game settings does not exist. Virtua Cop defaults to free play without it, which is convenient and not correct.
- Board variants. 2A-CRX is a memory-map and I/O-chip job on the same coprocessor. 2B-CRX (ADSP-21062 SHARC) and 2C-CRX (MB86235) are new DSP cores.
- Another title. The fastest way to find out what is title-specific in here is to point it at a game that is not Virtua Cop.
| Dependency | Why | Required |
|---|---|---|
| SDL2 | Window, presentation, input, audio output | Yes |
| CMake ≥ 3.20 | Build | Yes |
| C17 compiler | MSVC 2022 / GCC / Clang | Yes |
No other third-party code is linked. MAME is a reference, not a dependency — nothing from it ships here.
cmake --build build --config Release
./build/Release/model2recomp_test_tilemap
./build/Release/model2recomp_test_geometry
./build/Release/model2recomp_test_eeprom93c46Or ctest --test-dir build -C Release. All three are self-checking: each prints one line per check and exits non-zero if
any fail. They cover tilemap pixel order, transparency, scrolling and pass
selection; and, for the geometry engine, projection position, culling behind
the eye, and depth sorting. They do not need ROMs.
Does this run Model 2 games on its own? No. It is the board, not the game. You need a statically recompiled i960 program to link against it — see virtuacop for a worked example.
Do I need ROMs? To run a game, yes, and you must dump them yourself. None are distributed here and none ever will be. The library loads flat binaries that a game project's tooling produces from a ROM set.
Why is the geometry engine not emulated like the coprocessor is? Because it does not need to be, and MAME reached the same conclusion. The geometry TGP runs fixed Sega microcode that always does the same thing; modelling the result directly is simpler and faster than emulating the DSP. The math coprocessor runs microcode the game supplies, which varies per title, so there it is emulate or nothing.
Can it target Model 2A/2B/2C? Not yet. The enum accepts them and the log line prints them; no code branches on them.
Why is my recompiled game hanging?
Almost always the field sync. Guest code busy-waits on bit 2 of 0x0098000C,
and that read is what advances the frame. If your bus is not routing it to
model2recomp_field_sync(), the guest spins forever. MODEL2_TRACE=N names
the function it is spinning in.
MIT — see LICENSE. Parts of this library are ports of MAME code, and a port is a derivative work: those files keep MAME's licence and its copyright holders.
| Component | Licence | Copyright |
|---|---|---|
ports of MAME code in src/geometry.c, src/copro.c, src/video.c, src/bus.c, src/timer.c, src/i960.c, src/io.c |
BSD-3-Clause | R. Belmont; Olivier Galibert; ElSemi; Angelo Salese; Matthew Daniels; Farfetch'd; Dirk Best |
| everything else | MIT | sp00nz and contributors |
BSD-3-Clause is permissive and asks only that the notice and the copyright survive; LICENSES/BSD-3-Clause.txt is the verbatim text and NOTICE maps every derived file to the MAME source and the people who wrote it. No MAME source is redistributed in this repository.
The Model 2 was reverse-engineered by the MAME team over two decades. This library is a port of that work into a different shape, not a rediscovery of it. R. Belmont, Olivier Galibert, ElSemi, Angelo Salese, Matthew Daniels, Farfetch'd and Dirk Best wrote the code it is derived from.
Built with Claude Code (Anthropic).
model2recomp is built by more than one person. See CONTRIBUTORS.md for who did what. Thank you, all of you.
- MB86233 math coprocessor emulated (
src/copro.c, new). The Model 2's other TGP is a real DSP that games upload microcode to, and Virtua Cop builds every transformation matrix on it. With the FIFO stubbed as a loopback the game read its own inputs back as a matrix — a position, a raw integer angle, three 2.0s and a NaN — which pushed 44% of polygons into the nearest z bucket and flattened the scene. Now the full instruction set, the CPU board's math-table ROM (sin/cos, atan, 1/x, 1/√x), the banked buffer-RAM window and both FIFOs are modelled, and the DSP runs on demand: a FIFO read executes it until it produces a word or starves on empty input. - Stopped re-rendering stale display lists. Projection rewrites each vertex
in place, so re-rendering a field the game had not resubmitted threw the
geometry off screen; the picture alternated between the scene and nothing.
geo_render_polygonsnow honoursrender_done, as MAME does. MODEL2_SHOT_EVERY=Nwrites a screenshot every N fields, so one run samples a whole attract cycle.
- Geometry engine and 3D rasterizer ported from MAME's
model2_v.cpp: display-list parse, transform, lighting, clipping, z-sort, and a perspective-correct textured fill. - The real colour path. Palette entry selects one of 32 ramps per channel in colour-translate RAM, indexed by luma — from the texel through luma RAM for textured pixels — then gamma. Replaced a placeholder that guessed RGB.
- Texture origin fixed and translucency honoured; display lists are parsed only once the game publishes them, not mid-write.
- System 24 tilemaps: four layers, drawn in two passes around the 3D scene.
- Interrupt dispatch at the field boundary, following the hardware's path: interrupt controller → external line → ICR vector → PRCB interrupt table. Every line is serviced, and the sound interrupt is raised.
- Control flow the guest actually uses: IAC reinitialize (the reset stub's handoff to the real firmware entry), the I/O board command acknowledge, and the video field-sync frame boundary.
- Initial library: i960 CPU context, 32-bit memory bus, function dispatch table, timers, interrupt controller, EEPROM, I/O board DPRAM, SDL2 platform layer, and subsystem stubs for everything else.
- Flat-binary ROM loading.
- MAME —
sega/model2.cpp,model2_v.cpp,model2rd.ipp,segaic24.cpp,model1io2.cpp,cpu/i960/i960.cpp,cpu/mb86233/mb86233.cpp. The source of essentially everything known about this board. - N64Recomp — showed static recompilation of a console target was practical.
- XenonRecomp — the same idea for Xbox 360 PowerPC.
- ps3recomp, xboxrecomp — sibling projects in the same family.








