Skip to content

Muscles: a 0.8 m/s push from the side fells a standing POWERED figure that the servo holds by stepping #821

Open

Description

@nicolas-maman

The case. Siltreach (a private game on ae3d) tried the opt-in muscles (#815, motion.set_muscles) on its people. Its hit station pushes five POWERED figures at the chest the way ae3d's own balance tests do; on muscles, a side push that the servo stands through by stepping fells the figure.

Setup (ae3d d62ddbc, aephysics f0bd6d621, Aether 0.831.0; game build 3c47057, SILTREACH_STATION=hits SILTREACH_SEED=1, 60 Hz fixed step):

  • The figure: the game's own person (an MPFB 2 human built in Blender on a game-engine skeleton: pelvis, spine_01..03, neck_01, thigh/calf/foot, upperarm/lowerarm under Root; 57 bones, the feet hanging from the calves), 1.75 m, dressed with physics.ragdoll_dress_named on a physics.ragdoll, motion.active_ragdoll; POWERED, standing in its clip's idle (softer knees than ae3d's straight-legged test rig). Ragdoll mass 78.4 kg.
  • Muscles on: physics.physics_set_ankles(physics.of(e), true) before the ragdoll is made (14 bones), motion.set_muscles(m, true) right after active_ragdoll.
  • The push at 2.0 s: motion.hit(m, BONE_SPINE_03, chest, impulse, true, cost) with impulse 62.7 N·s along +x (0.8 m/s times the mass), the figure facing +z: square from the side.

Numbers, the same station and seed:

servo (muscles off) muscles on
dummy 3, 0.8 m/s from the side stands, steps: worst lean 0.53 rad, pelvis lowest 0.82 m falls: worst lean 1.64-1.89 rad, pelvis lowest 0.14 m, gets up
dummy 1, 0.5 m/s from behind lean 0.05 lean 0.02
dummy 2, 1.0 m/s from behind lean 0.18 lean 0.22
dummy 4, 0.5 m/s from in front lean 0.16 lean 0.20
dummy 5, 5 m/s from behind falls, gets up falls, gets up

tests/test_balance.ae's envelope has the figure out of 1.0 m/s from the side by stepping; on muscles this one goes over at 0.8. In the frames it staggers, stands about a second, then goes over sideways about 1.3 s after the push and lands face down.

Log lines (game journal):

servo:   {"kind":"push","dummy":3,"speed":0.8,"impulse":62.719,"from":[1,0]}
         {"kind":"dummy","dummy":3,"fell":false,"got_up":false,"lowest":0.822239,"worst_lean":0.526091,"hips_gap":0.0151329,"standing_end":true,"mode":"powered"}
muscles: {"kind":"push","dummy":3,"speed":0.8,"impulse":62.719,"from":[1,0]}
         {"kind":"dummy","dummy":3,"fell":true,"got_up":true,"lowest":0.140084,"worst_lean":1.63781,"hips_gap":0.015133,"standing_end":true,"mode":"powered"}

Frames (on the dev machine): contact sheets D:\Git\siltreach\build\scratch\mu_cap\push_m0.png (servo) and push_m1.png (muscles), frames 122, 130, 140, 150, 165, 180, 200, 240, 300, 420 at 60 Hz (the push lands on frame 122), left to right then the second row; dummy 3 is the middle figure, seen from in front. Every frame from 122 to 420 is beside them in push_m0\ and push_m1\ (p_NNNN.png). Journals: D:\Git\siltreach\build\scratch\mu2_hits_on\log.jsonl, mu2_hits_off\log.jsonl.

On the game's side, to rule it out: the game's body driver caps each bone's muscle by the injury model through motion.set_part_strength per part (twelve bones mapped to parts); an ankled figure's two feet are never capped, so they keep their full strength, which may matter here. Its per-bone loops (hit events, the collision filter by mode, a shape's bone) now cover all fourteen; that changed nothing in these numbers (lean 1.89 before, 1.64 after).

Activity

  1. nicolas-maman commented on Oct 19, 2026

    @nicolas-maman
    CollaboratorAuthor

    Not reproduced from ae3d alone. The probe: Siltreach's drifter.glb at 1.75 m, dressed by name as body_new does (physics.ragdoll(e, name, at, 8, 8, 1), ragdoll_dress_named with the twelve game-engine names), active_ragdoll + motion.attach, ankled, on a flat box ground at friction 0.8. It stands in the glb's own Idle clip, either POWERED from the start or ANIMATED then POWERED at the push. Then motion.hit(m, BONE_SPINE_03, chest, side * v * mass), square from its side, ae3d 1b8ebf7:

    servo muscles
    0.8 m/s from the side lean 0.10 rad, stands lean 0.24 rad, stands
    1.0 m/s from the side lean 0.24, stands lean 0.30, stands

    So the muscled figure leans more than the servo, but nowhere near a fall. The game's servo leans 0.53 at 0.8 m/s against 0.10 here, so the game's setup upsets its figure about five times as much. What the probe doesn't have: the game's animator and its captured idles (softer knees, the moods), the sand terrain under the feet, apply_caps / set_part_strength, and the body driver's own step order (motion_step after the world's).

    Could Siltreach add a standalone repro under repro/, like getup_topple.ae? One dummy standing in the idle it uses, on its ground, pushed 0.8 m/s from the side, with SILTREACH_MUSCLES 0/1. Then this can be bisected over those differences from the ae3d side.

    (#822, the backward roll on the knock, reproduced on the drifter and is fixed on PR #824: the fall keeps its brake until the figure lands.)

  2. nicolas-maman commented on Oct 20, 2026

    @nicolas-maman
    CollaboratorAuthor

    A repro on Siltreach's own stack, and what it narrows down (siltreach 9011f55: repro/side_push.ae, on this machine at D:\Git\siltreach\repro\side_push.ae; build ae build repro/side_push.ae -o build/side_push in D:\Git\siltreach with Aether 0.831, ext/ae3d at d62ddbc).

    It makes one hit dummy as the hits station does: the drifter (build/assets/people/drifter.glb) at the game's scale for 1.75 m, facing +z, POWERED; siltreach.animator writing its idle before the world steps (REPRO_ANIMATOR=0: figure.play(Idle)), siltreach.body building and stepping the body (motion_step after the world's step, then its per-bone caps), siltreach.limbs setting the caps each step (REPRO_LIMBS=0: none); pushed at 2.0 s through body_hit (motion.hit) at the chest, 0.8 m/s times the mass along +x. SILTREACH_MUSCLES=1 as before (ankles on).

    Worst lean after the push (rad), nothing falls in any of these:

    ground layers servo muscles
    box, friction 0.8 all on 0.22 0.09
    box, friction 0.8 glb Idle instead of our animator 0.21 0.11
    box, friction 0.8 no limbs (no caps) 0.22 0.09
    box, friction 0.9 (REPRO_FRICTION=0.9) all on 0.22 0.09
    the game's land at (16, -24) (REPRO_LAND=1): siltreach.ground's static mesh collider, friction 0.9, level there (normal 0.999997 up) all on 0.50 0.16

    The hits station's dummy 3: servo 0.53, muscles falls (1.64).

    So:

    1. The game's layers (our animator vs the clip, our caps vs none) change the lean by hundredths. Your probe's 0.10 vs our box's 0.22 is the rest of our stack, but small.
    2. Standing on a level triangle-mesh ground the servo leans 2.3 times what it does on a box (0.50 against 0.22; not the friction: a box at 0.9 leans 0.22). That accounts for the station's servo number. Something in how the feet or the bodies meet a mesh collider (its triangles' contacts, internal edges) upsets a standing figure that a box does not.
    3. The muscles' fall in the station is still not reproduced alone: on the land the muscled dummy leans 0.16 and stands. What the station has besides: five dummies and the player in one physics world (4 m apart, never touching), the hub's other static meshes, and its bodies made with ankles on for every ragdoll once the first is. That is the next thing I would bisect if you want it; a mesh ground in your own probe would test (2) without our code.
  3. nicolas-maman commented on Oct 22, 2026

    @nicolas-maman
    CollaboratorAuthor

    Moved under the animation epic #853, milestone #858 (the physics asset and physical animation layer).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions