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.