Avatar customization: skin tone, head swap, and hair now actually apply
checkpoint / checkpoint (push) Successful in 1m45s

Three dead controls on the customization screen:

- Skin tone slider did nothing: neither the visualizer nor AvatarSystem
  ever read loadout.skin_tone. Added AvatarLoader.apply_skin_tone() -- it
  retints the base body's skin surfaces (matched by the placeholder skin
  albedo, i.e. head + arms) -- and both build paths call it. Slider now
  maps across a deep-to-pale skin range.

- "Cyber Head" did nothing: its catalog entry was type "body_part", which
  load_attachment() rejects, and the placeholder body is one mesh so there
  was nothing to hide either. Retyped it as an attachment -> it mounts on
  the Head bone as a cyber visor.

- Mohawk / ponytail sat inside the head: the Head and HeadTop bones were
  well below the visual head (bone at 1.58, head crown at ~2.03). Moved
  Head to the head's centre and HeadTop just above the crown (mesh is
  rigidly bound, so it doesn't move), and retuned the hair/visor/glasses
  meshes and offsets to match.

Verified: avatar smoke 14/14 (source + pack), fresh-clone, 2p multiplayer,
and drove the real customization screen in rendered screenshots (equip
head+mohawk+mid skin, then ponytail+glasses+pale skin).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
This commit is contained in:
2026-09-06 16:10:46 +00:00
parent 181ba6ae02
commit 43a3b888da
11 changed files with 60 additions and 21 deletions
+17 -13
View File
@@ -25,11 +25,14 @@ const BONES := {
"Spine": ["Hips", Vector3(0.00, 1.00, 0.00)],
"Chest": ["Spine", Vector3(0.00, 1.30, 0.00)],
"Neck": ["Chest", Vector3(0.00, 1.50, 0.00)],
"Head": ["Neck", Vector3(0.00, 1.58, 0.00)],
"HeadTop": ["Head", Vector3(0.00, 2.00, 0.00)],
"Jaw": ["Head", Vector3(0.00, 1.64, -0.16)],
"LeftEye": ["Head", Vector3(0.12, 1.76, -0.27)],
"RightEye": ["Head", Vector3(-0.12, 1.76, -0.27)],
# Head bone sits at the visual head's centre and HeadTop just above its
# crown, so hats / hair / helmets mount where you'd expect. (The body mesh
# is rigidly bound, so moving these bones doesn't move the mesh.)
"Head": ["Neck", Vector3(0.00, 1.74, 0.00)],
"HeadTop": ["Head", Vector3(0.00, 2.06, 0.00)],
"Jaw": ["Head", Vector3(0.00, 1.66, -0.22)],
"LeftEye": ["Head", Vector3(0.12, 1.80, -0.28)],
"RightEye": ["Head", Vector3(-0.12, 1.80, -0.28)],
"LeftShoulder": ["Chest", Vector3(0.42, 1.50, 0.00)],
"LeftUpperArm": ["LeftShoulder", Vector3(0.60, 1.44, 0.00)],
"LeftLowerArm": ["LeftUpperArm", Vector3(0.60, 1.14, 0.00)],
@@ -84,10 +87,10 @@ const PARTS := [
# Attachments: id -> [Mesh, local_offset_from_mount_bone, colour]
# Mesh is built in _attachment_mesh().
const ATTACHMENTS := {
"head_cyber_01": ["head", Vector3(0, 0.04, 0.0), Color(0.20, 0.85, 0.95)],
"hair_mohawk_01": ["mohawk", Vector3(0, 0.0, 0.0), Color(0.90, 0.20, 0.35)],
"hair_ponytail_01": ["ponytail", Vector3(0, -0.06, 0.16), Color(0.45, 0.30, 0.15)],
"face_glasses_01": ["glasses", Vector3(0, 0.12, -0.16), Color(0.10, 0.10, 0.12)],
"head_cyber_01": ["head", Vector3(0, 0.12, 0.0), Color(0.20, 0.85, 0.95)],
"hair_mohawk_01": ["mohawk", Vector3(0, 0.10, 0.0), Color(0.90, 0.20, 0.35)],
"hair_ponytail_01": ["ponytail", Vector3(0, -0.22, 0.24), Color(0.45, 0.30, 0.15)],
"face_glasses_01": ["glasses", Vector3(0, 0.12, -0.07), Color(0.10, 0.10, 0.12)],
"chest_bandit_01": ["torso", Vector3(0, -0.12, 0.0), Color(0.55, 0.20, 0.20)],
"back_backpack_01": ["box", Vector3(0, -0.06, 0.28), Color(0.30, 0.35, 0.25)],
"pants_cargo_01": ["hips", Vector3(0, -0.04, 0.0), Color(0.30, 0.35, 0.45)],
@@ -261,13 +264,14 @@ func _gen_attachment(id: String, kind: String, offset: Vector3, col: Color) -> v
func _attachment_mesh(kind: String) -> Mesh:
match kind:
"head":
var m := BoxMesh.new(); m.size = Vector3(0.60, 0.56, 0.56); return m
# A cyber visor band across the upper half of the ~0.58 head box.
var m := BoxMesh.new(); m.size = Vector3(0.64, 0.30, 0.62); return m
"mohawk":
var m := PrismMesh.new(); m.size = Vector3(0.08, 0.20, 0.44); return m
var m := PrismMesh.new(); m.size = Vector3(0.10, 0.24, 0.50); return m
"ponytail":
var m := BoxMesh.new(); m.size = Vector3(0.14, 0.30, 0.16); return m
var m := BoxMesh.new(); m.size = Vector3(0.16, 0.36, 0.20); return m
"glasses":
var m := BoxMesh.new(); m.size = Vector3(0.44, 0.10, 0.03); return m
var m := BoxMesh.new(); m.size = Vector3(0.48, 0.11, 0.05); return m
"torso":
var m := BoxMesh.new(); m.size = Vector3(0.80, 0.66, 0.42); return m
"box":