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

11 KiB
Raw Blame History

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.fbxSalsa_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).
  4. 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.
  5. Sanity: file is multi-MB-ish and ≥300 frames. Commit + tinqs push (etiquette as Phase 0.2).
  6. 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)

  1. 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.
  2. 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).
  3. cc_retarget.py on seatbase1.fbxexchange/converted-glb/seatbase1.glb, clip SeatBase1. First-export QA: A-pose arm check in the showcase.
  4. Copy salsa.glb into ariki-game (assets/quaternius/mixamo/), seatbase1.glbdancegen/.

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.pynd_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).