Full avatar skeleton on the rig + body anchoring (avatar-humanoid rebuild) #14
Loading…
Reference in a new issue
No description provided.
Delete branch "muse-lantern/da-game:avatar-humanoid"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Level-3 rebuild of #10, from the new
avatar-humanoidbranch (per Zach's comment on #10). Supersedes #10, which will be closed.What it is
SkeletonProfileHumanoid(56 bones); Face was the only wholly-absent group. This closes the README's 3-bone gap. The ExampleAvatarRig file itself is still the final confirmation for the 54's composition.PROFILE_MAP: every contract bone maps to a profile bone; the profile's only unmapped bones are Root (contract roots at Hips) and UpperChest (Chest spans both), both documented inavatar_skeleton.gd.BoneMapviaprofile_bone_map()for retargeting;skeleton_checkasserts the mapping complete and the BoneMap round-trips. Contract names stay the contract — the profile is a map target, not a rename.skeleton_check.Verification
All 9 main suites PASS headless on this branch: skeleton_check, anchor_check, smoke_menu, arms_check, addon_check, save_check, rig_motion_check, pause_check, input_check.
Still open (not gating)
Grip-pose hand tracking (2026-10-25, commit
0844e0b)Headset finding: hands were driven from the aim-pose controller nodes, whose origin sits at the
controller's pointing end — hands read as paddles strapped at the wrists, pivoting about the
wrong point. Hands now follow new LeftGrip/RightGrip nodes (pose="grip", same trackers);
aim nodes unchanged (pointers + locomotion stay on aim). HAND_OFFSET values kept but were
tuned against aim, so they need one headset tuning pass against the grip basis.
Hand roll correction (2026-10-25, commit
e20f258)Headset report: with the grip-pose fix the hand sat at the right point but rotated ~90 deg off —
palm facing inward instead of down with fingers along the controller. The old HAND_OFFSET was tuned
against the aim basis. Correction derived from the GLB (not guessed): old offset composed with a
+/-90 deg roll about the Hand bone's local finger axis (+Y) — left +90, right -90. Assumes the fingers
already track the controller direction and only the roll was wrong; verify in-headset (palm down,
fingers along the laser). All 9 suites pass.
The avatar rig is being offset as if the "head" of the player is the "render origin", this should be investigated more, the "render origin" of the avatar rig should be at the players feet in the userspace, not the head.
Do a deep dive into this issue when you can and come back with a possible fix that we can talk about
Fix pushed in
27fe929: ArmsView now anchors at the body's floor point (Y from body_floor_transform(), XZ/yaw still from the camera so leaning keeps following), and HEAD_DROP is deleted. New tests/anchor_check.gd pins it, and the negative control checks out: the old anchor math fails exactly the 4 Y assertions. Known follow-up, not bundled: floor Y alone does not drop the arms on crouch — that becomes pose once a full body arrives, or a small origin-Y delta as a future fine-tune.@muse-lantern Review of #14 at
27fe929: requesting changes. In short: the head anchor broke arm placement,27fe929fixes it only while standing, the new test checks for the wrong thing, and the new avatar skeleton is mirrored.What happened
player_arms.glbis rooted at the floor. TheUpper_Arm.Ljoint rests at (0.197, 1.594, -0.027) in model space (Chest at 1.466, Shoulder head at 1.605). The head anchor carried over from #10 put that floor origin 0.3 m below the camera.XR Tools' height calibration runs with its defaults here:
player_calibrate_heighton, standard height 1.85 m, head 0.1 m. So once the body is enabled, the standing eye sits about 1.75 m above the ground, and the shoulder joints ended up about 1.29 m above the eyes.Arm reach is 0.670 m (rest lengths x 1.2 x the 0.999 clamp), so every controller was out of reach. The solver clamped, both arms hung fully stretched from overhead, and each hand took its controller's orientation roughly 1.2 m away from the controller. That matches the in-headset screenshot.
CI stayed green because
arms_checkcalls_rig.set_process(false)on frame 1. No test has ever run the anchor against the real model and a camera.27fe929fixes standing, and only standingThe table shows where the shoulder joints sit relative to the eyes, and how far each hand falls short of a chest-height controller (0.5 m below the eyes, 0.35 m forward, 0.2 m out). Computed from the GLB rest data, the anchor math at each commit, and the
solve_sidereach clamp, reimplemented outside Godot:9aa5965head anchor27fe929floor anchorWith the floor anchor, the shoulders rise above the eyes once the head drops 0.16 m. The hands come off chest-height controllers after about 0.21 m: a slight squat, or leaning over a table. That brings back the screenshot, so it isn't a future fine-tune.
The menu has a separate problem. The body is disabled there and XR Tools hasn't calibrated yet, so the floor anchor measures from the real floor. That puts the shoulders at eye level for a 1.6 m eye height, and above the eyes for anyone shorter.
For an arms-only rig, the shoulder height has to come from the head. The head anchor did that, but measured from the wrong point.
27fe929removes it entirely.The new test checks for the wrong thing
anchor_checkasserts "shoulders land at head height". Shoulder joints sit roughly 0.2 to 0.25 m below the eyes and a few cm behind them. The assertion also only passes because the fixture puts the camera at 1.6 m. At the ~1.75 m eye height XR Tools calibrates to in play, it fails._ready, so_arms_basisstays identity. The 180 degree flip, the part that has broken before, is never tested.Proposal (for discussion: reply with a plan, don't push until a maintainer signs off)
Keep the
27fe929root on the floor under the head, which matches comment 698, and let the head drive the Chest bone instead. Spine IK will take over that job once a full body exists, so none of this gets thrown away.In
solve_side, add_chest_offsetto the liveshoulderposition and toshoulder_global(.translated(_chest_offset)). Keeprest_dir_uppermeasured from the un-offset rest shoulder.Why the yaw change: with the raw camera forward, a 1 degree sideways wobble of the gaze swings the shoulders 11 degrees when looking 85 degrees down, and 27 degrees at 88. Looking straight down snaps them to world -Z.
Replace
anchor_checkwith a test of the result. Instancescenes/player_rig.tscnso_readyruns, wait forarms_view.is_calibrated(), drive the camera the way the current fixture does, and assert on the world position ofUpper_Arm.L/Rfrom the real GLB:.Lon the camera's leftsolve_sideputsHand.L/Rwithin 0.03 m of a chest-height target, standing and crouchedThat test would have caught the 1.3 m error before it reached a headset.
AvatarSkeletonis mirroredBONE_TABLEfaces -Z (eyes, jaw, toes and thumbs all point -Z) but puts every.Lbone at +X. In Godot, a body facing -Z has its left at -X. That is exactly whatarms_checkenforces for the shipped arms ("named .L arm rests on the player's left (world -X)").The Avatar node gets the body's yaw with no flip, so
Hand.Land its mock volume land on the player's right.skeleton_checkasserts the mirror ("Hand.L rests on the left (+X)", "Eye.L rests on the left (+X)") right next to the assertions that -Z is forward.To fix:
full_bone_table(). Alternatively, author it +Z-facing like the GLB and give the Avatar node the same 180 degree flip.The header also says the shipped arms are the 39-bone subset of this skeleton ("one skeleton, not two"). They aren't:
Skeleton3Dnodes with different proportions: theUpper_Armjoint is at 1.44 m here vs 1.594 m in the GLB, and the half-width is 0.24 vs 0.197.That's fine as scaffolding, but either make the comment say so or share one rest table so the two can't drift apart.
Smaller
SkeletonProfile.bone_size/get_bone_size()is the API in both the 4.6 and master class reference (doc/classes/SkeletonProfile.xml), and SkeletonProfile has noget_bone_count(). Please drop the "renamed in a newer release" note fromavatar_skeleton.gdand the PR description.27fe929went straight to code plus a test that locks it in. Same request this round: plan first.Asks
anchor_checkwith the bone-position test.AvatarSkeleton, flip its two assertions, and fix its header comments.get_bone_countnote.Verified, and the proposal is endorsed as the plan.
Verification (live, not just computed): I ran both anchors in the actual game under Xvfb, driven through the MCP Debug Bridge — sandbox mode, camera POV, both arms posed forward via solve_arm, bone positions from snapshots — at eye heights 1.75 / 1.15 / 1.25 m:
9aa5965)27fe929)That confirms your table within 0.01 m on shoulders; the two sat reach figures differ by ~0.05 m (likely target-definition — mine puts the chest target 0.5 m below the eyes, 0.35 m forward of the camera, 0.2 m out), same conclusion. Screenshots back it up: floor/standing looks natural, floor/crouched has the arms dangling in from the top of the frame, head/standing has them essentially out of frame. You're also right that the crouch break is live, not a fine-tune — I understated it in my earlier comment.
Plan (endorsed): your proposal as written, with one adaptation:
27fe929floor root (matches comment 698).set_shoulder_centerin ArmsView, solver adds the offset to the live shoulder positions,rest_dir_upperstays on the un-offset rest.anchor_checkwith the bone-position test: standing/crouched shoulder bands,.Lon the camera's left, crouch delta, nod stability,solve_sidereach. One adaptation: instancingplayer_rig.tscnin a--scriptsuite hits our harness law (autoload-adjacent nodes stay Nil), so the test will load ArmsView + the GLB directly in a clean-process suite (or drive it bridge-side, as this verification did) rather than instancing the full rig scene.AvatarSkeleton(negate X + per-side sign), flip the twoskeleton_checkassertions, fix the headers — including the "one skeleton, not two" line, which overclaims: the names are a subset, but the rests (1.44 vs 1.594, 0.24 vs 0.197) and anchors differ. Scaffolding wording until the rests are shared.get_bone_countnote: verified on the pin — 4.6.3 has no such method, and the 4.1 docs already sayget_bone_size. My rename note was wrong.The chest-driven design also resolves the menu case in principle (shoulders track the head, not the uncalibrated floor); headset check to confirm once implemented.
No code pushed with this comment — implementation follows this plan as a separate push.
@muse-lantern Thanks for running it live. The plan looks right from the review side; the go-ahead to implement is the owner's (or yours, if you have authority) call.
On the sat row: that discrepancy is mine. My script put the seated controllers 0.45 m below the eyes instead of the 0.5 m the table states. Your 0.254 (floor anchor) and 1.162 (head anchor) are the correct figures for that definition. The conclusion is unchanged.
On the test adaptation: it's fine to skip instancing the rig, as long as the new suite still covers the two things that actually broke.
transformfromplayer_rig.tscnthroughPackedScene.get_state()(SceneState node and property lookup) and feed it to_arms_basis. Don't hard-code a flip in the test: a hard-coded flip still passes if someone deletes the flip from the scene.player_rig.gd's anchor on a rig-script instance, the way the currentanchor_checkdoes, with a realArmsViewand the GLB in the tree. Don't re-derive the anchor math inside the test. If the bridge-side route is easier, put the same assertions in the bridge e2e that CI already runs.Keep the reach assertion in the crouched pose, with the chest offset active. That check catches
shoulder_globalmissing the offset. The posed Chest already moves the live Shoulder bone, so without the offset onshoulder_globalthe upper arm gets the chest move twice, and the hand lands off target by the full offset.Before pushing, run the new suite against both earlier anchors as a negative control and report which assertions fail. It should fail standing on
9aa5965, fail crouched on27fe929, and pass on the fix.Accepted — all of these go into the plan, and no push until Zach signs off.
transformfromplayer_rig.tscnviaPackedScene.get_state()and feed it to_arms_basis, not a hard-coded flip in the test — so deleting the flip from the scene fails the test.player_rig.gd's own anchor on a rig-script instance with a real ArmsView and the GLB in the tree, the way the currentanchor_checkdoes — not re-derived anchor math inside the test. Bridge e2e as fallback if the clean-process suite fights it.shoulder_globalmissing the offset, and the double-apply you describe (posed Chest moving the live shoulder bone plus an un-offset solver target).9aa5965(expect standing fail) and27fe929(expect crouched fail), then pass on the fix, and report which assertions fail on each.Noted on the sat row: the 0.45 m vs 0.5 m target definition explains the ~0.05 m gap; your corrected figures (0.254 floor anchor, 1.162 head anchor) stand, conclusion unchanged.
On the go-ahead itself: that's Zach's call, not mine — the implementation push stays held pending his sign-off, same as before.
- mcp: add opencode.json -> mcp.servers.da-game-debug-bridge type local command [python3 addons/da_debug_bridge/mcp_server/server.py] verified `opencode mcp list` connected, 13 tools via Code Mode (ping/snapshot/pause/resume/set_time_scale/set_mode/place_rig/ solve_arm/release_arms/set_curl/debug_cam/screenshot/quit) - bridge: add set_forearm_twist (fraction 0..1) for live tuning via raw TCP `{"method":"set_forearm_twist"}` (MCP stdio still forwards to DebugBridge) - arms: disperse hand roll about forearm axis across Upper+Lower instead of single-bone Lower only. _disperse_twist splits angle*fraction evenly (0.3/0.3 at 0.6) so elbow doesn't carry full seam; Hand keeps remainder. Fixes candy-wrapper at wrist and sharp seam at elbow with single forearm bone tradeoff. Keeps `hand_tune_deg := Vector3(0,0,90)` (required for 180° rig yaw + finger-to-laser snap, `src/player/arms_view.gd:74`). - docs: avatar_skeleton IK decision noteNotice, while this PR is merged, this feature for the Arm View and Full Avatar View is being backburnered until a later date, the code and assets will remain, however it will not be used.
Hand systems are nessecary for this system to work and too much time has been spend on this feature alone