Files
zwitschi 2ba40f3578
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
Reorganize docs into category folders and drop the retired head-review page
Group the top-level docs into docs/project, docs/design, docs/assets and
docs/process, each with its own README index. Delete the superseded
docs/screens/head-review page and sheets. Rewrite every markdown link,
absolute docs path and tool/test reference to the new locations, fix the
pre-existing broken anchors and remove the dead Joke Shop photo-board link.
check_docs OK, Python tooling 167/167.
2026-11-06 20:13:03 +01:00

19 KiB
Raw Permalink Blame History

NPC Model Sourcing

This page records the source evaluation and the active character plan. The decision is ADR-0017, which supersedes the generator-first sourcing decision in ADR-0015.

Active direction

Quaternius Ultimate Modular Characters (CC0) is the primary NPC mesh and animation source. The project-owned procedural library remains the shipped fallback until the Quaternius migration passes the silhouette and gear gates.

The migration keeps the runtime contract that Godot, gear and tests already consume:

  • one npc_base skinned mesh under the 13 required core bones (optional extras allowed);
  • five tint slots: skin, cloth, pants, accessory, hair;
  • front on Blender −Y / Godot +Z, feet grounded, height about 1.81 m;
  • 24,000 triangles / 24,000 glTF vertices per character;
  • gear stays at 1,500 triangles and props at 4,000;
  • committed GLBs and maps only; no Quaternius, Blender or network dependency in CI, build or runtime.

Stage 1 (default)

  1. Import the researched Quaternius GLBs and choose a canonical base.
  2. Map the 62 source joints to the 13-core contract and bake a proportion warp.
  3. Skin the mesh, retarget Idle and Walk, and verify seated/arm-down poses.
  4. Map the five slots, unwrap and bake 256² maps.
  5. Fit the existing gear and render the silhouette gate.
  6. Build the 12 variants, audit, and capture the 12 × 6 baseline.

The Quaternius 62-joint rig and its 24 animation clips are an authoring source. They are not loaded at runtime.

Stage 2 gate

The 62-joint T-pose source does not share the project's arm-down rest pose or proportions. If the Stage 1 proportion warp cannot preserve the Quaternius silhouette and deformation, stop the rollout and write a new ADR for adopting the 62-joint rig at runtime. Do not ship a hybrid rig, a second gear offset system or a silent NpcAvatar change.

Research findings

The researched files are the three converted Quaternius outfits used by the Ducz Character Creator: Casual, Casual_Hoodie and Worker. They are kept locally in tmp/npc-research/quaternius/ with the licence and mirror notes.

File SHA-256 Structure
Casual.glb bc2d84fba249ebaa0629cb9d133ad37234196e4b8004aa205a6397abed04e05c 4 meshes, 62 joints, 24 clips, 7 materials
Casual_Hoodie.glb f5b722199f7628b24550989af4230f65d5230727df9c326fd2b3e360effe5249 4 meshes, 62 joints, 24 clips, 7 materials
Worker.glb 8d0f454336c2effc5c08f63a177af3a82d1a7af025ca9588b40e4c90c4703534 4 meshes, 62 joints, 24 clips, 11 materials

Source: Quaternius Ultimate Modular Characters and Ultimate Modular Women, CC0 1.0. Download date: 2026-11-03 (via the Ducz Character Creator browser export). The Ducz mirror is a browser builder over the same packs; it adds 21 outfits and a shared skeleton. The three full GLBs are the initial scope; the 21-part builder is a fallback for role coverage, not the default.

Initial outfit scope (2026-11-05): the three full GLBs (Casual, Casual_Hoodie, Worker) are the canonical-base and silhouette-gate scope. They cover only ~3 of the 12 roles (casual, hood, vest); the jacket, coat, dress, responder (firefighter) and guard looks are not in the three GLBs, so §4 roster coverage must pull those from the 21-part modular builder or author new clothing. Do not start the modular builder until the canonical base passes §3.

The first prototype measured 12,518 imported vertices, 3,199 after welding and 2,423 vertices / 4,654 triangles after a 0.75 decimate. Idle and Walk read well. The source is a T-pose with a 62-joint realistic rig, roughly 0.96 m hip, 1.68 m arm span and 0.153 m shoulder offset, so a contract bake is required.

The 24,000 cap leaves room to stay much closer to the source LOD than the prototype's 4,654 triangles. Do not decimate to the old 7,000-triangle target before the silhouette gate.

Quaternius GLB inventory (2026-11-05)

All three GLBs share one 62-joint rig and the same 24 animation clips; only the meshes and materials differ. Each import also adds a 42-vert/80-tri Icosphere bone-display helper — ignore it.

Meshes (verts / tris)

GLB Body Feet Head Legs Total
Casual 5,338 / 2,654 1,082 / 538 5,122 / 2,526 1,426 / 706 12,968 / 6,424
Casual_Hoodie 6,132 / 3,048 1,024 / 512 3,836 / 1,888 1,526 / 758 12,518 / 6,206
Worker 6,074 / 3,018 928 / 464 1,862 / 936 1,670 / 822 10,534 / 5,240

Joints (62)

  • Spine: Root, Hips, Torso, Body, Abdomen, Chest, Neck, Head (8).
  • Arms: Shoulder.L/R, UpperArm.L/R, LowerArm.L/R, Wrist.L/R (8) plus PT.L/R (2 pole targets).
  • Fingers: Thumb×3, Index×4, Middle×4, Ring×4, Pinky×4 × L/R (30).
  • Legs: UpperLeg.L/R, LowerLeg.L/R, Foot.L/R (6).

Animation clips (24)

Idle, Idle_Neutral, Walk, Run, Run_Back, Run_Left, Run_Right, Wave, Interact, Death, Gun_Shoot, Idle_Gun, Idle_Gun_Pointing, Idle_Gun_Shoot, Idle_Sword, Run_Shoot, Sword_Slash, HitRecieve, HitRecieve_2, Kick_Left, Kick_Right, Punch_Left, Punch_Right, Roll.

The retarget scope is Idle and Walk; combat/gun/sword clips are discarded (see §2).

Material → slot map

The five project slots (skin, cloth, pants, accessory, hair) are assigned by mesh region first, then material — the same material name can mean cloth on the body and accessory on the feet.

Material Where Project slot
Skin all meshes skin
Hair_Blond, Hair_Brown, Hair, Brown (head), Eyebrows, Moustache Head hair
Eye Head accessory
Worker_Yellow Worker body (vest stripe) + head (helmet) cloth / accessory
White, Grey, Purple, Black Body / Feet cloth (body) / accessory (feet)
Orange, LightBlue, LightBrown, Brown, Brown2 Legs pants
Worker_Vest Worker body cloth

Canonical base (built 2026-11-05)

tmp/npc-research/quaternius_base.glb is the welded, triangulated, grounded, animation-free canonical base built from Casual_Hoodie.glb (the most detailed body) by tmp/npc-research/build_quaternius_base.py:

  • one npc_base mesh — 3,199 welded verts / 6,206 tris (Body + Feet + Head + Legs joined);
  • 62 joints, skinned (no unskinned faces);
  • height 1.81 m, feet at y 0, front on Godot +Z / Blender −Y (T-pose rest);
  • no animation clips; inspect clean (no issues).

This is the §2 contract-bake input: the 62-joint rig is authoring-only and is mapped to the 13-core contract next, not shipped as-is.

62-joint → 13-core joint map (2026-11-05)

The Quaternius hierarchy (measured from quaternius_base.glb):

  • Root → Body → Hips → Abdomen → Torso → Chest → Neck → Head (spine);
  • Chest → Shoulder.L/R → UpperArm.L/R → LowerArm.L/R → Wrist.L/R → fingers (arms);
  • Body → UpperLeg.L/R → LowerLeg.L/R and Root → Foot.L/R (legs — the foot is a separate child of Root, not of LowerLeg);
  • Root → PT.L/R (IK pole targets, no deforming weights).

The contract is 14 bones (13 core + Hips root): Hips, Spine, Neck, Head, Shoulder.L/R, Elbow.L/R, Hand.L/R, Hip.L/R, Knee.L/R. The map:

Contract bone Quaternius source Notes
Hips Hips Root + Body collapse into Hips
Spine Abdomen, Torso, Chest three collapse into one
Neck Neck
Head Head
Shoulder.L/R UpperArm.L/R the collar Shoulder.L/R collapses into Spine
Elbow.L/R LowerArm.L/R
Hand.L/R Wrist.L/R
Hip.L/R UpperLeg.L/R
Knee.L/R LowerLeg.L/R

Optional extras (allowed by the contract; kept if they survive within 24,000 vertices):

  • the 30 finger bones (Thumb×3, Index×4, Middle×4, Ring×4, Pinky×4 × L/R) → optional finger bones;
  • Foot.L/R → optional toe bones.

