feat(characters): import the Quaternius mannequin as a selectable skin

A third playable character, built with the pipeline skill from a source
that was already in the repo: the animation library ships a rigged
Mannequin mesh on the exact 53-joint reference skeleton, CC0, so it needed
no download and retargets perfectly. 18 clips, 0.3% cross-leg bleed, 7% of
verts at four influences — a clean authored-weight import. Licence
recorded in mannequin.license.json as the other skins do.

It has no cloth chains, correctly: it is a mannequin and has neither hair
nor clothes.

Importing it turned up two real bugs, both of which would have hit any
flat-coloured or single-piece model:

- LevelMaterials.apply_character_look treated ANY untextured surface on a
  character as the model's own outline shell and hid it, so the mannequin
  rendered as a solid black silhouette — its body and joint materials are
  untextured flat colours, not ink. _is_line_work() now asks whether the
  surface is named eyes*, is drawn front-face-culled (the inverted-hull
  setup), or is near-black. Taila and Miku are unaffected: their materials
  are textured and never reach that branch. Verified by render.

- verify_character.py failed the build for having one mesh. That check
  cannot tell "the pipeline joined them" from "the artist authored one
  mesh" — Quaternius' mannequin is one piece on purpose. It is advisory
  now; the join path's two unambiguous signatures, cross-leg bleed and the
  4-influences-everywhere spread, are still hard checks.

Also restored Miku's description, which the re-import had blanked.

3 GLB skins selectable (6 with the built-in colour skins). Smoke 0
failures, 11/11 movement tests, cloth idle 0.024-0.078 deg/frame.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-26 13:25:26 -04:00
co-authored by Claude Opus 5
parent 270d5f0973
commit cd0d1b2d99
10 changed files with 1046 additions and 24 deletions
+28 -2
View File
@@ -126,6 +126,32 @@ const CHARACTER_INK := Color(0.07, 0.06, 0.09)
## almost-invisible second step so the painted shading carries the form.
##
## Props and level geometry keep the crisp banding they were calibrated with.
## Is this untextured surface part of the model's own DRAWING, or is it just an
## untextured surface?
##
## "No albedo texture" alone is not the question, and answering it that way made
## every flat-coloured model render as a black silhouette — Quaternius' mannequin
## has two untextured materials, a yellow body and lilac joints, and both were
## being hidden as though they were an outline shell.
##
## What actually distinguishes line-work:
##
## DARK an ink shell or a lash card is black or nearly so. A flat-coloured
## character is any colour at all. This is the discriminator that
## does the work.
## INVERTED the classic inverted-hull outline is drawn front-face-culled so
## only its backfaces show. Nothing else on a character is.
## NAMED eye cards say so — they are kept, not hidden, and need to reach
## the branch below whatever colour they are.
static func _is_line_work(src: BaseMaterial3D) -> bool:
if src.resource_name.to_lower().begins_with("eyes"):
return true
if src.cull_mode == BaseMaterial3D.CULL_FRONT:
return true
var c: Color = src.albedo_color
return maxf(maxf(c.r, c.g), c.b) < 0.18
static func apply_character_look(root: Node) -> void:
for mi in root.find_children("*", "MeshInstance3D", true, false):
if not mi.mesh:
@@ -134,8 +160,8 @@ static func apply_character_look(root: Node) -> void:
var src: BaseMaterial3D = mi.mesh.surface_get_material(s) as BaseMaterial3D
if src == null:
continue
if src.albedo_texture == null:
# Untextured surface on a character = the model's own line-work.
if src.albedo_texture == null and _is_line_work(src):
# Untextured AND dark or inside-out — the model's own line-work.
var name := src.resource_name.to_lower()
var flat := StandardMaterial3D.new()
flat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED