Record the Quaternius pilot bake, measurements and face-gate decision point in the docs
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped

This commit is contained in:
zwitschi committed 2026-11-06 13:45:05 +01:00
1 parent 90f189a269
commit 1ce173e9d1
3 files changed
+79 -16

No files matched your search

+39 -12
View File
@@ -155,11 +155,36 @@ canonical implemented-features list in `docs/development-status.md`.
the rest). The canonical base must be **join-only** (`build_base_minimal.py`: no `remove_doubles` the rest). The canonical base must be **join-only** (`build_base_minimal.py`: no `remove_doubles`
weld, no scale/ground transform, keep the Armature modifiers) — the weld and the scale/ground weld, no scale/ground transform, keep the Armature modifiers) — the weld and the scale/ground
transform corrupt the skin weights (arm poses to ±0.51 instead of −0.23). transform corrupt the skin weights (arm poses to ±0.51 instead of −0.23).
- Quaternius `NpcAvatar` wiring (commit `2a6f29f`): the Quaternius labels `L`/`R` opposite the - Quaternius `NpcAvatar` wiring (commits `2a6f29f`, `cd285b3`): `RIG_BONES` +
project, so the dot-suffixed bone names are looked up with a **swapped suffix** (proxy `L` → `NpcAvatar.bone_name(joint, side)` hold the **only** side mapping — the Quaternius labels `L`/`R`
`UpperLeg.R`/`UpperArm.R`, proxy `R` → `.L`). The arm/elbow swing axis is `Vector3.LEFT` (legs opposite the project, so proxy side 0 (`-x`) drives `UpperLeg.R`/`UpperArm.R` while side 1 drives
stay `RIGHT`). The hand proxy position is `global_wrist − global_elbow` (subtracting the relative the `.L` bones. The arm/elbow swing axis is `Vector3.LEFT` (legs stay `RIGHT`). The hand proxy is
`elbow.position` misplaces handheld gear). Old 13-core names fall back unchanged. `global_wrist − global_elbow` (subtracting the parent-relative `elbow.position` misplaces
handheld gear). Old 13-core names resolve through the same table.
- **Bone posing must write the global pose** (`NpcAvatar._swing_bone`): `reset_bone_pose()` then
`set_bone_global_pose(Transform3D(Basis(swing * global_rot), current_origin))`, conjugated with
`get_bone_global_rest()`. Both rejected alternatives fail: the rest-relative
`set_bone_pose_rotation(rest * Quaternion(rest⁻¹·axis, angle))` swings about the parent-relative
rest (the glTF importer already folded the axis conversion into it) and splayed the Quaternius
arms sideways; the plain `rest⁻¹ * swing * rest` conjugation flips the limbs 180°. Verified by
measuring `get_bone_global_pose()` after the call — posed bone axis matches the proxy pivot at
dot 1.000 on the Quaternius and the procedural rigs render unchanged.
Two caveats: children need the parent's swing passed in (the write is absolute), and
`set_bone_global_pose()` is a **no-op outside the scene tree**, so the headless runner (tests run
in `_initialize()`) cannot assert on it.
- **The Quaternius source UV map is degenerate** — every loop at (0,0), flat-colour materials, no
atlas. The AO/texture bake silently produces a 100 % black map, which tints every AO'd slot
(skin, hair, accessory) black in Godot: a black leg is the symptom. `build_base_minimal.py` now
Smart-UV-unwraps and repacks the layout (texel density 0 → 73–74 px/m on 256²), and the base
pipeline is two explicit steps with distinct files: `build_base_minimal.py`
(`quaternius/Casual_Hoodie.glb` → `quaternius_tpose.glb`, joins without welding, renames the
materials to the five tint slots, unwraps) then `bake_armdown_v5.py` (`quaternius_tpose.glb` →
`quaternius_base.glb` + `assets/models/blender_src/npc_casual.blend`). Re-running v5 on its own
output double-bakes the rest.
- Hand gear must be self-contained: the camera carried a 0.42 m neck-strap loop that floats when the
item is parented to the wrist. Hand items get a short wrist strap, not a neck strap. The backpack
straps were authored for the wider procedural chest and floated ~5 cm proud of the Quaternius
torso; the chest points now sit at x 0.12 / Godot z 0.15, which fits both.
- Image models have per-model size floors/caps (Seedream needs 1920+; `GEN_SIZE=2048`, do not - Image models have per-model size floors/caps (Seedream needs 1920+; `GEN_SIZE=2048`, do not
lower; gpt-5-image-mini caps lower). `generate_image` applies model-specific sizing; concept lower; gpt-5-image-mini caps lower). `generate_image` applies model-specific sizing; concept
generation defaults to `--request-size 1536`. generation defaults to `--request-size 1536`.
@@ -250,13 +275,15 @@ patches (`0.9.9`–`0.9.12`) with `0.10.0` as the public-readiness minor. The ga
three release roadmaps now live in `PLAN.md`; `TODO.md` is the executable task list. ADR-0017 three release roadmaps now live in `PLAN.md`; `TODO.md` is the executable task list. ADR-0017
Stage 1 (proportion warp) is dead; ADR-0018 option 2 (62-joint runtime) is accepted. Stage 1 (proportion warp) is dead; ADR-0018 option 2 (62-joint runtime) is accepted.
**Quaternius pilot (2026-11-05):** the arm-down skin export and pose composition are now fixed **Quaternius pilot (2026-11-05, commits `cd285b3`, `fb3e75a`, `90f189a`):** `npc_casual` ships as
(`bake_armdown_v5.py`, `2a6f29f`). The join-only `quaternius_base.glb` renders arm-down in Godot; the Quaternius base. It renders arm-down, carries the five tint slots, plays the Idle/Walk/Sit pose
idle/sit/walk were vision-verified (coherent humanoid, seated arms on thighs, contralateral walk). code on the 62-joint rig, and the procedural rigs are visually unchanged. Verified: `inspect`
Remaining before 0.9.9: handheld-gear fitting (the 0.43 m camera reads shoulder-mounted on the (no issues), `audit` OK (6,206 tris / 12,988 verts, 62 bones), `fit` head 0.201 m, texel density
0.42 m arm), five-slot mapping + AO bake, the owner face/silhouette gate, then the rollout. 73–74 px/m, `npc_proportions` measured, 24-NPC budget smoke OK (168 meshes, 162,304 tris,
No character GLB has been rebuilt since the pivot. 0.82 ms avg frame). The face/silhouette gate sheet and the three options are in
`docs/screens/npc-face-gate/`; the **owner decision is the only thing left before the 0.9.9
rollout**.
Test counts as of 2026-11-05: Python 167/167, Godot 277/277, `check_docs` OK. Test counts as of 2026-11-05: Python 167/167, Godot 278/278, `check_docs` OK.
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu --> <!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
+5 -4
View File
@@ -34,10 +34,11 @@ and the active order/gaps in [`TODO.md`](../TODO.md).
## Known gaps ## Known gaps
- External first-time-player sessions and balance sweeps are not completed. - External first-time-player sessions and balance sweeps are not completed.
- The Quaternius NPC base migration is planned, not shipped: the arm-down skin export and the - The Quaternius NPC base is wired for one character (`npc_casual`): arm-down rest, five tint
rest-relative pose composition are the open blockers (`0.9.9`), and the roster rollout is slots, unwrapped UVs, 62-joint rig at source proportions, Idle/Walk/Sit pose code, 6,206 tris.
`0.9.10`. The current 12-character procedural library and the MakeHuman head remain the The remaining `0.9.9` blocker is the owner face/silhouette decision
tested fallbacks until the face/silhouette gate is approved. (`docs/screens/npc-face-gate/`); the full 12-role roster rollout is `0.9.10`. The other eleven
characters stay procedural with the MakeHuman head until that decision lands.
- Accessibility/localization acceptance, target-hardware profiling, macOS - Accessibility/localization acceptance, target-hardware profiling, macOS
signing/notarization and AI/storefront legal approval remain open. signing/notarization and AI/storefront legal approval remain open.
- Live 3D replay, recorded VO/music, more venues and seasonal content are future - Live 3D replay, recorded VO/music, more venues and seasonal content are future
+35
View File
@@ -203,6 +203,41 @@ start the 12-variant rebuild on the warped base.
mesh at source LOD, re-derive gear attach points from the Quaternius skeleton, and use the mesh at source LOD, re-derive gear attach points from the Quaternius skeleton, and use the
Quaternius Idle/Walk clips directly (no retarget). Quaternius Idle/Walk clips directly (no retarget).
## Pilot bake and shipped state (2026-11-05)
`npc_casual` is the first Quaternius-derived character. The reproducible pipeline lives in
`tmp/npc-research/` (local tooling, not shipped) and has two steps:
1. `build_base_minimal.py` — import `quaternius/Casual_Hoodie.glb`, join the four skinned meshes
**without welding**, rename the upstream materials to the project tint slots, Smart-UV-unwrap
and repack, export `quaternius_tpose.glb`.
2. `bake_armdown_v5.py` — pose `UpperArm.L/R` −90° local X, read the posed matrices, apply the
Armature modifier, **reset the pose to identity**, set the rest matrices, re-add the modifier,
export `quaternius_base.glb` and save `assets/models/blender_src/npc_casual.blend` for the AO
bake.
Then `tools/bake_ao.py --only npc_casual` and a copy of the GLB to
`assets/models/blender/npc/npc_casual.glb`.
| Measurement | npc_casual (Quaternius) | procedural library |
| --- | --- | --- |
| Triangles | 6,206 | 4,812–6,046 |
| glTF vertices | 12,988 | 4,908–5,785 |
| Bones | 62 | 13 |
| Head height / body height | 0.134 | 0.16–0.22 (stylized band) |
| Shoulder width | 0.201 m head width, `fit` OK | `fit` OK |
| Texel density (256² maps) | 73–74 px/m | 75–96 px/m |
| Materials | `skin`, `cloth`, `pants`, `hair`, `accessory` + fixed `Eye`, `Eyebrows` | five slots + fixed detail |
Two source quirks shaped the bake:
- the upstream UV map is degenerate (every loop at 0,0) and its materials are flat colours, so the
AO/texture bake must follow a real unwrap or it silently writes a black map;
- the upstream materials are named `Skin`/`Purple`/`LightBlue`/`White`/`Hair`, so the five-slot
contract is applied as a rename, not a re-author.
The face and silhouette gate sheet is in [`screens/npc-face-gate/`](screens/npc-face-gate/README.md).
## Contract mismatch ## Contract mismatch
The expensive part is the rig and proportions, not the mesh. The source has a The expensive part is the rig and proportions, not the mesh. The source has a