feat(client): character creator overhaul — manifest, segments, eye color

Major changes from runtime testing and iteration:
- Asset manifest (manifest.json) controls all available content —
  replaces filesystem scanning, solves DirAccess/PCK export issue
- Dynamic tabs: only show tabs with manifest content
- Body segmentation: face-based exclusive assignment, hips split from
  torso, torso_upper independent (not a variant)
- Eye color with iris-only mask from T_Eye_Split.png green channel
- Eyebrows tinted with hair color (from body segment, not separate GLB)
- Hair/clothing loaded as skinned meshes on shared skeleton
- Segment hiding disabled for clothing (solidify handles coverage)
- Gender-aware randomizer (no facial hair on female/teen)
- Clothing slots hidden when empty in manifest
- UI: color docks docked to bottom on all tabs, padding, flow layout
- Debug tab with per-segment visibility toggles
- Screenshot automation with test config JSON

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 17:42:33 +01:00
co-authored by Claude Opus 4.6
parent 8218d5a942
commit 4d9840b68f
4 changed files with 604 additions and 135 deletions
@@ -85,6 +85,9 @@ var facial_hair_tint: Color = Color.WHITE
var eyebrow_id: String = ""
var eyebrow_tint: Color = Color.WHITE
## Eye color
var eye_color: Color = Color(0.45, 0.3, 0.15) # brown default
## Skin tone index 0–8 into the 9-tone palette (pale_cool → very_deep_cool)
var skin_tone: int = 0
@@ -130,6 +133,7 @@ static func from_dict(data: Dictionary) -> CharacterVisualDescriptor:
desc.facial_hair_tint = _decode_color(data.get("facial_hair_tint"), Color.WHITE)
desc.eyebrow_id = data.get("eyebrow_id", "")
desc.eyebrow_tint = _decode_color(data.get("eyebrow_tint"), Color.WHITE)
desc.eye_color = _decode_color(data.get("eye_color"), Color(0.45, 0.3, 0.15))
desc.skin_tone = clampi(data.get("skin_tone", 0), 0, 8)
desc.clothing_slots = data.get("clothing_slots", {})
desc.clothing_tints = _decode_tint_map(data.get("clothing_tints", {}))
@@ -150,6 +154,7 @@ func to_dict() -> Dictionary:
"facial_hair_tint": _encode_color(facial_hair_tint),
"eyebrow_id": eyebrow_id,
"eyebrow_tint": _encode_color(eyebrow_tint),
"eye_color": _encode_color(eye_color),
"skin_tone": skin_tone,
"clothing_slots": clothing_slots,
"clothing_tints": _encode_tint_map(clothing_tints),