Compare commits

...

13 Commits

Author SHA1 Message Date
jeremy 854dbb5ce4 chore(agents): migrate to agents.md protocol — .agents/ wiki/plans/rules/skills
Root AGENTS.md is now a thin entry point + knowledge map; the batch-workflow
detail it duplicated already lived in .claude/skills/animation/SKILL.md.
Folded docs/ (dances registry + iclone-bridge stub), root plans/, and
devops-reports/ into .agents/wiki/ and .agents/plans/ per the per-repo .agents/
convention. Added .agents/SOUL.md, .agents/AGENTS.md, wiki/ARCHITECTURE.md,
wiki/architecture/, and wiki/master-plan.md (derived from the closed plans +
the dance registry's live ceremony-slot table). Fixed doc-path references in
exchange/GUIDE.md, the animation skill, and the iclone-bridge stub. All moves
via mv (git detected as renames) — no content deleted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:02:47 -07:00
jeremy 28fdf940f7 revert(inbox): restore Ozan's shipped QuatSkin — v8 rig experiment backed out
The v8 body pushed here (078b0b7) broke Lena's hands + eyebrow fit and its
rest-mismatch premise was unconfirmed (ariki-game 174a0429e). Back to baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:33:01 -07:00
jeremy 06d950649b docs: add AGENTS.md — agent orientation guide for the animation bridge
Fast-orientation guide synthesized from README.md and the animation skill:
batch workflow, canonical retarget command, nd_## naming, loop QC, gotchas,
and repo etiquette. Points to the skills and docs/dances/REGISTRY.md as
authoritative sources.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:20:27 -07:00
jeremy 078b0b7a07 feat(inbox): Ariki_Female_QuatSkin v8 — rig fixed onto the UAL clip rest
Supersedes the previous QuatSkin drop: skeleton now matches UAL1.glb exactly
(0.0001° all 65 joints; ariki-game e67df53e6). Plays the stock clip libraries
without crumpling. Gate: ariki-game tools/rig_pose_gate.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:08:42 -07:00
jeremy 1219954fdc chore(pm): move devops-reports/ to repo root (Jeremy)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:36:02 -07:00
jeremy 82266c97bb docs(pm): devops report — animation-standard convergence 2026-07-23/24
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 10:58:43 -07:00
jeremy b4d962167f fix(audit): converge on the 2026-07-23 animation standard — tool sync, QuatSkin INBOX, registry corrections
From the conformance audit (ariki-game .agents/audits/animation-standard-assessment-2026-07-23.md):
- tools/: cc_retarget + mixamo_retarget re-synced from ariki-game (were stale forks
  missing --despike/--fingers/--map ual); kevin_retarget path now repo-relative;
  mirror rule documented in exchange/GUIDE.md
- INBOX/lena: add canonical Ariki_Female_QuatSkin.glb; README marks MixamoSkin superseded
- REGISTRY: tide_dance_01 flagged (no DanceType enum entry); rain_dance note corrected
  (RainDance IS in the enum); boat table notes live canoedismount1/run_to_dive; QC split
  (loop_qc pre-commit vs anim_qc post-ingest) documented
- docs/iclone-bridge.md: stub created (GUIDE referenced it but it never existed)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:37:55 -07:00
jeremy 998e306804 feat(inbox): Lena character drop — game GLB, Mixamo-rigged FBX, textures
For PC import: standard-rig Lena (Ariki_Female_MixamoSkin.glb) as used
in-game, plus the rigged/unrigged T-pose FBX sources and toned textures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 13:03:22 -07:00
jeremy 228f26e7dc Merge branch 'main' of https://tinqs.com/tinqs/animation 2026-07-23 13:02:10 -07:00
jeremy cd2e598354 docs(exchange): add folder guide with tracks, archive triggers, and changeable path map
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 15:23:41 -07:00
jeremy 7f4300eae6 assets(exchange): add lena_tpose_legs5_input.fbx to INBOX from ariki-game
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:29:30 -07:00
jeremy 9f3f5ac891 @
feat(exchange): native Kevin Iglesias pickaxe mining FBX for iClone editing

Two-hand Mining set (Begin/Loop Ground/Loop Wall/Stop) on the native B-* rig,
pulled from ariki-assets animations/kevin-iglesias. Native rig (not the
Quaternius-retargeted game GLB) so iClone can characterize the skeleton to HIK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-21 13:29:18 -07:00
jeremy b68fa7b920 exchange: Lena (Mixamo-rigged) + two-handed axe for iClone scene work
- lena/lena_tpose_legs5_mixamo_rigged.fbx — 65-bone mixamorig skeleton,
  mesh intact, T-pose; iClone characterizes Mixamo rigs natively.
  Textures alongside (Body_Toned, Head_Toned, Head_Detail) for relinking.
- axe_two_handed.fbx — the game's Polynesian axe (assets/models/glbs/axe.glb
  via build_axe.py, 1.30 m), converted GLB→FBX with textures embedded;
  import as a prop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 12:21:06 -07:00
