SRB2 as a Chimera core
  • C 93.9%
  • Shell 2.5%
  • Python 1.2%
  • Makefile 1.2%
  • C++ 0.8%
  • Other 0.4%
Find a file
PAS 86b620e719 test: the GPU savestate test allows up to 16 different pixels a picture, as the Legacy fork's does
What changed
- tests/engine-gpu-states.py compares each picture after a rewind or a
  reopen with the straight run allowing up to 16 different pixels, instead
  of exactly. The code is the SRB2 Legacy fork's.
- README.md, run-gate.sh's description and docs/PLAN.md say so.

Why
- CI has failed this test since Chimera 14409f9 (its GL bridge changes after
  804e6b4), on x64 and arm64: after a rewind and a reopen, step 191 differed
  in one pixel. On an NVIDIA card here, five steps differed, each in one
  pixel on a polygon seam. The same package passes with Chimera 804e6b4.
- SRB2's OpenGL renderer sorts polygons by a hash of their texture's GL
  name (hw_batching.c). The driver gives out the names, and after a rebuild
  they can come in another order, so polygons at the same depth are drawn
  in another order. The picture is otherwise right.

Tested
- With Chimera 1e723d6 (what CI builds now): the test script passes, 50 of
  50, on NVIDIA; the GPU test alone passes on Mesa llvmpipe, at most 1
  pixel apart. Before this change both failed here as on CI.
- Teeth: a core built without the rebuild (but saying it rebuilt) still
  fails, because the reopen crashes, on NVIDIA and on llvmpipe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:24:03 -04:00
.github/workflows opengl: the Mesa softpipe is gone; OpenGL is the GPU's, through the bridge 2026-10-11 16:41:31 -04:00
docs test: the GPU savestate test allows up to 16 different pixels a picture, as the Legacy fork's does 2026-10-16 23:24:03 -04:00
extern audio: MIDI on emulated YM2612s, with Music Preference as a setting (Digital by default) 2026-10-12 01:53:34 -04:00
lua Game State: the boss's health and flashing, and Metal Sonic's dash mode, as the SRB2 TAS build shows them 2026-10-08 03:25:49 -04:00
patches OpenGL renderer, 8b: opengl-hw through Chimera's GPU bridge 2026-10-05 02:44:20 -04:00
waterbox test: the GPU savestate test allows up to 16 different pixels a picture, as the Legacy fork's does 2026-10-16 23:24:03 -04:00
.gitignore Milestone 0: SRB2 headless native build 2026-10-04 14:22:41 -04:00
.gitmodules audio: MIDI on emulated YM2612s, with Music Preference as a setting (Digital by default) 2026-10-12 01:53:34 -04:00
AGENTS.md text: settings, firmware, file slots and Game State properties are described in plain language 2026-10-16 22:54:06 -04:00
LICENSE Milestone 0: SRB2 headless native build 2026-10-04 14:22:41 -04:00
README.md test: the GPU savestate test allows up to 16 different pixels a picture, as the Legacy fork's does 2026-10-16 23:24:03 -04:00

chimera-core-srb2

Sonic Robo Blast 2 (Sonic Team Junior's 3D Sonic fangame, from Doom Legacy) as a Chimera game core ("kind": "game", see Chimera's docs/game-cores.md). It is an official core: on Chimera's roster (official-cores.json) since Chimera's 9aff711 (2026-10-10), and listed in Chimera's README with the game cores. It is the first official core published outside the ToolAssisted-run organisation: its author, P-AS, writes and publishes it from this repository, as a third party. It needs no change to Chimera. See docs/PLAN.md for the milestones and decisions.

The engine is upstream SRB2 (extern/SRB2, master, net-compatible with 2.2.15), compiled from source with a platform layer of the core's own (waterbox/platform/) in place of SDL: no window, no audio device (the core mixes the sound itself, in the machine), no network, no threads. The game's data - srb2.pk3, zones.pk3, characters.pk3, music.pk3 - is firmware: the core carries none of it.

Using it in Chimera

Chimera ships no cores and downloads nothing. Download the core's package from this repository's Releases page (a rolling dev build, dated nightly-YYYY-MM-DD builds), or build it, and put it in the Cores folder beside Chimera.exe (or the folder chosen in File > Core Manager > Change folder...).

Each release has two packages, one per CPU: srb2-<version>-x64.chimeraCore and srb2-<version>-arm64.chimeraCore. You most likely want the x64 one. Take the arm64 one only if you know you are using an arm64 device. Put only one of them in the Cores folder: two packages of one core list it twice. The same file works on Linux and on Windows. None of the game's data is in the package: a project brings SRB2 2.2.15's srb2.pk3, zones.pk3, characters.pk3 and music.pk3 as firmware.

