docs+fix: sidequest close-out — T-1095 head tint, design doc, wardrobe docs, changelog (T-1088/T-1089)

T-1095 fixed: template heads now receive the body's skin tone (same
treatment as seg_head) — _load_head passes tone.lit with a full-white
fallback mask through _attach_to_bone; without a mask sidecar the tint was
silently ignored, so untinted templates read grey against mid/dark tones.
Verified by lookbook render (head_001 on child, tone 6 — face matches arms).

Docs: the T-1088 locomotion design document committed to docs/design/ (the
sandbox code's 'design §' references now resolve in-repo); character-asset-
organization gains §11 Wardrobe Pipeline and sheds three stale claims (the
never-built indexed-greyscale mask plan, single-mask-per-asset for clothing,
the resolved multi-region open question); CHANGELOG carries the sidequest
entry; D-250 amended with the shipped cutaway-modes verdict (mode 2 default,
user-decided live).

Tickets: T-1088, T-1089, T-1095 closed; T-1096 filed (S9 live feel-tuning,
needs the user). The Fable-5 cascade exception is closed — the cascade
resumes at Phase 4 (T-750).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 14:15:56 +02:00
co-authored by Claude Fable 5
parent e709462a7b
commit 3d6dcc9d05
8 changed files with 684 additions and 14 deletions
+111 -12
View File
@@ -444,9 +444,14 @@ Convention: `<asset_name>_mask.png` as a sidecar file next to the GLB.
- `hat_hardhat.glb` + `hat_hardhat_mask.png`
- For clothing, the mask lives in the item directory: `coveralls_basic/reference_mask.png`
Masks are per-asset, not per-variant. The same mask applies to all 11 body-type variants of a
clothing item because the mask regions (collar is region A, sleeves are region B) correspond
to UV regions, and the UV layout is preserved through Surface Deform fitting.
Masks are per-asset, not per-variant, for hair/facial hair/eyebrows/accessories/heads -- one
mask applies across all 11 body-type variants because the mask is UV-space and the UV layout
is preserved through fitting.
**Superseded for clothing (T-1089, see Section 11):** offset-shell garments now author a mask
**per body type** (`<body_type>_mask.png`, that body's own UV0 layout), falling back to
`reference_mask.png` only for the Surface-Deform-fit route (suit, uniform, Fantasy-derived
cuts), which still shares one mask across variants as described above.
### Head Templates
@@ -567,13 +572,17 @@ For every GLB that supports recoloring, a `_mask.png` sidecar sits next to it:
- **Format:** PNG, same UV layout as the GLB's texture
- **Channels:** Greyscale (R channel only). White = tintable region, black = preserve original
- **For multi-region recoloring:** Use distinct greyscale values per region (e.g. 0.0, 0.33,
0.66, 1.0 for up to 4 independent tint regions). The shader samples the mask and routes
each region to a different `tint_color` uniform. This is not yet implemented in the shader
but the mask format supports it.
- **Default:** `hint_default_black` in the shader means "no mask loaded = no tinting."
Assets without masks render with their original Trellis/authored texture.
**Multi-region resolved differently than proposed here (T-1089, see Section 11).** The
distinct-greyscale-band idea above (0.0/0.33/0.66/1.0 in one channel) was never built --
indexed greyscale bilinear-filters badly at region boundaries (a filtered sample between two
index levels reads as a wrong third region, not a blend). Clothing multi-region instead
channel-routes an **RGBA mask** (R/G/B/A -> 4 independent tints, `toon_garment.gdshader`),
so bilinear filtering cross-fades neighboring regions correctly. This is clothing-only; the
single-channel binary convention above is unchanged for hair/facial hair/eyebrows/accessories.
### Surface Deform Batch Output
The Blender batch pipeline takes one reference clothing mesh and produces 11 fitted variants.
@@ -639,6 +648,7 @@ CharacterVisualDescriptor:
skin_tone: int # index into the 9-tone palette
clothing_slots: Dictionary # slot -> item_id (e.g. "torso" -> "coveralls_basic")
clothing_tints: Dictionary # item_id -> Array[Color] for multi-region recolor
clothing_logos: Dictionary # item_id -> brand id (logo_capable items only; T-1089, Section 11)
accessory_slots: Dictionary # slot -> item_id (e.g. "hat" -> "hat_hardhat")
accessory_tints: Dictionary # item_id -> Color
```
@@ -726,11 +736,11 @@ What does NOT carry forward:
## 10. Open Questions for Review
**Q: Multi-region recolor masks -- implement now or defer?**
The current `toon_masked` shader supports single-region (binary mask). Multi-region requires
a shader update to sample distinct greyscale bands and route them to separate tint uniforms.
Recommendation: defer to when the first multi-region clothing item is authored. The mask PNG
format supports it already (greyscale values), so no re-export is needed later.
**Q: Multi-region recolor masks -- implement now or defer?** **RESOLVED (T-1089, Section 11).**
Deferred until the first multi-region clothing item (the t-shirt) needed it, then implemented
as an RGBA channel-routed mask + `toon_garment.gdshader` -- not the greyscale-band shader
update this note anticipated (see the corrections in Sections 3 and 5 above). `toon_masked`
is retained for single-region assets (hair, accessories).
**Q: Teen clothing variants -- separate catalogue or shared with adult?**
Teen body types use the same clothing system (Surface Deform fitting) but teen proportions
@@ -741,6 +751,94 @@ revisit if visual quality is insufficient.
---
## 11. Wardrobe Pipeline (T-1089)
The clothing engine that shipped the first real catalogue (2026-07-06/07). This section
supersedes the single-region greyscale mask model assumed in Sections 3/5/10 above for
**clothing specifically** -- hair/facial hair/eyebrows/accessories/heads keep the
single-channel convention as originally written.
### Authoring: offset-shell from our own body segments
The preferred authoring route is **offset-shell**
(`tooling/garment-fit/blender_author_offset_shell.py`, one per-garment companion script per
item, e.g. `blender_author_hoodie.py`, `blender_author_boots.py`): extract the body segment(s)
a garment covers, cut neckline/hem/sleeve boundaries via bone-plane thresholds, offset the
surface outward along vertex normals, solidify for real cloth thickness. Because the shell
*is* body topology, bone weights are inherited by construction -- no Surface Deform, no re-rig.
**PER-BODY mode (`--per-body`) is now the standard for offset-shell garments**, not
single-reference-then-fit. Q-060 evidence: single-reference Surface-Deform fitting degraded
with girth divergence (muscular_m hit an 859px worst clip at 24mm standoff on the
reference-authored t-shirt); authoring per body guarantees clearance + exact weights by
construction and lets the offset drop back to the ~12mm ideal. Cut/mask thresholds are
derived from each body's own bone landmarks using the proportional ratios that reproduce the
hand-calibrated reference constants -- recomputed per body, not copied.
Shared geometry practices proven across garments: **boundary weld** (`weld_boundaries` --
merges coincident segment-boundary verts so the offset can't open cracks) and **open-rim
flattening** (`flatten_open_rims` -- pulls the jagged segment-splitter boundary rings onto
clean planes before the offset runs; first required by jeans and the track jacket).
**`convex_toe_box`** (shared helper, used by boots/sneakers/shoes_formal) reshapes the
forefoot into one smooth per-slice ellipse cap so individual-toe geometry doesn't poke the
shoe shell -- residual toe hints are accepted for first release (see Catalogue below).
### Fitting: Surface-Deform batch route
`tooling/garment-fit/blender_batch_fit_skinned.py` remains the route for garments sharing one
reference mesh + mask across all 11 bodies -- Fantasy-pack-derived cuts and hand-authored
pieces on a single UV layout (the suit, the uniform). SD-bind to each target body, bake the
deformed rest shape, transfer + normalize weights, retarget the armature, export with
**`export_skins=True`** (the predecessor script's `export_skins=False` output could not
animate on the shared skeleton -- why it was superseded, not merely extended).
### Runtime contract
- **`coverage.json`** per clothing item: `hides` (segment names to hide), `torso_variant`
(`full`/`upper`), and the new **`multi_region`** flag (routes the item through
`toon_garment.gdshader` instead of the single-tint `toon_masked.gdshader`).
- **Per-body masks:** `<body_type>_mask.png` sits beside each body's fitted GLB; the
compositor prefers it and falls back to `reference_mask.png` for the Surface-Deform-fit
route. See the mask-convention correction in Section 3.
- **`toon_garment.gdshader`** (`client/assets/characters/shaders/`): channel-routes FOUR tints
through an RGBA region mask (R/G/B/A -> `tint_0`..`tint_3`), channel-blended rather than
indexed-greyscale (see the correction in Section 5). A logo decal samples a dedicated UV2
(TEXCOORD_1) chest channel, composited **after** region tinting (brand colors stay
faithful) but **before** the toon shadow (the logo darkens with its fabric).
- **`CharacterVisualDescriptor.clothing_logos`:** `item_id -> brand id`, per-character logo
choice (Section 6), alongside `clothing_tints` (`item_id -> Array[Color]`, one per region).
- **Manifest clothing schema** (`client/assets/characters/manifest.json`, `clothing` key):
`slot`, `style` (`modern`/`formal`/`utility`/... -- the D-251 style-tag seam), `logo_capable`,
and for multi-region items `regions` (named R/G/B/A) + `default_tints`.
- **`outfits.json`:** named complete looks (`presets`) composing manifest items with per-item
tints/logos -- 12 presets (casual/business/sport/beach/worker/sleep) for NPC dressing and
shop display.
### QA: chromakey acceptance gate
`tooling/garment-qa` (`run-garment-qa <config>`) is the acceptance gate every garment in this
pipeline runs before it ships: a two-pass depth-proximity chromakey renders the same frozen
frame twice -- once keying claimed-covered body segments magenta, once with the garment
nudged a few cm toward the camera and re-keyed cyan. A body-key pixel that flips from magenta
to cyan is a genuine clip (`clip_through_pixels`, gates on connected-component size); a pixel
that never flips despite being exposed is informational (`exposed_skin_pixels` -- collars,
sleeveless armholes). Wave 1/2 garments were accepted at <=150px worst clip component.
`client/tools/garment_preview/lookbook_scene` renders the full catalogue for visual review --
the pass that caught the missing-albedo and manifest-nesting findings fixed in `e562418d0`.
### Current catalogue
24 entries: 21 modern (tops, bottoms, feet, full-body, swim -- wave 1 + wave 2 + the
fill-in-the-blanks addendum) + 3 legacy peasant pieces (pre-dating this pipeline, still on
the Surface-Deform-fit route). All 24 fitted across all 11 body types.
**Known artifact classes, accepted for first release:** sleeve/opening-hem epsilon blobs
(offset-shell boundary artifacts, offset-insensitive); residual toe hints on closed footwear
after `convex_toe_box` ("I still see some toes, but this will do" -- user review, 2026-07-07).
Further smoothing is polish backlog, not open scope.
---
## Decisions Referenced
- **D-159:** 5 adult body types x 2 genders + 1 child = 11 base meshes (updated: added Teen)
@@ -749,3 +847,4 @@ revisit if visual quality is insufficient.
- **D-162:** Clothing is pre-baked per body type via Surface Deform
- **D-163:** Trellis generates heads via BoneAttachment3D (offline asset production, curated into template library)
- **D-164:** Fork Quaternius skeleton, replace all body meshes (Source tier .blends as starting point)
- **D-251:** Character asset route reconfirmed 2026-07-06 -- Quaternius rig stays, wardrobe is in-house (offset-shell + Surface-Deform routes, Section 11), UAL1/UAL2 animation tiers purchased