LIVE PHYSICS // checking…
PalletBallet
A pallet enters the conveyor zone. Geometry, mass, wrap, temperature, friction — all of it shoved through MuJoCo. Out the other side: the speed it can actually survive, the failure mode that would otherwise govern, and a replay of the moment it would have toppled. Below, you get the dials before the solver shows its answer. Beat it if you can.
You set the belt. The physics decides.
Every dispatch is a live simulation: your dial settings go to the API, MuJoCo runs the conveyor profile against the actual pallet, and the 3D replay you watch is the solver's own trace — every box pose, every frame, straight from the physics engine. After the dust settles, the solver reveals the envelope it would have set. Score by shipping fast without shipping it onto the floor.
API traffic — every call this page makes, copyable as curl
No canned outcomes: the replay frames are MuJoCo's integrator output at 30 Hz, and the envelope reveal is a fresh bisection search (5–8 sims) per pallet. If the numbers feel conservative, read how the failure detectors work — then argue with the physics.
A static speed table is conservative on good pallets and blind to weird ones
Cold-storage warehouses move pallets across long conveyors at speeds set once, usually pessimistically, by a person guessing at the worst case. A perfectly stacked dairy pallet gets the same 1.0 m/s as a top-heavy unwrapped tower of frozen meat that should be capped at 0.4 m/s. The first is being throttled. The second is being launched.
PalletBallet skips the table. For each pallet — described by its actual geometry, mass distribution, wrap, and thermal state — it runs a small batch of physics simulations and answers a narrower question:
Given this pallet, in this thermal state, what conveyor motion profile is safe right now?
The result is per-pallet decisioning: speed, acceleration, deceleration, lateral g-tolerance, and the failure mode that would otherwise govern — slip, top-item slide, load shift, or tip-over. A PLC can throttle the next conveyor zone with it. A WMS can flag the pallet for re-wrap. A human can look at the replay and agree. You just did, above.
Failures in slow motion
Trust comes from watching things go wrong. Here's a top-heavy unwrapped pallet hitting an aggressive start ramp. Every frame is from a real solver run, not a render — same physics, same engine, same trace the API returns to the game above.
Six stages, end to end
- 01
Inputs land in a single contract
Scanner output, WMS export, a manual stack builder, or the random adapter all converge on the same
RawInputsPydantic schema. The configurator turns it into aPalletConfig— items with positions, masses, fragility, wrap, temperature. The curated pallets you just played live atGET /scenarios. - 02
Friction is temperature-aware
Cold-chain pallets move through frost-melt regimes where mu drops by half. The friction model interpolates a calibration table and applies a transition penalty when the pallet is near 0 °C with recent thermal motion — the regime that actually causes slips on real lines.
- 03
The MJCF builder emits MuJoCo XML
Each pallet becomes a free-body MJCF scene: base, items, contact pairs with the right friction coefficients, a velocity-actuated conveyor. Wrap becomes weld constraints with stiffness per wrap type. No GUI, no rendering — pure physics.
- 04
The solver runs the conveyor profile
Acceleration ramp, target speed, dwell, deceleration. The trace records pallet pose, item poses, and belt state every step. Failure detectors look for tip angle past threshold, item slide in the pallet frame, pairwise load shift, and pallet-vs-belt slip. With
include_replay, the API hands the whole 6-DoF pose history to your browser — that's what the 3D stage plays back. - 05
The threshold analyzer searches the boundary
A bisection search walks the speed and acceleration axes. Five to eight simulations later, you have a tight upper bound on each — the line between "survives" and "fails." Results are cached by config fingerprint, so duplicates cost nothing.
- 06
FastAPI hands back a SafetyResult
Plus the sweep points, the dominant failure mode, the cache hit count, and a confidence score. Available at https://palletballet-api.boothe.io/docs. Cache hits return in ~1 ms.
How this page actually serves you a physics result
your browser
│
▼
┌─────────────────────┐ ┌───────────────────────────────┐
│ boothe.io/ │ │ palletballet-api.boothe.io │
│ palletballet │ │ │
│ (Astro on │ fetch │ ┌─────────────────────────┐ │
│ Cloudflare │────────►│ │ Cloudflare Tunnel │ │
│ Workers) │ CORS │ └─────────────┬───────────┘ │
└─────────────────────┘ │ ▼ │
│ ┌─────────────────────────┐ │
│ │ home server (docker) │ │
│ │ ├── palletballet:8000 │ │
│ │ ├── cloudflared │ │
│ │ └── watchtower │ │
│ └─────────────────────────┘ │
└───────────────────────────────┘
▲
│ pull on every push to main
│
ghcr.io/ebootheee/palletballet
▲
│ build & push
│
GitHub Actions
Stack
The whole runtime self-hosts on a Ryzen 9 home server. A single conveyor sim lands in 60–200 ms; a full envelope search in 400–800 ms; cache hits in ~1 ms. Batch mode chews through ~7 pallets/second across all cores. You're hitting that machine through a Cloudflare Tunnel right now.
> Read the source. Run your own.
The code is open. The Dockerfile and tunnel config are in
infra/. Cloning and running docker compose up -d
gets you a working copy of the same system this page is talking to — and the
launch post walks through every
endpoint, the batch runner, and the physics.