feat: clothing lane, character sources, and DCC bridges

Bulk import of the working lanes that were living untracked on the PC.

Content:
- characters/  Lena/male body lanes, bakes, texture work, run logs
- clothing/    garment pipeline, configs, gates, contract docs
- garments/    MD-authored garment sources (.zprj/.zpac)
- UAL-Lib/     Universal Animation Library 2 source (.blend/.fbx/.glb)
- tools/       blender_bridge, iclone_bridge, md_bridge, tailor, glm_agent
- docs/, plans/, dev/, .agents/plans/

Repo hygiene:
- .gitattributes: LFS now covers .blend, .zprj, .zpac, .obj, .npy and the
  Reallusion .iAvatar/.ccAvatar/.ccRestore containers. Without this the
  ~3.8 GB in this commit would land as raw blobs. .png/.jpg are left out
  on purpose — ~250 are already tracked raw and converting them would
  rewrite every one without shrinking history.
- .gitignore: exclude /accurig/ (~1 GB AccuRig program files, redistributable
  from Reallusion, nothing authored here) and /dev/null/ (git-lfs hook copies
  dropped by a `>/dev/null` redirect on Windows).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 15:55:43 -07:00
parent 3363209cac
commit 3ba86b2ea8
558 changed files with 68622 additions and 8 deletions
+169
View File
@@ -0,0 +1,169 @@
# Plan v3: seated hearth dance (nd_02) — seated pose + salsa upper-body sway
**Status:** reviewed (independent Opus pass, GLM-4.6 API pass, Fable self-review),
awaiting Jeremy's go/route-confirmation · **Author + PC executor:** Fable 5 session 2026-07-21 (via
TinqsBridge) · **Mac-side steps:** later session
## Goal
A looping dance for a **hearth ceremony** — villagers seated around the hearth,
**seated the entire time** (lower body pinned to Jeremy's seated pose), **upper body
swaying through 4 Mixamo salsa clips**. Registry id: provisional **nd_02** (pack
`nd_02`, clip `ND02`, loop pair `nd_02_pp`/`ND02PP`); ceremony adoption is Jeremy's
call later (no hearth slot exists in the DanceType enum — closest candidate is
`fire_dance_01`, NOT assumed).
## §0 Inputs (confirmed by Jeremy 2026-07-21)
1. **Seated pose:** `A:\Reallusion Content\Reallusion Custom\Animation\Motion\External Motion\Male_Sitting_Pose.rlMotion` (39 KB — likely a single-frame pose; see Phase 1.2).
⚠ Provenance check at first eyeball: hearth circle implies **ground-seated**; if this
pose is chair-height the pelvis will float in-game.
2. **Sway sources:** the 4 Mixamo FBX in `C:\Users\Jeremy\Downloads\`:
`Salsa Dancing.fbx` (1.05 MB), `Salsa Dancing (1).fbx` (2.62 MB),
`Salsa Dancing (2).fbx` (1.84 MB), `Salsa Dancing (3).fbx` (0.55 MB).
3. **Avatar (for the seated export only):** any stock CC male.
4. **Length target:** 2030 s @ 60 fps overall; ~57 s per salsa segment.
## Route decision (changed in v2 after review)
**Primary is now Route B**: retarget the salsa FBX directly with the pipeline's own
`mixamo_retarget.py` (purpose-built, proven on `northern_soul`/`run_to_dive`) and
combine with the seated pose via the game-side **upper/lower mask composite**,
pre-baked to a GLB. iClone's role shrinks to its one irreplaceable contribution:
exporting Jeremy's seated pose as a clip (via the bridge).
Why (review consensus): Route A put two **unprobed** RLPy behaviors on its critical
path (`LoadMotion`-with-FBX — which even if it loads may skip Mixamo→CC
characterization and produce garbage — and `ExportFbxFile` option coverage), added a
lossy Mixamo→CC→game double-retarget plus the A-pose-bind risk, and then threw away
its own lower body at bake time. Route B is one proven retarget per source, iterates
via hot-reload JSON in the test bed (~0.5 s) instead of a full PC→Mac round-trip, and
gets beat-quantized transitions for free. Route A survives as fallback (§Route A) if
the salsa arm styling retargets badly onto the game skeleton.
**The lower-body guarantee moves into the composite bake itself** — the baked clip's
lower-mask tracks come from the seated clip and nowhere else. The separate
`lock_lower_body.py` from plan v1 is **dropped** (it only existed to repair Route A's
full-body take).
## Route B (primary)
### Phase 0 — salsa files into the repo (me, PC, now)
1. **Move** (not copy) the 4 FBX from Downloads into **`exchange/INBOX/`** (the repo's
existing external-input drop, alongside `lena_tpose_legs5_input.fbx`), renamed
`Salsa_Dancing_01.fbx``Salsa_Dancing_04.fbx`.
*Note:* plan v1 sent these to the Reallusion content folder on A:; review blocker —
the Mac retarget step can only see files that are **in the repo**. `.fbx` is
LFS-tracked here, so a normal commit + `tinqs push` carries them.
2. Commit (explicit paths only, co-author line
`Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>`), `tinqs push`.
### Phase 1 — seated clip out of iClone (me, PC, via bridge)
1. Launch check: `--ping`; if iClone is closed, start
`A:\Program Files (x86)\iClone 8\Bin64\iClone.exe`, poll ping (~6090 s).
Load a stock CC male (`RLPy.RFileIO.LoadFile`), apply
`LoadMotion(Male_Sitting_Pose.rlMotion, t=0, avatar)` — **check the returned
RStatus** (don't assume success), and verify a clip landed on the motion track.
2. **Ground-seat precheck (GLM review):** after applying the pose, read the pelvis/hip
world Y via the bridge and log it. Chair-height ≫ ground-height — if it reads like
a chair sit, stop and show Jeremy before wasting the export.
3. **Hold the pose ≥300 frames** (5 s @60): set the play range so the export is a real
take. Verified against code: `cc_retarget.py:104` rejects only `<5`-frame
`*TempMotion` takes (frame count, not motion content) — a 300-frame static hold
passes. Extend by re-placing the pose key at the range end (or Jeremy sets the clip
length in the timeline — one drag).
3. Export FBX → `exchange/incoming-fbx/seatbase1.fbx`. Probe
`RLPy.RFileIO.ExportFbxFile(*args)` for: Target Tool Preset **Blender**, 60 fps,
Range = All, **Include Motion ON**, Preserve Bone Names. This is the only
ExportFbxFile use left in the primary route — if the API can't reach the options,
**Jeremy clicks the export dialog once** and the route loses nothing.
4. Sanity: file is multi-MB-ish and ≥300 frames. Commit + `tinqs push` (etiquette as
Phase 0.2).
5. RTime/RFps handling: build times with RTime constructors from the stub; get numeric
fps via RFps methods, never repr.
### Phase 2 — Mac side: retarget everything (later session / handoff)
0. *Cheap check first (Path 1):* scan kevin/UAL packs for an existing ground-seated
idle — if one matches Jeremy's intended look better than the iClone export, offer
the swap at eyeball time; Jeremy's pose remains the default.
1. `mixamo_retarget.py` on the 4 salsa FBX →
`assets/quaternius/mixamo/salsa.glb` (clips `Salsa01..04` — batch mode per the
kevin precedent). **Read back the GLB's `animations[].name` immediately** and use
those exact strings in every later JSON/bake reference — don't assume the intended
names survived the batch tool (GLM review).
2. `cc_retarget.py` on `seatbase1.fbx``exchange/converted-glb/seatbase1.glb`, clip
`SeatBase1`. First-export QA: **A-pose arm check** in the showcase.
3. Copy `salsa.glb` into ariki-game (`assets/quaternius/mixamo/`), `seatbase1.glb`
`dancegen/`.
### Phase 3 — Mac side: composite, bake, loop, QC
1. **Iterate live first**: `nd_02.json` in `assets/dances/` with 4 chained moves, each
`{layers: [{mask:"upper", clip:"salsa/SalsaNN"}, {mask:"lower", clip:"seatbase1/SeatBase1"}]}`
— hot-reloads in the dance test bed (**ask Jeremy before launching the game**).
Tune segment order/durations/bpm here where a change costs 0.5 s.
2. **Pre-bake the approved composite to a GLB** (registry requires a baked pair):
generalize `tools/bake_run_punch.mjs` into a parameterized composite baker
(`tools/bake_composite.mjs`): inputs = ordered (upper-clip, duration) list + lower
clip; mask predicate **verbatim** from `bake_run_punch.mjs:103`
(`root || pelvis || /thigh|calf|foot|ball/i`); output `exchange/converted-glb/nd_02.glb`
clip `ND02`. **Verify gate built in:** assert every lower-mask bone track in the
output matches the seated clip's pose within an epsilon (per-channel max deviation,
e.g. 1e-3 — resampling/quantization makes strict equality false-fail; GLM review)
— exit nonzero otherwise (lesson from the boat prop: agent-authored gates must
assert what the eyeball would catch).
3. Loop pair: `pingpong_bake.py``nd_02_pp`/`ND02PP`. **Caveat (GLM review):**
ping-ponging a 4-segment composite plays A→B→C→D→D→C→B→A — the reversal is exposed
across *distinct* segments, not one homogeneous sway; it may read oddly even with a
clean seam. Mitigations, judged at the test-bed eyeball: bookend the bake with short
seated-base (arms-quiet) segments so `loop_fix.py`'s natural-loop trim becomes
viable, or reorder segments so the reversal lands on the most symmetric one. Both
variants must pass `loop_qc.py` (exit 0); nd_01 set the 0.00-seam bar.
4. Ship: GLBs + `.import` sidecars into ariki-game, final `nd_02.json` pointing at the
baked `nd_02/ND02` (+`_pp`), `game.sh import`, verify GLB `animations[].name ==
"ND02"`, registry row: origin "seated hearth sway — upper Mixamo salsa ×4, lower
iClone Male_Sitting_Pose (seatbase1)". ariki-game commits stay local.
## Route A (fallback only — if salsa retargets badly onto the game skeleton)
Assemble in iClone instead: import the salsa FBX **via the UI with the Mixamo
characterization profile** (the route that produced `Talking_mixamo_com.rlMotion`;
`LoadMotion(fbx)` may be probed as an optimization but is NOT assumed — loading
without characterization would splat raw `mixamorig:` tracks onto the CC rig), place
seated base + salsa segments + seated base on the timeline, Jeremy eyeballs, export
one take → `cc_retarget.py` → then a lower-body lock IS needed again: same §Phase 3.2
baker degenerates to it (upper = the take itself, lower = seatbase1) — no separate
tool. Known extra risks: transition hard-cuts via API placement, double-retarget
quality, A-pose bind.
## Acceptance
- `nd_02` and `nd_02_pp` pass `loop_qc.py` (exit 0).
- Bake verify gate proves lower-mask tracks constant == seated pose.
- Upper body visibly cycles all 4 salsa flavors without harsh cuts (test-bed eyeball).
- Ground-seat height looks right at the hearth (pose-provenance check §0.1).
- Registry row added; salsa sources + seatbase committed in this repo via LFS;
ariki-game not pushed.
## Review trail
- v1 reviewed 2026-07-21 by independent fresh-context Opus agent (GLM unavailable on
this PC): 1 blocker (Route B files never reach the Mac), 4 major (lock-script unspec'd;
frame-0 lock could freeze a standing pose; Route A's critical path rested on two
unprobed RLPy behaviors; Route A discards most of its own work), 4 minor (2-frame
guard on the seated export; loop-pair citation; co-author line; trim/pose-provenance).
All verified factual claims in v1 held.
- v2 by Fable: Route B promoted; lock tool folded into the composite baker's
verify gate; salsa destination changed from A:\Reallusion content to repo INBOX + LFS;
seated export spec'd ≥300 frames; kevin-pack Path-1 check added; co-author line added.
- v2 reviewed 2026-07-21 by **GLM-4.6** (direct API call — key from tinqs-docs/.env).
Accepted → v3: epsilon-tolerance verify gate (strict equality would false-fail on
resampling); pelvis-Y ground-seat precheck before export; read back salsa clip names
from the GLB instead of assuming; pingpong-reversal caveat for a 4-segment composite
+ bookend/reorder mitigations; check LoadMotion's returned RStatus. Rejected with
evidence: "LoadMotion needs LoadFile+ApplyMotion" (contradicts the verified stub
signature `RFileIO.LoadMotion(strFilePath, kTime, spObject)`, RLPy.py:13700 — still
probe-first per plan); "no Node toolchain exists" (`tools/bake_run_punch.mjs` is in
this repo and the gltf-transform route is documented in the animation-creation
skill); "static 300f take trips the no-motion guard" (cc_retarget.py:104 gates on
`<5` frames + `*TempMotion` name only); "Route A exports mixamorig bone names"
(characterized import retargets onto the CC rig before export).