Building

git submodule update --init
export MINIBOX_DIR=<a Chimera checkout>/extern/chimera-common-minibox
make -C waterbox -f native.mk -j$(nproc)    # the native reference and the harnesses: build/native/
make -C waterbox -f guest.mk -j$(nproc)     # the core: build/guest/core.wbx

miniBox must be built first, with its C++ guest toolchain (GME is C++), in its checkout:

meson setup build/meson-cpp -Dguest_cpp=true && ninja -C build/meson-cpp
ninja -C build/meson-cpp libstdcxx-installed.stamp source/guest/emulibc.c.o source/guest/cxxglue.c.o

The patches go onto extern/SRB2 and extern/openmpt on the first build (waterbox/apply-patches.sh [openmpt], each series all or nothing). extern/openmpt is shallow: git submodule update --init fetches only its pinned commit.

Data for the gate

waterbox/fetch-data.sh [<folder>] downloads STJr's own SRB2 2.2.15 release and takes out the four pk3s, checked against the declared firmware (default build/srb2-2.2.15); run-gate.sh -d <folder> runs on them. CI (.github/workflows/chimera.yml) does this, then the gate, the package and Chimera's contract tests, on x64 and on arm64, and publishes dev and nightly releases with both packages.

Being on Chimera's roster, the newest nightly is also checked by Chimera's own CI: every run there downloads it (the x64 package) and runs Chimera's package tests on it. A nightly those tests refuse fails Chimera's CI until a fixed nightly is out.

The package

waterbox/build-package.sh [-r <Chimera bundle or checkout>]   # build/package/srb2.chimeraCore, or into its Cores

-r <chimera> writes the package into a Chimera source checkout's build/Cores (a bundle's Cores). The whole build, as CI does it, is in docs/BUILDING.md; AGENTS.md is the guide for an AI coding agent.

Running the native reference

