A generated game handed box3d an infinite extent and took the whole process
down — found by the eval harness actually generating games, not by reading
code.
NaN was already absorbed (Rust's `max` returns the non-NaN operand), but
INFINITY survives it and poisons every plane normal to NaN. box3d's face query
then never beats its -f32::MAX starting separation, leaves max_face_index at
the -1 sentinel, and convex_manifold.rs casts that sentinel through u8 into
255 and uses it to index a 6-element array.
The port is FAITHFUL to upstream C here — convex_manifold.c does the same
(uint8_t)maxFaceIndex cast; C reads garbage where Rust panics — so box3d is
not the place to diverge. The boundary is: never hand the solver a value it
cannot reason about. sane_extent() clamps non-finite and out-of-range
dimensions (and density) to a workable range.
Regression test covers +inf, -inf, NaN, 0 and negative extents: none may panic
and all must leave a finite pose after 30 ticks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- libs/game/sim/dynamics.rs: box3d world inside GameWorld, body mirror
RECONCILED against the sorted entity list per tick (merge-walk), so
retain/rollback/reset are correct by construction. Statics + kinematics
mirrored (kinematics via target transforms so resting rigids inherit
platform velocity); smooth terrain as a box3d heightfield
- New BodyKind::Rigid + body:"rigid" with density/friction/restitution;
game.push = mass-scaled impulse; set_pos/set_vel detected via bit-exact
pose caches (no new dispatch arms). Sphere rigids roll on real spheres
- Entity.orient quat read back per tick; renderer builds quat instance
transforms for rigids (no shader change). Step order: mover sweep
verbatim -> reconcile -> world_step(dt, 4) -> readback; rigid-free
worlds skip the solver
- GameWorld stays Clone via box3d snapshot round-trip (bit-identical
continuation proven by test)
- Determinism: double-run equality + golden hash 0xa8a2baf71e4a564f
(aarch64, debug and release). Perf: 0.038 ms/tick for 100 movers +
50 rigids + 65x65 terrain (budget 2 ms)
- Tape probe BYTE_IDENTICAL; box3d crate untouched; arcade demo gains a
kicked crate stack (captures verified: settles upright, then topples
with rotated resting poses)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>