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
+14 -2
View File
@@ -466,7 +466,13 @@ func _load_head(desc: CharacterVisualDescriptor) -> void:
if desc.head_id.is_empty():
return
var path := BASE_PATH + "heads/templates/%s.glb" % desc.head_id
if _attach_to_bone(path, "Head") != null:
# T-1095: tint the template head with the body's skin tone (same treatment
# as seg_head gets via _apply_body_shader) — template heads ship no
# _mask.png sidecar, so pass the full-white mask as the fallback; without
# it the toon_masked recolor_mask defaults black and the tint is ignored
# (the untinted template read grey against mid/dark skin tones).
var tone := SKIN_TONES[clampi(desc.skin_tone, 0, SKIN_TONES.size() - 1)]
if _attach_to_bone(path, "Head", tone["lit"], 0, _white_mask) != null:
_loaded_slots.append("head")
@@ -494,7 +500,11 @@ func _load_eyebrows(_desc: CharacterVisualDescriptor) -> void:
func _attach_to_bone(
path: String, bone_name: String, tint: Color = Color.WHITE, _render_priority: int = 0
path: String,
bone_name: String,
tint: Color = Color.WHITE,
_render_priority: int = 0,
fallback_mask: Texture2D = null,
) -> BoneAttachment3D:
if _skeleton == null:
return null
@@ -520,6 +530,8 @@ func _attach_to_bone(
var mask_tex: Texture2D = null
if ResourceLoader.exists(mask_path):
mask_tex = load(mask_path) as Texture2D
if mask_tex == null:
mask_tex = fallback_mask # T-1095: e.g. full-white for skin-toned heads
var inst := scene.instantiate()
var meshes := _collect_meshes(inst)