docs(skill): the surface table, rig anchors, and asserting the consequence

The skill is this project's own instructions, and three commits made it wrong:
it described a runtime that re-guessed what every surface was, a lab that only
tuned weapon holds, and a suite that could not see any of the last five defects.
It also still listed a seventh character that is not in skins.json.

Records what is new — the surface table and how it is decided, the layered
tuning files and why a fixed rotation on the weapon mount is the wrong answer,
the rig lab — and what the work taught:

  * assert the CONSEQUENCE. Asking a model which clip it is playing reads a
    variable it set on itself, and says "Idle" just as happily when nothing is
    ticking. Asking whether an anchor saved says nothing about whether the gun
    moved.
  * four of the last five real defects came from looking at a PNG. Every one of
    them passed every assertion.
  * the line-work rule now exists twice, at build time and as the runtime
    fallback, and they must be changed together or a model with a surface table
    starts rendering differently from one without.

Also records momo's broken idle and the stray Icosphere under known-unsolved,
with what is worth suspecting first in each case, since neither is fixed.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-27 15:00:42 -04:00
co-authored by Claude Opus 5
parent 99a2ee131d
commit 1e6f3001ac
4 changed files with 199 additions and 21 deletions
@@ -23,10 +23,17 @@ rim term lights it brightly exactly where it is supposed to read as ink.
it flat and unshaded. "Untextured" alone is NOT the test — that made every
flat-coloured model render as a black silhouette, because Quaternius' mannequin
has two untextured materials (a yellow body, lilac joints) and both were hidden
as though they were an outline shell. `_is_line_work()` asks three things
instead: is it named `eyes*`, is it drawn front-face-culled (the classic
inverted-hull setup), or is its albedo near-black. An ink shell is black; a
flat-coloured character is any colour at all.
as though they were an outline shell. The test asks three things instead: is it
named `eyes*`, is it drawn front-face-culled (the classic inverted-hull setup),
or is its albedo near-black. An ink shell is black; a flat-coloured character is
any colour at all.
That test now runs at BUILD time (`tools/surface_map.classify_linework`) and its
answer lives in the sidecar. `SkinSurfaces.guess()` is the same rule kept as the
runtime fallback, for a model with no surface table — and the cull-mode half of
it is re-run at runtime even when the table exists, because glTF has no way to
say "draw only the backfaces" and an inverted hull cannot survive the round trip
as a cull mode. Blender genuinely cannot see it; Godot can.
What it then does:
@@ -37,9 +44,29 @@ What it then does:
with `HL` in the name, which is the glint in the pupil and really is white.
If a newly imported character comes out with a white halo, a black silhouette,
or black eyes that should have irises, `_is_line_work()` and the name-matching
below it are where to look. The conventions vary by source and this is the one
place they are read.
or black eyes that should have irises, the line-work test and the name-matching
below it are where to look — now in `tools/surface_map.py`, mirrored by
`SkinSurfaces.guess()`. **Change both or neither**: a model with a surface table
would start rendering differently from one without.
## Per-class art direction
Because each surface says what it is, each class takes its own numbers
(`LevelMaterials.CHARACTER_LOOK`). `body` is deliberately identical to what every
surface used to get, so the calibration this was all built on does not move. The
others are departures, each for a reason:
| Class | Outline | Band | Why |
|---|---|---|---|
| body | 5.0 mm | 0.16 | unchanged — the baseline |
| cloth | 5.8 mm | 0.13 | a garment's silhouette is most of what separates a character from the background at range; folds need a defined terminator to read as fabric |
| hair | 3.4 mm | 0.20 | **the one that matters.** A hair mesh is dozens of near-parallel strands millimetres apart; at the body's 5 mm each strand's hull swallows its neighbour and the head reads as one solid dark cap |
| accessory | 6.8 mm | 0.10 | small, rigid, usually the most saturated thing on the character — meant to pop |
This required moving the outline from `material_overlay` on the INSTANCE to
`next_pass` on each surface's material. Miku's body, face and hair are three
surfaces of one mesh, so an instance-wide overlay can only ever give all three
the same weight.
Taila's eyes still render as black cards rather than amber irises. Her eye
surfaces are untextured, and the glTF import hands every untextured surface a