Maxxing out a 1997 racing game one weekend at a time. A reverse-engineering, modding, and level-editing playground for Sonic R on Apple Silicon.
Status: In progress · Last updated 10 October 2026
Sonic R has lived in my head since I was a teenager. In April 2026 I started seeing how far I could push it on modern hardware. This page is the running log: each weekend's work added as a new entry, newest first.
Timeline
- 10 October 2026 · Green Hill, and closing the shortcuts (latest)
- 7 October 2026 · Dash panels, minimaps and three tracks on one slot
- 27 September 2026 · City Escape, and a sixth course on the menu
- 26 September 2026 · Brand new levels, built in Blender
- 19 September 2026 · Resort Island, expanded into a world
- 9 May 2026 · Smuggling Angel Island in from Sonic Adventure
- 3 May 2026 · Cracking the rest, building the editor
- 25 April 2026 · Getting it running, fixing the controls
10 October 2026 - Green Hill, and closing the shortcuts
A second original track, this time with the brief I actually wanted: shortcuts, water and verticality. Then a week of finding out that the engine keeps its secrets in the strangest places, including the water.
The layout. The first version was a figure of eight, and it was boring. The one I kept starts on a beach, climbs a zig-zag of two hairpins up to a cliff-top plateau, drops off the edge into a gold-and-brown checker canyon with a loop in it, crosses a log bridge over a pond, spirals round a mountain and over itself, and runs back down to the beach. There are two proper shortcuts: you can skim straight across the lake (Sonic runs on water, everyone else wades) or take a leap off the plateau onto the road below. The bots take both, at different odds for each character.
Sonic Adventure 2's look, without the rips. I wanted Green Hill as it appears in Sonic Adventure 2, and my first thought was to use ripped models. Getting them turned out to be more trouble than it was worth, so instead Claude looked at preview images and the stage map and rebuilt the kit from scratch. Every texture on the track is drawn in code, so each tile is exact and seamless: bevelled checker cliffs, grass with blades and clover in it, a curtain of grass hanging over every cliff edge, log bridges, palms, sunflowers, purple flowers, winged totems, lilac crystal rocks. The violet sky with its golden spires is the one thing an image model painted.
Swaying flowers. The flowers started as two flat cards each, which looked like it. Now they're properly modelled: a sunflower has two rings of petals, a seed disc, leaves and a little tuft at its foot, and they all lean towards oncoming racers. Then they started moving. Sonic R already animates a handful of objects, the spinning tokens and a bobbing machine, by tilting them through three angles every frame. A small mod moves the flowers into that group and rocks them with a wind wave that rolls across the map, so neighbours lean together in gusts.
The water is a reflection of the sky. The lake rendered violet and I couldn't work out why. It turns out there's no water texture at all. Below the horizon the game draws a rippling, upside-down reflection of the sky, and the lake is just a hole in the ground you see it through. Resort Island's sky is blue at the top, so its sea is blue; mine is violet, so the lake was too. The mod now redraws that reflection blended towards Sonic Adventure 2 blue on clear days, so the clouds still glint through. Night, rain and dusk keep the normal reflection. Chasing this also turned up that the game reads its sky images as one long strip, not the shape I'd been making them, so my skies had been showing the panorama twice round the horizon. That's fixed too.
More textures than the game expects. Sonic R has room for 52 texture pages and uses 21 of them. The mod now loads extra pages from a custom track's own folder just before the game uploads its textures, which gives the ground and the props twice the detail. Grass, road and checker come in several versions that join seamlessly, picked tile by tile, so long lawns stop looking stamped.
Wider, and gentler corners. My memory of Sonic R is that its tracks were wider, and Sonic kept coming off at the corners of mine. Measuring it against Resort Island said otherwise: my road was already wider than the original's, and its corners gentler. The real culprit was the curve maths. The spline I'd drawn the course with pinched every bend at its control points, down to about 7 world units, and Sonic can't turn tighter than about 12 at full speed. The course is now drawn with a smoother curve, the road is 30% wider, and no corner is tighter than 12.2.
An autopilot for making videos. The test mod could only ever hold accelerate, so Sonic ran into the first wall. Now it steers: it reads the racing line and presses left, right and accelerate like a player would, so the camera, animation and HUD all behave normally. With the game recording every frame and its own start jingle and Island song laid back on top, that's where the clips on this page come from. My first attempt let the bots' AI drive Sonic instead. He moved, and also tumbled round the course like a dropped plate.
How do you cheat on it? Laps in Sonic R only check that you crossed the start line going forwards. There are no checkpoints at all, so any route Sonic can physically run counts, and the original tracks rely entirely on walls, water and cliffs. I asked whether you could just go up the hill inside the spiral, and tested it, along with every other shortcut the course offered. Going over the summit is actually slower. But four other cuts each took six or seven seconds off a 55 second lap, mostly straight up grassy slopes between the zig-zag roads.
My plan was terraces, sheer checker cliffs between the roads, very Green Hill. The test runs killed that idea before I'd built it: Sonic climbs any slope, just slowly, even a near-vertical bank eight units high in about half a second. The only thing that actually stops a racer is an invisible collision wall. So the course now has one along each side, just past the grass verge, with a low log fence on top so you can see it. Where the spiral passes over itself, walls can be set to stop only racers on the ground or only racers on the bridge, so neither level blocks the other.
The funniest find came out of this. Every collision wall I'd ever written was missing a side. The game reads a wall as an open line and expects you to repeat the first corner at the end to close it, which the original tracks all do and I never had. So a wall stopped you from one side and let you straight through from the other. It had been like that since my first track. All six cheat routes are blocked now, and the normal lap takes the same time as before.
Racing it feels fun, in a safari kind of way, but not as a race. Mostly I'm just mesmerised by seeing this kind of level in Sonic R.
Writing it down. There's now a proper guide to making a track for this engine, from the first sketch of the racing line to installing it on the menu, with every gotcha that bit me and its fix. It's also a skill, so the next session that works on a track reads it first instead of rediscovering it all.
7 October 2026 - Dash panels, minimaps and three tracks on one slot
Ticked off most of last time's "Next" list. Claude can also drive Ghidra directly now, which changed how fast the rest went.
Claude driving Ghidra. Until now, reverse engineering meant me running scripts and pasting the output back. With a Ghidra MCP server set up, Claude can decompile functions, follow references and rename things itself. The first thing it did with that was a deep dive through the whole executable looking for loose ends, and it found several.
Records and ghosts. The custom track had been writing its best times and time-attack ghosts over Resort Island's. Each custom track now keeps its own, so beating City Escape doesn't overwrite your Resort Island record.
Dash panels. Last time's list said the red star disc doesn't boost. It turns out it's an item box. So dash panels are now their own thing: a panel on the road that kicks anyone who crosses it forward, bots included.
Minimaps and a model on the menu. Every custom track gets its own minimap in the race HUD instead of Resort Island's. The spinning model on the course select screen is just another object in a menu file, so City Escape now has its own: a little city block with the loop.
Three tracks on one slot. The sixth entry on course select now holds up to three custom tracks; up and down switch between them, and each brings its own files, records, minimap, music and model. Which is just as well, because the next track was already under way.
27 September 2026 - City Escape, and a sixth course on the menu
Made an explainer video about how all of this works, remixed City Escape from Sonic Adventure 2 into a Sonic R track, and gave it its own slot on the course select screen. Resort Island stays exactly as it shipped.
The explainer. I'd seen people making explainer videos with Claude and GPT, so I had a go. The script came straight out of this project's own notes, the voice is from OpenRouter, and every visual is drawn in code with Remotion, with each animation cued to a word in the narration. The disassembly on screen is real bytes from SonicR.exe, not a typed-up approximation. The whole thing cost about $1.80 in API calls.
City Escape, remixed. City Escape is the Sonic level that lives rent-free in my head: Sonic boarding down a San Francisco hill with a runaway truck on his tail. I had a rip of its geometry and textures, but the original is a one-way sprint, not a circuit. So instead of porting it, I used it as the reference for a new circuit designed in Blender, with one brief: much more verticality. The lap starts on Market Street at the top of the hill, drops down it in four steps, hairpins at the bottom, runs along a boardwalk by the bay, then climbs back up through Lombard-style switchbacks and over a flyover that crosses the downhill below.
The crash hunt. Every build with buildings in it crashed at the start of the race, at a different address each time. Twelve test builds narrowed it down to "buildings", but not to size, object count or anything else sensible. The answer turned out to be a texture detail. Resort Island animates its water by scanning every face in the level for anything on texture page 5 that sits inside three hard-coded rectangles, and adds each match to a list of about 1,030 entries. There's no bounds check. My yellow plaster happened to fill one of those rectangles exactly, so a few hundred building walls tried to become animated water and spilled the list into whatever memory came after it. Moving a handful of textures fixed it, and the track builder now refuses to write anything that would overflow that list.
Springs. Looking for boost pads, I found springs instead. Every piece of the collision mesh carries a 16-bit word that nobody had decoded; Resort Island leaves it at zero, which is why it went unnoticed for so long. The low twelve bits set the launch strength, and two flag bits choose between straight up and a kick along the slope. City Escape has a few spring pads, though I haven't properly tested them mid-race yet.
A proper backdrop. The sky behind a Sonic R race is a single 832 by 256 pixel strip. Reading the code that draws it showed the strip wraps exactly 360 degrees and covers about 45 degrees up from the horizon, which is everything you need to render your own. So the new backdrop is a 360 degree panorama of the bay, rendered in Blender from the level's own buildings: downtown with a pyramid tower, the Golden Gate in front of the Marin hills, the Bay Bridge, Alcatraz, all under the original Resort Island clouds and sun.
Morning light and a soundtrack. The track's lighting is baked into each vertex, and it turns out the three tint values are plain red, green and blue. So the sun is a touch warmer now and the shade a touch cooler, like an early San Francisco morning. The race music comes from a small table in the executable, one entry per course, so City Escape plays Sonic R's own city song, "Living in the City", instead of Resort Island's.
A sixth course. Until today every custom track replaced Resort Island. The engine only knows five courses: the course number gets checked in about 237 places, and patching them all is a fool's errand. So the new entry pretends to be Resort Island, and a small mod swaps its files. The game opens every file through one Windows function, so when you pick the sixth entry the mod quietly hands it City Escape's track, collision, AI, textures and sky instead of Island's. The course select screen needed six-entry tables, smaller icons, a thumbnail and a name. The name is spelt in the game's own chrome lettering, cut letter by letter from the existing course names. There's no P anywhere in RESORT ISLAND, RADICAL CITY, REGAL RUIN, REACTIVE FACTORY or RADIANT EMERALD, so the P is an R with its leg chopped off.
One gotcha worth writing down: the mod loader has its own feature that swaps any game file for a same-named file found in a mod's folder, always. My first test happily loaded City Escape when I picked Resort Island. The track data lives outside the mods folder now.
26 September 2026 - Brand new levels, built in Blender
Moved level making into Blender, raced the first properly original tracks, then ran into a limit the engine has had since 1997.
Blender as the level editor. Rather than keep growing my own editor, I moved the tools into Blender as an add-on: a Sonic R tab with New Level, Rebuild Road From Curve, New Island Terrain, Scatter Scenery and Build & Play. You draw the racing line as a curve, where the curve's height is the road's height, sculpt the terrain, and the road and its collision follow along. Build & Play exports the scene, converts it into all five of the game's files, installs them and launches the game in about twenty seconds. I get all of Blender's modelling tools for free.
Overpasses. Sonic R's collision turns out to have layers. Give the bridge deck layer 1 and everything else layer 0, and players and bots drive over and under it without snapping between the two. The other trick was gameplay objects: the game finds its tokens, balloons and Chaos Emerald by hard-coded object number, so a new track needs exactly 390 track parts with the specials sitting at the retail numbers. Get it wrong and they turn into invisible triggers.
Resort Island style. A second builder takes a centre line and grows a Resort Island-style circuit around it: hills, a Green Hill checker canyon, a lagoon boardwalk, Sonic Jam scenery, sunlight baked into the terrain, and an AI racing line with corner speeds so the bots actually race rather than wander.
The 65,535 wall. As the Blender track grew, the stage started vanishing in-game, with the road replaced by stretched terrain. The loader stores vertex indices in 16 bits, so past 65,535 vertices later objects point back into the road's own vertices and drag them across the map. Bigger buffers can't fix that; the index itself wraps. Here's the trick: the vertex data is loaded in order, so if no object straddles a 65,536 boundary, every 16-bit index is still exact within its own "bank". The patched engine remembers each object's bank and adds it back on every lookup. Tracks can now use 262,144 vertices. Faces are still capped at 65,535, which is a problem for another weekend.
Testing while I sleep. macOS blocks screen capture and synthetic key presses while the Mac is locked, so a small test mod now drives the menus, starts a race, screenshots the frame just before it's shown and quits. It's how the fixes above got checked overnight, and where most of the City Escape shots on this page come from.
19 September 2026 - Resort Island, expanded into a world
Loaded the original Resort Island track into Blender and asked GPT-6 Astra to expand it. Not tweak it. Expand it, from a racetrack into a world.
From track to world. The starting point was the original Resort Island geometry. Astra grew it into a cluster of islands linked by boardwalks and bridges, each area with its own name and character: palm groves, rock arches, waterfalls, a little village, a lighthouse at sunset. Same visual language as the 1997 original, just a lot more of it.
It made the video too. Astra animated the camera in Blender, built the flythrough and exported it with music from Suno. The whole thing, start to finish.
What this changes. The "Next" section below has been about authoring an original track from scratch. This is a different route to the same place: start from a track the engine already understands and grow outwards. Getting it racing in the Sonic R engine is the next step.
9 May 2026 - Smuggling Angel Island in from Sonic Adventure
Got the Angel Island map from Sonic Adventure (1998) running natively in the Sonic R (1997) engine. Another step closer to authoring my own level.
Cross-engine import. This is the first time the toolchain has swallowed a third-party mesh rather than round-tripping an existing Sonic R track. The geometry is recognisable, textures sit on the right faces, you can race the loop. Important step toward authoring an original track, because most of the same pipeline is involved.
The hard parts. Object importing was a slog. Texture remapping was worse: Sonic Adventure's UV layout doesn't agree with how Sonic R indexes textures, so I had to write a remapper that translates one scheme into the other. Then mesh segmentation, to give the per-frame collision check a hope of keeping up at race speed. Still a bit of clipping around the seams where the falloff fights the segmentation, but it's racing.
Reworking the engine memory limits. Higher-poly Sonic Adventure assets blew through the per-track buffer Sonic R reserves at load time. I had to patch the limits in a few places to let bigger meshes through; before that, the loader was either truncating the geometry or crashing depending on which budget hit first. The patch is per-binary not per-track, so any future big import gets it for free.
What this changes for from-scratch authoring. I'd assumed I'd need to write the SRT writer end-to-end before authoring an original track. Turns out a Sonic Adventure import is roughly 80% of the same problem: triangulate, segment, remap, place start grid, generate AI line. From-scratch is the same pipeline minus the existing geometry. One more weekend should do it.
3 May 2026 - Cracking the rest, building the editor
Decoded the last opaque file formats, found a vestigial section every track has shipped for twenty-eight years, and built a working level editor in one evening.
The remaining file formats. I'd flagged .ply and .map as opaque collision formats. Turned out they're sister formats and have nothing to do with collision: they're playfield textures. .map is a 128×128 grid of one-byte tile indices; .ply is a 320×384 RGB image laid out as 120 tiles you reference by index. The same byte simultaneously drives rendering and a sub-pixel collision check. A 1996 Saturn engineer was clearly counting bytes. I tested the writer by painting all the water tiles in Resort Island cyan; whole lake turned neon.
The seven mystery SRT sections. Cross-referencing the entry counts across all five tracks gave huge hints. A section that holds exactly the same number of entries in every track is doing something fixed, and the number itself usually tells you what.
| Section | Entries | What it is | |---|---|---| | 3 | 543 on Resort Island | Smooth XYZ trajectory data. Nothing reads it - see below | | 4 | Always exactly 360 | A camera spline for the intro and outro flyby | | 8 | Varies | The rank-checkpoint loop | | 9 | Always 51 | A 50-frame animation curve, probably the start-grid rev |
Section 3 is vestigial. This was the fun one. 543 entries of beautifully smooth XYZ trajectory data on Resort Island. The loader copies it into memory every time you load a level. Nothing reads it. Ever. Some Saturn engineer in 1996 used it; the 1998 PC port didn't strip it; the 2004 retail rebuild is still shipping dead bytes in every track. Twenty-eight years of nobody noticing.
Hidden command-line flags. Grepping the binary for uppercase strings turned up an argv walker, and a handful of switches nobody appears to have documented in twenty-eight years. They still work.
| Flag | What it does |
|---|---|
| UNLOCKGAME | Unlocks the full secret roster |
| IGNORECD | Skips the disc check |
| WINDOWED | Runs in a window |
| ALLOWMODESWAP | Permits display mode switching |
There's also a complete software renderer code path still compiled in, and a BIN/OBJECTS/old/ folder of pre-release character animations dated September 1997 that retail just never cleaned up.
A level editor. A single Qt6 window: paint playfield textures by clicking cells, sculpt terrain with a 3D brush (dome, cone, plateau, crater, ridge), stamp from 234 existing scenery objects, race the result. Both the collision mesh and the visible mesh get lifted in matched falloff so hills are walkable AND visible. About 1500 lines of Python, written between 8pm and midnight.
The longest bug of the night. Stamped decorations rendered fine from some camera angles and disappeared from others. Each deco's file header has TWO XYZ slots. Bytes 0 to 11 are a frustum-cull reference point ("is this on-screen?"); bytes 16 to 27 are the render origin ("draw it here"). I was updating only the second one. The engine was asking "is the original location visible?" while drawing the new copy at the new spot. Most existing decos have those two slots set to the same value, which is why naive cloning usually works.
25 April 2026 - Getting it running, fixing the controls
Got Sonic R running on my Mac via CrossOver, traced the physics through Ghidra, and patched out the tank-like turning that every review has complained about for two decades.
A native macOS setup. CrossOver runs the original 1997 Win98 binary on Apple Silicon. The open-source Sonic R Mod Loader acts as a d3d9.dll shim that lets you inject custom DLLs and binary patches into the running game.
A tank-controls fix. Every review of Sonic R for twenty-five years has complained about the cars turning like Soviet tractors. I traced the per-frame physics tick to a function at FUN_0042c430 and found a per-character table of ten scalars at 0x45e300. Field +0x08 is the turn rate. Multiplied by 1.5x across the board, per-character ratios preserved. The whole patch is two bytes per character. Tails Doll remains the worst character in the game by design.
A track-format parser. Sonic R's .srt files are undocumented. I sat with a hex editor for a couple of evenings and built a Python parser and writer that round-trips all five tracks byte-for-byte. Track parts have integer XYZ vertices at twelve bytes each plus face indices; deco parts use u8 RGB vertex colours; track parts use i16 RGB tints with a -200 "ignore tint" sentinel. Once you can round-trip you can mod.
Generic texture export and import. Sonic R stores textures as raw RGB24 with no header. The engine looks up dimensions by file size. A small auto-detecting exporter pulls all 224 PNGs in one pass; the importer slots higher-resolution variants back in. I fed a few through Nano Banana Pro to upscale and re-style them and the engine doesn't notice.
A reverse-engineering pipeline. Ghidra in headless mode driven from PyGhidra, plus six small scripts I keep reaching for: find functions that reference an address, find users of a string, decompile by entry point. The map I built up so far includes a Players[] array hard-coded to 5 slots, a giant screen-state switch in MainGameLoop at 0x43AA60, sin/cos lookup tables, and MIN_FOG_Z/MAX_FOG_Z floats that genuinely extend the draw distance when bumped.
Pipeline
Public repo coming soon. Python, a Blender add-on and a handful of C++ DLLs:
texture_io.py- generic raw export and import with format auto-detectionsrt_roundtrip.py- track format parser and writerparallax_io.py- sky textures, with stretch/cover/fit/tile import modesmod-src/AutoBoot.cpp- loader DLL with the original binary patchesblender/sonic_r_tools.py- the Blender add-on (New Level, Rebuild Road, Build & Play)blender_track.py,terrain_track.py- Blender scenes or centre lines into all five game filesmod-src/BigBuffers.cpp- bigger engine buffers and the 64k vertex banksmod-src/CourseSlot.cpp- the sixth course on the course select screenmod-src/TestHarness.cpp- unattended test races with screenshots, and the autopilotgh_theme.py,blender/make_green_hill.py,blender/gh_props.py- Green Hill's textures, layout and props, all generatedmod-src/record_video.sh- a gameplay video of any custom track, with the race musiccourse_cuts.py,mod-src/cut_run.py- find shortcuts, then drive them to see if they workdocs/making-a-track.md- the guide to making a trackghidra-scripts/*.py- the PyGhidra recipes I keep reaching foreditor/- the Qt6 + OpenGL editor app
Next
Boost pads, minimaps and more course slots are all done, and so is a second original track. Next: Green Hill's own model on the course select screen and its own music, sunflower heads that spin the way they do in Sonic Adventure 2, reverse mode, and tidying the repo enough to publish it. Nobody on PC has hooked DirectInput to give Sonic R proper analog stick support either, so that's still on the list.
Related thoughts
- Green Hill in Sonic R, and every way I could find to cheat on it - 10 October 2026
- A brand new level running in Sonic R, built in Blender - 27 September 2026
- GPT-6 Astra turned Resort Island into a whole world - 19 September 2026
- Sonic Adventure's Angel Island, smuggled into Sonic R - 10 May 2026
- Reverse-engineering Sonic R with Claude Code is genuinely wild - the original weekend writeup, 25 April 2026
See also
- Sonic Fusion Fan Game - the 2003 prequel to all of this
