macrosoft3dpinball/godot/README.md
m3ultra 2ca67bb28b Add Godot 4.7 physics testbed: Box3D/Jolt/GodotPhysics A/B pinball table
Playable table (hinge-motor flippers, plunger, bumper, drain) plus a headless
probe harness that measured, per engine: tunneling of a fast ball through a
1 cm wall with CCD off/on, and hinge-motor drive. Findings in godot/README.md:
Box3D never tunnels even with CCD off, but its hinge motor sign is inverted
vs Jolt/GodotPhysics, and Jolt blows through angular limits that Box3D holds.
Ships a from-source macOS arm64 libgodot-box3d.dylib (upstream has no
releases). Also gitignore the remaining Microsoft media at repo root.

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

93 lines
3.9 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.
## 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).