macrosoft3dpinball/godot/README.md
m3ultra 804e4c0fb8 Add Box3D upstream research notes for the testbed
Findings from the engine evaluation that led to this project, kept separate from
the table's own A/B results:

- Apple Silicon threading: Box3D gets SLOWER past the performance-core count on
  the M3 Ultra (30 workers 5158ms -> 32 workers 5646ms, 24P+8E). A physics step
  is a barrier, so a share of the work on a third-speed E-core makes the whole
  step wait. Read hw.perflevel0.physicalcpu, never hw.ncpu. Applies to any
  threaded physics on this fleet, not just Box3D.
- The benchmark harness: the CMake option is BOX3D_BENCHMARKS (plural), the
  scene/flag list, upstream's M2 Air + AMD baselines, and a warning that a full
  sweep runs over an hour on 32 cores — always pin -b and -w.
- What the binding does NOT implement (Generic6DOF, ConeTwist, SoftBody3D,
  per-pair exceptions) plus the silent Area3D-vs-trimesh divergence.
- Why pinball and not the ragdoll game: ~/Documents/alright is the better fit but
  is BLOCKED — its ragdoll is built from Generic6DOFJoint3D. Watch that upstream.
- Provenance: Box3D is serious (Catto, MIT, s&box + Esoterica adoption); the
  binding is five weeks old with zero releases and self-declares experimental.
  This is a testbed, not a bet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 08:54:25 +10:00

4.2 KiB
Raw Permalink Blame History

Godot physics testbed (Box3D / Jolt / GodotPhysics A/B)

A minimal playable pinball table for Godot 4.7 whose real job is comparing 3D physics backends on the things pinball stresses: fast-small-ball CCD, motorised hinge flippers, and joint limits. The backend is one project setting, so the same table runs under all three engines.

Run it

/Applications/Godot.app/Contents/MacOS/Godot --path godot

Keys: Z left flipper, / right flipper, Space plunger, R reset ball.

The project defaults to Box3D Physics (Extension) via godot-box3d (MIT). bin/ carries a macOS arm64 dylib built from source (upstream has no releases; build recipe below).

A/B probe harness

godot/run_probe.sh          # GODOT_BIN=... to point at a different binary

Writes override.cfg to switch physics/3d/physics_engine, runs probe/probe.tscn headless under each backend, and prints per-engine results (also saved to probe/results/, untracked). Two tests:

  1. Tunneling — ball fired at a 1 cm static wall at 5160 m/s, CCD off and on.
  2. Hinge motor — a motorised HingeJoint3D arm; a flipper is a motorised hinge.

There is also a table-level flipper check: PINBALL_AUTOTEST=1 godot --headless --path godot flips both real flippers for 0.5 s and prints the swing angles.

Results (2026-08-06, Godot 4.7.stable, M3 Ultra)

Tunneling — ball contained by the 1 cm wall?

speed m/s Godot ccd off Jolt ccd off Box3D ccd off all three, ccd on
5 tunneled* tunneled* contained contained
10 tunneled contained* contained contained
20 contained* contained* contained contained
40 contained* contained* contained contained
80 tunneled tunneled contained contained
160 tunneled tunneled contained contained

* With a 60 Hz tick, every speed here can step over a 1 cm wall; the discrete-engine "contained" rows are phase luck (2 m at 20 and 40 m/s lands a tick exactly on the wall — and both engines produce identical trajectories, so they get lucky identically). The signal is the pattern, not individual rows: Box3D never tunnels even with the CCD flag off (speculative contacts are always on), the other two need continuous_cd = true, and with it all three hold at 160 m/s.

Hinge motor (target 5 rad/s, 1 s):

engine reached rad/s moved verdict
GodotPhysics3D 5.0 73.5° (wrapped) works
Jolt 5.0 75.1° (wrapped) works
Box3D +5.0 73.5° (wrapped) works

Flipper autotest on the real table (motors at ±25 rad/s, limits 5°…+50°):

engine left swing right swing
Box3D +51.8° — stops at the 50° limit 51.8° — stops at limit
Jolt 106.6° — wrong way, blows ~100° past the limit +104.3° — same

Two real findings, half a second of headless runtime each:

  1. Box3D's hinge motor sign is inverted relative to GodotPhysics/Jolt (upstream drop-in-compatibility bug candidate for godot-box3d). The table's flipper signs are tuned for Box3D, the project default — flip FLIP_SPEED/REST_SPEED signs in scenes/table.gd to play under Jolt.
  2. With the motor overdriving into them, Box3D enforces the angular limits hard while Jolt lets this setup punch far through them.

Engine notes

Upstream Box3D research — Apple Silicon threading (cap workers at 24, not 32), the benchmark harness and how to re-run it, what godot-box3d does not support, and why this game became the testbed — is in BOX3D_ENGINE_NOTES.md.

Rebuilding the extension

git clone --recurse-submodules https://github.com/bearlikelion/godot-box3d
cd godot-box3d && cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j
cp bin/libgodot-box3d.dylib <here>/bin/

Known Box3D gaps that don't matter for pinball: no Generic6DOF/ConeTwist joints, no SoftBody3D, Area3D can't detect trimesh/heightmap bodies (the ball is a sphere; fine). Box3D also ignores Godot's hinge BIAS/LIMIT_SOFTNESS/LIMIT_RELAXATION params (warns at load; harmless here).