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

99 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```bash
/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](https://github.com/bearlikelion/godot-box3d) (MIT). `bin/` carries a
macOS arm64 dylib built from source (upstream has no releases; build recipe below).
## A/B probe harness
```bash
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](BOX3D_ENGINE_NOTES.md).
## Rebuilding the extension
```bash
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).