47 changed files with 772 additions and 15 deletions
+11
View File
@@ -0,0 +1,11 @@
# .agents/AGENTS.md — registry + delegation
Detailed layer behind root `AGENTS.md`. This repo has no repo-specific
multi-agent registry beyond:
- **`loop-qc`** (`.claude/agents/loop-qc.md`) — sub-agent for the loop-QC pass
on converted GLBs; use it instead of hand-running `tools/loop_qc.py` when
checking a batch of clips.
For cross-repo agent roster, MCP services, and delegation rules, see the hub:
`tinqs-ltd/docs/.agents/AGENTS.md`.
+26
View File
@@ -0,0 +1,26 @@
# SOUL.md — animation repo identity
Inherits the shared identity, values, and confidentiality rules from the hub:
`tinqs-ltd/docs/.agents/SOUL.md`. Read that first if this is your first session
on any Tinqs repo.
## What this repo is
The animation-pipeline hub and Mac↔PC bridge for Ariki. iClone 8 + Video Mocap
run on Jeremy's Windows PC; Blender 5, the retarget tools, and `ariki-game` live
on both machines. This repo carries the skills, tools, and files-in-flight that
turn PC-side motion capture (iClone renders, video mocap) into game-ready
Quaternius-rig GLB clips consumed by `ariki-game`.
## Scope
- **In scope:** FBX→GLB retargeting (Blender headless), loop QC and loop-fixing,
dance/clip naming and the registry, boat/prop export, the exchange-folder
bridge protocol, and the skills that encode iClone/mocap operator knowledge.
- **Out of scope:** game-side rendering, ClipCatalog code, dance ceremony
design (owned by `ariki-game`) — this repo only produces and ships the assets.
## Confidentiality
Same as the hub: this repo is private/internal. No special classified material
lives here (Cosmos-related confidentiality does not apply to animation tooling).
+7
View File
@@ -0,0 +1,7 @@
# rules/ — always-on conventions for this repo
No repo-specific rules beyond what's already always-on in root `AGENTS.md`
(git via `tinqs push`/`tinqs pull`, stage-by-explicit-path, naming law) and the
shared hub rules at `tinqs-ltd/docs/.agents/rules/`. Add a file here when a
convention needs to be enforced repo-wide and doesn't fit in `AGENTS.md`'s
thin-entry budget.
+18
View File
@@ -0,0 +1,18 @@
# skills/ — playbooks
This repo's operator playbooks are Claude-Code-native skills and live at
`.claude/skills/` (auto-discovered by Claude Code), not duplicated here:
| Skill | Covers |
|---|---|
| `animation` | Operator playbook — the batch workflow, naming, loop QC, gotchas |
| `animation-creation` | Authoring new clips on the shared Quaternius skeleton |
| `iclone-video-mocap` | iClone 8 + Video Mocap: filming, cleanup, FBX export |
| `pose-estimation` | Video→pose-landmark extraction (MediaPipe) |
| `retarget-animations` | Deprecated early retarget notes, kept for history |
An agent-level playbook (`loop-qc`) lives at `.claude/agents/loop-qc.md`. Follow
the per-repo convention (`.agents/skills/` as the canonical location) once this
repo's skills are ported off `.claude/`; until then this file is the pointer so
there's a single place agents look, per the SoT rule (no second source of
truth).
+90
View File
@@ -0,0 +1,90 @@
# Architecture — animation pipeline
Entry point to architecture law for this repo. Per-system detail lives in
`.agents/wiki/architecture/`.
## The one-sentence shape
PC-side motion capture (iClone 8 + Video Mocap) → this repo (Mac↔PC bridge,
Blender-headless retargeting, loop QC) → `ariki-game` (Quaternius-rig GLB
consumption via ClipCatalog).
## Machines and where things live
| Machine | Owns |
|---|---|
| PC (Windows) | iClone 8 + Video Mocap plugin — authors motion, exports FBX |
| Mac (and PC) | Blender 5.1.2, this repo's retarget tools, `ariki-game` checkout |
Files move machine-to-machine only through `exchange/` in this repo, synced
with `tinqs push`/`tinqs pull` (Git LFS for `.fbx/.glb/.mp4/.task` — see
`.gitattributes`). Raw `git pull` hangs on LFS in tinqs.com repos.
## Pipeline stages
1. **Capture (PC)** — iClone export, Blender preset, Range=All, Preserve Bone
Names ON → `exchange/incoming-fbx/`.
2. **Retarget (either machine)** — Blender-headless, source-specific retargeter
in `tools/`, all bake onto the shared Quaternius rig and export a
mesh-stripped GLB with one NLA track per clip:
- `cc_retarget.py` — Reallusion CC/iClone `CC_Base_*` FBX (60fps; rejects
2-frame `_TempMotion` exports as a guard against motion-less takes)
- `mixamo_retarget.py` — Mixamo FBX (prefix auto-detect)
- `kevin_retarget.py` — Kevin Iglesias Unity FBX (`B-*` rig; hardcoded
target path, non-portable)
- `mocap_retarget.py` — MediaPipe pose JSON → GLB (rotation solving, no
source armature)
`exchange/converted-glb/`.
3. **Loop QC**`tools/loop_qc.py` (pre-commit gate: pose/velocity/root-drift
continuity across the seam, exit 0 = smooth). Fixers when it fails:
`tools/loop_fix.py` (de-drift/trim/seam-blend → `exchange/looped-glb/`) and
`tools/pingpong_bake.py` (palindrome bake, seam-perfect but motion reverses
at the turn → `exchange/pingpong-glb/`, clip suffix `PP`). Every adopted
dance ships as a base clip + `_pp` pair.
4. **Ship** — commit + `tinqs push` the GLBs in this repo, then copy into
`ariki-game/assets/quaternius/dancegen/` (auto-discovered by ClipCatalog as
`<pack>/<ClipName>` — also watches `kevin/`, `mixamo/`), write one dance JSON
per clip in `ariki-game/assets/dances/`, `game.sh import`, verify clip names
match GLB `animations[].name` exactly, commit in ariki-game (push only when
Jeremy asks).
5. **Post-ingest QC (game side, out of this repo)**`ariki-game/tools/anim_qc.py`
re-checks world-space seam continuity + renders a seam-cross video for eye
review. Two QC tools by design: this repo's gate is pre-commit/local-space,
the game's is post-ingest/world-space.
## Naming law
Full detail: `.agents/wiki/dances/REGISTRY.md` (source of truth — do not
duplicate its content elsewhere). Summary: new takes get a provisional `nd_##`
id (never a content-derived name); a take is renamed to its ceremony code
(snake_case of the sim's `DanceType` enum + index) only once Jeremy assigns it
to a specific ceremony, via `tools/rename_clip.py`. Rejected/superseded takes
are archived (`archive/`), never deleted; numbers are never reused.
## Canonical target rig
All retargeters bake onto ariki-game's
`assets/quaternius/base-characters/.../Godot - UE/Superhero_Male_FullBody.gltf`.
Run from this repo, `--target` must be passed explicitly (the tool's default
path is ariki-relative and won't resolve here). The rig/pack standard itself
(65-bone Quaternius UE-named skeleton, hero body `Ariki_Female_QuatSkin.glb`) is
governed by `ariki-game/.humans/animation-bone-sources.html` and
`ariki-game/.agents/wiki/quaternius-ual-packs.md` — this repo's iClone/mocap
lane is explicitly a separate clip source (not UAL1/UAL2/Kevin/Mixamo), landing
in `dancegen/` only.
## Tool provenance / mirroring
`tools/cc_retarget.py`, `mixamo_retarget.py`, `kevin_retarget.py`,
`mocap_retarget.py` are **mirrors of `ariki-game/tools/`** — the game copies are
authoritative; re-copy from ariki-game when they change (they have silently
diverged before). `.claude/skills/` here mirror a subset of `ariki-game/.claude/skills/`
plus `~/.claude/skills/pose-estimation` — see `README.md` Provenance section.
## See also
- `.agents/wiki/architecture/` — per-system detail (currently: this file covers
the whole pipeline; split out if a subsystem grows its own doc).
- `.agents/wiki/dances/REGISTRY.md` — naming/registry source of truth.
- `.agents/wiki/iclone-bridge.md` — PC-lane routing stub.
- `.agents/wiki/devops-reports/` — point-in-time audits and convergence reports.
+12
View File
@@ -0,0 +1,12 @@
# architecture/ — per-system detail
This repo's pipeline is small enough that `.agents/wiki/ARCHITECTURE.md` covers
it end to end today; nothing has grown large enough yet to need its own
per-system doc here. Split a topic out into this folder (and link it from
`ARCHITECTURE.md`) when it does — likely candidates as the repo grows:
- Retarget-tool internals (per-source rig mapping, bone-name conventions).
- Loop QC/fix algorithms (pose-gap, velocity-gap, root-drift math).
- The exchange-folder state machine (`exchange/GUIDE.md` currently owns this —
promote here if it needs architecture-level treatment beyond an operator
guide).
@@ -39,7 +39,7 @@ renamed once Jeremy says which ceremony it corresponds to. Rename with:
|---|---|---|---|
| `water_dance_01` | 1 | awaiting take | — |
| `fire_dance_01` | 1 | awaiting take | — (the old "firedance1" was mislabeled dance1 choreography) |
| `tide_dance_01` | 1 | awaiting take | — |
| `tide_dance_01` | 1 | **no enum entry** | — (no `TideDance` in the sim `DanceType` enum as of 2026-07-23 — confirm with Ozan whether the slot is real before filming a take) |
| `fertility_dance_01` | 1 | **adopted** 2026-07-16 | aloha1 (hula) — shipped with `_pp` loop |
| `food_dance_01` | 1 | awaiting take | — |
| `taming_dance_01` | 1 | **adopted** 2026-07-16 | dance1 — shipped with `_pp` loop |
@@ -49,7 +49,7 @@ renamed once Jeremy says which ceremony it corresponds to. Rename with:
| `colony_shield_dance_01` | 2 | awaiting take | — |
| `age_progression_dance_01` | 3 | awaiting take | — (gates Age advancement) |
| `star_watching_dance_01` | 3 | awaiting take | — |
| `rain_dance_01` | tech | awaiting take | — (tech-tree "Rain Maker"; not in DanceType enum yet) |
| `rain_dance_01` | 1 | awaiting take | — (tech-tree "Rain Maker"; `RainDance` **is** in the DanceType enum, Age 1 — earlier "not in enum yet" note was wrong, corrected 2026-07-23) |
## Register
@@ -82,3 +82,14 @@ Cyclic clips (`*_loop`, `*_idle`) must pass `loop_qc.py` before shipping; one-sh
| `boat_row_loop` | `BoatRowLoop` | awaiting take | helm, stern-oar sweep ±18° |
| `boat_fish_loop` | `BoatFishLoop` | awaiting take | Seat_Fisher; coexists with kevin Fishing01_Loop |
| `boat_sail_idle` | `BoatSailIdle` | awaiting take | sway/line-hauling loop |
| *(unregistered)* | `CanoeDismount`, `CanoeSitIdle` | **live in-game** since 2026-07-21 | shipped as `ariki-game/assets/quaternius/dancegen/canoedismount1.glb` (content-guessed name, predates this table) — register under a `boat_*` code at next adoption pass |
| *(unregistered)* | `run_to_dive` | **live in-game** | Mixamo one-off at `ariki-game/assets/quaternius/mixamo/run_to_dive.glb` — outside this pipeline, listed for completeness |
## QC tool split (audit 2026-07-23)
Two QC tools exist and gate different stages — both stay:
- **`tools/loop_qc.py` (this repo)** — the **pre-commit loop gate**: local-rotation pose gap,
velocity gap, root drift; exit 0/1. Every `_pp` (and `*_loop`/`*_idle` boat clip) must pass
it before adoption — this is the "must pass loop_qc" rule above.
- **`ariki-game/tools/anim_qc.py`** — **post-ingest QC** on the game side: world-space seam
position/velocity, mid-clip stutter scan, seam-cross video render for eye review.
@@ -0,0 +1,87 @@
# DevOps Report — Animation Standard Convergence (2026-07-23 → 24)
**Author:** Claude (Fable 5), Jeremy's session · **Repos:** `animation` + `ariki-game`
**Trigger:** Ozan landed the animation standardization contract on ariki-game main
(2026-07-23: `5409a82e0`, `3d7f3ef94`); Jeremy requested a full conformance assessment
of our lane (dance pipeline, this bridge repo, test beds), then execution of the fixes.
---
## 1. What the standard says (one paragraph)
One runtime skeleton — the Quaternius 65-bone UE-named rig. Hero body =
`Ariki_Female_QuatSkin.glb` (Mixamo weights on Quaternius rest orientations; the earlier
MixamoSkin+msrig pairing is superseded). Clip policy: UAL1 Pro = locomotion, UAL2 Source =
work (under `ariki-game/assets/quaternius/anim/`), Kevin packs gaps-only, Mixamo one-offs,
and **our iClone/video-mocap lane is explicitly separate**: `cc_retarget.py`
`dancegen/` → dance JSON. Canonical docs: `ariki-game/.humans/animation-bone-sources.html`,
wiki `player-quaternius-animator.md` + `quaternius-ual-packs.md`, skill
`aaa-player-animation`.
## 2. The assessment (how it ran)
- 52 checkable rules extracted from the 7 standard docs.
- 10 audit roles, model-routed: GLM-5.2 ×2 (inline-input tasks), DeepSeek v4-pro ×5
(code/doc/bed sweeps; one run died and was completed by the orchestrator), Fable ×3
(wiring, dance system, synthesis). Every CRITICAL/HIGH re-verified mechanically.
- Bone-census probe over 21 GLBs (this repo's `exchange/{converted,pingpong}-glb/` +
ariki `dancegen/` + the three body GLBs).
- Full report: `ariki-game/.agents/audits/animation-standard-assessment-2026-07-23.md`;
meeting page: `ariki-game/.humans/animation-standard-assessment.html`.
**Result:** the pipeline core was already conformant — 100% of runtime clip GLBs on the
65-bone contract, 4/4 `_pp` loop pairs, all dance JSONs clean post-rename, 16/17 test
beds compliant. The defects clustered in the superseded MixamoSkin/msrig mechanism and
stale doc/tool mirrors.
## 3. What we shipped
### This repo (`b4d9621`, pushed 2026-07-23)
| Change | Why |
|---|---|
| `tools/cc_retarget.py` + `mixamo_retarget.py` re-synced from ariki-game | They were stale forks (2026-07-15) silently missing `--despike`, `--fingers`, `--map ual` — PC batches would have lost QC |
| Tool **mirroring rule** added to `exchange/GUIDE.md` | Game copies are authoritative; never edit mirrors here |
| `tools/kevin_retarget.py` path fix (mirrored) | Was hardcoded to Ozan's home dir |
| `exchange/INBOX/lena/Ariki_Female_QuatSkin.glb` added (LFS) + README rewrite | The drop shipped only the superseded MixamoSkin body; **import the QuatSkin one on the PC** |
| `docs/dances/REGISTRY.md` corrections | `tide_dance_01` flagged (NO `TideDance` in the sim enum); rain note fixed (`RainDance` IS in the enum); boat table now records live `canoedismount1`/`run_to_dive`; QC split documented (loop_qc = pre-commit gate here, anim_qc = post-ingest game-side) |
| `docs/iclone-bridge.md` created | GUIDE referenced it; it never existed |
Earlier same day (`998e306`): the original Lena INBOX drop (GLB + rigged/unrigged T-pose
FBX + toned textures).
### ariki-game (`b70d5a11c` + `934b6d4dc`, pushed)
- **Dance bed `LenaBodyPath` → QuatSkin** (the bed had gone Lena-only on the superseded
MixamoSkin body whose msrig clip-reroute is dead — basename rot after the Pro rename).
Build green.
- Doc convergence: clip-inventory wiki corrected (23→30 wired UAL2; shield/sword set was
mislabeled "unused"), UALPACKS "Standard removed" claim scoped, `animation-pipelines.md`
coverage extended (+`anim-combined/ msrig/ derived-bodies/`), `pose-estimation` skill
reference re-pointed to THIS repo.
- Bug filed for Ozan: `.agents/bugs/2026-07-23-ninjajump-idle-missing-clip.md`
code requests `NinjaJump_Idle`, pack ships `NinjaJump_Idle_Loop`, resolver can't bridge.
## 4. Live verification (2026-07-24)
Screenshot-verified on a running dance bed (post-fix build): QuatSkin Lena dancers
upright, phase-locked, `war_challenge` playing on the stock clip libraries. Engine log
carries **zero** `MISSING clip` warnings → the NinjaJump bug is **latent** (current
Godot import config strips `_Loop`), not active; hardening still recommended. Bug file
updated with this evidence (`934b6d4dc`).
## 5. Open items (owners)
| Item | Owner |
|---|---|
| Import `INBOX/lena/Ariki_Female_QuatSkin.glb` on the PC (not the MixamoSkin one) | Jeremy |
| Retire `msrig/` + MixamoSkin GLB outright (recommended) or re-retarget under new basenames | Ozan (negotiate) |
| Amend the Kevin-swim keep-set rule to match the restored swim override in code | Ozan (negotiate) |
| Add derived/import packs (Run_Punch, mixamo one-offs, canoe clips) to the BONES tables | Ozan (negotiate) |
| NinjaJump 1-token fix + resolver `+_Loop` arm | Ozan (bug filed) |
| Register `canoedismount1` under a `boat_*` code at next adoption pass | Jeremy (this repo) |
## 6. Ops notes
- All syncs via `tinqs pull`/`tinqs push` (LFS-safe); QuatSkin GLB verified as an LFS pointer.
- Executor telemetry: GLM headless = 1 clean / 1 silent-empty; DeepSeek = 4 clean / 1 SIGTERM;
Fable tracks found both deep bugs (dead msrig reroute, NinjaJump). Full disclosure table
in the audit report.
+18
View File
@@ -0,0 +1,18 @@
# iClone bridge — PC-lane overview
(Stub created 2026-07-23 — this path was referenced by `exchange/GUIDE.md` but never
written. The full operational detail lives elsewhere; this page just routes you.)
The Mac↔PC animation bridge: iClone 8 + Video Mocap on Jeremy's Windows machine authors
motion; this repo carries files both directions; ariki-game consumes 65-bone Quaternius
GLBs.
| Need | Go to |
|---|---|
| Which exchange/ folder to use, when files move/archive | `exchange/GUIDE.md` |
| Naming (nd_## → ceremony codes), loop-pair rule, registry | `.agents/wiki/dances/REGISTRY.md` |
| Filming rules, iClone/AccuFACE settings, mocap cleanup, FBX export | `iclone-video-mocap` skill (`.claude/skills/` here and in ariki-game) |
| CC FBX → Quaternius GLB conversion | `tools/cc_retarget.py` (mirror of ariki-game — game copy is authoritative) |
| Looping (`_pp`) + QC gates | `tools/pingpong_bake.py` + `tools/loop_qc.py`; game-side `anim_qc.py` (split documented in REGISTRY.md) |
| Game-side landing zone | `ariki-game/assets/quaternius/dancegen/` + `assets/dances/*.json` |
| The skeleton/pack standard itself | `ariki-game/.humans/animation-bone-sources.html` + `.agents/wiki/quaternius-ual-packs.md` |
+56
View File
@@ -0,0 +1,56 @@
# Master plan — animation repo
What's in flight and recently shipped. See `.agents/plans/` for the closed-out
plan/results docs behind these items; see `.agents/wiki/dances/REGISTRY.md` for
the live per-clip status table (source of truth, not duplicated here).
## Recently shipped (closed plans, folded into wiki)
- **Animation-gen pipeline** (2026-07-13) — the FBX→Blender-retarget→GLB→dance-JSON
bridge itself; core of `.agents/wiki/ARCHITECTURE.md` today.
- **Animation skills adoption** (2026-07-13) — `.claude/skills/animation*`,
`iclone-video-mocap`, `pose-estimation` brought in as repo skills.
- **Loop fix** (2026-07-16, plan + results) — `tools/loop_fix.py` (de-drift/trim/
seam-blend) and `tools/pingpong_bake.py` shipped; every adopted dance now ships
as a base clip + `_pp` pair, gated by `tools/loop_qc.py`.
- **Boat prop export** (2026-07-17, plan + fixes + results) — `tools/export_boat_prop.py`
staging pipeline for the boat action-clip set (see "Boat actions" below).
- **Animation-standard convergence** (2026-07-23/24, `.agents/wiki/devops-reports/`) —
full audit against ariki-game's animation-standardization contract. Result:
pipeline core already conformant (100% of runtime clip GLBs on the 65-bone
Quaternius rig, all `_pp` pairs present, dance JSONs clean). Fixes landed:
registry corrections (`tide_dance_01` has no sim enum entry — flag before
filming; `rain_dance_01` confirmed in-enum), `iclone-bridge.md` written
(previously a dead reference), QC-tool split documented (`loop_qc.py` here =
pre-commit gate, `ariki-game/tools/anim_qc.py` = post-ingest gate).
- **agents.md migration** (2026-07-24, this change) — repo restructured onto the
open agent protocol: root `AGENTS.md` (thin) + `.agents/` (SOUL, wiki, plans,
skills-pointer). `docs/` and root `plans/`/`devops-reports/` folded into
`.agents/wiki/`.
## In flight — ceremony dance slots awaiting a take
Per `.agents/wiki/dances/REGISTRY.md`, adopted so far: `fertility_dance_01`
(hula), `taming_dance_01`, `war_dance_01` (haka) — each shipped with its `_pp`
loop. One provisional take (`nd_01`, baraka1) is filmed and QC-passed but not
yet assigned to a ceremony. Still **awaiting a take**: `water_dance_01`,
`fire_dance_01`, `food_dance_01`, `sailing_dance_01`, `building_dance_01`,
`colony_shield_dance_01`, `age_progression_dance_01`, `star_watching_dance_01`,
`rain_dance_01`. `tide_dance_01` has no confirmed sim `DanceType` entry — resolve
with Ozan before filming.
## In flight — boat action clips
Staging tooling (`tools/export_boat_prop.py`) is built and the boat prop rig
verified; the action-clip set itself is **awaiting takes**: `boat_dive`,
`boat_climb`, `boat_collect`, `boat_row_loop`, `boat_fish_loop`,
`boat_sail_idle`. Two boat-adjacent clips are already live in-game
(`CanoeDismount`/`CanoeSitIdle`, `run_to_dive`) but predate this registry and
need a `boat_*` code assigned at the next adoption pass.
## Open threads
- `tools/kevin_retarget.py` has a hardcoded, non-portable target path — fine for
now, flagged in `AGENTS.md` gotchas.
- `.claude/skills/retarget-animations/` is kept only for history (deprecated,
superseded by the per-source retargeters in `tools/`).
+1 -1
View File
@@ -33,7 +33,7 @@ This skill is the accumulated **do-it-this-way** knowledge from real runs.
```
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: `docs/dances/REGISTRY.md` — read it, it is the source of truth)
## 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`,
+56
View File
@@ -0,0 +1,56 @@
# AGENTS.md — animation repo
Entry point for AI agents working in `local.repo/animation` (all tools: pi,
Claude Code, Cursor). This repo is Jeremy's animation-pipeline hub **and** the
Mac↔PC bridge that carries iClone/mocap exports into `ariki-game`. iClone 8 +
Video Mocap live on the PC; Blender 5, the retarget tools, and the game repo
live on both machines.
Read `.agents/SOUL.md` at session start, then use the knowledge map below —
this file stays thin; the operational detail lives in `.claude/skills/` and
`.agents/`.
## Knowledge map
| Category | Path | When |
|---|---|---|
| Identity | `.agents/SOUL.md` | always-on |
| Batch workflow, canonical retarget command, naming, loop QC, gotchas, repo etiquette | `.claude/skills/animation/SKILL.md` | always-on — read before any batch or clip work |
| Architecture (pipeline stages, canonical rig, tool provenance) | `.agents/wiki/ARCHITECTURE.md` | consult when touching the pipeline |
| Per-system architecture detail | `.agents/wiki/architecture/` | consult-when |
| In-flight work + recently shipped | `.agents/wiki/master-plan.md` | check before starting new work |
| Dance/clip naming registry (source of truth for names) | `.agents/wiki/dances/REGISTRY.md` | always-on when naming or shipping a clip |
| PC-lane routing stub | `.agents/wiki/iclone-bridge.md` | consult-when |
| Point-in-time audits / convergence reports | `.agents/wiki/devops-reports/` | consult-when |
| Open/executing plans | `.agents/plans/` | consult-when |
| Other operator playbooks (iClone/mocap, clip authoring, pose estimation) | `.claude/skills/` (index: `.agents/skills/README.md`) | consult-when |
| Loop-QC sub-agent | `.claude/agents/loop-qc.md` | consult-when |
| Repo-wide conventions | `.agents/rules/` | always-on |
| Exchange-folder operator guide (which folder, when files move/archive) | `exchange/GUIDE.md` | consult-when doing a batch |
## Layout
```
AGENTS.md ← you are here
.agents/ ← agent context: SOUL, wiki (architecture/master-plan/registry/
devops-reports), plans, rules, skills-pointer
.claude/ ← Claude-Code-native skills + agents (auto-discovered)
tools/ ← Blender-headless retargeters, loop tools, prop export
exchange/ ← files in flight between Mac and PC (see exchange/GUIDE.md)
archive/ ← retired takes — never deleted, never renumbered
```
## Git
- Branch is **`main`**. Sync via **`tinqs push`/`tinqs pull`** (not raw git —
this repo uses Git LFS for `.fbx/.glb/.mp4/.task`; a plain `git pull` hangs).
Report the commit hash after pushing.
- **Stage explicit paths, never `git add -A`** — ariki-game and this repo often
carry unrelated in-flight files.
- Never commit secrets.
## The rule that bites most often
**Green verify ≠ correct.** An agent-authored gate once passed an upside-down
boat export. Always render and eyeball a new visual asset before shipping —
full gotcha list in `.claude/skills/animation/SKILL.md`.
+1 -1
View File
@@ -41,7 +41,7 @@ into the game repo.
- `kevin_retarget.py` — Kevin Iglesias Unity FBX (`B-*` rig; hardcoded target path — non-portable)
- `mocap_retarget.py` — MediaPipe pose JSON → GLB (rotation solving, no source armature)
- `bake_run_punch.mjs` — composite upper/lower clip baking reference
- `plans/` — pipeline design docs (animation generation pipeline, skills adoption)
- `AGENTS.md` / `.agents/` — agent context (identity, wiki, plans, rules); see `AGENTS.md` for the knowledge map
## Canonical target rig
+168
View File
@@ -0,0 +1,168 @@
# exchange/ — Folder guide for agents
This folder is the Mac↔PC bridge for the animation pipeline (iClone on the PC ↔
ariki-game on the Mac). Every file in here is **in transit** on one of the tracks
below. If you are an agent dropping files into this repo, or processing a batch,
this file tells you which folder to use, when a file moves to the next stage, and
when the previous version gets archived.
Related docs: `.agents/wiki/dances/REGISTRY.md` (naming — the source of truth),
`.agents/wiki/iclone-bridge.md` (PC-lane overview), and the `animation` skill (batch workflow details).
**Tool mirroring rule (audit 2026-07-23):** the retargeters in `tools/`
(`cc_retarget.py`, `mixamo_retarget.py`, `kevin_retarget.py`, `mocap_retarget.py`) are
**mirrors of `ariki-game/tools/`** — the game copies are authoritative. Never edit them
here; re-copy from ariki-game when they change (they silently diverged once already:
2026-07-15 → 2026-07-23, losing `--despike`/`--fingers`/`--map ual`).
---
## Folder map — EDIT PATHS HERE ONLY
Folder locations may change. Every rule in this guide refers to folders by the
**KEY** below, never by a hardcoded path. If a folder moves, update this table
(and nothing else in this file).
| KEY | Current path | One-liner |
|---|---|---|
| `INBOX` | `exchange/INBOX/` | Untriaged drop zone for anything sent to Jeremy |
| `INCOMING_FBX` | `exchange/incoming-fbx/` | Accepted FBX takes queued for retargeting |
| `CONVERTED_GLB` | `exchange/converted-glb/` | Retargeted game-ready GLBs (base clips) |
| `LOOPED_GLB` | `exchange/looped-glb/` | Seam-fixed loop variants from `tools/loop_fix.py` (created on demand) |
| `PINGPONG_GLB` | `exchange/pingpong-glb/` | Palindrome `_pp` loop variants from `tools/pingpong_bake.py` |
| `OUTGOING_PROPS` | `exchange/outgoing-props/` | Game → iClone direction: staging props + source motions for iClone editing |
| `REFERENCE_VIDEO` | `exchange/reference-video/` | Choreography/mocap reference footage |
| `ICLONE_PROJECTS` | `exchange/workingfiles-iclone/` | iClone `.iProject` working-file backups |
| `ARCHIVE` | `archive/` (repo root) | Rejected/superseded files — mirrors the exchange subfolder names |
| `GAME_DANCEGEN` | `<ariki-game>/assets/quaternius/dancegen/` | Final GLB destination in the game repo |
| `GAME_DANCES` | `<ariki-game>/assets/dances/` | Dance JSON destination in the game repo |
Notes that apply everywhere:
- Binary formats (`.fbx`, `.glb`, `.mp4`, `.task`) go through **Git LFS**; push with `tinqs push`.
- **Move, never delete.** Anything obsolete goes to `ARCHIVE` (see archiving rules at the bottom).
- Stage files **by explicit path** — never `git add -A` (this repo often has unrelated in-flight work).
---
## Track 1 — INBOX triage (anyone → Jeremy)
**Folder:** `INBOX`
The only folder outside agents should ever write to. Drop whatever you're sending
(FBX, textures, JSON, video, etc.), commit, push. Do **not** place files directly
into the pipeline folders and do not reorganize the rest of `exchange/`.
| Event | Action |
|---|---|
| File arrives | Sits in `INBOX` untouched until Jeremy (or a session he directs) triages it |
| Triaged as an animation take | Move to `INCOMING_FBX` → enters Track 2 |
| Triaged as reference footage | Move to `REFERENCE_VIDEO` → Track 4 |
| Triaged as anything else | Jeremy decides; ask, don't guess |
| Junk / duplicate | Move to the matching `ARCHIVE` subfolder |
`INBOX` should trend toward **empty** — a file living there long-term means triage
was missed.
## Track 2 — Animation intake (iClone/PC → game) — the main track
**Stages:** `INBOX``INCOMING_FBX``CONVERTED_GLB` (+ loop variant in
`PINGPONG_GLB` or `LOOPED_GLB`) → copied to `GAME_DANCEGEN` + JSON in `GAME_DANCES`.
A "take" is a **file set that always travels together**: `<take>.fbx` +
`<take>.json` + `<take>.fbm/` texture folder (when present). Move all three,
always.
| Stage | What lives here | Moves forward when |
|---|---|---|
| `INCOMING_FBX` | Raw iClone FBX exports (1800f @ 60fps typical) awaiting retarget | Retargeted via `tools/cc_retarget.py` → GLB written to `CONVERTED_GLB` |
| `CONVERTED_GLB` | Game-ready GLB, named per the registry (`nd_##` provisional, ceremony code once adopted) | Copied (not moved) into `GAME_DANCEGEN` with a dance JSON |
| `PINGPONG_GLB` / `LOOPED_GLB` | The loop variant (`<pack>_pp` / seam-fixed). **Every shipped dance is a pair: base + loop.** Must pass `tools/loop_qc.py` (exit 0) | Copied into `GAME_DANCEGEN` alongside the base clip |
Rules on this track:
- **Naming:** new takes get the next free `nd_##` from `.agents/wiki/dances/REGISTRY.md`
at conversion — never a content name. Update the registry row at every
conversion/adoption/archive.
- **FBX stays in `INCOMING_FBX` after conversion** (it's the re-export source)
until its take is adopted or rejected.
- The GLB is **copied** to the game repo, not moved — `CONVERTED_GLB` keeps the
canonical copy.
**Archive triggers (move old version → matching `ARCHIVE` subfolder):**
| Trigger | What to archive |
|---|---|
| A **re-export of the same take** arrives (same choreography, new FBX) | The old FBX set from `INCOMING_FBX` and its old GLBs from `CONVERTED_GLB`/`PINGPONG_GLB`/`LOOPED_GLB` |
| Take is **rejected** | Its entire file set across all stage folders (and `archive/dances/` on the game side) |
| Take is **adopted** (renamed `nd_##` → ceremony code via `tools/rename_clip.py`) | The old `nd_##`-named files, once the renamed set is verified in-game |
Never archive mid-batch — wait until Jeremy confirms the batch looks right in-game.
## Track 3 — Outgoing to iClone (game/Mac → PC)
**Folder:** `OUTGOING_PROPS`
The reverse direction: anything exported from the game side for use inside
iClone. Two kinds live here today:
- **Staging props** (e.g. `boat/` — built by `tools/export_boat_prop.py`, with
renders for eyeball QA). Each prop gets its own subfolder with a README.
- **Source motion FBX** (Mixamo/Kevin-Iglesias clips like `Swimming.fbx`) sent
over as raw material for iClone editing. Note: some earlier source-motion drops
(the `HumanM@Mining*` set) landed in `INCOMING_FBX`; going forward,
game→PC source motions belong here.
| Event | Action |
|---|---|
| New version of a prop | Overwrite in place (git history is the archive for props) — but **render and eyeball before shipping**; a green verify gate has passed a visually-wrong prop before |
| PC has consumed the files and the resulting take shipped | Leave in place until Jeremy says to clean up |
## Track 4 — Reference footage
**Folder:** `REFERENCE_VIDEO`
Choreography/mocap reference videos (inputs for iClone Video Mocap and for
`pose-estimation` scoring). These are **inputs, not pipeline items** — they don't
advance through stages.
| Event | Action |
|---|---|
| New reference arrives (usually via `INBOX`) | Move here |
| The dance it references is adopted and shipped | Video becomes archivable — ask Jeremy before moving (these are large LFS objects; he may want them kept for re-scoring) |
## Track 5 — iClone working files
**Folder:** `ICLONE_PROJECTS`
`.iProject` backups from the PC so iClone sessions can be resumed/recovered.
These are big (100 MB+) LFS blobs.
| Event | Action |
|---|---|
| Newer save of the same project | Overwrite in place — git history keeps priors |
| Project's takes all adopted or rejected | Archivable on Jeremy's call only |
---
## Archiving rules (all tracks)
1. `ARCHIVE` **mirrors the exchange subfolder names** — a file from
`INCOMING_FBX` goes to `ARCHIVE/incoming-fbx/`, a GLB from `CONVERTED_GLB` to
`ARCHIVE/converted-glb/`, etc. If a mirror subfolder doesn't exist yet, create it.
2. **`git mv`, never delete.** Archived files keep their names; nothing in
`ARCHIVE` is ever renamed or reused.
3. **Registry numbers are never reused.** An archived `nd_03` retires the number
forever.
4. Every archive action gets a **registry update** (`.agents/wiki/dances/REGISTRY.md`)
when it involves a tracked dance.
5. Game-side counterparts (GLBs/JSON already copied into ariki-game) archive to
`<ariki-game>/archive/dances/` (which is `.gdignore`d), in the same commit
cycle.
## Quick decision table
| You are… | You should… |
|---|---|
| An outside agent sending files | Drop in `INBOX`, commit, push. Touch nothing else. |
| Processing a new batch | Follow Track 2 top to bottom; read the `animation` skill first |
| Delivering game assets to the PC/iClone | Put them in `OUTGOING_PROPS` (subfolder per prop) |
| Holding a newer version of something already here | Same-name working files: overwrite. Pipeline takes: move the old set to `ARCHIVE` first (Track 2 triggers) |
| Unsure which track applies | Leave it in `INBOX` and say so — wrong-folder moves are worse than no move |
+2
View File
@@ -9,3 +9,5 @@ folders (`incoming-fbx/`, etc.) from there.
Notes:
- Binary formats (`.fbx`, `.glb`, `.mp4`, `.task`) go through Git LFS automatically.
- Don't reorganize other folders in `exchange/` — just drop your files here.
- Full folder guide (what each `exchange/` folder is for, and how files move
between them): see [`../GUIDE.md`](../GUIDE.md).
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 MiB

+13
View File
@@ -0,0 +1,13 @@
# Lena character (from ariki-game)
The new Lena character, exported from `ariki-game/assets/quaternius/derived-bodies/`
(Ozan's standard-rig work, 2026-07-21 → standardized 2026-07-23).
| File | What it is |
|---|---|
| `Ariki_Female_QuatSkin.glb` | **⭐ THE canonical game body (standard 2026-07-23)** — Mixamo weights on Quaternius rest orientations, exact 65-bone UE-named contract, plays the stock clip libraries natively. Import THIS one. |
| `Ariki_Female_MixamoSkin.glb` | **SUPERSEDED** by QuatSkin (kept for reference) — Mixamo-convention rig (no `root` bone, Mixamo leaf names); needed the now-stale msrig clip reroute. |
| `lena_tpose_legs5_mixamo_rigged.fbx` | Mixamo-rigged T-pose FBX (legs5 variant) — the rigged source; best starting point for importing into iClone/CC or Mixamo on the PC. |
| `lena_tpose_for_mixamo.fbx` | Unrigged T-pose mesh, the file that was uploaded to Mixamo's auto-rigger. |
| `Lena_Body_Toned.png` / `Lena_Head_Toned.png` | Toned base-color textures (FBX files don't embed them). |
| `Lena_Head_Detail.png` | High-res head detail texture. |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 MiB

+118 -1
View File
@@ -12,6 +12,20 @@
# Usage:
# blender --background --python tools/cc_retarget.py -- \
# --src "<dir-or-fbx>[,<more>…]" --out <out.glb> [--name <ClipName>] [--target <gltf>]
# [--fingers track|static] static = ignore captured finger articulation; fingers
# hold the target rig's rest pose (open hand) and the
# hand moves as one unit. Use for noisy AI-mocap fingers.
# [--despike] heal short AI-mocap glitches: frames whose world-space
# acceleration spikes far above the clip's median get
# re-interpolated from the good frames around them.
# Only short windows are touched, so intentional sharp
# moves survive. Extra important for ping-pong clips —
# a glitch plays twice (forward + reversed).
#
# Looping note: seamless looping is owned by the ping-pong bake (animation repo
# pingpong_bake.py → <pack>_pp), which loops and returns to start by construction.
# The former --inplace / --loop-blend passes were removed 2026-07-17; resurrect from
# git history (commit 535719601) if a forward-loop clip ever needs them again.
import bpy, sys, os, re
from mathutils import Matrix, Quaternion, Vector
@@ -190,12 +204,113 @@ def retarget_one(src_arm, tgt_arm, action, name, scn, unit):
return new_act
DESPIKE_FACTOR = 200.0 # world accel must exceed FACTOR x the clip's median accel
DESPIKE_MAX_WIN = 12 # frames; longer regions are real motion, leave them alone
DESPIKE_PAD = 3
def _hermite_patch(kps, a, b):
"""Replace kps[a+1..b-1] with a cubic that matches value AND slope at both edges."""
v = [k.co.y for k in kps]
n = b - a
m0 = (v[a] - v[a - 1]) * n
m1 = (v[b + 1] - v[b]) * n
for i in range(a + 1, b):
t = (i - a) / n
h00 = 2 * t ** 3 - 3 * t ** 2 + 1
h10 = t ** 3 - 2 * t ** 2 + t
h01 = -2 * t ** 3 + 3 * t ** 2
h11 = t ** 3 - t ** 2
kps[i].co.y = h00 * v[a] + h10 * m0 + h01 * v[b] + h11 * m1
def despike(act, tgt_arm, scn):
"""Heal short AI-mocap glitches. Detection runs in WORLD space (same metric as
tools/anim_qc.py): a bone whose world acceleration towers over the clip's median
for a few frames is a tracking error, not choreography. The flagged bone's local
curves — and its parent's, where the glitch usually originates — are re-interpolated
across the window with slope-matched cubics."""
bones = sorted(set(BONE_MAP.values()))
prev = tgt_arm.animation_data.action
tgt_arm.animation_data.action = act
if getattr(act, "slots", None) and tgt_arm.animation_data.action_slot is None:
tgt_arm.animation_data.action_slot = act.slots[0]
f0, f1 = (int(x) for x in act.frame_range)
pos = {n: [] for n in bones}
for f in range(f0, f1 + 1):
scn.frame_set(f)
dg = bpy.context.evaluated_depsgraph_get()
te = tgt_arm.evaluated_get(dg)
for n in bones:
pos[n].append((tgt_arm.matrix_world @ te.pose.bones[n].matrix).translation.copy())
tgt_arm.animation_data.action = prev
acc = {n: [(p[i + 1] - 2 * p[i] + p[i - 1]).length for i in range(1, len(p) - 1)]
for n, p in pos.items()}
flat = sorted(x for arr in acc.values() for x in arr)
thr = DESPIKE_FACTOR * (flat[len(flat) // 2] or 1e-9)
by_bone = {}
for fc in action_fcurves(act):
if fc.data_path.startswith('pose.bones["'):
by_bone.setdefault(fc.data_path.split('"')[1], []).append(fc)
repaired = 0
for n, arr in acc.items():
bad = [i + 1 for i, a in enumerate(arr) if a > thr] # -> pos/key index
if not bad:
continue
spans, s, e = [], bad[0], bad[0]
for i in bad[1:]:
if i - e <= DESPIKE_PAD * 2:
e = i
else:
spans.append((s, e))
s = e = i
spans.append((s, e))
parent = tgt_arm.data.bones[n].parent
chain = [n] + ([parent.name] if parent else [])
for s, e in spans:
a = max(1, s - DESPIKE_PAD)
b = min(len(pos[n]) - 2, e + DESPIKE_PAD)
if b - a > DESPIKE_MAX_WIN or b - a < 2:
continue
for bone in chain:
for fc in by_bone.get(bone, []):
kps = sorted(fc.keyframe_points, key=lambda k: k.co.x)
_hermite_patch(kps, a, b)
fc.update()
repaired += 1
print(f"[cc] --despike: {n} @ frame {f0 + s}"
f" ({(s) / 60:.2f}s) window {b - a}f via {'+'.join(chain)}")
print(f"[cc] --despike: repaired {repaired} glitch window(s), threshold {thr:.4f}")
def main():
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
args = dict(zip(argv[::2], argv[1::2]))
args, flags, i = {}, set(), 0
while i < len(argv):
if argv[i] == "--despike":
flags.add("despike")
i += 1
else:
args[argv[i]] = argv[i + 1]
i += 2
srcs, out = args["--src"].split(","), args["--out"]
target = args.get("--target", DEFAULT_TARGET_GLTF)
forced_name = args.get("--name")
fingers = args.get("--fingers", "track")
if fingers not in ("track", "static"):
raise SystemExit(f"--fingers must be 'track' or 'static', got '{fingers}'")
if fingers == "static":
# Unmapped bones ride their parent at rest offset, so dropping the finger
# entries makes each hand move as one rigid unit in the rig's rest hand pose.
dropped = [k for k, v in BONE_MAP.items()
if re.match(r"(thumb|index|middle|ring|pinky)_", v)]
for k in dropped:
del BONE_MAP[k]
print(f"[cc] --fingers static: ignoring {len(dropped)} finger bone mappings")
fbx_files = []
for s in srcs:
@@ -241,6 +356,8 @@ def main():
print(f"[cc] SKIP (no armature take): {f}")
else:
baked = retarget_one(src_arm, tgt_arm, act, name, scn, unit)
if "despike" in flags:
despike(baked, tgt_arm, scn)
done.append(baked)
print(f"[cc] {i + 1}/{len(fbx_files)} {name} "
f"({int(baked.frame_range[1])}f @60, from take '{act.name}')")
+2 -1
View File
@@ -23,7 +23,8 @@
import bpy, sys, os, re
from mathutils import Matrix, Quaternion, Vector
TARGET_GLTF = "/Users/behcetozanbozkurt/Documents/tinqs-ltd/ariki-game/assets/quaternius/base-characters/Universal Base Characters[Standard]/Base Characters/Godot - UE/Superhero_Male_FullBody.gltf"
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TARGET_GLTF = os.path.join(REPO_ROOT, "assets/quaternius/base-characters/Universal Base Characters[Standard]/Base Characters/Godot - UE/Superhero_Male_FullBody.gltf")
# Kevin "B-*" → Quaternius UE names. Fingers map 1:1 (both rigs have 3-segment fingers).
BONE_MAP = {
+38 -7
View File
@@ -57,6 +57,22 @@ def detect_prefix(arm):
raise RuntimeError("no *Hips bone found — not a Mixamo rig?")
# ── UAL/Quaternius-source mode (2026-07-21): retarget stock-skeleton clips (UAL,
# Kevin) onto a DIFFERENT-convention skeleton (e.g. the renamed Mixamo rig — its
# bone local frames differ, so raw clip playback tips her face-down). Identity
# bone map (names already match), source frame bones are the Quaternius names.
QUAT_BONES = ["pelvis", "spine_01", "spine_02", "spine_03", "neck_01", "Head"]
for _T in ("l", "r"):
QUAT_BONES += [f"clavicle_{_T}", f"upperarm_{_T}", f"lowerarm_{_T}", f"hand_{_T}",
f"thigh_{_T}", f"calf_{_T}", f"foot_{_T}", f"ball_{_T}"]
for _i in (1, 2, 3):
QUAT_BONES += [f"index_0{_i}_{_T}", f"middle_0{_i}_{_T}", f"ring_0{_i}_{_T}",
f"pinky_0{_i}_{_T}", f"thumb_0{_i}_{_T}"]
UAL_MAP = {n: n for n in QUAT_BONES}
# Per-mode source-frame bones (see retarget_one): mixamo | ual
SRC_FRAME_BONES = ("Hips", "Head", "LeftArm")
def apply_object_transform(obj):
"""Bake importer object-level rotation/scale into the armature data (meters, Z-up)."""
bpy.ops.object.select_all(action="DESELECT")
@@ -109,11 +125,12 @@ def retarget_one(src_arm, tgt_arm, action, name, scn, prefix, unit):
tgt_rest_q = {n: m.to_quaternion().normalized() for n, m in tgt_rest.items()}
C = (char_frame(tgt_rest, "pelvis", "Head", "upperarm_l")
@ char_frame(src_rest, prefix + "Hips", prefix + "Head", prefix + "LeftArm").inverted()).normalized()
@ char_frame(src_rest, prefix + SRC_FRAME_BONES[0], prefix + SRC_FRAME_BONES[1],
prefix + SRC_FRAME_BONES[2]).inverted()).normalized()
Cinv = C.inverted()
# Uniform size ratio from hips→head world distance (unit/scale agnostic).
h_src = (src_rest[prefix + "Head"].translation - src_rest[prefix + "Hips"].translation).length
h_src = (src_rest[prefix + SRC_FRAME_BONES[1]].translation - src_rest[prefix + SRC_FRAME_BONES[0]].translation).length
h_tgt = (tgt_rest["Head"].translation - tgt_rest["pelvis"].translation).length
tscale = h_tgt / h_src if h_src > 1e-5 else 1.0
@@ -127,7 +144,7 @@ def retarget_one(src_arm, tgt_arm, action, name, scn, prefix, unit):
for pb in tgt_arm.pose.bones:
pb.rotation_mode = "QUATERNION"
src_hips_rest_t = src_rest[prefix + "Hips"].translation
src_hips_rest_t = src_rest[prefix + SRC_FRAME_BONES[0]].translation
for f in range(f0, f1 + 1):
scn.frame_set(f)
@@ -181,18 +198,23 @@ def retarget_one(src_arm, tgt_arm, action, name, scn, prefix, unit):
def main():
global BONE_MAP, SRC_FRAME_BONES
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
args = dict(zip(argv[::2], argv[1::2]))
srcs, out = args["--src"].split(","), args["--out"]
limit = int(args.get("--limit", 0))
target = args.get("--target", DEFAULT_TARGET_GLTF)
clips_filter = [c.strip().lower() for c in args.get("--clips", "").split(",") if c.strip()]
if args.get("--map") == "ual":
BONE_MAP = UAL_MAP # identity: source bones already quat-named
SRC_FRAME_BONES = ("pelvis", "Head", "upperarm_l")
fbx_files = []
for s in srcs:
s = s.strip()
if os.path.isdir(s):
for root, _, files in os.walk(s):
fbx_files += [os.path.join(root, x) for x in sorted(files) if x.lower().endswith(".fbx")]
fbx_files += [os.path.join(root, x) for x in sorted(files) if x.lower().endswith((".fbx", ".glb", ".gltf"))]
else:
fbx_files.append(s)
if limit:
@@ -218,22 +240,31 @@ def main():
name = clip_name(f)
pre = set(bpy.data.objects)
pre_actions = set(bpy.data.actions)
if f.lower().endswith((".glb", ".gltf")):
bpy.ops.import_scene.gltf(filepath=f)
else:
bpy.ops.import_scene.fbx(filepath=f)
new_objs = [o for o in bpy.data.objects if o not in pre]
src_arm = next((o for o in new_objs if o.type == "ARMATURE"), None)
new_acts = [a for a in bpy.data.actions if a not in pre_actions]
if clips_filter:
new_acts = [a for a in new_acts if any(c in a.name.lower() for c in clips_filter)]
if src_arm is None or not new_acts:
print(f"[mixamo] SKIP (no armature/action): {f}")
else:
bpy.context.view_layer.update()
prefix = detect_prefix(src_arm)
prefix = "" if args.get("--map") == "ual" else detect_prefix(src_arm)
# Importer unit scale (cm→m 0.01) — capture BEFORE transform_apply wipes it;
# the hips translation math needs it (fcurves stay in source units).
unit = src_arm.scale.x
apply_object_transform(src_arm)
act = retarget_one(src_arm, tgt_arm, new_acts[0], name, scn, prefix, unit)
for act_src in new_acts:
out_name = act_src.name if args.get("--map") == "ual" else name
if args.get("--map") == "ual":
act_src.name += "__src" # free the clean name — Blender dedupes the output to .001 otherwise
act = retarget_one(src_arm, tgt_arm, act_src, out_name, scn, prefix, unit)
done.append(act)
print(f"[mixamo] {i + 1}/{len(fbx_files)} {name} ({int(act.frame_range[1])}f, prefix='{prefix}')")
print(f"[mixamo] {i + 1}/{len(fbx_files)} {out_name} ({int(act.frame_range[1])}f, prefix='{prefix}')")
for o in new_objs:
bpy.data.objects.remove(o, do_unlink=True)
for a in new_acts: