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> |
||
|---|---|---|
| .. | ||
| lib/macos-arm64 | ||
| native | ||
| tests | ||
| bodies.ludic | ||
| index.ludic | ||
| native.ludic | ||
| package.ludic | ||
| queries.ludic | ||
| README.md | ||
| shapes.ludic | ||
| state.ludic | ||
| system.ludic | ||
| walker.ludic | ||
| water.ludic | ||
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.shfetches Jolt v5.6.0, checks its SHA-256, and builds it with the shimnative/shim/jph_shim.cppintolib/<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_DETERMINISTICand 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_tickruns 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_removetakes 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.