Files
settled-reach/tooling/domains/character/GARMENT_QA.md
T
jpmschweitzerandClaude Opus 5.5 26cc8de7f3 refactor(tooling): T-1290 — the character domain, and six payloads the map misfiled
`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>
2026-09-23 19:55:46 +02:00

2.7 KiB
Raw Blame History

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 capture fails when its largest connected clip blob ≥ --min-pixels (default 8, tolerating AA edges). clip_through_failures + the by_group clipfail/worstClip columns 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.