Dropped (never shipped): PT.L/R (pole targets carry no deforming weights), and the Root/Body/collar-Shoulder collapse bones above.

Rest-pose and proportion-warp measurement (2026-11-05)

The rest-pose bake (T-pose → arm-down) works: rotate the UpperArm.L/R pose bones −90° around their local X, apply the Armature modifier to bake the deformation, then armature_apply to bake it as the new rest pose (see tmp/npc-research/warp_full.py). The mesh deforms cleanly to the arm-down pose (deformed bounds x ±0.221 vs the T-pose ±0.811).

The proportion warp to the contract positions is measured and it distorts the silhouette:

Joint Quaternius Contract Delta
Hips height 0.869 0.62 −0.249 (lower hip)
Head top 1.614 1.82 +0.206 (raise head)
Shoulder x 0.148 0.27 +0.122 (widen)
Arm (shoulder→wrist) 0.402 0.60 ×1.49
Arm (shoulder→hand tip) 0.429 0.70 ×1.63
Leg (hip→ankle) 0.835 0.62 ×0.74
Torso (hip→head) 0.745 1.20 ×1.61

The vision judge reads the warped mesh as: paddle/claw hands (the 1.63× arm stretch blows the hand out), forearms lost into the torso, stretched shins, and "long legs + short torso". The arm must either stay short (a "shortened arm" vs the contract hand) or stretch 1.63× (a distorted deltoid + hand) — both are the gate's rejection conditions.

Finding: the Stage 1 proportion warp cannot preserve the Quaternius silhouette at the contract proportions; the gate fails and Stage 2 (a 62-joint runtime ADR) is indicated. Do not start the 12-variant rebuild on the warped base.

ADR-0018 records the Stage 2 decision (accepted 2026-11-05, owner chose option 2): adopt the Quaternius 62-joint rig at runtime, keep the 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/silhouette gate that chose the full swap is recorded in the follow-up decision of ADR-0018; its comparison sheets were retired with the review pages.

Shipped roster (0.9.9, owner chose the full swap)

Every public character name is baked from one of the three CC0 sources. The build is tools/blender_assets.py build (default --source quaternius); the sources live in assets/models/blender_src/quaternius/ and the geometry pipeline is two passes:

  1. _quaternius_tpose_glb() — import, drop the Icosphere helper, join without welding, repoint the upstream materials onto the five tint slots, Smart-UV-unwrap + repack, export an intermediate T-pose GLB. The round trip also normalises the mirrored upstream bone rolls, which is what makes the next pass symmetric.
  2. _bake_quaternius_rest() — pose UpperArm.L/R -90° local X, read the posed matrices, apply the Armature modifier, reset the pose, write the rest matrices, re-add the modifier, export with export_apply=False.

Then tools/bake_ao.py --only <name>.

Character Source Tris Material slots
npc_casual hoodie 6,206 skin, cloth, pants, hair, accessory + Eye, Eyebrows
npc_jacket casual 6,424 five slots + eyes/brows
npc_coat hoodie 6,206 five slots + eyes/brows
npc_vest worker 5,240 five slots + hivis vest/hat, eyes, moustache
npc_dress casual 6,424 five slots + eyes/brows
npc_hoodie hoodie 6,206 five slots + eyes/brows
npc_firefighter worker (vest + hat dropped) 4,458 five slots + eyes, moustache
npc_guard worker (vest + hat dropped) 4,458 five slots + eyes, moustache
npc_crowd_a casual 6,424 five slots + eyes/brows
npc_crowd_b casual 6,424 five slots + eyes/brows
npc_crowd_c hoodie 6,206 five slots + eyes/brows
npc_crowd_d worker (vest + hat dropped) 4,458 five slots + eyes, moustache

Source slot maps (QUATERNIUS_SOURCES): the casual tee is Skin/White/Orange/Grey/ Hair_Blond; the hoodie is Skin/Purple/LightBlue/Hair/White; the worker is Skin/ LightBrown/Brown+Brown2/Grey+Black/Eyebrows. Everything not in the map keeps its authored colour (eyes, brows, moustache, hivis vest and hard hat), so the contract still holds if upstream adds a material — it shows up as an extra fixed-colour slot instead of vanishing.