mkdir -p build/work && ln -s /usr/share/games/SRB2/*.pk3 build/work/
echo '{"warp": "1"}' > build/work/settings          # optional: start in Greenflower Zone Act 1
build/native/run-native build/work -n 400 --ppm frame.ppm
build/native/run-wbx build/guest/core.wbx build/work -n 400   # the same, in the sandbox

Both run -n steps (a step is a tic, on the machine's own clock), print the picture's hash, the tic and the clock every -p steps and a hash of the whole run at the end, and --ppm writes the last picture. Their lines diff directly.

The gate

./waterbox/run-gate.sh [-d <SRB2 data folder>] [-m <miniBox>]: the legs, each saying what it compared, each with teeth. equivalence: native == sandbox, step for step, on the intro and Greenflower Zone Act 1. steps: a step is a tic, a wipe's frames are lag steps, play is a tic a step. savestates: a save and load before every step, and a new host mid-wipe, change nothing. declaration: the package's controller is the core's. settings: each setting reaches the engine. opengl: with no GPU bridge the OpenGL renderer warns in the log and the run is the software renderer's. engine (with -c) also runs opengl-hw through the GPU bridge where the machine has a context: the character select drawn as in software, and the picture right again after a rewind and a reopen (up to 16 pixels may differ, on seams: SRB2's OpenGL renderer orders polygons by the graphics driver's texture numbers). properties: the Game State domain, read by its property table, holds what Greenflower's movie did, the timers tests/timers.lua sets, a spindash's charge (tests/spindash.txt), and a boss tests/boss.lua spawns and hits. input: movies through the menus and in Greenflower, the same every way. files: what the game writes is kept in the machine and exported identically. time: a host stall mid-run changes nothing. audio: the music and sounds are heard (a GME song, a tracker module, and the game's MIDI songs with Music Preference MIDI among them), and every leg's run line carries the sound's hash too.

The harnesses' --wav FILE writes a run's sound.

Settings

Declared in waterbox/waterbox.config, so Chimera shows them as the core's options; each is SRB2's own option, given to the engine at start: Play Style (default Manual), Camera Speed (1.0), Camera Distance (192), Camera Height (40), Field of View (90; 60 to 179, the picture's alone - leave it at 90 for normal levels, 120 is recommended for NiGHTS levels), Score/Time/Rings (Mania), Flip Camera with Gravity (Yes), Automatic Braking (On), Music Preference (Digital: the recorded songs; MIDI: the game's MIDI songs on six emulated Sega Genesis/Mega Drive YM2612 sound chips, MAME's, as upstream's Windows builds play them - the sound's alone), Tutorial Prompt (Off), Unlock Record Attack/NiGHTS Mode/Marathon Run, Unlock All Characters, Unlock All Maps in Record Attack and NiGHTS Mode, Unlock All Secrets (each Off), Resolution (1280x800: SRB2's video modes, and 2560x1440 and 3840x2160, shown with square pixels; the game plays the same at every one), Renderer (software; opengl-hw: SRB2's OpenGL renderer on this machine's GPU through Chimera's GPU bridge - fast, its picture the driver's; where there is no bridge it draws in software, which plays differently, and the log warns; the renderer is part of the game, so a movie is its renderer's), OpenGL Shaders (On; the picture's alone), Start Map (empty: the intro and the title), Start Map Character (a skin name, e.g. knuckles; empty: Sonic).

The controller

SRB2's default keyboard as buttons (Forward, Jump, Spin... the menus' Enter and Escape, which answer a prompt too), pressed as keys, so menus and play work as on a keyboard (the ring-slinger controls too - Fire, Fire Normal, Toss Flag, Weapon Next/Prev - but not the number row's weapon slots); plus axes for exact values: Forward Move, Side Move, Turn, Aim. The harnesses take a movie as text, --input FILE with FROM-TO: Button; Axis=value lines (waterbox/tests/).

Game State and the TAS info script

The core exposes one memory domain, Game State, and a property table naming its fields, so RAM Watch (Watches > Add Game Properties), RAM Search and Lua's game.get see them by name. The core copies them from the game after every step; they are read-only. Game.Tic, Game.Level Time, Game.State, Game.Map; Player.In Level, Player.X/Y/Z, Player.Momentum X/Y/Z, Player.Conveyor Momentum X/Y (player->cmomx/cmomy: what a conveyor or a moving platform adds), Player.Platform Momentum Z (mo->pmomz: the moving floor's) and Player.Speed (16.16 fixed point: 65536 is one unit; speed is the game's own player->speed), Player.Dash Speed, Player.Min Dash, Player.Max Dash (the spindash's charge and its bounds, 16.16), Player.Flags (pflags) and Player.Charging Spindash (its PF_STARTDASH bit), Player.Dashmode and Player.Normal Speed (Metal Sonic's dash mode counter and the top speed it raises); Boss.Active, Boss.Health, Boss.Max Health, Boss.Flashing (the first MF_BOSS object, as the SRB2 TAS build finds the boss: its health, its type's starting health, and MF2_FRET); Player.Angle (2^32 a turn); Timers.Speed Shoes, Timers.Invincibility, Timers.Space, Timers.Air (tics left). Out of a level the player's fields are 0.

lua/tasinfo.lua (also an asset of every dev and nightly release, beside the package) draws them in the bottom right corner of the game, clear of Chimera's own HUD: open it in Tools > Lua Console with an SRB2 project loaded. It shows speed, the angle in hex and in degrees (each to 4 decimal places, on lines of their own), the position and the momentum, the conveyor and platform momentum, the spindash's revs, Metal Sonic's dash mode (and its speed at 108, as the SRB2 TAS build shows it), the four timers in tics and seconds, and while a boss is in the level its health (left / at the start) and whether it is flashing. SRB2 has no discrete revs: a spindash charges 1.0 of speed a tic while Spin is held, from Min Dash to Max Dash (Sonic: 15 to 70), so the script counts a rev a tic, as revs so far / revs to full charge. The conveyor and platform momentum, the revs, the dash mode and the timers are shown only while they are not 0.

The harnesses' --game-state FILE writes every step's block, and the table beside it in FILE.json.

Licence

GPL-2.0, as SRB2 is (LICENSE). zlib and libpng are compiled from the copies in SRB2's libs/, under their own licences; libco (miniBox's extern/libco) is public domain; libogg and libvorbis (extern/ogg, extern/vorbis) are BSD-3-Clause; Game_Music_Emu (extern/gme) is LGPL-2.1-or-later (its Nuked YM2612 too); libopenmpt (extern/openmpt) is BSD-3-Clause. The MIDI player (waterbox/platform/midi_fm.cpp) is the core's own, GPL-2.0, because libOPNMIDI's player is GPL-3.0-or-later and cannot be part of a GPL-2.0 program; it is built on the parts of libOPNMIDI (extern/opnmidi) whose terms allow it: its MIDI sequencer, WOPN bank loader, XG bank and volume and frequency models (MIT), its chip interface (LGPL-2.1-or-later) and MAME's YM2612 (GPL-2.0-or-later). Sonic the Hedgehog and related characters are trademarks of SEGA; this repository carries none of the game's data.