`reach character {logo, strip-glb, qa, qa-analyze}` replaces make_logo.py,
glb_strip_utility_nodes.py, analyze_captures.py and the run-garment-qa bash
driver. The QA configs and method doc move beside the domain (qa_configs/,
GARMENT_QA.md), and `qa` takes a config name (`reach character qa hoodie_modern`)
or a path.
Parity, from baselines taken before anything moved:
- the logo PNG is byte-identical
- a synthetic GLB with three real utility nodes strips to identical bytes
(the committed bodies strip 0 nodes, so they proved nothing)
- re-analyzing a cached capture set gives a byte-identical report.json and
summary
run-garment-qa is rewritten, not wrapped (D-263). Its decisions — which config,
which Godot ($GODOT, then ~/bin/godot4, then PATH), and whether xvfb-run is
needed — are capture_plan(), pinned by tooling/test_character.py without
launching Godot. The bash exit codes are kept: 2 for a missing config, 3 for
no Godot.
The T-1271 domain map was wrong about this domain. Six of its ten files import
bpy: convert_outfit, inspect_glb, check_hair_symmetry, check_icosphere,
render_quaternius_test and test_quaternius_raw. They are Blender payloads and
joined the carve-out as blender_* (41 payloads now). The 22 existing payloads'
docstrings still cited tooling/garment-fit/ from before T-1273; fixed.
Archived, with reasons in tooling/archive/README.md:
- setup_clothing_metadata.py wrote coverage data for five garments that no
longer exist in the 24-garment wardrobe
- wipe-bodies.sh ran raw DELETEs on systems.db
segment_reference_distribution.md moved to docs/assets/visual/.
Behaviour changes:
- The QA analyzer exited 0 whatever it found, though its own README says
clip-through "is the real defect and it gates". qa and qa-analyze now exit 1
on clip-through, and the remedy names --min-pixels (Wave 1/2 were accepted
at 150). The cached peasant set has 33 failures at the default 8 px.
- glb strip re-reported the same nodes as stripped on every re-run and
rewrote an unchanged file: it left them as orphans and then found them
again. Only nodes still linked into the graph count now, and a first pass
writes the same bytes as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.7 KiB
garment-qa — chromakey garment-clipping QA (T-1089)
Automatically detects clothing clip-through with a two-pass depth-proximity
chromakey. Per view (clips × sampled frames × 4 camera yaws), the capture scene renders
the SAME frozen animation frame twice: pass A paints the body segments a garment
claims to cover (coverage.json hides) flat unshaded magenta, garment + head/hands
normal; pass B is identical except the garment is flat cyan and every garment
vertex is nudged clip_epsilon_m (default 3 cm) toward the camera. A body-key pixel
that is magenta in A but cyan in B means the ε-shifted cloth now covers it — the body sat
within ε in front of the cloth = poking through. This separates a true clip from a limb
merely crossing in front of the torso, an open collar, or a bare arm over background
(all of which stay magenta because the cloth behind them is > ε away).
Run: reach character qa [config] — a name from qa_configs/ (peasant, the
default, hoodie_modern, …) or a path. It launches Godot ($GODOT, else ~/bin/godot4,
opengl3, xvfb-run when headless) on the capture scene, then runs the analyzer.
Re-analyze existing captures without re-rendering: reach character qa-analyze --config …
(or --dir). Both exit 1 when any capture clips through — the gate the text below
describes; before T-1290 the analyzer exited 0 whatever it found.
Config (path passed to the scene via GARMENT_QA_CONFIG): garments
({item_id,slot} catalogue items, or {glb,covers[]} for a raw WIP garment),
body_types, clips, frames_per_clip, yaws, head_id/hair_id/eyebrow_id/skin_tone,
out_dir.
Read the report: <out_dir>/report.json — the same summary prints to stdout. Two
metrics per capture:
clip_through_pixels/largest_clip_component— key pixels the ε-shifted cloth covers (skin ≤ ε in front of cloth). This is the real defect and it gates: a capturefails when its largest connected clip blob ≥--min-pixels(default 8, tolerating AA edges).clip_through_failures+ theby_groupclipfail/worstClipcolumns roll it up.exposed_skin_pixels— key pixels the shift did NOT cover (skin well in front of any cloth: collar/sleeve/ankle gaps, a limb crossing the torso). Informational; does not gate.
Tune sensitivity with clip_epsilon_m in the config (smaller = only tighter pokes count)
and --min-pixels on the analyzer. Failing/flagged frames are copied to
<out_dir>/failures/*_HL.png with exposed_skin lime, clip_through filled red, and
each clip blob boxed.
Note: the Godot scene lives at client/tools/garment_qa/chromakey_scene.gd (not here)
because Godot res:// paths cannot leave the client project root.