From c70a523f0ffc50fed45018466a738e9e2982bb64 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Fri, 6 Nov 2026 18:39:53 +0100 Subject: [PATCH] Close and archive the 0.9.9 milestone, record that the upstream clips stay unused, and harden the fallback test --- DONE.md | 102 +++++++++++++++++ MEMORY.md | 7 +- TODO.md | 103 +----------------- .../adr/0018-quaternius-stage2-runtime-rig.md | 6 +- docs/development-status.md | 5 +- src/test/tests/test_npc_character.gd | 11 +- 6 files changed, 126 insertions(+), 108 deletions(-) diff --git a/DONE.md b/DONE.md index 1f79952..069611c 100644 --- a/DONE.md +++ b/DONE.md @@ -2192,3 +2192,105 @@ variants, the four crowd-variety characters and the per-role pools — as a **pa **Exit:** met — 0.9.7 ships the 12-character library (face realism + crowd variety) with the pose harness fixed; the runtime contract is unchanged. The Quaternius NPC base and the map rebuild are not in this patch (they target 0.10.0). + +## Milestone 0.9.9 — Quaternius NPC base swap (patch) + +Goal: make one Quaternius-derived NPC render and animate correctly in Godot and ship it as the +pilot base, with the procedural library still selectable as the fallback. This is the first +versioned character milestone; it contains the rig correction that has been blocking the +ADR-0018 rollout. + +**Why this is a patch:** it changes character presentation and animation quality but adds no new +player-facing system. The 0.10.0 minor is reserved for the release gates (see below), so a +blocked rig can no longer block the whole release. + +**Completed 2026-11-06.** Shipped as `v0.9.9` (tagged, local release build with checksums) +and accepted by the owner after the follow-up fixes. Durable evidence: `docs/3d-model-review.md`, +`docs/blender-assets.md`, `docs/adr/0018-quaternius-stage2-runtime-rig.md`. + +### Rig and export correction + +- [x] Reproduce and record the failure: the `armature_apply` + `export_apply=True` bake wrote + wrong inverse bind matrices (Godot rendered A-pose while the rest was arm-down). Recorded + in `MEMORY.md`; `bake_armdown_clean.py` was the failing recipe. +- [x] Fix the arm-down bake so the exported skin matches the rest. `tmp/npc-research/bake_armdown_v2.py` + (pose-rotate → apply modifier → reset pose → edit-mode rotate arms → re-add modifier → + export `export_apply=False`) now yields a correct arm-down base: Blender deformed x ±0.221 + and the rest bone tail points down. Still local (`tmp/`), not shipped. +- [x] Re-derive the pose composition for a non-180° rest. Superseded by the rig-agnostic + `_swing_bone()` below: the rest-relative `set_bone_pose_rotation` form was never exact, + because `get_bone_rest()` is parent-relative and already carries the importer's axis + conversion. Kept in `MEMORY.md` as a rejected approach. +- [x] Fix the skinning/rest bake end to end (commit `cd285b3`): + (1) the canonical base is **join-only** (`tmp/npc-research/build_base_minimal.py`: no + `remove_doubles` weld, no scale/ground transform, keep the Armature modifiers) — the weld + and the transform corrupted the Quaternius weights; + (2) the arm-down bake (`tmp/npc-research/bake_armdown_v5.py` — pose, read pose matrices, + apply modifier, **reset pose to identity**, set rest matrices, re-add modifier) bakes the + rest correctly (DEFORMED x −0.228/0.228, Godot bind pose arm-down); + (3) `NpcAvatar._swing_bone()` now writes the **global** bone pose from + `get_bone_global_rest()`, so one formula drives both the 62-joint Quaternius rig and the + 13-core fallback (measured: posed bone axis matches the proxy pivot at dot 1.000 on the + Quaternius, and the procedural rigs are unchanged); + (4) `RIG_BONES` + `NpcAvatar.bone_name()` hold the only side mapping (the Quaternius labels + `L`/`R` opposite the project), and the hand proxy is `global_wrist − global_elbow`. + Verified in Godot: idle/sit/walk captures on npc_casual plus the unchanged procedural rigs. +- [x] Add regression tests: `test_shipped_rig_resolves_the_animation_joints` covers the joint map, + the baked arm-down rest, the down-pointing arm axis and the proxy side mapping; the Blender + audit/`inspect` and `npc_proportions` now understand both rigs. Godot 278/278, Python + 167/167. +- [x] Give the Quaternius base the five tint slots: `build_base_minimal.py` renames the upstream + materials to `skin`/`cloth`/`pants`/`hair`/`accessory` and keeps `Eye`/`Eyebrows` as + fixed-colour detail. The source UV map is degenerate (every loop at 0,0), so the build now + Smart-UV-unwraps and repacks the layout — texel density went from an unmeasurable 0 to + 73–74 px/m on the 256² maps and the 256² AO bake works again. +- [x] Idle and Walk verified on the 62-joint rig through the pose code, not the clips: the 24 + upstream clips stay authoring reference only, because `_animate()` + `_sync_skeleton()` + already drive sitting, hand-raising and head turns, and playing clips would replace that + state machine rather than supplement it. Recorded in ADR-0018 and `MEMORY.md`. +- [x] Poses the clips do not cover are driven by the same proxy pivots (`_legs`, `_knees`, + `_arms`, `_elbows`, `_head`), which is why no clip retarget was needed. +- [x] Fallback stays tested: `_build_body()` still builds the 13-core rig, and + `test_forced_fallback_still_builds_the_procedural_rig` now asserts the hip swings and a hand + pivot exists on that path. No MPFB or Stage-1 proportion work reopened. +- [x] Backpack straps re-fitted for the Quaternius torso (`fb3e75a`) and the back attach raised to + shoulder-blade height; the camera kept its wrist strap. + +### Silhouette and face gate (owner) + +- [x] Re-run the silhouette gate at gameplay distance and in portrait against the current + `npc_casual`, front / three-quarter / profile. ADR-0018 removed the proportion warp, so + source proportions are accepted by definition (head 13.4 % of height, measured by + `npc_proportions`). +- [x] Owner visual signoff obtained (2026-11-06) on the roster, the faces, the head gear and the + hand/back gear. The review page and its sheets were retired after acceptance; the measurements + live on in `docs/3d-model-review.md` and `docs/blender-assets.md`. +- [x] Face-fidelity comparison was made and the full swap chosen; the comparison sheets were + retired with the review pages, and the decision is recorded in the follow-up section of + [`docs/adr/0018-quaternius-stage2-runtime-rig.md`](docs/adr/0018-quaternius-stage2-runtime-rig.md). +- [x] **Owner decision:** option (a) full swap (2026-11-05). Applied: all twelve characters are + baked from the three Quaternius sources inside 24,000 tris / 24,000 glTF vertices + (`a6ccd86`), and the gate was re-run. +- [x] Record the decision and its consequence in `MEMORY.md`, + `docs/npc-model-sourcing.md#shipped-roster-099-owner-chose-the-full-swap` and the + follow-up decision section of + [`docs/adr/0018-quaternius-stage2-runtime-rig.md`](docs/adr/0018-quaternius-stage2-runtime-rig.md). +- [x] Owner visual signoff on the shipped roster (2026-11-06): accepted. + +### Pilot rollout + +- [x] Map the five tint slots onto the Quaternius materials and unwrap the degenerate source + UV map; bake the 256² AO and check texel density (73–74 px/m). +- [x] Re-fit every gear attach point against the Quaternius skeleton and test each piece on the + character (the 0.9.10 *Prop and gear fit pass* below carries the measurements). +- [x] Wired the approved base into all twelve roles, with the procedural library as the instant + fallback; no runtime download or optional dependency. +- [x] Ran `inspect` (clean), `audit` (12/12), `fit` (OK), `npc_proportions` (measured), the 24-NPC + budget smoke (175,264 tris, 0.87 ms) and the capture gates before/after the fixes. +- [x] Released `0.9.9`: `config/version` 0.9.9, `CHANGELOG.md` entry, local build with checksums, + commit, tag `v0.9.9` and push. Owner accepted the roster on 2026-11-06. + +**Exit:** one committed Quaternius GLB renders arm-down with correct skinning, plays Idle and +Walk on the 62-joint rig, poses correctly under the new composition, passes the owner face and +silhouette gate, fits the 18 gear pieces, stays inside budget, and ships as `0.9.9` with the +procedural library still available as a fallback. diff --git a/MEMORY.md b/MEMORY.md index c9b46f1..ed18746 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -87,8 +87,11 @@ canonical implemented-features list in `docs/development-status.md`. lost forearms, stretched shins). The owner chose to **adopt the Quaternius 62-joint rig at runtime** instead of warping: `NpcAvatar` maps the Quaternius joint names, gear attach points are re-derived from the Quaternius skeleton (hip ≈0.87, shoulder ≈0.148/1.39, wrist ≈0.99, - knee ≈0.52), the mesh keeps source LOD (arm-down rest pose baked), and Idle/Walk play directly - on the 62-joint rig (no retarget). + knee ≈0.52) and the mesh keeps source LOD (arm-down rest pose baked). **The 24 upstream clips are + not played**: `_animate()` drives the proxies and `_sync_skeleton()` writes the bone poses, which + is what the FSM already needed for sitting, hand-raising and head turns. Playing the clips would + replace that state machine, not supplement it, so all 24 stay unused and the clips are authoring + reference only. - **Engine baseline:** Godot 4.7.2 stable (official editor `~/.local/bin/godot`). The system's `godot-runner` cannot load arbitrary project paths (compiled without path overrides); use the official editor binary. diff --git a/TODO.md b/TODO.md index 8f83833..4538be3 100644 --- a/TODO.md +++ b/TODO.md @@ -755,107 +755,6 @@ reference-image licence and privacy gates above rather than replacing them. passes visual, performance and provenance review; or the approach is rejected with reproducible findings and removed from the release path. -## Milestone 0.9.9 — Quaternius NPC base swap (patch) - -Goal: make one Quaternius-derived NPC render and animate correctly in Godot and ship it as the -pilot base, with the procedural library still selectable as the fallback. This is the first -versioned character milestone; it contains the rig correction that has been blocking the -ADR-0018 rollout. - -**Why this is a patch:** it changes character presentation and animation quality but adds no new -player-facing system. The 0.10.0 minor is reserved for the release gates (see below), so a -blocked rig can no longer block the whole release. - -### Rig and export correction - -- [x] Reproduce and record the failure: the `armature_apply` + `export_apply=True` bake wrote - wrong inverse bind matrices (Godot rendered A-pose while the rest was arm-down). Recorded - in `MEMORY.md`; `bake_armdown_clean.py` was the failing recipe. -- [x] Fix the arm-down bake so the exported skin matches the rest. `tmp/npc-research/bake_armdown_v2.py` - (pose-rotate → apply modifier → reset pose → edit-mode rotate arms → re-add modifier → - export `export_apply=False`) now yields a correct arm-down base: Blender deformed x ±0.221 - and the rest bone tail points down. Still local (`tmp/`), not shipped. -- [x] Re-derive the pose composition for a non-180° rest. Superseded by the rig-agnostic - `_swing_bone()` below: the rest-relative `set_bone_pose_rotation` form was never exact, - because `get_bone_rest()` is parent-relative and already carries the importer's axis - conversion. Kept in `MEMORY.md` as a rejected approach. -- [x] Fix the skinning/rest bake end to end (commit `cd285b3`): - (1) the canonical base is **join-only** (`tmp/npc-research/build_base_minimal.py`: no - `remove_doubles` weld, no scale/ground transform, keep the Armature modifiers) — the weld - and the transform corrupted the Quaternius weights; - (2) the arm-down bake (`tmp/npc-research/bake_armdown_v5.py` — pose, read pose matrices, - apply modifier, **reset pose to identity**, set rest matrices, re-add modifier) bakes the - rest correctly (DEFORMED x −0.228/0.228, Godot bind pose arm-down); - (3) `NpcAvatar._swing_bone()` now writes the **global** bone pose from - `get_bone_global_rest()`, so one formula drives both the 62-joint Quaternius rig and the - 13-core fallback (measured: posed bone axis matches the proxy pivot at dot 1.000 on the - Quaternius, and the procedural rigs are unchanged); - (4) `RIG_BONES` + `NpcAvatar.bone_name()` hold the only side mapping (the Quaternius labels - `L`/`R` opposite the project), and the hand proxy is `global_wrist − global_elbow`. - Verified in Godot: idle/sit/walk captures on npc_casual plus the unchanged procedural rigs. -- [x] Add regression tests: `test_shipped_rig_resolves_the_animation_joints` covers the joint map, - the baked arm-down rest, the down-pointing arm axis and the proxy side mapping; the Blender - audit/`inspect` and `npc_proportions` now understand both rigs. Godot 278/278, Python - 167/167. -- [x] Give the Quaternius base the five tint slots: `build_base_minimal.py` renames the upstream - materials to `skin`/`cloth`/`pants`/`hair`/`accessory` and keeps `Eye`/`Eyebrows` as - fixed-colour detail. The source UV map is degenerate (every loop at 0,0), so the build now - Smart-UV-unwraps and repacks the layout — texel density went from an unmeasurable 0 to - 73–74 px/m on the 256² maps and the 256² AO bake works again. -- [ ] Verify Idle and Walk on the 62-joint rig: loop seam, foot contact, root motion and frame - rate. Record which of the 24 clips are in scope and which stay unused. -- [ ] Decide and record how poses the clips do not cover (seated, raise-hand, head turn) are - driven at runtime. -- [ ] Keep the fallback tested: `npc_base`, the MakeHuman head and `_build_body()` remain - selectable; no MPFB work is reopened and no Stage-1 proportion warp returns. (The procedural - library is still the majority of `CHARACTER_POOLS` and captures clean, but the fallback path - itself has no dedicated test yet.) -- [ ] Re-fit the remaining gear against the Quaternius torso: the `backpack` strap geometry was - authored for the old body width and now hangs in front of the chest. The camera was fixed - with a wrist strap (a neck strap floats when the item is hand-attached). - -### Silhouette and face gate (owner) - -- [x] Re-run the silhouette gate at gameplay distance and in portrait against the current - `npc_casual`, front / three-quarter / profile. ADR-0018 removed the proportion warp, so - source proportions are accepted by definition (head 13.4 % of height, measured by - `npc_proportions`). -- [x] Owner visual signoff obtained (2026-11-06) on the roster, the faces, the head gear and the - hand/back gear. The review page and its sheets were retired after acceptance; the measurements - live on in `docs/3d-model-review.md` and `docs/blender-assets.md`. -- [x] Face-fidelity comparison was made and the full swap chosen; the comparison sheets were - retired with the review pages, and the decision is recorded in the follow-up section of - [`docs/adr/0018-quaternius-stage2-runtime-rig.md`](docs/adr/0018-quaternius-stage2-runtime-rig.md). -- [x] **Owner decision:** option (a) full swap (2026-11-05). Applied: all twelve characters are - baked from the three Quaternius sources inside 24,000 tris / 24,000 glTF vertices - (`a6ccd86`), and the gate was re-run. -- [x] Record the decision and its consequence in `MEMORY.md`, - `docs/npc-model-sourcing.md#shipped-roster-099-owner-chose-the-full-swap` and the - follow-up decision section of - [`docs/adr/0018-quaternius-stage2-runtime-rig.md`](docs/adr/0018-quaternius-stage2-runtime-rig.md). -- [x] Owner visual signoff on the shipped roster (2026-11-06): accepted. - -### Pilot rollout - -- [x] Map the five tint slots onto the Quaternius materials and unwrap the degenerate source - UV map; bake the 256² AO and check texel density (73–74 px/m). -- [ ] Bake the per-slot albedo/normal/ORM maps and a per-slot atlas (the five slots currently - share one whole-body UV set). -- [-] Re-fit gear attach points against the Quaternius skeleton and test cap, helmet, backpack, - shoulder bag, lanyard, clipboard and firefighter tank. (Camera done: wrist strap instead of - the floating neck strap. Backpack straps still cut across the Quaternius chest.) -- [ ] Wire the approved base into one role (or the crowd roles if option b is chosen) with the - procedural model as an instant fallback; no runtime download or optional dependency. -- [ ] Run `blender_assets.py inspect`/`audit`/`fit`, `npc_proportions.py`, the 24-NPC budget smoke - and the capture gate; record the measurements before/after. -- [ ] Release `0.9.9`: update the version source in `project.godot` (`config/version`), write the - `CHANGELOG.md` entry, run `tools/build_releases.sh`, verify checksums, commit, tag and push. - -**Exit:** one committed Quaternius GLB renders arm-down with correct skinning, plays Idle and -Walk on the 62-joint rig, poses correctly under the new composition, passes the owner face and -silhouette gate, fits the 18 gear pieces, stays inside budget, and ships as `0.9.9` with the -procedural library still available as a fallback. - ## Milestone 0.9.10 — NPC roster rollout (patch) Goal: extend the approved base to the full 12-role roster in asset-family batches, without @@ -869,6 +768,8 @@ regressing gear fit, budget or readability. upstream hivis vest and hard hat. All twelve built, AO-baked and committed (`a6ccd86`). - [ ] Add the modular part library (hair styles, outfits) so the roster regains the silhouette variety the twelve procedural meshes had; three shared meshes is the current ceiling. +- [ ] Bake per-slot albedo/normal/ORM maps and a per-slot atlas; the five slots currently share one + whole-body UV set, which caps how much the cloth/pants tints can differ. ### Owner review findings (2026-11-06) diff --git a/docs/adr/0018-quaternius-stage2-runtime-rig.md b/docs/adr/0018-quaternius-stage2-runtime-rig.md index af432bf..4f5b929 100644 --- a/docs/adr/0018-quaternius-stage2-runtime-rig.md +++ b/docs/adr/0018-quaternius-stage2-runtime-rig.md @@ -36,8 +36,10 @@ option 2). The mesh is not proportion-warped: it keeps its source LOD (3,199 wel - `NpcAvatar` maps the Quaternius joint names instead of the 13-core names. - Gear attach points are re-derived from the Quaternius skeleton (hip ≈0.87, shoulder ≈0.148/1.39, wrist ≈0.99, knee ≈0.52). -- The Quaternius Idle and Walk clips are used directly — they are authored for the 62-joint rig, - so no retargeting pass is needed. Combat/gun/sword clips stay unused. +- The 24 upstream clips ship as authoring reference only and are **not played at runtime**. + `NpcAvatar._animate()` drives the proxy pivots and `_sync_skeleton()` writes the bone poses, which + is what the FSM already needed for sitting, raising a hand and turning the head. Playing the clips + would replace that state machine rather than supplement it; revisiting this needs its own ADR. Option 1 (keep the 13-core names with Quaternius rest positions) is rejected in favour of the full source rig. diff --git a/docs/development-status.md b/docs/development-status.md index d3a78a3..56c0a16 100644 --- a/docs/development-status.md +++ b/docs/development-status.md @@ -28,8 +28,9 @@ and the active order/gaps in [`TODO.md`](../TODO.md). `npc_proportions.py` pass for the committed library. - 24-NPC smoke: 168 meshes, 152,464 triangles, 6.94 ms (50 ms budget). - Screenshot gate: 21 good / 0 bad; screen-design judge 21/21 match. -- Release baseline: v0.9.8 is published; the 0.10.0 release candidate remains - gated on external playtest, legal/storefront, signing and platform-matrix work. +- Release baseline: **v0.9.9** is published (Quaternius roster, accepted by the owner on + 2026-11-06); the 0.10.0 release candidate remains gated on external playtest, legal/storefront, + signing and platform-matrix work. ## Known gaps diff --git a/src/test/tests/test_npc_character.gd b/src/test/tests/test_npc_character.gd index 348cf88..9ab5dab 100644 --- a/src/test/tests/test_npc_character.gd +++ b/src/test/tests/test_npc_character.gd @@ -460,8 +460,17 @@ func test_forced_fallback_still_builds_the_procedural_rig() -> bool: "fallback still exposes Rig/Head") var c := TestUtils.check(avatar.find_child("HipL", true, false) != null, "fallback limb pivots exist") + # The fallback must still animate, not just exist: the pose code drives the same + # proxy pivots the skinned path uses. + var hip := avatar.find_child("HipL", true, false) as Node3D + avatar.velocity = Vector3(0, 0, 1.2) + avatar._animate(1.0 / 60.0) + var d := TestUtils.check(hip != null and absf(hip.rotation.x) > 0.01, + "fallback hip swings when walking") + var hand := avatar.find_child("Hand", true, false) as Node3D + var e := TestUtils.check(hand != null, "fallback exposes a hand pivot for gear") avatar.free() - return a and b and c + return a and b and c and d and e func test_rig_is_articulated() -> bool: var avatar := _avatar(Rules.NpcType.TOURIST, 77)