Head gear is authored for this head: HEAD_GEAR_SCALE = 0.64 and HEAD_GEAR_LIFT = 0.07 map the 0.315 m procedural head onto the 0.201–0.214 m Quaternius head. The procedural fallback therefore wears slightly small hats.

Modular outfit intake (2026-11-06)

The owner downloaded the Ultimate Modular Men pack once (CC0 1.0, 54.5 MB zip, 15 outfit families plus weapons and a horse) into the gitignored tmp/quaternius-intake/. The pack ships FBX, not GLB, so the intake composes each family into a single skinned base:

python3 tools/quaternius_parts.py make-base \
  --family "tmp/quaternius-intake/Ultimate Modular Men- Feb 2022/Separate Skeletal Meshes and Animations/Suit" \
  --out assets/models/blender_src/quaternius/Suit.glb

What make-base has to undo, and why it is not a straight import:

  1. each of the four FBX files carries its own copy of the armature — one is kept and the meshes are repointed at it, otherwise join leaves three meshes bound to deleted armatures;
  2. the FBX rig adds zero-length _end leaf bones and a neutral_bone root; Godot's importer fails the exported GLB with them (resource_format_binary error), so they are stripped back to the accepted 62 joints;
  3. the object transforms are baked before export, or the glTF node transforms change the rest pose on import and the character renders with a giant head;
  4. the pack reuses one fabric material for jacket and trousers, so the leg meshes get their own *_legs copies that the slot map sends to pants;
  5. imported parts share seams, which leaves zero-area triangles the audit rejects.

The pack itself is never committed. Only the composed bases are, with their SHA-256 here and in CREDITS.md.

Base Source family SHA-256 Used by
Suit.glb Suit 674f2a63… npc_coat
Adventurer.glb Adventurer 062caf69… npc_crowd_c
Beach.glb Beach 81c69837… npc_crowd_a
Casual_2.glb Casual_2 fe4edb73… npc_jacket
Farmer.glb Farmer b9b67058… npc_crowd_b (hair slot patched)
King.glb King 85aae2a0… npc_dress
Punk.glb Punk 56993274… npc_hoodie (the mohawk is the Red material)
Swat.glb Swat 43659684… not used — authored facing the other way; see below

The roster therefore runs eleven distinct outfits across twelve roles (the worker base serves npc_vest, npc_firefighter, npc_guard and npc_crowd_d).

Open: the Swat base faces backwards

Swat.glb is authored facing Godot −Z. Rotating the exported base about world Z rotates the skin with the armature node and the character renders headless, and rotating the mesh as well collapses it into a spike; the reliable fix is to yaw the four FBX parts before the join and re-bake. Until then npc_guard stays on the worker base.

Slots a family cannot paint

Farmer has no hair shell and Swat/SpaceSuit have neither hair nor skin. The audit and the runtime both need the five slots as used materials, so patch_slots buries a 4 cm triangle inside the skull or the pelvis for each missing slot (_patch_missing_slots). It is invisible, it keeps the material in the export, and it means a helmeted head still satisfies the contract.

Contract mismatch

The expensive part is the rig and proportions, not the mesh. The source has a T-pose and a realistic skeleton; the runtime expects an arm-down 13-core rig, fixed gear attach points and the existing pose composition. The Stage 1 bake must therefore:

  • rotate the arms down and match the contract wrist positions;
  • warp the hips, knees, shoulders and head without collapsing the deltoid, shortening the arms or changing the silhouette;
  • keep one skinned npc_base mesh and the five slots;
  • verify gear against the new shoulder, hip and head positions.

If those constraints distort the model, Stage 2 is the correct next decision, not a local workaround.

Licence and provenance

  • CC0 preferred. CC-BY is allowed only with attribution in CREDITS.md.
  • No service asset whose terms restrict redistribution may be shipped.
  • Keep the source URL, author, licence, download date and SHA-256 in this page and in CREDITS.md.
  • Research files and generated previews stay in tmp/ (gitignored). Only final audited assets and committed review sheets enter the repository.
  • Quaternius models and animations are CC0; the existing credits entry already names Quaternius. Keep it accurate if the shipped source set changes.

Closed tracks

The MakeHuman/MPFB body pipeline is closed for the NPC base. The accepted MakeHuman head and the procedural npc_base remain tested fallbacks until the Quaternius rollout is complete. Their research scripts and renders were removed from tmp/ after the pivot; the measurements and decisions remain in DONE.md, MEMORY.md and the review docs.