Files
animation/plans/seated-sway-idle-plan-2026-07-21.md
T
jeremy 3ba86b2ea8 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>
2026-08-06 15:55:43 -07:00

170 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).