ludic/packages/ludic.physics
Orkuncakilkaya e909ec122c feat(character): 16.8 - on land a physics walker moves the body (CharacterGround.walk); the package's own gravity, ballistic air and slope-probe refusal are gone
The package keeps the rules and hands them over each step: speed and heading, whether a jump
leaves, CHAR_STEP and the boots' slope limit. The walker (Jolt CharacterVirtual) does gravity,
landing, steps, slopes and following the ground down. Too steep is ground too steep facing the way
the body wanted to go; stepping off an edge is not. ludic.physics: phys_walker_move (follow the body
if something else moved it, then step), the slope limit, the jump always honoured. The character's
tests run on a real Jolt walker over the same fake world: 15 pass, a knee-high boulder now a step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:47:14 +03:00
..
lib/macos-arm64 feat(character): 16.8 - on land a physics walker moves the body (CharacterGround.walk); the package's own gravity, ballistic air and slope-probe refusal are gone 2026-09-27 00:47:14 +03:00
native feat(character): 16.8 - on land a physics walker moves the body (CharacterGround.walk); the package's own gravity, ballistic air and slope-probe refusal are gone 2026-09-27 00:47:14 +03:00
tests feat(character): 16.8 - on land a physics walker moves the body (CharacterGround.walk); the package's own gravity, ballistic air and slope-probe refusal are gone 2026-09-27 00:47:14 +03:00
bodies.ludic feat(physics): a walker (Jolt CharacterVirtual with an inner body) and the verbs a boat and a guest's copy need - force, torque, angular impulse, spin, set_pose 2026-09-27 00:24:47 +03:00
index.ludic feat(physics): a walker (Jolt CharacterVirtual with an inner body) and the verbs a boat and a guest's copy need - force, torque, angular impulse, spin, set_pose 2026-09-27 00:24:47 +03:00
native.ludic feat(character): 16.8 - on land a physics walker moves the body (CharacterGround.walk); the package's own gravity, ballistic air and slope-probe refusal are gone 2026-09-27 00:47:14 +03:00
package.ludic feat(physics): phase 16 - ludic.physics over Jolt Physics v5.6.0 2026-09-26 23:41:37 +03:00
queries.ludic fix(physics): push keeps the old collider contract exactly - a thing wholly under the feet or over the head is not in the way, anything else pushes out sideways (Jolt's axis when side-on, else radially by the overlap across) 2026-09-27 00:08:36 +03:00
README.md feat(physics): a walker (Jolt CharacterVirtual with an inner body) and the verbs a boat and a guest's copy need - force, torque, angular impulse, spin, set_pose 2026-09-27 00:24:47 +03:00
shapes.ludic feat(physics): phys_heightfield_smooth - the ground as a renderer draws a cubic B-spline height map, smoothed in C 2026-09-27 00:27:15 +03:00
state.ludic feat(physics): a walker (Jolt CharacterVirtual with an inner body) and the verbs a boat and a guest's copy need - force, torque, angular impulse, spin, set_pose 2026-09-27 00:24:47 +03:00
system.ludic feat(physics): phase 16 - ludic.physics over Jolt Physics v5.6.0 2026-09-26 23:41:37 +03:00
walker.ludic feat(character): 16.8 - on land a physics walker moves the body (CharacterGround.walk); the package's own gravity, ballistic air and slope-probe refusal are gone 2026-09-27 00:47:14 +03:00
water.ludic feat(physics): phase 16 - ludic.physics over Jolt Physics v5.6.0 2026-09-26 23:41:37 +03:00

ludic.physics

Rigid bodies, still shapes that can be removed, queries and buoyancy, over Jolt Physics (MIT) built here from a pinned tag (phase 16). Uses ludic.base and nothing else.

import "ludic.physics"

The rules it keeps

  • A package with a native library (phase 15): native/build.sh fetches Jolt v5.6.0, checks its SHA-256, and builds it with the shim native/shim/jph_shim.cpp into lib/<target>/ (libludicjolt.dylib, ludicjolt.dll). The shim keeps the rules in packages/README.md: no callback into Ludic, contacts recorded during the step and drained after it, Jolt's job threads inside C.
  • Deterministic across machines: JPH_CROSS_PLATFORM_DETERMINISTIC and no floating point contraction, so the Mac and the PC agree in co-op and a run repeats to the bit (a test holds it).
  • Fixed steps: phys_tick runs as many 1/60 s steps as the frame's time holds, at most four - frame rate never changes the rules, and a stall is not replayed.
  • It saves nothing. A thing at rest is saved by the Thing's owner; the world is rebuilt on load and on a map swap (phys_close, phys_open).
  • A shape and the world are handles it keeps; a game names a shape and a body by an int.
  • Removal is real: phys_remove takes a body out of the world - no parking it far away.

The API

phys_open(st, max_bodies, threads) -> bool, phys_close(st), phys_is_open(st) a world, and everything in it let go
phys_box, phys_sphere, phys_capsule, phys_cylinder, phys_dome(r, h), phys_offset(shape, x, y, z, yaw) shapes, each an int (-1 refused); a dome is a boulder standing on its origin
phys_heightfield(st, h, n, ox, oz, cell), phys_mesh(st, v, nv, tri, nt) the ground (PHYS_HOLE is none) and a dock or cabin
phys_ground_add, phys_static_add, phys_kinematic_add, phys_body_add(..., mass) bodies: the ground, still things, what the game moves (the player), what falls and floats
phys_settle(st) after adding many still things, before the first query
phys_remove(st, id), phys_count(st) gone for good
phys_pose(st, id) -> PhysPose, phys_yaw_of(p) position, rotation, velocity
phys_place, phys_impulse, phys_set_velocity, phys_set_kinematic_target, phys_awake moving a body
phys_ray(st, o, d, mask) -> PhysHit the first thing along a ray
phys_top_at(st, x, z, r, from_y, depth, mask) -> float what a foot would land on (CharacterGround.top_at), PHYS_NONE if nothing
phys_push(st, x, z, r, y0, y1, mask) -> PhysPush the move that stands an upright body clear (CharacterGround.push)
phys_resolve(st, x, z, r, y0, y1, mask) -> bool, phys_resolved_x / _z pushed clear in two passes, the place kept (CharacterGround.push, pushed_x / _z); a thing wholly under the feet or over the head is not in the way
phys_step_top(st, x, z, r, feet, reach, mask) -> float the highest top a foot could step up to, anything taller than feet + reach left out as a wall
phys_overlap(st, x, y, z, r, mask) -> []int the bodies a ball touches
phys_walker_add(st, r, h, x, y, z, max_slope, mass), phys_walker_step(st, id, dt, vx, vz, jump) -> ground, phys_walker_pose -> PhysWalk, phys_walker_place, phys_walker_limits(step_up, stick), phys_walker_remove a walking body (Jolt's CharacterVirtual): gravity, landing, steps, slopes, following the ground down; it shoves dynamic things and rays find its body
phys_force, phys_torque, phys_angular_impulse, phys_spin_y, phys_set_pose(st, id, pose) an oar's stroke and a turn; a guest's copy put where the host said
phys_float(st, id, buoyancy, linear_drag, angular_drag), phys_sink, phys_floating a body buoyed each step against PhysWater and carried by its current
phys_facts(st) PhysFact: PHYS_HIT (two bodies met at ph_hard m/s or more), PHYS_SPLASH (a floating body reached water)
phys_tick, phys_advance(st, dt) -> steps, phys_step, phys_system() the system, in PH_SIMULATE
phys_config(st, step, sub, hard), phys_gravity(st, y) the numbers

A query's mask is PHYS_M_GROUND, PHYS_M_STATIC, PHYS_M_MOVING, or PHYS_M_SOLID / PHYS_M_ALL - the body's own questions leave the ground out, because hk_ground stays the pure ground query.

The port

PhysWater { height(x, z), current_x(x, z), current_z(x, z) } - the surface under a point (PHYS_NONE where there is no water) and the current there. Unbound, there is no water. The game binds it to the same function its water is drawn with, so a boat never floats on a different wave from the one on screen.

Tests

ludic test packages/ludic.physics runs the real library: a box onto a heightfield and a ray to it, still things removed, a boulder's top, a push out of a post, a crate floating level and drifting, hard and soft landings, a kinematic shove, fixed steps, and a pile run twice to the bit.