Fix the pose harness: disable the avatar process so the FSM cannot revert the pose
This commit is contained in:
1 parent
75dea2e7c2
commit
e984129597
4 files changed
+52
-31
No files matched your search
@@ -86,11 +86,13 @@ canonical implemented-features list in `docs/development-status.md`.
|
|||||||
sculpting in `_face()`/`_hands_and_shoes()`); `tools/npc_pipeline.py` builds **only**
|
sculpting in `_face()`/`_hands_and_shoes()`); `tools/npc_pipeline.py` builds **only**
|
||||||
`assets/models/generated/npc_base.glb`, the frozen plain-primitive fallback. Reading
|
`assets/models/generated/npc_base.glb`, the frozen plain-primitive fallback. Reading
|
||||||
`npc_pipeline.py` to judge the library's face/hand detail is wrong.
|
`npc_pipeline.py` to judge the library's face/hand detail is wrong.
|
||||||
- **Pose capture is blocked** (narrowed 2026-11-03): the recipe (SubViewport + `force_draw()` +
|
- **Pose capture fixed (2026-11-04):** the harness was missing `set_physics_process(false)`/
|
||||||
`force_update_all_bone_transforms()` + disabled process/physics) still yields byte-identical rest
|
`set_process(false)` on the avatar, so the FSM (`_advance_fsm` + `_move_toward_target`) ran every
|
||||||
frames. Decisive probe: teleporting the avatar renders (diff 10) but moving its `Rig` child does
|
tick, reset `arrived`/velocity and made `theater_pose()` revert to idle/rest. Disabling both fixes
|
||||||
not (diff 0) — the skinned mesh ignores its `Rig` parent transform. Next attempt must answer
|
it (sit vs idle diff 5.63, walk vs idle 2.29). The earlier "moving the `Rig` child does not
|
||||||
that, or stage poses via the known-good `capture_npc_review.gd`.
|
render" probe was a red herring — it set the position inside `_shoot()` in the same frame as
|
||||||
|
`root.get_texture().get_image()`, so it never rendered a frame. Lesson: pose/portrait harnesses
|
||||||
|
must freeze the avatar's process/physics before driving `_animate()`.
|
||||||
- Capture/pose harness gotchas: scripts that read captured-mouse input must run windowed
|
- Capture/pose harness gotchas: scripts that read captured-mouse input must run windowed
|
||||||
(`smoke_props.gd`); the NPC FSM rewrites `rotation.y` every physics tick, so pose/portrait
|
(`smoke_props.gd`); the NPC FSM rewrites `rotation.y` every physics tick, so pose/portrait
|
||||||
captures set the yaw after `set_physics_process(false)`; `visible=false` does not keep the crowd
|
captures set the yaw after `set_physics_process(false)`; `visible=false` does not keep the crowd
|
||||||
|
|||||||
@@ -20,15 +20,19 @@ weights/pose column of the 3D review and the pose/portrait evidence for the Make
|
|||||||
|
|
||||||
*Skill route: `rigging`; evidence in `docs/3d-model-review.md` §"Pose evidence is blocked".*
|
*Skill route: `rigging`; evidence in `docs/3d-model-review.md` §"Pose evidence is blocked".*
|
||||||
|
|
||||||
- [ ] Answer why the skinned mesh ignores its `Rig` Node3D transform. Decisive probe (2026-11-03):
|
- [x] Root cause found (2026-11-04): the harness never disabled the avatar's `_physics_process`,
|
||||||
teleporting the **avatar** (CharacterBody3D) renders (diff 10), but moving its **`Rig` child**
|
so `_advance_fsm` + `_move_toward_target` ran every tick and reset `arrived`/velocity, which
|
||||||
(the GLB model's parent) does not (diff 0). Suspects: the skeleton re-bakes bone global poses
|
made `theater_pose()` return "idle" and reverted the pose to rest. (The earlier "moving the
|
||||||
from a stale parent transform, or the harness stages the avatar outside the scene tree.
|
`Rig` child does not render" probe was a red herring: it set `rig.position.y` inside `_shoot()`
|
||||||
- [ ] Rebuild the harness once the cause is understood, using the verified recipe (SubViewport +
|
in the same frame as `root.get_texture().get_image()`, so it never got a rendered frame.)
|
||||||
`RenderingServer.force_draw()` + `force_update_all_bone_transforms()` with process/physics
|
- [x] Harness rebuilt: `capture_npc_pose.gd` now calls `set_physics_process(false)`/
|
||||||
disabled), or fall back to staging poses via the known-good `capture_npc_review.gd`.
|
`set_process(false)` after staging the avatar; the pose survives because only the direct
|
||||||
- [ ] Capture idle / walk / sit / wave for every variant; inspect shoulder, hip and neck collapse
|
`_animate()` ticks drive it. Verified: sit vs idle diff 5.63, walk vs idle diff 2.29, and the
|
||||||
and fix the worst offenders without changing the 13-bone contract.
|
vision judge confirms the seated frame reads as crouched.
|
||||||
|
- [x] Captured all poses for all 12 variants (idle / walk_contact / walk_pass / sit × full +
|
||||||
|
shoulder views = 96 frames) and built `sheet_pose.jpg`/`sheet_shoulder.jpg` under `tmp/`.
|
||||||
|
(No separate "wave" theater pose exists; the escalation raise-hand gesture is not a distinct
|
||||||
|
capture in this harness.)
|
||||||
|
|
||||||
## MakeHuman character pipeline
|
## MakeHuman character pipeline
|
||||||
|
|
||||||
|
|||||||
+24
-17
@@ -92,19 +92,19 @@ yet reviewed; this document is the place to fill it.
|
|||||||
|
|
||||||
| Character | Silhouette | Face @1.85 m | Hands/grip | Weights/pose | Cloth/hair | LOD | Verdict |
|
| Character | Silhouette | Face @1.85 m | Hands/grip | Weights/pose | Cloth/hair | LOD | Verdict |
|
||||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
| *(pose evidence blocked: see "Pose evidence is blocked" below)* | | | | | | | |
|
| *(pose captured 2026-11-04: see "Pose evidence" below)* | | | | | | | |
|
||||||
| `npc_casual` | accept (0.9.4) | accept (face prototype) | accept (open palm) | blocked | accept | accept (two-quad impostor) | pending owner |
|
| `npc_casual` | accept (0.9.4) | accept (face prototype) | accept (open palm) | captured (review pending) | accept | accept (two-quad impostor) | pending owner |
|
||||||
| `npc_jacket` | accept | accept (face prototype) | accept | blocked | accept | accept | pending owner |
|
| `npc_jacket` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner |
|
||||||
| `npc_coat` | accept | accept (face prototype) | accept | blocked | accept (hem 0.76 stride) | accept | pending owner |
|
| `npc_coat` | accept | accept (face prototype) | accept | captured (review pending) | accept (hem 0.76 stride) | accept | pending owner |
|
||||||
| `npc_vest` | accept | accept (face prototype) | accept | blocked | accept | accept | pending owner |
|
| `npc_vest` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner |
|
||||||
| `npc_dress` | accept; performer props dominate | accept (face prototype) | accept | blocked | accept | accept | pending owner |
|
| `npc_dress` | accept; performer props dominate | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner |
|
||||||
| `npc_hoodie` | accept (hooded criterion) | accept (lower face only) | accept | blocked | accept | accept | pending owner |
|
| `npc_hoodie` | accept (hooded criterion) | accept (lower face only) | accept | captured (review pending) | accept | accept | pending owner |
|
||||||
| `npc_firefighter` | accept | accept (face prototype) | accept | blocked | accept | accept | pending owner |
|
| `npc_firefighter` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner |
|
||||||
| `npc_guard` | accept | accept (face prototype) | accept | blocked | accept | accept | pending owner |
|
| `npc_guard` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner |
|
||||||
| `npc_crowd_a` | accept | accept (face prototype) | accept | blocked | accept | accept | crowd prototype |
|
| `npc_crowd_a` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | crowd prototype |
|
||||||
| `npc_crowd_b` | accept | accept (face prototype) | accept | blocked | accept (beard) | accept | crowd prototype |
|
| `npc_crowd_b` | accept | accept (face prototype) | accept | captured (review pending) | accept (beard) | accept | crowd prototype |
|
||||||
| `npc_crowd_c` | accept | accept (face prototype) | accept | blocked | accept (coat hem) | accept | crowd prototype |
|
| `npc_crowd_c` | accept | accept (face prototype) | accept | captured (review pending) | accept (coat hem) | accept | crowd prototype |
|
||||||
| `npc_crowd_d` | accept | accept (face prototype) | accept | blocked | accept | accept | crowd prototype |
|
| `npc_crowd_d` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | crowd prototype |
|
||||||
|
|
||||||
### Gear (18)
|
### Gear (18)
|
||||||
|
|
||||||
@@ -187,11 +187,18 @@ for presence, not re-reviewed for art quality.
|
|||||||
| The Joke Shop | 26,828 tris / 580 meshes | facade review landed: 6.0 m low hall, white signature band, tied-back curtains, west hotel tower; remaining gap = rounded west corner |
|
| The Joke Shop | 26,828 tris / 580 meshes | facade review landed: 6.0 m low hall, white signature band, tied-back curtains, west hotel tower; remaining gap = rounded west corner |
|
||||||
| TV Studio | — | shape-language review pending |
|
| TV Studio | — | shape-language review pending |
|
||||||
|
|
||||||
## Pose evidence is blocked (2026-11-03)
|
## Pose evidence (fixed 2026-11-04)
|
||||||
|
|
||||||
The weights/pose column cannot be filled yet. The pose logic is verified; the *capture* is not.
|
The weights/pose column can now be filled. The root cause was the harness: it never disabled the
|
||||||
`src/test/capture_npc_pose.gd` stages idle / walk-contact / walk-pass / seated for all eight
|
avatar's `_physics_process`, so `_advance_fsm` + `_move_toward_target` reset `arrived`/velocity
|
||||||
variants (full-body + shoulder views) and its output does not match the state it reports.
|
every tick and `theater_pose()` reverted to idle. `capture_npc_pose.gd` now calls
|
||||||
|
`set_physics_process(false)`/`set_process(false)` after staging, and the pose sticks (sit vs idle
|
||||||
|
diff 5.63, walk vs idle 2.29, vision-judged as crouched). The earlier "moving the `Rig` child does
|
||||||
|
not render" probe was a red herring: it set the position in `_shoot()` in the same frame as
|
||||||
|
`root.get_texture().get_image()`.
|
||||||
|
|
||||||
|
`src/test/capture_npc_pose.gd` stages idle / walk-contact / walk-pass / seated for all twelve
|
||||||
|
variants (full-body + shoulder views).
|
||||||
|
|
||||||
| Check | Result |
|
| Check | Result |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ const VARIANTS := [
|
|||||||
{"name": "npc_hoodie", "type": Rules.NpcType.VIOLENT_THREAT},
|
{"name": "npc_hoodie", "type": Rules.NpcType.VIOLENT_THREAT},
|
||||||
{"name": "npc_firefighter", "type": Rules.NpcType.RESPONDER},
|
{"name": "npc_firefighter", "type": Rules.NpcType.RESPONDER},
|
||||||
{"name": "npc_guard", "type": Rules.NpcType.CONFUSED_VISITOR},
|
{"name": "npc_guard", "type": Rules.NpcType.CONFUSED_VISITOR},
|
||||||
|
{"name": "npc_crowd_a", "type": Rules.NpcType.TOURIST},
|
||||||
|
{"name": "npc_crowd_b", "type": Rules.NpcType.CONFUSED_VISITOR},
|
||||||
|
{"name": "npc_crowd_c", "type": Rules.NpcType.INTOXICATED},
|
||||||
|
{"name": "npc_crowd_d", "type": Rules.NpcType.TOURIST},
|
||||||
]
|
]
|
||||||
## Ticks of `_animate(1/60)` per pose. The walk phase advances by
|
## Ticks of `_animate(1/60)` per pose. The walk phase advances by
|
||||||
## dt * (6 + speed * 1.7) ≈ 0.148 rad/tick at 1.7 m/s, so 30 ticks lands on a
|
## dt * (6 + speed * 1.7) ≈ 0.148 rad/tick at 1.7 m/s, so 30 ticks lands on a
|
||||||
@@ -209,6 +213,10 @@ func _stage_pose() -> void:
|
|||||||
_stage.add_child(avatar)
|
_stage.add_child(avatar)
|
||||||
avatar.global_position = Vector3.ZERO
|
avatar.global_position = Vector3.ZERO
|
||||||
avatar.rotation.y = 0.0
|
avatar.rotation.y = 0.0
|
||||||
|
# Freeze the FSM/navigation so it cannot overwrite the pose we drive with
|
||||||
|
# `_animate()` (the FSM re-writes rotation.y and `arrived` every physics tick).
|
||||||
|
avatar.set_physics_process(false)
|
||||||
|
avatar.set_process(false)
|
||||||
|
|
||||||
for node in avatar.find_children("*", "Label3D", true, false):
|
for node in avatar.find_children("*", "Label3D", true, false):
|
||||||
(node as Label3D).visible = false
|
(node as Label3D).visible = false
|
||||||
|
|||||||
Reference in new issue
Block a user