HardYards/tools/character/windclip_candidates/README.md
type-two 91bc391633 Lane E, Sprint 13: no wind clip — the pipeline can't supply one, and the lean was never a clip
Gate 2.4 asked for lean/stagger/brace clips staged by wind band. The answer,
measured twice, is that the asset shouldn't exist.

1. The lean cannot be a clip. _rotOnly drops Hips.quaternion from every clip at
   load (player.js:53, applied :239). Parsed the shipped pack to confirm rather
   than trust the read: 17 clips, 195 channels each, 64 survive. A lean baked
   into a clip's hips is discarded. And stripping it is right — the wind's
   bearing swings (the change; site_02's venturi) and a canned clip has one
   fixed lean axis, so only the sim can aim it. Same trick as the knockdown and
   climbY. The lean is D's root pitch; thresholds proposed off Beaufort 7
   ("inconvenience felt when walking against the wind" = 13.9 m/s), which lands
   storm_01 never leaning, storm_03 gusting through the line, storm_02 in gale.

2. Mixamo has no wind animations. Searched the real API, not assumed: shiver
   returns zero, brace returns eleven ledge-hang clips, wind returns Catwalk
   Turn. The factory header already recorded this in Sprint 3.

Rendered the two real candidates on a skinned rig, twice each — as authored and
as the game shows them. Pushing is a squat shoving a couch. Leaning is a man
waiting for a bus. Both rejected. Both would have passed every check we own:
they load, they're metre-scale, their nodes survive. Dims can't answer "is this
the right shape" — the bike rule, catching a whole feature this time.

Brace and stagger already ship (TakeCover, StumbleBack, Reaction). D should wire
the root lean first; I'll hand-author a WindPosture additive into the gap we can
both see, if one remains. Spine rotation survives _rotOnly, so it would land.

Evidence, harness and repro in tools/character/windclip_candidates/, plus the
four pipeline traps I hit in order so the next lane doesn't.

selftest 335/0/0, unchanged — no asset added, no shipped file touched.
2026-07-18 00:45:22 +10:00

83 lines
4.2 KiB
Markdown

# Wind clips — the Mixamo candidates, and why they're rejected
Lane E, Sprint 13 gate 2.4 ("wind clips for the player — lean / stagger / brace,
staged by wind band, through the Mixamo pipeline").
**Outcome: no clip shipped. The Mixamo pipeline cannot supply a wind lean, and the
lean was never going to be a clip in the first place.** Both halves of that are
measured, and the evidence is in this folder. Full argument in THREADS [E] 2026-07-17/18.
## 1. Mixamo has no wind animations
Searched the real API (`fetch.cjs search`, on ultra), not assumed:
| term | what comes back |
|---|---|
| `brace` | eleven **Braced Hang** ledge-climbing clips |
| `wind` | Standing Idle 04, Catwalk Walk Turn 180 |
| `shiver` | nothing, 0 results |
| `stagger` | three walks (Swagger Walk, Walking, Walking) |
| `lean` | Leaning On A Wall, Leaning, One Shoulder Lean |
| `push` | Pushing, Push Up, Button Pushing |
The factory's own header already recorded half of this in Sprint 3 — *"Hammering /
Sweeping Floor / **Bracing** don't exist on Mixamo — skipped."* This is the case
the asset-pipeline skill names directly: *truly bespoke clips don't exist on
Mixamo — hand-author, don't keep searching.*
## 2. The two real candidates, rendered and rejected
`skinshot.py` films a skinned Mixamo FBX twice: **as authored**, and **as the game
shows it** (Hips rotation cleared, which is what `player.js:_rotOnly` does at load).
The second row is the honest one.
- **`rejected_Pushing.png`** — not a wind lean. A deep squat shoving something heavy
along the ground, standing up at the end. It reads as *moving a couch*. The
forward angle I hoped to borrow is a squat, not a lean.
- **`rejected_Leaning.png`** — a casual weight-on-the-back-foot lean, the "propped
against a bar" pose. Hands slack, no tension, near-identical across all five
frames. A person waiting for a bus, not one in a gale.
Neither is close enough to fix by retiming. Shipping either would have passed every
check we have — they load, they're metre-scale, their nodes survive — and been wrong
on screen. That's the bike rule: **the verify can't answer "is this the right shape."**
## 3. The useful technical result
In both strips the raw and stripped rows are **near-identical**, which is worth
knowing: these clips carry their lean in the **spine**, and spine rotation survives
`_rotOnly` intact. So a hand-authored posture (spine curl, shoulder hunch, arm up)
*would* survive the loader. What cannot survive is a lean authored into the **hips**
`_rotOnly` drops `Hips.quaternion` from every clip (`player.js:53`, applied `:239`).
Measured on the shipped pack: 17 clips, 195 channels each, **64 survive**.
Which is why the lean belongs in `player.sim.js` as a root pitch aimed at
`wind.dirAt(t)` — the wind's bearing swings (the change; site_02's venturi) and a
canned clip has one fixed lean axis. Same trick as the knockdown and `climbY`.
## Reproducing
```sh
# on ultra (the FBX libraries and the Mixamo login live there; JING5 has neither)
mkdir -p /tmp/shadeswind && cd /tmp/shadeswind
cp ~/Documents/mixamo-fetch/fetch.cjs .
printf 'Pushing\nLeaning\n' > wishlist.txt
SKIN=1 node fetch.cjs "X Bot" # SKIN=1: needs a mesh to judge a silhouette
/Applications/Blender.app/Contents/MacOS/Blender -b --factory-startup \
-P skinshot.py -- /tmp/shadeswind/out/Pushing.fbx Pushing
```
Notes for whoever next reaches for this pipeline:
- `fetch.cjs` reads its token from a **logged-in Brave on ultra via CDP** (port 9222).
Nobody types a password; the session is already there. It 429s after a handful of
exports — space them out.
- `resolveChar` defaults to `Grandma_mixamo`, which is not currently uploaded to the
account. **`X Bot`** is a Mixamo stock rig and works; for anim-only export the source
rig is irrelevant anyway, since `_rotOnly` keeps rotations only.
- Anim-only FBX (`SKIN` unset) has **no mesh** and renders nothing. Judging a
silhouette needs `SKIN=1`.
- Don't retarget onto `Hum_M_1_mixamo.fbx` to preview: its armature carries FBX's
0.01 unit scale and the clip's action doesn't, so the body collapses to ~3 cm.
That's the same "peds cannot round-trip through Blender" trap `build_player_anims.py`
documents.