--- name: animation description: Jeremy's working preferences and hard-won lessons for the animation pipeline in this repo — the Mac↔PC bridge, retargeting iClone/CC FBX to Quaternius GLB, wiring clips into ariki-game as dances, launching the dance test bed, and the gotchas that bite each step. Use when processing incoming animations through the pipeline, or when deciding how to commit/verify/launch anything in this repo or ariki-game. --- # Animation pipeline — preferences & lessons This is the operator's playbook for *this* repo (`local.repo/animation`), the Mac↔PC bridge that carries iClone exports into ariki-game. For iClone-side "how do I do X" questions use `iclone-video-mocap`; for authoring new clips use `animation-creation`. This skill is the accumulated **do-it-this-way** knowledge from real runs. ## The batch workflow (what "process the incoming animations" means) 1. **Pull first.** `git pull` (or `tinqs pull`) — incoming FBX + textures + JSON land in `exchange/incoming-fbx/`. 2. **Retarget each FBX** with the exact command below → GLB in `exchange/converted-glb/`. **Every dance ships as a pair**: also bake its loop variant with `tools/pingpong_bake.py` → `exchange/pingpong-glb/_pp.glb` (clip `PP`). 3. **Commit + push the GLBs** in *this* repo (use `tinqs push`, not raw `git push` — it self-heals auth and uploads LFS direct-to-S3). 4. **Copy GLBs into ariki-game** `assets/quaternius/dancegen/`, write a dance JSON per clip. 5. **Loop QC gate** — run `loop_qc.py` on each converted GLB (see below). Flag anything that isn't smooth *before* it ships. 6. **Import** so Godot generates sidecars: `bash tools/game.sh import` (from ariki-game root). 7. **Verify** clip names, then commit in ariki-game. 8. **Launch the test bed only after asking** (see below). 9. Leave `exchange/` cleanup until Jeremy confirms the batch looks right in-game. ### The retarget command (canonical) ```bash /Applications/Blender.app/Contents/MacOS/Blender --background --python tools/cc_retarget.py -- \ --src exchange/incoming-fbx/.fbx \ --out exchange/converted-glb/.glb --name \ --target "/Users/jeremykashkett/Tinqs/local.repo/ariki-game/assets/quaternius/base-characters/Universal Base Characters[Standard]/Base Characters/Godot - UE/Superhero_Male_FullBody.gltf" ``` Blender here is **5.1.2**. When running from this repo the `--target` is **required** — the tool's default target path is ariki-relative and won't resolve. ## Naming system (registry: `.agents/wiki/dances/REGISTRY.md` — read it, it is the source of truth) Two-stage names, following the **game's item-id convention (lowercase snake_case)**. **New dances get a provisional id, never a content name**: at conversion, assign the next free `nd_##` from the registry — pack `nd_08`, clip `ND08`, JSON `nd_08.json`, variants `nd_08_pp`/`ND08PP` — regardless of what the PC export was called (a take named "firedance1" turned out to be dance1's choreography). Only when Jeremy **adopts** a take for a specific in-game dance ceremony is it renamed to its dance code — snake_case of the sim's `DanceType` enum + index: pack `war_dance_01`, clip `WarDance01`, variants `war_dance_01_pp`/`WarDance01PP`. The 13 ceremony slots are tabled in the registry. Rename via `tools/rename_clip.py` + JSON rename + registry update. Never guess the ceremony; Jeremy assigns it. Rejected takes are archived (`archive/` here; `archive/dances/` in ariki-game, which is `.gdignore`d), never deleted. Numbers are never reused. Always update the registry row when converting/adopting/archiving. Structural rules (unchanged): - **Pack name = GLB basename** (lowercase); **clip name = `--name` baked into the GLB**. - **Clip ref in JSON = `/`**: `nd08/ND08`. - ClipCatalog auto-discovers anything dropped in `dancegen/` (also `kevin/`, `mixamo/`) — no code change needed. ### Dance JSON (single-clip default) ```json { "name": "aloha1", "bpm": 100, "moves": [ { "clip": "aloha1/Aloha1", "loops": 1 } ] } ``` Default **bpm 100, loops 1** to match the sibling `hakadance1`. bpm only matters once moves are chained (it drives transition beat-quantization). Lives in `ariki-game/assets/dances/*.json`, hot-reloads in the test bed. ## Gotchas that have actually bitten - **2-frame TempMotion = no motion.** iClone names every take `..._TempMotion`; the tool distinguishes by *frame count*, not name. A ~2-frame FBX was exported **without motion** and `cc_retarget.py` rejects it. Fix: re-export from iClone with **Include Motion / Export Motion** checked. (Real takes here are 1800f @60fps.) Don't treat the rejection as a tool bug — it's the guard working. - **A-pose arm QA (open item).** CC/iClone characters bind in **A-pose**, not T-pose. First exports through this pipeline can bake arms rotated. Eyeball arm orientation in the test bed on the first clip of any new source; if wrong, re-export with **FBX Advanced > Use T-Pose As Bind Pose**, or add a per-arm rest-alignment fix in `cc_retarget.py`. - **Verify GLB animation name == JSON clip ref before shipping.** Read the GLB's `animations[].name` (glTF JSON chunk) and confirm it equals ``. A mismatch = silent no-play. - **Commit the `.glb.import` sidecar with the GLB.** Godot embeds the `uid://` inside the `.import` file — there is **no separate `.uid`** for GLBs. Commit GLB + `.import` + JSON together. - **`game.sh import` shows scary warnings** (tree impostors, meta drift, stale bakes, hair-texture case mismatch, missing `res://src/polynesia/data`) — those are **pre-existing and unrelated**. Only care about `errors`, not `warnings`. ## Loop QC (clips are meant to loop — most raw takes don't) The game loops clips with Godot `LoopMode.Linear` (wraps last keyframe → frame 0), so a clip only looks right if the **pose, motion velocity, and root position** are continuous across that seam. Raw iClone/mocap takes almost never satisfy this — they start from a held pose and end mid-motion, or the performer walks across the floor. **QC every clip before shipping** with the `loop-qc` agent, or directly: ```bash /Applications/Blender.app/Contents/MacOS/Blender --background --python tools/loop_qc.py -- \ --src exchange/converted-glb/.glb # exit 0 = SMOOTH, 1 = NOT SMOOTH, 2 = error. Don't pipe through grep if you need the code. ``` Three metrics, three failure modes (the tool ranks the worst bones so you know where): - **root drift high, pose gap low** → performer travels; needs in-place authoring / trim back to origin. - **pose gap high, velocity gap ~0** → starts & ends in different *still* holds; match end pose to start in iClone. - **both high** → starts held / ends mid-move; trim to a full cycle or blend the seam (Animation Layer weight ramp, or bracket clean keys — see `iclone-video-mocap`). Two Blender-side fixers exist (no iClone round-trip needed): - `tools/loop_fix.py` — de-drift + trim to best natural loop point + motion-graph seam blend (crossfade toward the frames before the loop start). Keeps 60–90% of the clip. Outputs to `exchange/looped-glb/`. See `plans/loop-fix-plan-2026-07-16.md` + results. - `tools/pingpong_bake.py` — palindrome bake (forward then reversed as ONE clip): end frame == start frame by construction, drift self-cancels, full choreography kept, but motion visibly reverses at the turn point. Outputs `exchange/pingpong-glb/*_pp.glb`, shipped as separate `_pp` packs. Prefer for sway-heavy dances (hula); judge per-dance for percussive ones (haka) where reverse playback can read as moonwalking. Both verified by re-running `loop_qc.py` to exit 0. iClone re-authoring remains the fallback for clips where neither result looks right in the test bed. ## Outgoing props (game → iClone staging) `exchange/outgoing-props/` carries game assets exported as iClone staging props (the reverse direction of the bridge). `tools/export_boat_prop.py` builds the boat prop (hull at game scale + placeholder mast/sail/oar + seat markers + waterline + 1.9 m ref figure) with a self-verify gate incl. right-side-up checks — the FBX ships via git for the PC to pull. Boat ACTION clips use **direct names** (no nd_## stage): see the "Boat actions" table in the registry. Lesson from building it: an agent-authored verify gate can pass a visually-wrong export (the first boat was upside down with all checks green) — **always render and eyeball a new prop** before shipping, and encode what the eyeball caught as new asserts. ## Launching the game - **Always ask Jeremy before launching** (this session he said "let's launch" — that's the go-ahead; don't assume it next time). - Command: `SCENE=dance_test_bed bash tools/game.sh spawn` from `ariki-game/` root. Also `animation_showcase` for paging every clip. - **Never** use `*RM` / `*RootMotion` clip variants — everything is in-place. - **Spawn-log buffering trap:** if you pipe the spawn through `| tail`, the log file stays **0 bytes until the process exits** — do NOT conclude the launch failed. Confirm it's actually up via the lease file (`ariki-game/.game-cli/sessions/lease-.json`) and `pgrep -fl tinqs.macos.editor`. Closing the game window auto-stops that spawn's sim. ## Repo etiquette - **Don't touch unrelated uncommitted work.** ariki-game often has in-flight files from prior sessions (e.g. `hakadance1_static.*`, local `cc_retarget.py` edits) — stage only this batch's files by explicit path, never `git add -A`. - **Commit co-author line:** `Co-Authored-By: Claude Fable 5 `. - This repo uses **Git LFS** for `.fbx/.glb/.mp4/.task`; prefer `tinqs push`. - ariki-game commits are **local until Jeremy asks to push** — pushing the game repo is not part of the bridge steps.