Repository navigation
feat: 0.6.0 balance + behaviors — step 1: feet load-bearing - #101
blugart-dev wants to merge 11 commits into
Conversation
…te; 0.6.0 removal list from the feature inventory - main is release-only; milestones PR into develop and fast-forward it. - 0.5.0 marked code complete on develop, feel gate deferred by the maintainer. - 0.6.0: removal list (micro reactions, motor-mode velocity clamps, legacy root spring, dice knockdowns out of the physics path), tip-over owned by BalanceState (nothing physical decides a fall in motor mode today). - 0.7.0: retire VELOCITY_OVERWRITE after 0.6.0's A/B. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF
…g feet, anchor without vertical authority The legs carry the body. Before: the root anchor held the pelvis with up to 2500 N, the feet were masked out of collision while foot IK solved, and when allowed to collide they carried ~2 % of the 804 N body (tools/bench/foot_probe.gd). - BoneDefinition.sole_aligned (feet): the foot box is built level with the character, bottom face at foot_ik_ankle_height below the ankle (the IK sole), heel added (SkeletonDetector.FOOT_HEEL_RATIO 0.3, FOOT_SOLE_OFFSET). The old bone-aligned box followed the Mixamo foot bone 27 deg down: its corner sat 7 cm under the floor, a colliding foot was pushed 15 deg off its pose and the character up — why the feet had to be masked. build_body takes the up axis; sole_shape_transform is the geometry; both profile factories set it. - foot_ik_disable_foot_collision defaults to false (feet collide in every state); foot bodies report contacts (balance layer input). - RagdollTuning.muscle_root_support (default 0): share of muscle_root_force the position motor may spend along world UP. Implemented with a second, world-aligned anchor (<Root>_anchor_lin) — per-axis limits in the tilted pelvis frame still lifted ~500 N through the "sideways" axes. - Get-up on load-bearing feet: the controller gives the anchor full support and makes the feet frictionless for the canned blend (a sole box pinned by friction ended the blend 0.5 m from target, legs 40-50 deg off), then fades the support back over 0.75 s. Shooting range: <= 8 deg at recovery_finished, 1-4 deg after 3 s, pelvis at target height on its own legs. - SpringResolver.get_bone_target_global(); set_root_support_override(). - Bench: SAG / FEET columns, HIT measured from the clean idle, SETTLE after the react clip; variants nofootcol / support025 / support05 / support1. 60 Hz motor: idle 1.10/3.51, hit 1.9 deg / 4 ticks, sag 6 mm, CPU 0.22 ms vs legacy 0.16; 120 Hz idle 0.73/1.87 beats legacy; 30 Hz 5.45 (open). Exposed: SETTLE after react_front 9.9 deg at 60 Hz (legacy 0.96) — loaded feet stay where friction planted them; the step behavior must move them. - Harness skeleton: hips 0.935 m so the soles rest on y = 0. - Tests: test_feet_load_bearing.gd (9); muscle tests pin the anchor at full support (they characterise the muscles on a groundless rig); 253 green, all 8 demos load headless clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF
…nch) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF
…CI saw the rig before its first solve) On the CI runner setup_complete fires before the resolver's first physics tick, so test_rig_builds_all_joints counted no anchor joints. The anchors and motor flags are now applied at the end of _init_bones when the tuning is JOINT_MOTOR; the lazy path in _tick remains for runtime mode switches. The test also waits two physics frames. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF
…bs; limits left alone on evidence - RagdollTuning.self_collision defaults to true. Jointed pairs are excluded by their joint; any non-adjacent pair already overlapping in the build pose is excluded for good by a safety net one physics tick after the build (PhysicsRigBuilder.get_self_collision_exclusions); everything else collides. find_overlapping_pairs() is the space query behind the probe and the tests. Off (the pre-0.6.0 behaviour) still excludes every pair. - Why it was off: the velocity-overwrite resolver's commands were rewritten by every contact impulse. Under bounded joint motors the ybot has no non-adjacent overlaps in idle / react / a 4 s ragdoll and the bench is bit-identical on and off (tools/bench/overlap_probe.gd); the shooting-range get-up still lands within 2-5 deg. - Joint limits NOT tightened: tools/bench/limit_envelope.gd samples all 21 ybot clips against the authored limits and finds 17 axes already exceeded (elbow lateral +-58 vs +-20, spine X -75 vs +-35, knee 149 vs 140, shoulder twist 158 vs +-90). The folding limbs were a self-collision problem. - shape_local_transform() extracted from build_body (sole box or bone-aligned). - Tests: test_self_collision.gd (6): defaults, jointed-pair exclusion, the safety net on an oversized Hips box, a limp hand shoved at the chest blocked with self-collision on / passing inside with it off. Fidelity tests pin the 0.4.1 numbers with self_collision = false explicitly. 259 green; all 8 demos load headless clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF
|
Step 2 — self-collision on (maintainer asked for the ragdoll look before the balance state). |
…n, ratio, loaded foot One balance measurement per physics tick, refreshed in every state and read by every decision (ActiveRagdollController.get_balance(); the 0.4.x dictionary API keeps working with the new fields added). - addons/kickback/balance_state.gd: mass-weighted CoM and velocity; support polygon = convex hull (monotone chain) of the sole footprints of the feet in contact (foot contact reports; no contact -> no support); XCoM = CoM + v/w0, w0 = sqrt(g / CoM height above the support plane); signed margin to the polygon edge; ratio = XCoM offset from the centroid over the polygon radius in that direction (0 centre, 1 edge, > 1 outside); per-foot load share and the loaded foot. reset_velocity() after the recovery re-base. - The static CoM-vs-ankle-midpoint ratio is gone: it read 0.5-0.6 for a perfectly standing character on load-bearing feet and staggered it on a hand shot. Ybot idle now: margin +12.5 cm, ratio 0.40-0.42 over 2 s of sway -> thresholds recalibrated: balance_stagger_threshold 0.8, recovery 0.6, ragdoll 1.0 (capture point outside the feet), ranges to 1.5. - F3 HUD draws the contact hull on the support plane, the CoM diamond and the XCoM ring with its margin in cm; foot_probe prints the balance components. - Tests: test_balance_state.gd (7): hull, signed margin, edge distance, XCoM = CoM + v/w0 on a synthetic rig, the standing harness, airborne = no support, a 1.5 m/s shove puts the XCoM outside. 266 green; all 8 demos load clean. - Not yet: the tip-over decision on the XCoM and the anchor release (needs the step behavior first: a 150 N.s shove must step, not fall). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF
|
Step 3 — |
…ead of asserting penetration (CI landed the arm where the limits held it out) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF
…g, limp arm shoved across the torso) A ragdoll landing is not a fair A/B: self-collision changes the fall itself, so the two runs landed the arm in different places (CI: the limits held it out). The rig now stands (anchor support 1) with the left arm limp; the hand is shoved across the body and is blocked by the torso boxes with self-collision on, swings through them with it off. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF
…t teleport) and the arm windmill are gone - KickbackBehavior / BehaviorContext (addons/kickback/behaviors/): a behavior reads the shared BalanceState each tick and answers with stiffness floors and pose targets; the controller runs a fixed ordered list in NORMAL / STAGGER (get_behaviors()); the 0.7.0 arbiter replaces the order. - StepBehavior: balance steps (XCoM at the edge of the feet for 4 ticks -> the fall-side foot swings to the capture point) and re-plants (calm + a loaded foot > 10 cm from its animation spot -> the less loaded foot is lifted and stepped there), through FootIKSolver.begin_step (lifted arc, then a hover at the goal until the physical foot arrives) on top of foot LOCKS (a contacting foot that drifted > 5 cm from its spot is held where it stands so the leg does not fight friction). Motor mode only; legacy numbers unchanged. Signal step_started(foot_rig, target). - Removed: _update_directed_stumble (the root teleport), _do_directed_step, _apply_stumble_brace, the windmill, the stumble_* / arm_windmill_* / arm_brace_weight knobs, stumble_step_started, test_stumble_step.gd. grep "global_position +=" addons/ is empty. Tuning Lab: STEPS sliders. - RagdollTuning "Balance: Steps" (8 knobs, replacing the 8 stumble knobs) and muscle_root_hold (sideways share of the anchor force, default 1). - Measured (ybot 60 Hz): root moved 0.25 m with planted feet -> both feet re-planted by 0.83 s, legs 1.5 deg; after react_front 5 steps, legs 3 deg by 2.8 s (bench SETTLE 10.4 -> 6.2 in the 2nd second, was 9.9 flat); 150 N.s shove with the hold: 2-4 steps, pelvis moves 1-2 cm. With the hold released and no upright behavior the idle is unstable (7.2 deg, constant steps) - the UprightBehavior is next and takes muscle_root_hold to 0. - Harness: ankles 5 cm behind the leg line (CoM mid-foot, was on the heels), straight_legs option for the knee-fallback tests. 269 tests green; all 8 demos load headless clean. SELF_PRESERVATION.md rewritten as the spec. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF
|
Step 4 — behavior layer + |
…h under addons/ is GUT's input factory) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF
…hysics decides the fall - muscle_root_hold defaults to 0.25 (~625 N sideways): the anchor assists balance instead of holding the character up. Ybot 60 Hz: idle 0.98/4.4 deg (same as the full hold), settle after react_front 10.4 -> 6.1 deg (2nd second 5.0), a 150 N.s shove is caught by the legs + steps with the pelvis moving ~6 cm. Hold 0 not reached: the passive stance reads 3.4-4.4 deg and is chaotic under the shove in every lever combination measured. - Ankle torque 60 -> 150 N.m (MUSCLE_TORQUE_TABLE Foot): the ankles sat at ~60 % of their limit just standing with the CoM 9 cm off centre. - muscle_leg_gain (new, 0.2): the leg chains + pelvis run a faster motor loop than the arms (SpringResolver._leg_set from the profile roles). - step_trigger_ratio 0.9 -> 1.0; BalanceState.com_velocity exponentially smoothed (3-tick constant) so the XCoM does not jitter. - Physical fall detector (ActiveRagdollController._check_fall, motor mode, NORMAL / STAGGER): pelvis below 60 % of its target height or tilted past 0.55 for 0.15 s -> RAGDOLL, no dice. A 400 N.s shove with the assist released is detected within 2 s; no false fall while standing or getting up. - UprightBehavior (experimental, off by default): whole-body target shift against the XCoM error via FootIKSolver.set_body_shift. Measured worse than the passive stance at every gain; kept as the starting point for a torque-level ankle strategy. - Bench variants hold0/015/025/05/1, nosteps, noupright, gainup/down, leggain2/3, ankle120/150/200. Tests: test_balance_control.gd (6); balance / step tests adjusted for the smoothed velocity and the assist. 275 green; all 8 demos load headless clean; shooting-range get-up 2-4 deg (one 24). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF
|
Step 5 — balance control. |
Summary
Milestone 0.6.0 — Balance + behaviors (
docs/PLAN.md), intodevelop(mainis frozen until the maintainer's feel gate). This PR carries the milestone; step 1, feet load-bearing, is complete and measured. Later steps (BalanceState, behaviors, the deletions) land as further commits here.Step 1 — the legs carry the body
Before: the root anchor's position motor held the pelvis with up to 2500 N, the feet were masked out of collision while foot IK solved, and when allowed to collide they carried ~2 % of the 804 N body (
tools/bench/foot_probe.gd).BoneDefinition.sole_aligned): a level box with its bottom face on the foot IK sole and a heel behind the ankle. The old bone-aligned box followed the Mixamo foot bone 27° down; its corner sat 7 cm under the floor, a colliding foot was pushed 15° off its pose — the reason the feet had to be masked.foot_ik_disable_foot_collisiondefaultfalse) and report contacts.muscle_root_support(default 0): the anchor has no vertical authority; a second, world-aligned anchor carries the position motor so the up-axis limit is truly vertical.recovery_finished, 1–4° after 3 s, pelvis at target height on its own legs.Numbers (ybot,
tools/bench/ybot_bench.gd)Exposed, and the next step's job: after the violent react clip the loaded feet stay where friction planted them while the animation returns to idle (SETTLE 9.9° vs legacy 0.96°). A load-bearing foot cannot be dragged into place — it must be unloaded and stepped. That is
BalanceState+StepBehavior. 30 Hz regressed 4.85 → 5.45 and stays open.Suite: 253 tests green (9 new in
test_feet_load_bearing.gd); all 8 demos load headless clean.Human gate (visual, when the milestone is complete)
🤖 Generated with Claude Code
https://claude.ai/code/session_018RbYJ9LscLhHypHqWfhtGF