diff --git a/MEMORY.md b/MEMORY.md index e4da29c..7dbda17 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -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` 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). -- Quaternius `NpcAvatar` wiring (commit `2a6f29f`): the Quaternius labels `L`/`R` opposite the - project, so the dot-suffixed bone names are looked up with a **swapped suffix** (proxy `L` → - `UpperLeg.R`/`UpperArm.R`, proxy `R` → `.L`). The arm/elbow swing axis is `Vector3.LEFT` (legs - stay `RIGHT`). The hand proxy position is `global_wrist − global_elbow` (subtracting the relative - `elbow.position` misplaces handheld gear). Old 13-core names fall back unchanged. +- Quaternius `NpcAvatar` wiring (commits `2a6f29f`, `cd285b3`): `RIG_BONES` + + `NpcAvatar.bone_name(joint, side)` hold the **only** side mapping — the Quaternius labels `L`/`R` + opposite the project, so proxy side 0 (`-x`) drives `UpperLeg.R`/`UpperArm.R` while side 1 drives + the `.L` bones. The arm/elbow swing axis is `Vector3.LEFT` (legs stay `RIGHT`). The hand proxy is + `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 lower; gpt-5-image-mini caps lower). `generate_image` applies model-specific sizing; concept 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 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 -(`bake_armdown_v5.py`, `2a6f29f`). The join-only `quaternius_base.glb` renders arm-down in Godot; -idle/sit/walk were vision-verified (coherent humanoid, seated arms on thighs, contralateral walk). -Remaining before 0.9.9: handheld-gear fitting (the 0.43 m camera reads shoulder-mounted on the -0.42 m arm), five-slot mapping + AO bake, the owner face/silhouette gate, then the rollout. -No character GLB has been rebuilt since the pivot. +**Quaternius pilot (2026-11-05, commits `cd285b3`, `fb3e75a`, `90f189a`):** `npc_casual` ships as +the Quaternius base. It renders arm-down, carries the five tint slots, plays the Idle/Walk/Sit pose +code on the 62-joint rig, and the procedural rigs are visually unchanged. Verified: `inspect` +(no issues), `audit` OK (6,206 tris / 12,988 verts, 62 bones), `fit` head 0.201 m, texel density +73–74 px/m, `npc_proportions` measured, 24-NPC budget smoke OK (168 meshes, 162,304 tris, +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. diff --git a/docs/development-status.md b/docs/development-status.md index fb7fcce..096fb61 100644 --- a/docs/development-status.md +++ b/docs/development-status.md @@ -34,10 +34,11 @@ and the active order/gaps in [`TODO.md`](../TODO.md). ## Known gaps - 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 - rest-relative pose composition are the open blockers (`0.9.9`), and the roster rollout is - `0.9.10`. The current 12-character procedural library and the MakeHuman head remain the - tested fallbacks until the face/silhouette gate is approved. +- The Quaternius NPC base is wired for one character (`npc_casual`): arm-down rest, five tint + slots, unwrapped UVs, 62-joint rig at source proportions, Idle/Walk/Sit pose code, 6,206 tris. + The remaining `0.9.9` blocker is the owner face/silhouette decision + (`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 signing/notarization and AI/storefront legal approval remain open. - Live 3D replay, recorded VO/music, more venues and seasonal content are future diff --git a/docs/npc-model-sourcing.md b/docs/npc-model-sourcing.md index ac4049e..49e8f92 100644 --- a/docs/npc-model-sourcing.md +++ b/docs/npc-model-sourcing.md @@ -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 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 The expensive part is the rig and proportions, not the mesh. The source has a