macrosoft3dpinball/godot/BOX3D_ENGINE_NOTES.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

131 lines
6.6 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.

# Box3D — upstream research notes
*Findings from the evaluation session that led to this testbed existing (2026-08-06, M3 Ultra).
Complements [README.md](README.md), which covers this project's own A/B probe results. Nothing
here is about the table — it's about the engine underneath it and the hardware it runs on.*
## 1. Cap physics workers at 24 on this machine, not 32
The single most actionable finding, and it applies to **any** threaded physics on this fleet —
Box3D, Jolt, or anything else.
The M3 Ultra is **24 performance + 8 efficiency cores**. Box3D's own benchmark, swept across
worker counts on this machine, gets *slower* once work spills onto the E-cores:
| workers | ms | |
|---|---|---|
| 30 | 5,158 | |
| 31 | 5,310 | |
| 32 | 5,646 | **10% worse than 30** |
A physics step is a barrier — every worker waits for the slowest one — so handing a share of the
work to a core that runs at roughly a third the speed makes the whole step wait on it. More
threads is not more throughput past the performance-core count.
**So:** don't set worker count from `OS.get_processor_count()` / `hw.ncpu` on Apple Silicon.
Read the performance-core count instead:
```sh
sysctl -n hw.perflevel0.physicalcpu # 24 on this box — use this
sysctl -n hw.ncpu # 32 — do NOT use this
```
Within the performance cores the threading scales well: upstream's M2 Air `joint_grid` figures
are 1,089 ms single-threaded → 309 ms on 4 threads (**3.52×**).
Caveat on the table above: it is one scene (8,002 bodies / 40,444 contacts), best-of-4 runs, and
the low-thread-count rows of that particular sweep were lost to a truncated capture. The
*direction* is unambiguous and reproduces; treat the exact millisecond figures as indicative.
A pinball table is ~1 ball and a few dozen bodies, so none of this is a bottleneck **here** — it
matters when this testbed is used to answer questions for the bigger physics games.
## 2. The benchmark harness, and how to re-run it
Box3D ships a real benchmark suite. It is not built by default — note the option is
`BOX3D_BENCHMARKS`, **plural** (an easy 20 minutes to lose):
```sh
git clone --depth 1 https://github.com/erincatto/box3d && cd box3d
cmake --preset macos -DBOX3D_BENCHMARKS=ON
cmake --build --preset macos-release --target benchmark -j 24
./build/bin/Release/benchmark --list
```
Flags: `-b <index|name>` one scene · `-w <n>` a single worker count · `-t <n>` max threads ·
`-r <n>` repeats (default 4) · `-nc` disable continuous collision · `-s` record step times.
Scenes: `convex_pile`, `joint_grid`, `junkyard`, `large_pyramid`, `large_world`, `many_pyramids`,
`rain`, `trees100/50/25`, `washer`. Upstream keeps reference results in `benchmark/m2air_neon/`
(Apple Silicon) and `benchmark/amd7950x_*` — useful baselines to compare a run against.
**Budget the time.** A full sweep is ~11 scenes × every worker count × 4 repeats and runs for
well over an hour on 32 cores. Always pin `-b` and `-w`, and capture with `tee`, not `tail`.
The library itself builds clean on Apple Silicon (`cmake --preset macos`) and its whole test
suite passes in **0.61 s** — a fast, honest smoke test after any upstream bump.
## 3. What godot-box3d does NOT support
From the binding's own README. Hitting one of these looks like an engine bug and isn't:
- **Generic6DOF and ConeTwist joints** — not implemented
- **`SoftBody3D`** — not implemented
- Separation ray shapes
- Per-pair collision exceptions (use layers/masks)
- Changing a `PinJoint3D` anchor after creation — recreate the joint instead
And one behavioural divergence that will bite silently rather than error:
> **`Area3D` does not detect trimesh or heightmap bodies.** In Box3D a concave or heightmap shape
> can never be a sensor *visitor*, by design. No `body_entered` / `body_exited` fires for such a
> body. Godot's built-in physics and Jolt both report it. **Give anything that must be
> area-detected a convex shape.**
Pinball is unaffected — it needs hinge joints (supported) and CCD (Box3D's strength), and none of
the above. That is exactly why this became the testbed game.
## 4. Why pinball, and not the ragdoll game
`~/Documents/alright` (the fall-down-the-stairs game) is the portfolio's strongest *fit* for
Box3D — an active ragdoll of 14 rigid bodies and 12 joints with PD motor control, already paying
`physics_ticks_per_second=120` and `solver_iterations=16` and hand-picking `continuous_cd` on
head/torso/pelvis, which is the signature of fighting a solver.
**It is blocked:** that ragdoll is built from `Generic6DOFJoint3D`, which the binding does not
implement. Watch that single line item upstream — when Generic6DOF lands, `alright` becomes the
real experiment, and the question to answer is whether the 120 Hz / 16-iteration tax can come
back down.
Elsewhere in the portfolio physics is 2D, absent, or deliberately bespoke (Shitbox's car is a
raycast-suspension `RigidBody3D` with a hand-written tyre model — its feel lives in *its* code,
not the solver; Destroyulator's cascade is a support graph). `toastsim` is the only browser game
with a real 3D solver need and it already runs Rapier.
## 5. Provenance and maturity — worth being clear-eyed about
**Box3D** — Erin Catto (author of Box2D), MIT, portable C17, data-oriented, ARM NEON SIMD,
cross-platform determinism, record/replay. Repo created 2026-05-10, announced 2026-06-30,
~5,900 stars, actively developed. Shipping adoption: Facepunch's **s&box** and the **Esoterica**
engine. This is a serious engine by someone with 20+ years of solver experience.
**godot-box3d** (bearlikelion) — created 2026-07-02, ~134 stars, **zero releases**, and its README
says plainly: *"Status: early and experimental."* Performance benchmarking is still on its own
to-do list, so no one has yet shown it beats Jolt at anything. Modelled on godot-jolt's approach.
There are **no prebuilt binaries** — build from source (~30 s on this machine).
Practical consequence: this project is a **testbed**, not a bet. Jolt remains the sane default for
anything shipping; the value here is being able to answer engine questions with measurements
instead of opinions.
## 6. Browser note
Box3D builds to **WASM** — its CMake maps the SSE2 path onto wasm SIMD128 and supports pthreads.
But it ships **no JS bindings**, so a browser game would mean hand-writing that layer. Rapier
already gives you a polished JS API. Relevant if a web pinball or web physics toy is ever floated.
---
*Cross-reference: this project's own measured results — Box3D never tunnelling even with CCD off,
and the inverted hinge-motor sign versus Jolt/GodotPhysics — are in [README.md](README.md). That
sign inversion is a genuine drop-in-compatibility bug worth reporting upstream.*