05 · Road to 60, again
The road to 60, all over again
On the Old 3DS, the original interpreter could not keep up. So from 09-07 we replaced it, piece by piece, with code that writes PICA200 commands directly. This page covers that native renderer, the Old 3DS road to 60, and what shipped with it.
Part 1 · The renderer
The convention
Every N64 PC port in this family renders the same way.
Where we were — v0.9.0
v0.9.0 ran that convention unchanged. On a 3DS it is CPU-bound.
Where we ended up — v1.1.0
The game still decides what to draw; the native renderer replaces how it reaches the GPU, one category at a time.
Why fork the renderer
On 09-07 the 3DS build compiled 36 libultraship files, 15,043 lines, dominated by the Fast3D interpreter (10,466 lines), plus a 12,353-line bridge that only widens the game's 32-bit display-list words to 64 bits. Everything else (window, audio, input, config) was already the port's own code. A census found machines, track and effects each drawn differently, so the fork is a hybrid: our own interpreter for everything, plus race-only paths for what a race frame spends its time on. The long tail (editors, ending, one-off screens) stays interpreted.
| Tier | What it does | At release |
|---|---|---|
| 1 · Own the interpreter | A purpose-built F3DEX2 → citro3d walker reads the game's display list directly. No bridge. | Default on both consoles; [perf] preset = off restores the libultraship path. |
| 2 · Bypass it for races | Track, machines, sky, HUD, effects and shadows drawn from baked or recorded data: the native race renderer (NRR). | Default on both consoles; since v1.1.0 it also covers menus, course select and machine select. |
Every lever is an ini killswitch with a verify mode that draws both paths and compares the vertex bytes bit for bit. [perf] preset = auto resolves per console at startup (n60 on New 3DS, o3ds-auto on Old 3DS) and turns the whole set on; an explicit ini key always wins.
The renderer work waited two weeks for the dev loop: a network listener that accepts a build, writes it to SD and relaunches through the Homebrew Launcher, and a live log link over 3dslink's protocol. One Y-press per power-on; after that the console reloaded builds on its own.
Case by case
Each part of a frame at v1.1.0 defaults. The last column is Old 3DS CPU per rendered 2D race frame: the 10-01 interpreter build against native-renderer card 3 the next day.
| Category | Before (v0.9.0) | Shipped path (v1.1.0 defaults) | Old 3DS ms |
|---|---|---|---|
| Machines | All 30 racers rebuilt as display-list commands and transformed on the CPU every frame. | A machine's first ordinary draw is its recording: from the next frame it is a retained mesh drawn as one GPU-transformed draw per material run, fed by a snapshot of its matrix, LOD and colours. First sight, crashed machines and time-attack ghosts stay on the ordinary path. | 4.05 → 1.09 |
| Track | The course tessellator re-wrote every ring's vertices each frame; the interpreter transformed them again. | Every distinct ring is baked once per course load into a GPU buffer, inside one arena reserved at boot (no heap fragmentation across loads). The game's own chunk cull and walk still choose what is drawn; each frame only writes indices, from 18 index templates. The GPU adds an exact integer eye offset. | 4.13 → 1.35 |
| Sky and backdrop | Walked and transformed like everything else. | The backdrop's state blocks are learned once; each frame the CPU transforms a handful of snapshot vertices and draws one packed draw per state block. | 3.02 → 0.40 |
| HUD (top screen) | One draw per texture rectangle, colour changes splitting batches. | The game's HUD code still runs; its commands are compacted behind markers and replayed as memoised, compiled register programs. About 12 of 14 runs are byte-identical from frame to frame. | 5.14 → 3.17 |
| Effects, shadows, scenery | Sprite by sprite through the interpreter. | One shared engine: state learned once, then the per-tick snapshot drawn as sprite batches. Course decorations use the machines' record-at-first-sight pattern. | 3.37 → 1.72 |
| Fog | The PICA fog unit, plus a combiner stage for the game's shade-alpha fog. | The NRR switches the fog unit off and blends fog in a combiner stage with the colour written as a constant; a track run that needs fog it cannot express falls back. | |
| Menus | Every non-race screen walked by the interpreter. | Since v1.1.0, menus, course previews and machine select are captured once and replayed as compiled programs (ni_*). The spiral transitions and one outline pass still go through the walker. | |
| Bottom screen | A debug console. | DASH and the bottom-screen menus never touch the GPU: the CPU paints a 320×240 buffer and copies only the changed columns. | |
| Stereo 3D | Each draw issued twice, once per eye. | The frame body is emitted once; a per-draw uniform carries the eye shift. Since v1.1.0 both eyes go in one GPU submission: the GPU runs the body, jumps to a right-eye prologue, runs it again. Old 3DS 3D menu 40.2 → 44.6 fps. | |
| Still on an interpreter | Everything. | Our own F3DEX2 walker (Tier 1) handles the frame setup between markers, credits, editors, demos and transitions. libultraship's interpreter runs only under preset = off. | 0.82 → 0.86 |
Part 2 · The Old 3DS
The Old 3DS road
The 08-11 feasibility study said the port could not fit an Old 3DS (268 MHz, no L2 cache, 64 MB). It booted there on 09-29 at about 10 fps, raced at 60 in 2D on 10-02 and in 3D on 10-08. Every card was a physical SD-card trip.
| Old 3DS, 10-26 | Now | Measured on | Before |
|---|---|---|---|
| 1P race, 2D | 60 fps, median frame 16.5 ms, 0 hitches | card 3, 10-02 | ~10 fps, 09-29 |
| 1P race, 3D | 59.7–59.8 fps with one submission (suggestive) | v1.1 card 6, 10-26 | 56.4 / 59.3 baseline; 53.7 with 16 machines (10-24) |
| Menus, 3D | 44.6 fps | v1.1 card 6, 10-26 | 40.2 |
| Multiplayer | 2D 60 fps (2 players + 14 CPU); 3D locked 30 by design | v1.1 card 3, 10-25; 10-24 probe | new in v1.1.0 |
Every card, on the calendar
A card is one round trip: build, ini, test plan, play session, log back. Hover or tap a card for what it carried.
September 2026
Dies in the boot preload:
[fatal] operator new failed size=175844 heapFree=32424. Console answers memtype=0, heap grant 24 MiB (25,165,824 B)hw-first-boot-0928-DGDX_3DS_RDRAM_MB=8, audio_stream=1, tex_upload_kb=512; legacy interpreter (native all=0)Boots and races. Wall p50 96.0 ms (p10 88.7, p90 111.3); GPU busy 3.6 ms;
oth 37.3 ms. User: "it ran ! at like 9fps"hw-rdram8-0929run 1 · 1109-29 · C4 run 1: O3DS profile-DGDX_3DS_O3DS=ON (RDRAM 8 MiB + 16 MiB linear pin: 38.9 MB heap), owned interpreter all=1 interp=1GP wall 88 ms (11.3 fps), TT/menu 43.7 ms;
aud 25.8 ms = audio-lock wait, confirmed on hardwarehw-c4run1-0929run 2 · 1509-29 · C4 run 2: narrow audio lockaudio_ctx_narrow=1aud 25.8 → 0.2 ms; race wall 67.8 ms (14.7 fps), light scenes 40 mshw-c4run2-0929HOME / SIMD09-29 · Wave 2: HOME + audio SIMDHOME bisect builds; audio_simd verifyHOME freeze root cause isolated (bottom backlight session); 7/7 HOME resumes with screen on;
audio_simd verify 391/0/0, 0 mismatches on siliconhw-wave2-0929skip · 19–2809-29 · Wave 3: frameskip + O3DS presetframeskip=auto, preset=o3ds, gpuxf=1, DrawProbe calibration CIALogic 57–60 ticks/s (game at full speed for the first time); drawn 19–28 fps in races. User: "playable but nausea inducing". DrawProbe: 10.9 µs CPU per draw, 14 Mtri/shw-wave3-0929REL1 ✗ boot09-29 · REL1All C5 stage-1 levers + O3DS code generation (cold
-Os, hot-first order)Boot crash, deterministic. Four-title bisect: cold
-Os crashes, stock and -O2+order boothw-rel1-fail, hw-rel1b-fail, hw-rel1crash-bisectframeskip=steady, HUD/machine/track retained pathsBoots. Race ~15 fps rendered (steady stuck at D=4), rw50 26–35 ms, "lots of awkward hitching": synchronous SD log flushes of 26–270 ms on mainhw-rel2REL3 · 2009-30 · REL3Every exact lever on, async file log, overlap kick
"no hitches, steadier"; steady mostly D=3 (20 fps rendered), rw50 24–36 mshw-rel3-run1, hw-rel3-run2PMU · CPI 4.309-30 · HWPROF BPMU counters (
c5pmu) and the r60 probeRender CPI 4.34, 43.6% of cycles in instruction-fetch stalls; probe CPI 1.06 at 16 KB of hot code, 4.10 at 24 KB, 5.62 at 32 KBhw-hwprof-B
October 2026
20 rendered + 20 replayed images/s, 1.3 ms per replay, but sky jitter, black tearing edges, giant fx triangles. 26.8 ms CPU per rendered frame. User: "we should go for a real 60!"hw-rel4NRR ✗ freeze10-01 · n60-m1 (NRR milestone 1)Native race renderer: HUD, track, machines, sky;
nrr_direct=2 verify runHard freeze at race start. Cause: verify mode's state save/restore rolled back libctru's live GX queue (the emulator's GSP completes synchronously and cannot show it)hw-n60m1-V
RUN V: 199,472 draws verified, bad=0, float24 max 0.011 px. RUN C: TT "silk 60 fps"; GP W median 14.2 ms = 57.3 fps outside log-flush windows (51.9 fps all windows), harsh hitch every ~1/5 laphw-n60card2-V, hw-n60card2-Ccard 3 · 60 2D10-02 · n60-card3Never-blocking log,
frameskip=race0, save fix, swap gate, texture fixes, HUD memo, native tunnel joins and decorationsRace median 16.5 ms, 0 race hitches; save persisted across a power cycle. User: "omg it ran amazing!"hw-n60card3
[stereo] enabled=1 (NRR refused stereo, legacy path)Black sky: the NRR sky consumed the section but could not draw under stereohw-interim3d3D · 4010-03 · n60-3dStereo on the NRR: one frame body, resubmitted for the right eye with a per-eye uniform
No glitches; 3D race ~19.8 ms vs 16.5 ms 2D (frame-begin GPU wait 6.6 vs 3.3 ms) → 33 ms quantum, cost guard flapping, ~40 fpshw-n60-3d3D · 48–5710-03 · n60-3d2Right eye interleaved per submitted chunk
2D 60 in GP and TT. 3D "40s and 50s": every race tripped the guard in the intro; steady 3D GP ≈ 48–57 fpshw-n60-3d2
Mute City 3D ~60 in end mode (hold 1 on 250–255 of 256 frames); Silence and Sand Ocean heavier (work 16–18 ms)hw-n60-3d3
lcd_vram=1Crash at boot on all three variants, no log: LCD framebuffers in VRAM, which the CIA maps read-only for the CPUhw-n60-3d4card 4 · 3D 6010-08 · card 4 (VRAM fix)Same build, two levers off in the SD ini
3D GP courses 12–16: race windows hold 1 on 245–256 of 256 frames at work ≈15.5 ms → ≈59–60 fps. VERIFY variant bad=0. Heavy windows only on the post-race results tablehw-n60-3d4b1
Idling the GPU first did not make the frame cheaper (13.5 → 14.2 ms, 11.8 → 12.3 ms): Old 3DS 3D is CPU-bound; the deferred-wait plan was dropped. 1P 3D race with 16 machines: wall 18.6 ms = 53.7 fps. 3D multiplayer locked to 30 (ed04b17)hwlogs/o3ds-0c2; cpu-profile-answer.md; ed04b17a3b10-24 · o3ds-a3bOld 3DS hardware log set from the v1.1.0 tuning work; not analysed on this pagehwlogs/o3ds-a3b90810-24 · o3ds-908Old 3DS hardware log set from the v1.1.0 tuning work; not analysed on this pagehwlogs/o3ds-908b5210-24 · o3ds-b52Old 3DS hardware log set from the v1.1.0 tuning work; not analysed on this pagehwlogs/o3ds-b52
Race windows at a locked 60 (wall 16.71 ms, 3.8–4.1 ms of vblank slack); the steady governor still flapped 60/30 at D=1, fixed by the drift rule (a89289f)hwlogs/o3ds-card3; cpu-profile-answer.md §A.4online110-25 · o3ds-online1Old 3DS hardware log set from the v1.1.0 tuning work; not analysed on this pagehwlogs/o3ds-online1card210-25 · o3ds-card2Old 3DS hardware log set from the v1.1.0 tuning work; not analysed on this pagehwlogs/o3ds-card2card2b10-25 · o3ds-card2bOld 3DS hardware log set from the v1.1.0 tuning work; not analysed on this pagehwlogs/o3ds-card2bcard2c10-25 · o3ds-card2cOld 3DS hardware log set from the v1.1.0 tuning work; not analysed on this pagehwlogs/o3ds-card2c
HOME-close hard lock (a 73 KB allocation in the 64DD save flush)hwlogs/o3ds-card5card 6 · 3D 6010-26 · v1.1 card 6 (ba0692e)One GX submission for both eyes, four-arm automatic A/B; heap fixes
1P 3D race 59.7–59.8 fps with one submission (baseline 56.4 / 59.3; two blocks per arm, so suggestive); 3D menu 40.2 → 44.6 fps; HOME close cleanhwlogs/o3ds-card6; onesub-hw/o3ds-card6-analysis.md
- 09-28 Shipped build (RDRAM 16) — Dies in the boot preload:
[fatal] operator new failed size=175844 heapFree=32424. Console answersmemtype=0, heap grant 24 MiB (25,165,824 B) - 09-29 First boot (RDRAM 8) — Boots and races. Wall p50 96.0 ms (p10 88.7, p90 111.3); GPU busy 3.6 ms;
oth37.3 ms. User: "it ran ! at like 9fps" - 09-29 C4 run 1: O3DS profile — GP wall 88 ms (11.3 fps), TT/menu 43.7 ms;
aud25.8 ms = audio-lock wait, confirmed on hardware - 09-29 C4 run 2: narrow audio lock —
aud25.8 → 0.2 ms; race wall 67.8 ms (14.7 fps), light scenes 40 ms - 09-29 Wave 2: HOME + audio SIMD — HOME freeze root cause isolated (bottom backlight session); 7/7 HOME resumes with screen on;
audio_simdverify 391/0/0, 0 mismatches on silicon - 09-29 Wave 3: frameskip + O3DS preset — Logic 57–60 ticks/s (game at full speed for the first time); drawn 19–28 fps in races. User: "playable but nausea inducing". DrawProbe: 10.9 µs CPU per draw, 14 Mtri/s
- 09-29 REL1 — Boot crash, deterministic. Four-title bisect: cold
-Oscrashes, stock and-O2+order boot - 09-30 REL2 — Boots. Race ~15 fps rendered (steady stuck at D=4), rw50 26–35 ms, "lots of awkward hitching": synchronous SD log flushes of 26–270 ms on main
- 09-30 REL3 — "no hitches, steadier"; steady mostly D=3 (20 fps rendered), rw50 24–36 ms
- 09-30 HWPROF B — Render CPI 4.34, 43.6% of cycles in instruction-fetch stalls; probe CPI 1.06 at 16 KB of hot code, 4.10 at 24 KB, 5.62 at 32 KB
- 10-01 REL4 — 20 rendered + 20 replayed images/s, 1.3 ms per replay, but sky jitter, black tearing edges, giant fx triangles. 26.8 ms CPU per rendered frame. User: "we should go for a real 60!"
- 10-01 n60-m1 (NRR milestone 1) — Hard freeze at race start. Cause: verify mode's state save/restore rolled back libctru's live GX queue (the emulator's GSP completes synchronously and cannot show it)
- 10-02 n60-card2 — RUN V: 199,472 draws verified, bad=0, float24 max 0.011 px. RUN C: TT "silk 60 fps"; GP W median 14.2 ms = 57.3 fps outside log-flush windows (51.9 fps all windows), harsh hitch every ~1/5 lap
- 10-02 n60-card3 — Race median 16.5 ms, 0 race hitches; save persisted across a power cycle. User: "omg it ran amazing!"
- 10-03 interim legacy 3D — Black sky: the NRR sky consumed the section but could not draw under stereo
- 10-03 n60-3d — No glitches; 3D race ~19.8 ms vs 16.5 ms 2D (frame-begin GPU wait 6.6 vs 3.3 ms) → 33 ms quantum, cost guard flapping, ~40 fps
- 10-03 n60-3d2 — 2D 60 in GP and TT. 3D "40s and 50s": every race tripped the guard in the intro; steady 3D GP ≈ 48–57 fps
- 10-07 n60-3d3 — Mute City 3D ~60 in end mode (hold 1 on 250–255 of 256 frames); Silence and Sand Ocean heavier (work 16–18 ms)
- 10-08 n60-3d4 — Crash at boot on all three variants, no log: LCD framebuffers in VRAM, which the CIA maps read-only for the CPU
- 10-08 card 4 (VRAM fix) — 3D GP courses 12–16: race windows hold 1 on 245–256 of 256 frames at work ≈15.5 ms → ≈59–60 fps. VERIFY variant bad=0. Heavy windows only on the post-race results table
- 10-24 o3ds-3d ceiling probe (0c2668a) — Idling the GPU first did not make the frame cheaper (13.5 → 14.2 ms, 11.8 → 12.3 ms): Old 3DS 3D is CPU-bound; the deferred-wait plan was dropped. 1P 3D race with 16 machines: wall 18.6 ms = 53.7 fps. 3D multiplayer locked to 30 (ed04b17)
- 10-25 v1.1 card 3 (3c0c7b7) — Race windows at a locked 60 (wall 16.71 ms, 3.8–4.1 ms of vblank slack); the steady governor still flapped 60/30 at D=1, fixed by the drift rule (a89289f)
- 10-26 v1.1 card 5 (5e19320) — HOME-close hard lock (a 73 KB allocation in the 64DD save flush)
- 10-26 v1.1 card 6 (ba0692e) — 1P 3D race 59.7–59.8 fps with one submission (baseline 56.4 / 59.3; two blocks per arm, so suggestive); 3D menu 40.2 → 44.6 fps; HOME close clean
The machine
| Old 3DS | New 3DS | |
|---|---|---|
| CPU | 2 × ARM11, 268 MHz, no L2 | 4 × ARM11, up to 804 MHz, 2 MB L2 |
| L1 instruction cache | 16 KB; a miss costs ~37 cycles (measured) | misses can hit L2 |
| App memory | 64 MB region, 24 MiB heap grant | 124 MB |
| Usable cores | core 0; core 1 is a 30% share (audio) | core 0 + core 2 |
Five phases
1 · Make it fit (09-24 → 09-29). RDRAM arena halved to 8 MiB, audio_table streamed; replay 49/49 with 2.65 MiB spare under simulation, and the console matched it byte for byte. One flag, -DGDX_3DS_O3DS=ON, makes the Old 3DS build.
2 · Find the costs (09-29 → 09-30). First race: 96 ms a frame, GPU busy 3.6 ms. The audio lock wait went 25.8 → 0.2 ms, ARMv6 SIMD made synthesis 2.2× cheaper, and frame skipping fixed the slow motion ("playable but nausea inducing"). A retained renderer won in the emulator and did almost nothing on hardware.
3 · The probe that changed the plan (09-30 → 10-01). Performance counters: CPI 4.34, 43.6% of cycles stalled on instruction fetch. A probe loop ran at one instruction per cycle with 16 KB of hot code and four at 24 KB. Research called native 60 "marginal" and offered pose replay; the user: "we should go for a real 60!"
4 · The native race renderer (10-01 → 10-02). One small loop per race category, each sized to the 16 KB L1-I, writing command words from GPU-resident meshes. Game logic untouched and hash-checked every tick.
5 · 3D (10-03 → 10-08). The frame body is built once and resubmitted for the right eye with a per-eye uniform. The CPU fit, but frames missed vblank behind a 5–7 ms right-eye tail; interleaving per chunk and end-mode swaps gave Grand Prix windows at 59–60 on 10-08.
Later (10-24 → 10-26): a heavier 16-machine race measured 53.7 fps and idling the GPU first did not help, so 3D here is CPU-bound and multiplayer 3D is locked to 30. One submission (below) raised the 1P race to 59.7–59.8. — hwlogs/o3ds-0c2, o3ds-card6; ed04b17One submission for both eyes (v1.1.0)
After v1.0.0, a 3DS homebrew developer asked in a public comment whether the stereo path submits each eye separately, and suggested one command list for both. It did not re-encode the right eye, but every replayed list was its own GX queue entry, with its own GSP submit, completion interrupt and event-thread wake on core 0. Old 3DS probes put a clean citro3d draw at 10.9 µs and showed 0.4–0.5 ms of GPU dead time per backlogged queue entry (about 40 µs on New 3DS).
The fix uses the PICA200's second command-buffer channel: each stereo chunk ends by jumping to a short right-eye prologue, which jumps back into the same chunk, then to the finalize words. GSP gets one entry; the GPU runs the body twice and raises one interrupt. In the emulator every frame hash, game-state hash and screenshot matched the old path. On hardware, one build per console cycled four arms automatically:
[gxcost] receipts.| Old 3DS | 0 baseline | 1 one submit | 2 + no kick | 3 + narrow flush |
|---|---|---|---|---|
| 3D menu, static screen | 40.2 fps | 44.6 | 44.6 | 44.6 |
| 1P 3D race (2 blocks per arm) | 56.4 / 59.3 | 59.7–59.8 | 59.7–59.8 | 59.7–59.8 |
| Headroom on the critical path | – | ≈ 1.5 ms | ≈ 0 | ≈ 1.8 ms |
The menu is the clean result (6 against 9 blocks, ±0.2 fps); the race line is suggestive. Dropping the mid-frame kick saved CPU but lost CPU/GPU overlap, so it netted zero. The New 3DS was neutral. Arm 3 ships as the v1.1.0 default on both consoles ([stereo] onesubmit = 1, onesubmit_narrow = 1).
Memory
The Old 3DS margin was thin at v1.0.0 and stayed the main source of post-release bugs:
- HOME close hard-locked the console (card 5). The 64DD save flush at exit wanted one contiguous 73 KB buffer; about 390 KB was free, but not in one piece.
bad_allocled tosvcBreak, which locks instead of exiting. The flush now streams with no heap allocation, and a break during teardown exits. Card 6 logged a clean exit. - The fast track path dropped after six course loads. A 96,768-byte allocation failed with 1.6 MB free: fragmentation, not exhaustion. The track now lives in one 777,360-byte arena reserved at boot (emulator-verified over a 6-course Grand Prix).
- About 320 KB moved off the main heap, mostly the bottom screen's art buffers, which had been static arrays in the program image.
What the Old 3DS forced
- Leaving the display list in races. A faster interpreter on a second core was enough on the New 3DS; at a third of the clock with no L2 and no spare core, the interpreter's own code did not fit in cache.
- A hardware profiler. The emulator models neither the I-cache nor the console's memory permissions, so its wins stopped predicting hardware. ARM11 performance counters became the judge.
- A memory diet. A smaller RDRAM arena, streamed audio tables, capped uploads, a pinned heap layout, and a high-water mark instead of a free-list figure that had been misread as headroom.
- Frame pacing as design. With a millisecond or two of margin, when the swap is requested matters: a swap gate, an end-of-frame mode, and a race-scoped frame-skip guard.
Still open
- Heavier venues (Silence, Sand Ocean) can step 3D down to 30.
- The results table after a race is still heavy.
- Brief audio gaps during loads. The vblank-rate crackle theory was measured and refuted.
- Memory remains a listed known issue.
Part 3 · What shipped
The Expansion Kit
The 64DD Expansion Kit (course editor, machine creator, disk content) is its own build, "G-Diffuser EK", with its own HOME-menu title, save and log, so it installs beside the plain game. It needs the player's own retail Japanese 64DD disk image, streamed from SD rather than held in RAM (62 MB saved); without one, the EK entries simply don't open. A fan-translated English image froze at boot in testing and is a known issue.
Under the Old 3DS profile in the emulator it fits with ~6 MB spare and matches the plain game tick for tick (6,002/6,002 on a ROM course, 8,601/8,601 on a disk course). On Old 3DS hardware disk courses stuttered, disk music broke up and the editors crawled, so it ships for the New 3DS only.
The bottom screen: DASH
The second screen started as a debug console and became a race dashboard: lap, position, energy, splits and the map, painted by the CPU straight into the bottom framebuffer, no GPU. Three layouts (all top, split, all bottom) cycle from a touch chip or a SELECT long-press. On Old 3DS hardware it cost 4.2–5.6 ms a frame, so it was put on a 1 ms budget. In 2–4 player races the HUD stays on the top screen. A drag-and-resize layout editor was shelved; a one-frame speed spike (up to ~826 KPH) at the finish line is known and cosmetic.
The New 3DS freeze
A real New 3DS froze, with no dump and no log line, every time the attract demo reached its full 30-machine race; the emulator never reproduced it. Bisection on the console found the GPU command buffer sized at the edge of that frame: with one lever off, the same frame hit a bounds-checked panic in the buffer writer instead. The buffer went from 2× to 4× the citro3d default on both consoles, with a log line before any future overflow. Four hardware boots through that frame came back clean.
Multiplayer
Most of v1.1.0: 2–4 players over local wireless, the same Wi-Fi, or online with a room code, each console showing its own racer, CPU machines filling the field. Old and New 3DS can share a room. On the Old 3DS, multiplayer races run in 2D at 60 by default; 3D is locked to 30. Player guide: MULTIPLAYER.md.




Emulator captures (Azahar), not hardware: four New 3DS instances in an online room through a local rendezvous server.
Release: v1.0.0, then v1.1.0
v1.0.0 (10-19) is three downloads from one tree: Old 3DS, New 3DS, and New 3DS with the Expansion Kit. All share one SD folder and pick their preset at startup. v1.1.0 (10-26) keeps the same three downloads and adds multiplayer, native menus and one-submission 3D. See RELEASE-NOTES.md and STATUS.md.
| Known issue (v1.0.0, still open) | What it means |
|---|---|
| 3D is not a locked 60 on either console | Old 3DS 3D is CPU-bound: 59–60 on most courses, some step to 30; multiplayer 3D is 30 |
| The Old 3DS memory ceiling | A large mid-race asset load can exhaust the budget in a worst-case emulator run; hardware peaks near 24.2 MB |
| Brief audio gaps during loads | Short and occasional |
| A speed-readout spike at the finish | Cosmetic, DASH only |
| Not every mode tested on every model | Coverage favours the modes used most in development |
The day-by-day account is on Every step; the hardest fights on Difficulties.