Narrow the pose-capture discrepancy to the harness and lock bone posing
This commit is contained in:
1 parent
7a5b902797
commit
1d4f718376
5 files changed
+63
-26
No files matched your search
@@ -11,8 +11,12 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
||||
direct `set_bone_pose_rotation` on HipL/KneeL/Spine/ShoulderL/ElbowL/Head changes rendered
|
||||
pixels; a bone *position* offset moves the head; a standalone probe running the same
|
||||
`_animate()` sequence renders the crouch. A staged in-game `film` frame also showed the arms
|
||||
down, so live venue animation may be affected too. Do not treat the pose sheets as evidence;
|
||||
a scene test (posed frame differs from rest frame) is the next step.
|
||||
Standalone SceneTree probes (avatar under `root`, under a `Node3D` stage, and created
|
||||
mid-`_process`) all render the seated crouch, so the rig and animation math are fine and the
|
||||
harness capture path is the outlier. A headless test now locks the proxy→bone contract
|
||||
(`test_pose_reaches_the_skeleton_bones`, suite 273). Next leads: reproduce the stage inside
|
||||
`capture_npc_review.gd` (known-good captures) or capture via `SubViewport` +
|
||||
`RenderingServer.force_draw()`.
|
||||
- **3D review baseline (2026-11-03):** `docs/3d-model-review.md` records the first skill-guided
|
||||
measurements: 46/46 assets clean, library 59,012 tris, characters 4,482–5,474 tris (68–69% of
|
||||
the 8,000 caps), `npc_base` only 1,204/2,488, 24-NPC 6.94 ms, and authored texel density
|
||||
|
||||
@@ -64,10 +64,12 @@ except the rebuilt assets themselves.
|
||||
frames still show the rest pose; direct bone writes deform the mesh, so this is a staging/
|
||||
capture discrepancy that may also affect live venue frames. See
|
||||
`docs/3d-model-review.md` §"Pose evidence is blocked".
|
||||
- [ ] Diagnose the pose-capture discrepancy: compare the harness staging with the working
|
||||
standalone probe (SceneTree lifetime, node parents, first-frame skin bind, capture timing)
|
||||
and add a scene test asserting a posed avatar renders differently from its rest frame.
|
||||
Do not trust pose sheets or claim rig deformation until that test passes.
|
||||
- [ ] Finish the pose-capture diagnosis. Done so far: the pose reaches proxies and skeleton
|
||||
bones (new headless test, 273/273), direct bone rotations deform the rendered mesh, and
|
||||
three standalone probes render the seated crouch — only `capture_npc_pose.gd` saves the
|
||||
rest pose. Remaining leads: reproduce the stage inside `capture_npc_review.gd` (known to
|
||||
render) or capture via `SubViewport` + `RenderingServer.force_draw()`, then add a scene
|
||||
test that a posed avatar renders differently from its rest frame.
|
||||
- [ ] UV/texel density: measure the 256² slot maps; decide whether atlas, UDIM or a resolution
|
||||
change is justified. Do not change `CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` without re-running the
|
||||
budget smoke and the Web/Compatibility path.
|
||||
|
||||
+19
-18
@@ -165,26 +165,27 @@ for presence, not re-reviewed for art quality.
|
||||
|
||||
## Pose evidence is blocked (2026-11-03)
|
||||
|
||||
The weights/pose column cannot be filled yet. A pose harness was added
|
||||
(`src/test/capture_npc_pose.gd`, staged in `tmp/model-pose*`), and its output does not match the
|
||||
state it reports:
|
||||
The weights/pose column cannot be filled yet. The pose logic is verified; the *capture* is not.
|
||||
`src/test/capture_npc_pose.gd` stages idle / walk-contact / walk-pass / seated for all eight
|
||||
variants (full-body + shoulder views) and its output does not match the state it reports.
|
||||
|
||||
- `_animate()` applies the seated pose (logged `rig_y=-0.339`, `leg0=-1.357`) and
|
||||
`Skeleton3D.get_bone_pose_rotation()` returns the rotated quaternion at capture time, yet the
|
||||
saved frame shows the rest pose.
|
||||
- Ruled out: missing skeleton (one `Skeleton3D`, mesh bound to it, `same=true`), hidden mesh
|
||||
(hiding `npc_base` removes the body), pose not applied (proxies and bone poses verified),
|
||||
and unbound skinning: writing a single bone rotation directly (`HipL`, `KneeL`, `Spine`,
|
||||
`ShoulderL`, `ElbowL`, `Head`) changes the rendered pixels, and a bone *position* offset moves
|
||||
the head. A standalone probe that calls the same `_animate()` sequence renders the crouch.
|
||||
- Also observed: a staged in-game `film` pose frame showed the arms down, so this may affect the
|
||||
live game rather than only the harness. That is why it is a P0/P1 candidate rather than a QA
|
||||
tooling nit.
|
||||
| Check | Result |
|
||||
| --- | --- |
|
||||
| Pose reaches proxies | Seated pose logs `rig_y=-0.339`, `leg0=-1.357` |
|
||||
| Pose reaches skeleton bones | `Skeleton3D.get_bone_pose_rotation(HipL/KneeL)` differs from rest; new headless test `test_pose_reaches_the_skeleton_bones` locks it (suite 273/273) |
|
||||
| Idle restores rest | Same test asserts the hip returns to its rest orientation |
|
||||
| Skinning bound | One `Skeleton3D`, mesh bound to it (`same=true`); hiding `npc_base` removes the body |
|
||||
| Bone rotations deform the mesh | Direct writes change rendered pixels: Spine 1,719 / HipL 1,052 / KneeL 810 / ElbowL 745 / Head 671 / ShoulderL 640 sampled pixels |
|
||||
| Bone translation deforms the mesh | A +0.5 m Head offset moves the head in frame |
|
||||
| Standalone probes render the pose | Three variants (avatar under `root`, under a `Node3D` stage, created mid-`_process`) all render the seated crouch (`posed` vs rest diff ≈ 2,200–2,360 samples) |
|
||||
| The harness renders the pose | **No.** All 8 variants × 4 poses save the rest pose while the logged state and bone poses are posed |
|
||||
|
||||
Next step is a focused diagnosis: compare the harness/venue staging with the standalone probe
|
||||
(SceneTree lifetime, node parents, first-frame skin bind, capture timing) and add a scene test
|
||||
that asserts a posed avatar renders differently from its rest frame. Do not mark the pose/weight
|
||||
scorecard row until that test exists.
|
||||
The cause is therefore in the harness's capture path, not in the rig or the animation math. Two
|
||||
leads are left and both are cheap: reproduce the stage inside a harness that is known to render
|
||||
correctly (`capture_npc_review.gd` already produces 96 valid frames), or capture through a
|
||||
`SubViewport` with `RenderingServer.force_draw()` so the frame cannot lag the state. Until a
|
||||
scene test asserts that a posed avatar renders differently from its rest frame, do not treat pose
|
||||
sheets as evidence and do not claim rig deformation in the review.
|
||||
|
||||
## Ranked candidate gaps
|
||||
|
||||
|
||||
@@ -261,8 +261,6 @@ func _shoot() -> void:
|
||||
while FileAccess.file_exists(path):
|
||||
path = dir.path_join("%s-v%d.png" % [base, index])
|
||||
index += 1
|
||||
var hip_bone: int = int(_avatar._bone_indices.get("HipL", -1))
|
||||
var knee_bone: int = int(_avatar._bone_indices.get("KneeL", -1))
|
||||
var img: Image = root.get_texture().get_image()
|
||||
var err := img.save_png(path)
|
||||
print("%s -> err=%d" % [base, err])
|
||||
@@ -147,6 +147,38 @@ func test_skinned_model_loads_and_keeps_the_head_contract() -> bool:
|
||||
avatar.free()
|
||||
return a and b and c
|
||||
|
||||
## The pose harness captures frames, but a regression here is cheap to catch:
|
||||
## the pose logic must reach the skeleton bones, not only the proxy pivots.
|
||||
func test_pose_reaches_the_skeleton_bones() -> bool:
|
||||
var avatar := _avatar(Rules.NpcType.TOURIST, 55)
|
||||
var sk := avatar._skeleton
|
||||
var hip: int = int(avatar._bone_indices["HipL"])
|
||||
var knee: int = int(avatar._bone_indices["KneeL"])
|
||||
var rest_hip := sk.get_bone_rest(hip).basis.get_rotation_quaternion()
|
||||
var rest_knee := sk.get_bone_rest(knee).basis.get_rotation_quaternion()
|
||||
avatar.arrived = true
|
||||
avatar.npc.intent = "sit"
|
||||
for _i in 45:
|
||||
avatar._animate(1.0 / 60.0)
|
||||
var sit_hip := sk.get_bone_pose_rotation(hip)
|
||||
var sit_knee := sk.get_bone_pose_rotation(knee)
|
||||
var a := TestUtils.check(not sit_hip.is_equal_approx(rest_hip),
|
||||
"seated pose rotates the HipL bone away from rest")
|
||||
var b := TestUtils.check(not sit_knee.is_equal_approx(rest_knee),
|
||||
"seated pose rotates the KneeL bone away from rest")
|
||||
var c := TestUtils.check(avatar._rig.position.y < -0.25,
|
||||
"seated pose lowers the rig (%.3f)" % avatar._rig.position.y)
|
||||
# Returning to idle must restore the rest orientation, not leave the sit pose.
|
||||
avatar.arrived = false
|
||||
avatar.npc.intent = "roam"
|
||||
avatar.velocity = Vector3.ZERO
|
||||
for _i in 120:
|
||||
avatar._animate(1.0 / 60.0)
|
||||
var d := TestUtils.check(sk.get_bone_pose_rotation(hip).is_equal_approx(rest_hip),
|
||||
"idle restores the HipL rest orientation")
|
||||
avatar.free()
|
||||
return a and b and c and d
|
||||
|
||||
func test_character_selection_is_deterministic_and_complete() -> bool:
|
||||
var expected := {
|
||||
Rules.NpcType.TOURIST: "npc_casual",
|
||||
|
||||
Reference in new issue
Block a user