Skip to content

About

A Sega Model 2 hardware runtime for statically recompiled games: geometry engine, rasterizer, tilemaps and TGP coprocessor.

Topics

Resources

Contributing

Stars

15 stars

Watchers

3 watching

Forks

Repository files navigation

model2recomp

 #   #   ###   ####   #####  #          ###
 ## ##  #   #  #   #  #      #         #   #
 # # #  #   #  #   #  ####   #            #
 #   #  #   #  #   #  #      #           #
 #   #   ###   ####   #####  #####      #####

 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.

Status

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 attract mode

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.

Getting Started

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:

  1. Install the tools.

    • Windows: Visual Studio 2022 with Desktop development with C++, CMake 3.20 or newer, Git, and vcpkg (cloned to C:�cpkg and bootstrapped). Then C:�cpkg�cpkg install sdl2:x64-windows.
    • Linux: sudo apt install build-essential cmake git libsdl2-dev.
  2. Clone: git clone https://github.com/sp00nznet/model2recomp.git and cd model2recomp.

  3. Configure and build.

    • Windows: cmake -B build -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows then cmake --build build --config Release.
    • Linux: cmake -B build then cmake --build build -j.
  4. Run the tests: ctest --test-dir build -C Release. Expected:

    100% tests passed, 0 tests failed out of 3
    

    They 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.


What Is This?

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.

Why Not Just Use an Emulator?

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.

The Board

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

Hardware Coverage

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

What "rasterizer" means here, and what it does not

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.

The Two TGPs

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.

Building

cmake -B build
cmake --build build --config Release

Requires 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 Release

This 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)

Integration Pattern

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.

Repository Structure

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)

Documentation

Start here

  • 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.

Technical deep dives

  • 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.

Games That Work Well As Targets

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.

The Corpus Sweep

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 all

That 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".

Where the corpus has got to

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 Desert Tank Virtua Cop 2
Virtua Cop — the reference title, Model 2 Desert Tank — Model 2, and MACHINE_NOT_WORKING in MAME Virtua Cop 2 — 2A-CRX
Gunblade NY Dynamite Baseball 97
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.

It fed back into the reference title

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
before after

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.

Virtua Cop, hit

Stage 1, after taking a hit: the cracked-screen overlay over the 3D scene, the HUD, and the targeting reticle.

What it is for

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.0 in 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.

Projects Using This Library

  • virtuacop — Virtua Cop (Sega, 1994). Boots, runs its own frame loop, and renders attract mode.

How You Can Help

The gaps are well defined, and none of them need permission to start:

  • Sound. src/sound.c is 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.

Dependencies

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.

Running the tests

cmake --build build --config Release
./build/Release/model2recomp_test_tilemap
./build/Release/model2recomp_test_geometry
./build/Release/model2recomp_test_eeprom93c46

Or 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.

FAQ

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.

License

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.

Credits

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).

Contributors

model2recomp is built by more than one person. See CONTRIBUTORS.md for who did what. Thank you, all of you.

Changelog

v0.3.0 — "Real Silicon" (September 2026)

  • 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_polygons now honours render_done, as MAME does.
  • MODEL2_SHOT_EVERY=N writes a screenshot every N fields, so one run samples a whole attract cycle.

v0.2.0 — "Pixels" (September 2026)

  • 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.

v0.1.0 — "Board Bring-Up" (March 2026)

  • 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.

References

  • 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.

About

A Sega Model 2 hardware runtime for statically recompiled games: geometry engine, rasterizer, tilemaps and TGP coprocessor.

Topics

Resources

Contributing

Stars

15 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages