I forked QuakeSpasm to see if I could use Claude Code to get a build tuned for six old Macs spanning 1999 to 2019 — PowerPC G3, three G4s, a 2007 Intel Core 2 Duo on Lion, and a 2019 iMac as the modern baseline. One fat binary, one app bundle, a per-machine autoexec on each host.
# The bench
| Machine | Year | CPU | GPU | macOS |
|---|---|---|---|---|
| yosemite | 1999 | PowerMac G3 B&W, 449 MHz | ATI Rage 128, 16 MB | 10.3.9 Panther |
| sawtooth | 1999 | PowerMac G4 AGP, 500 MHz 7400 | NVIDIA GeForce2 MX, 32 MB | 10.4.11 Tiger |
| quicksilver | 2001 | PowerMac G4, 733 MHz 7450 | Radeon 9000 Pro, 64 MB | 10.4.11 Tiger |
| mini-g4 | 2005 | Mac mini G4, 1.25 GHz 7447A | Radeon 9200, 32 MB | 10.4.11 Tiger |
| mini-intel | 2007 | Mac mini, 2.33 GHz Core 2 Duo | Intel GMA 950 | 10.7.5 Lion |
| imac-2019 | 2019 | iMac 27", 3.7 GHz i5-9600K | Radeon Pro 580X, 8 GB | 15.7.5 Sequoia |
# The setup
Six headless Macs driven over ssh from an Ubuntu workstation. The Lion mini is both a bench target and the cross-build host — it's the last Mac that ships PPC-targeting GCC 4.0.1 alongside the 10.3.9 and 10.4u SDKs.
Three compilers build three CPU-specific binaries. Apple's lipo tool glues them together into one universal file; on each Mac, macOS picks the slice for its CPU at launch time. Plain rsync + ssh + scp on top of make.
Quake's built-in timedemo plays a recorded demo as fast as the engine can render it and prints the fps to the console log. Each bench run is one combination of machine, demo, and resolution. Three runs per combination, the first dropped as a warm-up, median of the other two lands in the CSV. parallel-bench.sh runs the whole grid across all six machines in parallel.
# Before and after
Both rows are demo timings on the 1999 G3 — before, and after the work with the full visual stack on (see-through water, drop-shadows under monsters, glowing dynamic lights).
| Machine | Demo | Resolution | Before fps | After fps | Δ |
|---|---|---|---|---|---|
| yosemite (G3) | demo3 | 1024×768 | 5.10 | 20.95 | +311% (4.1×) |
| yosemite (G3) | demo1 | 1024×768 | 7.70 | 17.35 | +125% (2.3×) |
| yosemite (G3) | demo3 | 640×480 | 15.60 | 36.85 | +136% (2.4×) |
| yosemite (G3) | demo1 | 640×480 | 23.90 | 34.45 | +44% |
Only the G3 row is positive across every cell — every round on G3 chased fps and nothing else. The other rows in the full earliest-to-current grid are mixed because each machine's first build was plain stock QuakeSpasm with the engine's default settings, and the project has stacked visual upgrades on top since then — 16× anisotropic filtering, smoother texture filtering, drop-shadows under monsters, see-through water and lava and slime and teleporters, glowing dynamic lights. The negative cells are deliberate visual trade-offs, not regressions — every feature landed in a commit that measured the fps cost across the matrix and weighed it against the visual gain.
# Frames per second across the rack
Round v11.1 full-grid bench at commit d64427db. Median of three runs per cell. Round v11.1 added a per-frame GL state cache in R_DrawAliasModel that lands +19% to +46% on demo3 1024 across the G4/Intel/iMac slices; the G3 ppc750 slice compiles it out via QS_DISABLE_ALIAS_STATE_CACHE because same-session A/B showed a regression on the Rage 128.
| Machine | demo1 1024×768 | demo1 640×480 | demo2 1024×768 | demo2 640×480 | demo3 1024×768 | demo3 640×480 |
|---|---|---|---|---|---|---|
| yosemite (G3) | 17.35 | 34.45 | 15.25 | 33.40 | 20.95 | 36.85 |
| sawtooth (G4) | 40.25 | 55.65 | 32.70 | 50.15 | 46.90 | 57.55 |
| quicksilver (G4) | 62.75 | 70.30 | 60.10 | 68.00 | 84.05 | 95.35 |
| mini-g4 (G4) | 48.45 | 86.30 | 37.40 | 73.85 | 65.60 | 113.20 |
| mini-intel (Lion) | 72.85 | 163.95 | 54.50 | 130.70 | 44.60 | 185.90 |
| imac-2019 | 1610.95 | 1894.45 | 1490.20 | 1876.20 | 1575.15 | 1907.25 |
# Screenshots from each machine
# yosemite — PowerMac G3 B&W, ATI Rage 128, 1999
# sawtooth — PowerMac G4 AGP, GeForce2 MX, 1999
# quicksilver — PowerMac G4 733 MHz, Radeon 9000 Pro, 2001
# mini-g4 — Mac mini G4 1.25 GHz, Radeon 9200, 2005
# mini-intel — Mac mini Core 2 Duo, GMA 950, Lion, 2007
# imac-2019 — iMac 27" 5K, Radeon Pro 580X, Sequoia
# Claude Code setup
CLAUDE.md is the durable knowledge a session needs: the hardware inventory, naming conventions, per-Mac engine-setting rules, and the rule that every change ships behind an opt-in flag (off by default until it's proven its worth). MISTAKES.md is append-only — every approach tried and reverted, with dates and reasoning.
A ppc-ops skill tells Claude when to invoke the build, deploy and bench scripts. Two slash commands wrap them: /bench mini-g4 demo3 1024x768 for one single run, /bench (no arguments) for the parallel sweep across all six machines, and /deploy quicksilver to build and ship one slice to a named Mac.
Static analysis runs on a Linux build target compiling the same source the PPC and Lion binaries do — cppcheck, clang-tidy, scan-build, gcc -fanalyzer, flawfinder, sparse, iwyu, shellcheck, ASan and UBSan against a headless timedemo. Apple's gcc 4.0.1 from 2007 doesn't flag what gcc 15 does, so the Linux target catches a different class of issue.
# Get the build
The current release is v1.2-round-v8.
| Bundle | Size | Runs on |
|---|---|---|
| quakespasm-fat-app-v0.97.0.zip | 4.8 MB | All six benched Macs (Panther 10.3.9 → Sequoia 15.7) |
The bundle expects to live at ~/Desktop/quake/; release notes have full install steps and Gatekeeper handling.