fix: hide the model's broken outline hull, reverse the backpedal, slower blends

The "linked ankle cuffs" and the "squashed legs" were the same thing, and it
was never the cloth or the weights: it is the model's OWN outline hull.

Isolating it took rendering the raw GLB in a bright scene and toggling one
surface group at a time. The hull ("FullBlack" and "material", the untextured
surfaces that trace the body and hair silhouettes) is a duplicated shell whose
skin weights do not track the base mesh through a deep bend. During a run it
tears into spikes and stretches sheets across the ankles. Hiding just those
surfaces makes the legs render perfectly clean, with no other change — so the
two previous attempts here were both treating symptoms:

  * re-weighting stray ankle vertices only tore the cloth (reverted last time)
  * deleting cross-leg triangles removed real geometry for no benefit
    (SkinMeshRepair is deleted in this commit — the clean render above was
    produced WITHOUT it)

The hull is also redundant: characters already get an inverted-hull overlay
from apply_toon_recursive AND the screen-space ink_edge pass, so dropping it
costs nothing visually. The EYE cards (EyesFullBlack lashes, EyesInvL, EyesHL
highlight) are real facial features rather than a hull, so those are kept, flat
as before. They are told apart by name prefix, not by guesswork about geometry.

Worth recording: no bone is EVER scaled, in any clip. Measured again here
across the whole Run cycle — worst deviation of a bone's pose basis from a pure
rotation is 0.00000. There is no squash-and-stretch in this rig; it only ever
looked that way because of the shell.

Backpedalling now plays the locomotion cycle in reverse (negative TimeScale)
instead of moon-walking with the forward clip — the library ships no authored
backward run. Directional lean is raised from 0.18/0.30 to 0.30/0.42 rad: with
one forward cycle and no strafe clips, the lean is the only cue for which way
the character is travelling, so it has to be legible rather than subtle.

Blends raised again: base 0.22 -> 0.32, locomotion 0.28 -> 0.40. Verified the
cross-fade genuinely applies rather than snapping — instrumented an idle->run
switch and the thigh ramps gradually over the window instead of stepping on
frame one, with xfade_time reading 0.400 s on the Transition node.

FSM tests 11/11, spawn smoke test 0 failures.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-21 22:20:02 -04:00
co-authored by Claude Opus 4.8
parent ec6b8228da
commit 28dc255d17
4 changed files with 48 additions and 180 deletions
+29 -11
View File
@@ -102,13 +102,23 @@ const CHARACTER_INK := Color(0.07, 0.06, 0.09)
## Second pass for IMPORTED CHARACTER models (the anime GLB skins), run right
## after apply_toon_recursive. Two things those models need that props don't:
##
## 1. Their line-work is part of the mesh. Anime GLBs ship an inverted-hull
## outline shell plus eye-line/eye-highlight cards as extra, UNTEXTURED
## surfaces (Taila names them FullBlack / EyesFullBlack / EyesInvL / EyesHL).
## They are authored to read as flat black — or flat white for a highlight —
## and the glTF import hands them a default near-white albedo. Toon-LIGHTING
## that turns every black shell pale: that was the thin white rim tracing
## every hair strand.
## 1. Their line-work is part of the mesh, as extra UNTEXTURED surfaces, and it
## splits into two kinds that need opposite treatment:
##
## * The body/hair OUTLINE HULL (Taila's "FullBlack" and "material") is a
## duplicated shell. Its skin weights do not track the base mesh through a
## deep bend, so during a run it tears into spikes and stretches sheets
## between the ankles — that is what made the ankle cuffs look welded
## together. It is also redundant: characters already get an inverted-hull
## overlay from apply_toon_recursive AND the screen-space ink_edge pass.
## So it is HIDDEN outright, which removes the artefact and the redundancy
## in one go.
##
## * The EYE cards ("EyesFullBlack" lashes, "EyesInvL", "EyesHL" highlight)
## are real facial features, not a hull, and they are kept — flat, because
## the glTF import hands every untextured surface a default near-white
## albedo and toon-LIGHTING the black ones was the thin white rim that used
## to trace every hair strand.
##
## 2. Their textures are ALREADY painted with cel shading. Stacking the hard
## 3-tone break on top read as gloss — a bright stripe sliding across the
@@ -126,13 +136,21 @@ static func apply_character_look(root: Node) -> void:
continue
if src.albedo_texture == null:
# Untextured surface on a character = the model's own line-work.
var name := src.resource_name.to_lower()
var flat := StandardMaterial3D.new()
flat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
flat.cull_mode = src.cull_mode
# "HL" marks a highlight card (the glint in the pupil) — that
# one really is meant to be white.
var is_highlight: bool = src.resource_name.to_lower().contains("hl")
flat.albedo_color = Color.WHITE if is_highlight else CHARACTER_INK
if not name.begins_with("eyes"):
# The outline HULL: hide it (see above). Fully transparent
# rather than deleted so the surface indices, and therefore
# the mesh's own skin bindings, stay exactly as imported.
flat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
flat.albedo_color = Color(0, 0, 0, 0)
else:
# An eye card. "HL" marks the highlight (the glint in the
# pupil) — that one really is meant to be white.
flat.albedo_color = Color.WHITE if name.contains("hl") \
else CHARACTER_INK
mi.set_surface_override_material(s, flat)
continue
var toon: ShaderMaterial = mi.get_surface_override_material(s) as ShaderMaterial