Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ so a test written against the reference reads the same here.
| `aephysics.contact_solver_wide` | a colour's convex contacts eight at a time: the constraint of eight contacts as one single-precision record in lanes (`f32x8`, std.lanes -- the reference's SIMD path; one AVX2 register with `AETHER_AEPHYSICS_CFLAGS=-mavx2`, two four-lane halves without), the same prepare, warm start, solve, restitution and store, the bodies gathered into lanes and scattered back | `test_contact_solver_wide.ae` (one scene two ways -- the stack, the bounce with hit events, rolling resistance, a conveyor, a slide -- the lanes within a millimetre of the scalar solve, and the same lanes twice bit for bit) | [the falling grid: scalar 241 ms, lanes 142, the reference 155](bench/RESULTS.md#solver) |
| `aephysics.joint_solver` | the seven joints solved: each kind's prepare (frames relative to the centres of mass, effective masses, spring softness), warm start and solve (rigid or soft constraints, speculative limits, springs, motors), the kinds' accessors (limits, springs, motors, current angles and translations, forces and torques), joint.c's dispatch with the constraint hertz clamp; beyond the reference, a spherical joint's cone may be centred off frame A's z and may be an ellipse of two half-angles (the circle stays the default, bit for bit) | `test_joint_solver.ae` (181 checks: test_joint.c's accessors on every kind, each kind solved by hand) | [ours alone](bench/RESULTS.md#joint_solver) |
| `aephysics.solver` | the Soft Step over the workers, the reference's stages: the work in blocks (the bodies', each colour's joints, wide and scalar constraints) that workers claim by compare-and-swap, the stages published as sync bits by worker 0 -- the constraints prepared, per sub-step the velocities integrated (gravity, damping, the gyroscopic torque), warm start, solve, positions, relax, colour by colour with the overflow first on worker 0; restitution, the impulses stored; the bodies finalised in parallel (sleep velocities, move events, fast bodies swept for the time of impact, bounds and proxies), the joint and hit events, the trees refit, bullets, the sensors' hits, islands put to sleep | `test_solver.ae` (45 checks: free fall against the closed form, resting and sleeping, a stack, a bounce and its hit event, a joint event, a pendulum, a fast sphere stopped by a thin wall, a bullet, a sensor swept, two worlds bit for bit alike) | [1.2-1.3x the reference's step, stage by stage](bench/RESULTS.md#stage-by-stage) |
| `aephysics.physics_world` | the world's face: the step (pairs, narrow phase, solve, sensors, events), the events read back, the settings and counters, queries over every shape (overlap of a box or a proxy, the mover's planes, ray, shape and mover casts, the closest ray hit) and against one body at a transform of the caller's, explosions | `test_physics_world.ae` (118 checks: test_world.c's HelloWorld, contact, hit, move and sensor events, the explosion near and far; test_body_query.c's casts, overlaps, mover planes and time of impact; the world queries; a wave pile stepping alike twice) | [the reference's own benchmark scenes with the same checksums: 1.25x its AVX2 build on the large pyramid, 1.2x on the many pyramids, 1.05x on the joint grid, ours at eight lanes in AVX2 with float records (#121)](bench/RESULTS.md#body-sims-in-float-2026-10-07-121-37) |
| `aephysics.physics_world` | the world's face: the step (pairs, narrow phase, solve, sensors, events), the events read back, the settings and counters, queries over every shape (overlap of a box or a proxy, the mover's planes, ray, shape and mover casts, the closest ray hit) and against one body at a transform of the caller's, explosions | `test_physics_world.ae` (118 checks: test_world.c's HelloWorld, contact, hit, move and sensor events, the explosion near and far; test_body_query.c's casts, overlaps, mover planes and time of impact; the world queries; a wave pile stepping alike twice) | [the reference's own benchmark scenes with the same checksums: 1.2x its AVX2 build on the large pyramid, 1.15x on the many pyramids, 1.05x on the joint grid, ours at eight lanes in AVX2 with float records (#121)](bench/RESULTS.md#the-collide-pass-as-the-reference-keeps-it-2026-10-09-121) |
| `aephysics.human` | the ragdoll: twelve capsule bones on spherical joints with cone and twist limits and revolute joints with angle limits -- a person's ranges by default (shoulders that reach overhead on a cone centred off the rest, hips on an elliptical cone through 125 degrees ahead, 25 back, 45 out and 30 across, knees and elbows that never pass straight), the reference's tight ones on request -- soles that grip, a spring on every joint toward the reference pose, a motor whose torque limit is joint friction, filter joints for the limbs that clash; the align spring, kinematic anchors through motor or parallel joints and their targets driven over a step (the pose drive of an active ragdoll), velocity, kicks, bullets | `test_human.ae` (96 checks: the figure's shape, a fall to rest in one piece, the setters, a kick, standing under the align spring, the pose held on anchors, the anchors driven two metres and the figure walked behind them, the same drop twice bit for bit, a fitted shape, each arm reaching flexion 170, abduction 160 and extension 45 degrees standing and prone, the hips' cone through a person's four ranges and short of a split, the hinges stopping at straight and folding to their range, the soles' grip) | [the reference's rain benchmark, 300 ragdolls over 400 steps, at 0.9x](bench/RESULTS.md#human) |
| `aephysics` | the public API | |

Expand Down Expand Up @@ -170,7 +170,9 @@ geometry that cannot collide, a heavy chain, an unlimited motor, a
hinge whose limits meet, a hinge's motor pressing past its limit settling
where the reference's does, an elliptical cone stopping a swing at each
half-angle, a kinematic body's contacts made stiff on request, and a
hull rocking about a vertex on the floor coming to rest; 128 checks).
hull rocking about a vertex on the floor coming to rest, and the
contacts' sim indices kept current through every move of a sim; 142
checks).

A joint's limits are soft constraints, as the reference's are: driven
past one by a motor or held under a steady load, it settles past itself
Expand Down
4 changes: 2 additions & 2 deletions aephysics/contact_solver/module.ae
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ prepare_contact(ctx: *StepContext, c: *Contact, cc: *ContactConstraint, manifold
warm_start_scale = 0.0
if ctx.enable_warm_starting { warm_start_scale = 1.0 }
inv_tau = 1.0 / dynamics.SPECULATIVE_DISTANCE
index_a = c.body_sim_index_a
index_b = c.body_sim_index_b
index_a = dynamics.decode_awake_index(c.encoded_body_sim_a)
index_b = dynamics.decode_awake_index(c.encoded_body_sim_b)
m_a = 0.0
i_a = math.mat3_zero()
v_a = math.vec3_zero()
Expand Down
4 changes: 2 additions & 2 deletions aephysics/contact_solver_wide/module.ae
Original file line number Diff line number Diff line change
Expand Up @@ -479,8 +479,8 @@ prepare_wide_slot(ctx: *StepContext, color: *GraphColor, slot: int, end: int) {
m5 = gather_manifold(c.manifolds.l5 as *Manifold)
m6 = gather_manifold(c.manifolds.l6 as *Manifold)
m7 = gather_manifold(c.manifolds.l7 as *Manifold)
c.index_a = IntW { l0: lane_index1(k0, k0.body_sim_index_a), l1: lane_index1(k1, k1.body_sim_index_a), l2: lane_index1(k2, k2.body_sim_index_a), l3: lane_index1(k3, k3.body_sim_index_a), l4: lane_index1(k4, k4.body_sim_index_a), l5: lane_index1(k5, k5.body_sim_index_a), l6: lane_index1(k6, k6.body_sim_index_a), l7: lane_index1(k7, k7.body_sim_index_a) }
c.index_b = IntW { l0: lane_index1(k0, k0.body_sim_index_b), l1: lane_index1(k1, k1.body_sim_index_b), l2: lane_index1(k2, k2.body_sim_index_b), l3: lane_index1(k3, k3.body_sim_index_b), l4: lane_index1(k4, k4.body_sim_index_b), l5: lane_index1(k5, k5.body_sim_index_b), l6: lane_index1(k6, k6.body_sim_index_b), l7: lane_index1(k7, k7.body_sim_index_b) }
c.index_a = IntW { l0: lane_index1(k0, dynamics.decode_awake_index(k0.encoded_body_sim_a)), l1: lane_index1(k1, dynamics.decode_awake_index(k1.encoded_body_sim_a)), l2: lane_index1(k2, dynamics.decode_awake_index(k2.encoded_body_sim_a)), l3: lane_index1(k3, dynamics.decode_awake_index(k3.encoded_body_sim_a)), l4: lane_index1(k4, dynamics.decode_awake_index(k4.encoded_body_sim_a)), l5: lane_index1(k5, dynamics.decode_awake_index(k5.encoded_body_sim_a)), l6: lane_index1(k6, dynamics.decode_awake_index(k6.encoded_body_sim_a)), l7: lane_index1(k7, dynamics.decode_awake_index(k7.encoded_body_sim_a)) }
c.index_b = IntW { l0: lane_index1(k0, dynamics.decode_awake_index(k0.encoded_body_sim_b)), l1: lane_index1(k1, dynamics.decode_awake_index(k1.encoded_body_sim_b)), l2: lane_index1(k2, dynamics.decode_awake_index(k2.encoded_body_sim_b)), l3: lane_index1(k3, dynamics.decode_awake_index(k3.encoded_body_sim_b)), l4: lane_index1(k4, dynamics.decode_awake_index(k4.encoded_body_sim_b)), l5: lane_index1(k5, dynamics.decode_awake_index(k5.encoded_body_sim_b)), l6: lane_index1(k6, dynamics.decode_awake_index(k6.encoded_body_sim_b)), l7: lane_index1(k7, dynamics.decode_awake_index(k7.encoded_body_sim_b)) }
c.point_counts = IntW { l0: m0.point_count, l1: m1.point_count, l2: m2.point_count, l3: m3.point_count, l4: m4.point_count, l5: m5.point_count, l6: m6.point_count, l7: m7.point_count }
// The store reads this rather than go back out to the contacts.
c.hit_lanes = hit_bit(k0, 0) | hit_bit(k1, 1) | hit_bit(k2, 2) | hit_bit(k3, 3) | hit_bit(k4, 4) | hit_bit(k5, 5) | hit_bit(k6, 6) | hit_bit(k7, 7)
Expand Down
Loading
Loading