Releases: Stink-O/box3d-godot
Release list
v0.4.2: record, replay, scrub
Recording gets a player. This release turns 0.4.1's recording API into a full record-and-replay workflow in the demo, closes the editor-experience gap with native gizmos, and fixes everything found in a week of live testing.
Record and replay, with a timeline
The timeline is very early and still needs iteration. It works, and everything below is measured, but expect rough edges in its UI and behaviour, and expect it to change shape in coming releases.
- Record any sample from the sidebar: a whole session from step 0, or from the moment you press the button. Everything you do to the world is captured, fired balls, bombs and their blasts, grab-drags, because the recording is the world's own mutation log.
- A draggable timeline scrubs the result in both directions, plays it backwards, single-steps either way, and shows a buffer band plus a live percentage while it indexes in the background. Spacebar toggles play.
- Reverse playback does not re-simulate: displayed frames are cached as 20-byte quantised transform rows, so scrubbing a 16,000-body pyramid collapse backwards costs well under a millisecond a frame once indexed. Recordings bigger than the 96 MB memory budget spill to a session temp file (deleted on close) and stay scrubbable end to end; seeks into unsimulated territory keep the UI responsive with a progress readout.
- Replays look like the scene you recorded. A small sidecar next to the recording captures each body's colour (including vertex-coloured materials, averaged in linear light) and material response; the .b3rec itself stays byte-identical to upstream's format. No sidecar means the neutral look, so old and upstream recordings still play.
- Stopping a recording is instant: the capture is amortised across the session and the file writes happen on a background thread, with a visible saving state. On the heaviest scene in the demo the click went from a 114 second freeze to under 10 ms.
- Replaying at a different worker count re-checks the embedded state hashes, which makes the timeline a live cross-thread determinism harness. The Wave Pile sample demonstrates it, replaying bit-identically at 1, 2, 4 and 8 workers on desktop. Determinism on the web build remains unverified.
Editor and binding
- In-editor gizmos for every collider type and all nine joints, drawn in upstream's own debug palette. A hinge shows its axis and limit arc in the editor viewport before you press play; hulls re-hull the authored cloud so the outline is exactly what the solver gets.
- Debug shells in the demo's debug view now cover convex hulls, height fields and fitted meshes, not just primitives and triangle meshes.
- The last developer-facing globals are bound: Box3DWorld.dump_memory_stats(), get_box3d_version(), is_double_precision().
Demo
- Four new samples (69 total): Wave Pile (Determinism), Box Hull and Hull Reduction (Geometry), Far Stack (World).
- The sample picker groups by category with a submenu each and marks the sample you are on; the touch picker mirrors it with collapsible sections.
- Bombs now work in every sample: the blast auto-calibrates to scenes authored at upstream's body density, verified to leave all previously working samples byte-identical. A crash when exploding near dynamic triangle-mesh bodies is guarded sample-side; the underlying null-deref is upstream's and has been written up for filing.
- Spinning Stick now walks upstream's own XorShift32 generator instead of a frozen Godot RNG draw, so it settles where the og sample app settles. The Wrecking Ball's crane actually hangs over its rope and the wall starts at rest.
The headless selftest suite grew from 442 to 709 assertions this release, all green, plus the 69-sample suite and a dedicated replay performance rig.
Assets
Same layout as 0.4.1: Linux/Android/Windows libraries (debug and release), both web builds, the debug-signed demo APK, and both browser-demo zips. The threaded web zip is what the itch.io page runs; the single-threaded zip suits plain static hosts. Windows DLLs are MinGW cross-builds, verified to import only KERNEL32 and msvcrt, and remain untested on Windows itself.
v0.4.1: full API parity, recording and replay
Full API parity with upstream Box3D, and the demo browser roughly doubles in size.
Highlights
- Near-complete binding surface. Every upstream feature area is bound, with deliberate, documented exceptions (the external task-system hooks stay unbound for thread-safety, the raw solver callbacks ship as the Box3DContactRules table instead of script callbacks, and some C-level utilities Godot already provides are out of scope): live setters everywhere (no property rebuilds a body any more), contact/hit/sensor/joint events with full geometry, all-hits queries with two-way 64-bit filtering, height fields, mesh-from-data, baked compounds, per-triangle materials, and the whole b3World_Draw debug overlay.
- Recording and replay. New Box3DRecording and Box3DReplayPlayer classes wrap upstream's recording system, with scrubbing, keyframe seeking and divergence checking. The new Rewind sample shows it off: the scene records itself, then scrubs ghost playback backwards and forwards through the take.
- Contact rules. Box3DContactRules gives the solver callbacks a thread-safe, data-driven path: never-collide pairs, one-way platforms, and friction/restitution mixing overrides, authored from script with zero callbacks on worker threads.
- Determinism, proven from Godot. A recording made at 1 worker replays hash-identical at 2, 4 and 8 workers, as a permanent selftest.
- 32 new samples (33 -> 65), each verified against its upstream source, with hover tooltips and a revealable description in every scene. The character controller is now upstream's full mover course: spring suspension, sprint, stairs, ramps, one-way platforms, pushable crates, a sprung door and a third-person camera.
- Editor experience. In-editor class reference for all 20 classes, scene-dock icons, configuration warnings on every misconfigurable node, inspector groups with real units. Sidebar settings you change now survive resets and sample switches.
- Feature selftest grew from 47 to 442 checks; sample coverage from 33 to 65 scenes. All green on every commit.
Known limitation
Some of the newer samples have no native rebuild for the comparison engines yet: switching the demo to Jolt or Godot Physics on those scenes may show a blank scene. Box3D is the tested path.
Assets
Same layout as v0.4.0: Linux/Android/Windows GDExtension binaries (debug and release), the two web builds (threaded for hosts with cross-origin isolation, single-threaded for plain static hosts), the Android demo APK, and the two self-hostable web demo zips. The Windows DLLs are MinGW cross-builds, untested on Windows. The browser demo is playable at stinkysunstep.itch.io/box3d-godot.
v0.4.0 — Mobile browser, native-engine parity, Ball Flood
The demo runs properly on phones, the two native engines finally match Box3D on the samples they were quietly getting wrong, and there is a new stress sample that pours unlimited balls into a glass tank.
The browser demo lives on itch.io
Play it on itch.io — the multi-threaded build, full-size scenes, no download. Desktop, Android and iOS all run that build there.
An earlier release claimed Safari could not run the threaded build and was bounced to a fallback on GitHub Pages. That was wrong, and the Pages deployment is retired — it is stale and receives no updates. itch is the only page that is kept current. The threaded build's isolation guard still exists, but as a readable notice for genuinely un-isolated hosts, not as a Safari path.
Mobile
The browser demo was close to unusable on a phone. All of this is gated behind touch/mobile feature detection, so desktop behaviour is untouched:
- Multitouch no longer flails the camera. Touches are tracked per finger index instead of through Godot's emulated mouse, which had been hopping between fingers. A joystick finger's drags can no longer register as a phantom pinch.
- Landscape only. Portrait did not merely look wrong, it failed to load on iOS; the page now prompts to rotate.
- The UI fits. Panels sit on their own layer above the touch controls, are opaque, and scroll by drag — the settings menu was previously cut off with no way to reach the rest.
- Async physics step no longer softlocks the tab. It is now refused on every wasm build, threaded included, and the sidebar hides the checkbox when the build refuses it.
- The Reset button's ⟲ rendered as a tofu box on web, where there are no system fonts to fall back to.
Godot Physics and Jolt: parity fixes
The engine selector rebuilds each sample with native bodies. Several samples were rebuilding into something quietly different from what Box3D shows:
- Multicoloured meshes were flat grey. Materials chosen in
_readydo not exist on the unparented instance the rig walks; bodies now publish that choice explicitly. - The bomb did nothing. There is now a native twin with the same fuse, blink and blast.
- The Huge Pyramid did not appear at all — its 16,290 blocks are generated in code, which the rig had no way to see. On Jolt the top half then froze in mid-air: bodies past Jolt's default 10,240 cap are silently never created, so the caps are raised in
project.godot. - The marble emitter spawned nothing, and the gyroscopic precession tops did not exist.
- The gyroscopic torque handle did not spin. Its spin is imparted in
_ready, so it never reached the rig; the handle floated motionless in the sample's zero gravity.
That last one comes with a measured caveat now shown in the sample's port notes: with the spin restored, both native servers hold the handle's spin axis on a smooth drift for a full minute where Box3D swings it through repeated inversions. Neither integrates the gyroscopic term, so the Dzhanibekov flip — the reason the sample exists — cannot appear there, and a steadily spinning handle would otherwise read as the sample working.
Ball Flood
A new stress sample: three uncapped emitters pour immortal balls into a glass tank, with an on-screen body counter and a Pause Emitters button. It is a rendering benchmark as much as a solver one — per-ball MeshInstance3Ds died at 45,000+ draw calls and 21 fps while the solver read 0.00 ms. Every emitter ball now draws through one shared MultiMesh: 25,202 bodies at 81 draw calls.
The body counter is opt-in from Settings, drags anywhere, and remembers both its position and whether it is shown.
Marble Run was rebuilt around the same flood, with node scales baked into the shapes — Box3D shape frames never read node scale, so a scaled node moved the mesh and left the collider behind.
Also
- Emitter spawns are spread over a disc. Perfectly aligned overlapping spawns extrude upward as a growing column on Godot Physics, which resolves an axis-aligned sphere stack purely vertically.
- The engine badge no longer reads "(unverified)" indefinitely — the behavioural probe retries until it can actually conclude.
- Social card templates for the GitHub preview and the itch cover live in
godot/tools/social_cards/.
Assets
Drop the library for your platform into godot/demo/bin/ next to box3d.gdextension, then open godot/demo/project.godot in Godot 4.7. template_debug is the one to use with the editor; template_release is for exported games.
| file | what it is |
|---|---|
libbox3d_godot.linux.template_{debug,release}.x86_64.so |
Linux |
libbox3d_godot.windows.template_{debug,release}.x86_64.dll |
Windows — cross-built with MinGW, never run on Windows |
libbox3d_godot.android.template_{debug,release}.{arm64,x86_64}.so |
Android |
libbox3d_godot.web.template_release.wasm32.wasm |
Web, threaded |
libbox3d_godot.web.template_release.wasm32.nothreads.wasm |
Web, single-threaded |
box3d-demo-android.apk |
the demo as an installable app, debug-signed, arm64 + x86_64 |
box3d-demo-web-threaded.zip |
the demo for itch.io or any host sending real COOP/COEP headers |
box3d-demo-web.zip |
the demo for a plain static host — no special headers, works anywhere |
Pick the web zip by what your host can do, not by which sounds faster: a threaded wasm module declares shared memory and cannot load without cross-origin isolation. On itch, enable the SharedArrayBuffer option and use the threaded zip.
Caveats that have not changed
- The Windows DLLs are cross-compiled from Linux with MinGW-w64 and have not been run on Windows. They import only
KERNEL32.dllandmsvcrt.dll, and-ffp-contract=offis preserved, but treat them as untested. - Determinism on wasm is unverified — upstream's Emscripten CI is build-only — so the browser builds are not a reference for behaviour.
- The browser build is a preview. WebAssembly costs something over native and it renders through Compatibility (WebGL2) because Godot 4.7 has no WebGPU. Judge performance from a desktop run.
v0.3.0 — Multi-engine demo and solver profiler
Superseded by v0.4.0. These assets are kept for anyone pinned to this tag; the demo in them predates the mobile browser fixes, the Godot Physics / Jolt parity fixes and the Ball Flood sample. For the current binaries and the playable browser demo, start at v0.4.0.
Two claims in these notes were wrong and are corrected below. Current iOS Safari runs the threaded build on itch perfectly well, and the GitHub Pages deployment they pointed at is retired — it is stale and receives no updates. itch.io is the only browser demo that is kept current.
Run the demo's own samples on Box3D, Godot Physics or Jolt Physics, and read a per-phase solver profiler while they run.
Physics engine selector
The bottom of the demo's Settings sidebar now picks the solver. Same menu, same camera, same shoot/grab/bomb tools, same reset — only the engine underneath changes. Godot reads physics/3d/physics_engine once at startup and offers no runtime switch, so selecting one relaunches the demo on the same sample.
The sample scenes are untouched. They are read into a backend-neutral description and rebuilt with native bodies, which keeps the Box3D path exactly as it was and means every sample stays authored once.
Differences that would otherwise make the comparison dishonest are corrected: mass from density and volume, explicit inertia and centre of mass (Godot Physics uses an AABB approximation for capsule, cylinder, hull and concave shapes), explicit friction and restitution, cylinders and cones as N-gon hulls to match how Box3D builds them, and Box3D's AND collision filtering re-expressed in Godot's OR model. What cannot be matched — combine rules, substeps versus iterations, joints Godot has no equivalent for — is reported on screen per sample.
Six samples are Box3D-only and say so: gyro_torque (needs the gyroscopic term), car (wheel joint), explosion (projected-area impulse), ragdoll (joint springs), gyro_precession (fast-rotation bypass), huge_pyramid (multimesh renderer, and Jolt's 10240 body cap).
Solver profiler
A bottom drawer with Profile, Counters and Frame Time tabs, cloned from the upstream sample app's Metrics panel — same rows, same labels, same statistics, so a number here means what it means there.
Box3D reports all 22 phases through new Box3DWorld.get_profile() and get_counters() bindings over b3World_GetProfile / b3World_GetCounters.
Godot Physics and Jolt report a total only. Both compute per-phase timings and emit them to the "servers" debugger profiler, but EngineDebugger.profiler_enable() is a no-op outside a debug session, so a running game receives nothing. The panel says so rather than showing a flat bar. Jolt additionally returns 0 for every PHYSICS_3D_* counter.
Draggable, resizable, and it remembers its geometry, expanded rows and visibility across launches.
Also
- Upstream synced through
Fixes 09 (#104). godot/tools/compare.shruns a standalone side-by-side harness for recording.- The live engine is identified behaviourally rather than from the project setting, because an unregistered engine name is not an error in Godot — it falls back silently. A mismatch paints a banner instead of mislabelling a recording.
Assets
| file | use |
|---|---|
box3d-demo-android.apk |
the demo, debug-signed, arm64 + x86_64 |
libbox3d_godot.linux.template_{debug,release}.x86_64.so |
Linux |
libbox3d_godot.android.template_{debug,release}.{arm64,x86_64}.so |
Android |
libbox3d_godot.windows.template_{debug,release}.x86_64.dll |
Windows (MinGW cross-built, untested) |
libbox3d_godot.web.template_release.wasm32{,.nothreads}.wasm |
Web, threaded and single-threaded |
box3d-demo-web-threaded.zip |
the browser demo for itch.io, or any host sending real COOP/COEP headers |
box3d-demo-web.zip |
the browser demo for a plain static host, no special headers needed |
Drop the libraries into godot/demo/bin/ alongside box3d.gdextension.
Browser build
Play it on itch.io — multi-threaded solver, full-size scenes, no download. Desktop, Android and iOS all run that threaded build there.
Both builds are attached if you want to host your own:
box3d-demo-web-threaded.zip— the itch build. Needs cross-origin isolation: either a host that sends real COOP/COEP headers or itch's SharedArrayBuffer option. A visitor without isolation gets a readable notice explaining why, not a black screen.box3d-demo-web.zip— the single-threaded fallback. Works on any static host with no special headers, in every browser. The heaviest scene is halved to keep one thread comfortable.
Determinism on wasm is unverified — upstream's Emscripten CI is build-only — so the browser builds are not a reference for behaviour.
Windows
Windows DLLs are attached to this release, cross-compiled from Linux with MinGW-w64 (GCC 16.1.1). They are self-contained: the only imports are KERNEL32.dll and msvcrt.dll, with libgcc and libstdc++ linked statically, and box3d_library_init is exported. -ffp-contract=off is applied on this path, so Box3D's deterministic-math build flag is preserved.
They have not been run on Windows. They are a different toolchain from MSVC, and this port's determinism guarantee is only verified across scalar/SSE2/NEON. Treat them as untested until someone confirms them on a real machine.
No more binaries in the repo
Prebuilt binaries are no longer committed. The Windows DLLs that used to be were four upstream core syncs stale and predated the get_profile() / get_counters() bindings, so a fresh clone got a degraded demo with nothing to say why. Every binary now ships here instead, versioned against the tag that produced it. Download what you need and drop it into godot/demo/bin/.
2026-07-27: assets refreshed at the current tag. The demo gained scroll-wheel reeling of a grabbed body and keyboard-focus fixes; the browser builds and their hosting solidified into a threaded primary (stinkysunstep.itch.io/box3d-godot) with a single-threaded zip for self-hosting on a plain static host. The native libraries are unchanged builds — no C++ changed.
v0.2.0 — Android support
Superseded by v0.4.0. These assets are kept for anyone pinned to this tag; the demo in them predates the mobile browser fixes, the Godot Physics / Jolt parity fixes and the Ball Flood sample. For the current binaries and the playable browser demo, start at v0.4.0.
Android support
The GDExtension now builds and runs on Android (arm64 + x86_64), verified on real hardware: the full 42-assertion binding selftest suite passes on an arm64 device, including on a physical handset under Vulkan / Forward Mobile. The demo gains touch controls (virtual joystick, touch grab, pinch/pan camera, charged-shot button) and a DPI-scaled UI, plus a measured 2x rendering speedup on the Cube Pile sample. Desktop behavior is unchanged — the desktop selftests are byte-identical to v0.1.0.
Attached:
box3d-demo-android.apk— the sample browser, debug-signed, installable on any Android 7.0+ (API 24) device:adb install box3d-demo-android.apklibbox3d_godot.android.*.so— prebuilt Android extension libraries (arm64 + x86_64, debug + release), 16 KB page alignedlibbox3d_godot.linux.*.so— prebuilt Linux x86_64 extension libraries (debug + release), so Linux users can clone and play without building
Other platforms: Windows x86_64 DLLs are committed in the repo (godot/demo/bin/), so a clone plays out of the box on Windows. macOS has manifest entries but no binaries have been built or tested — contributions welcome.
Toolchain setup, exact build commands, and every problem hit along the way are documented in godot/ANDROID_BUILD.md, including an explicit accounting of what is tested and what is not.
Known limitation: 32-bit ARM (armeabi-v7a) does not build — upstream Box3D's NEON path uses AArch64-only intrinsics (erincatto/box3d#92). 64-bit has been required by Google Play since 2019.