@rig-c/pose 0.39.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -152,10 +152,24 @@ The record says it: `"depth": { "gait" }` in `params`, and the `CLEAN` line ends
152
152
 
153
153
  **Drawn** — `tools/skeleton.ts` (not shipped, the demo's stick-figure pane) colours each segment a frame's `depth` names: toward the camera warm (orange), away cool (blue), flat neutral (grey), the rest of the figure thin and dark, R and L at the wrists and ankles, and a legend in the frame's top-left corner naming the source and the gait. A sequence without depth is drawn exactly as before.
154
154
 
155
- **Layered** — `retarget` orders the limbs (`r_arm`, `l_arm`, `r_leg`, `l_leg`) front to back on every frame of a sequence that carries depth. Each joint's depth is its parent's plus its segment's, sign × rest × √(1 − ratio²) px (rest = drawn length ÷ ratio), from the shoulders and hips at 0; a limb's depth is the mean of its middle and end joints'; the nearest comes first, and equal depths (to 0.01 px) share a layer. The `DEPTH` line counts the frames ordered and which limb was in front alone how often, and the report carries `layers` ([below](#what-it-does)). Nothing is keyed from it: the motion is byte for byte the same with and without depth, and changing a rig's draw order from it is the consumer's.
155
+ **Layered** — `retarget` orders the limbs (`r_arm`, `l_arm`, `r_leg`, `l_leg`) front to back on every frame of a sequence that carries depth. Each joint's depth is its parent's plus its segment's, sign × rest × √(1 − ratio²) px (rest = drawn length ÷ ratio), from the shoulders and hips at 0; a limb's depth is the mean of its middle and end joints'; the nearest comes first, and equal depths (to 0.01 px) share a layer. The `DEPTH` line counts the frames ordered and which limb was in front alone how often, and the report carries `layers` ([below](#what-it-does)). Nothing is keyed from it unless `--draw-order depth` asks ([below](#draw-order-from-depth---draw-order-depth)): without the flag the motion is byte for byte the same with and without depth.
156
156
 
157
157
  On the synthetic fixtures ([`examples/walk/make-depth.ts`](https://github.com/rig-c/pose/blob/main/examples/walk/make-depth.ts): the walk's proportions, made in 3D from joint angles and drawn by a camera in front, behind or beside, so the generator knows every joint's true depth; `test/depth.test.ts`): on the frontal run in place, the 24 shin-frames drawn at ≤ 0.5 of rest (down to 0.409), four on every stride of each leg, read −1 under `--depth run`, as the generator draws them, and +1 under `--depth walk`; on the frontal walk, the 32 shin-frames the generator draws toward the camera read +1, and none reads −1; seen from behind, the run's kicked-back shins read +1; seen from the side, the 480 segment-frames read 0.995–1.000, every sign 0, and no segment is drawn warm or cool. `layers` agrees with the generator on 60 of the run's 60 frames and 44 of the walk's 48, two limbs counting as ordered when their true depths differ by at least the depth a limb can hold while both its segments read flat (√(1 − 0.95²) × (upper + lower ÷ 2): 31.2 px for an arm, 35.9 for a leg), and tied otherwise. The walk's four (frames 6, 18, 30, 42) are where both thighs read flat while the generator has the forward leg 36.4 px in front: the band hides it.
158
158
 
159
+ ### Draw order from depth: `--draw-order depth`
160
+
161
+ ```sh
162
+ rig-pose retarget --rig rig/ --map map.json --sequence run.pose.json --out motion.json --name run --draw-order depth
163
+ ```
164
+
165
+ With `--draw-order depth` (off by default; without it nothing changes) `retarget` keys the legs' draw order from the sequence's depth ([#304](https://github.com/rig-c/pose/issues/304)), so a run's kicked-back leg is drawn behind the standing one and a walk's forward foot in front. It writes rig-c's draw-order timeline, `"drawOrder": [{ "t", "offsets": [{ "slot", "offset" }] }, …]` on the animation beside its `tracks`: each key moves slots against the setup order, and a key with no `offsets` is the setup order again.
166
+
167
+ - **The pair.** The legs: a leg is the map's chains naming its knee or ankle, their bones and the bones under them no other chain maps. Its slots are the slots on those bones. A slot of an alternate set (`alternates`) on none of them goes with the leg the set depicts, so a set keeps its own switch and takes the order of the leg it replaces. The legs are keyed when their slots overlap in the draw order, filling one unbroken run of the setup order. A slot of anything else drawn between them would be re-layered by the swap, and the setup order is not changed here. The pair is then not keyed, and the line names that slot. The arms across the trunk are a later card.
168
+ - **The rule.** On each frame the leg whose depth (`layers`, above) is the lower is drawn behind: its slots first in the pair's run, in their setup order, then the other's. The depths must differ by at least #281's tie, √(1 − 0.95²) × (thigh + shin ÷ 2) of the rest lengths (35.89 px on the fixtures). Inside the tie, or where a leg is not ordered, the order before holds. Before the first decided frame it is the setup order, or on a looped sequence the order the clip ends in. A key is written where the order changes.
169
+ - **No depth.** On a sequence without depth the flag is refused (`DRAWORDER_NO_DEPTH`): the order would come from nowhere, and a motion written without it would read as ordered.
170
+
171
+ On #281's fixtures, retargeted onto a limb-cut rig whose legs overlap (the left leg drawn first; `test/draworder.test.ts`): on the frontal run the order changes on frames 8, 18, … 58, within a frame of each change of support (the last within two of the loop's seam). On each of the 53 frames where the swinging shin reads away, that leg is drawn behind. On frame 12 none of the 8,763 pixels the standing leg draws shows the kicked-back leg, against 2,192 in the setup order. On the walk, each of the 32 shin-frames read toward draws that leg in front. The tie costs 18 of the 36 run frames the generator orders: the late swing (frames 5–7, 15–17, …), where the folding knee comes forward of the push-off leg by more than the tie in 3D but by 22.7–31.7 px in the derived depth, so the order holds. Without the tie the run would change order mid-swing (frames 3, 13, …), and 30 of the 53 kicked-back frames would draw the kicked-back leg in front.
172
+
159
173
  ## What it does
160
174
 
161
175
  ```sh
@@ -193,9 +207,11 @@ and what the run cannot decide for you it lists:
193
207
  - `SEAM` — rig-c's seam reading (`rigc render --seam`, rig-c ≥ 2.60.0) over the new animation, for the rig's declared neighbour pairs ([#216](https://github.com/rig-c/pose/issues/216)): one line per span of a **gap** (background inside the joint) or a **ghost** (one piece's overlap swung clear of its partner) over the one-cell floor, with the pair, the frames and their times, the rate and the most cells; per pair the frames rigc named that were one cell thick (`under the floor — not reported`), a pair not read and why, or rigc's SKIP (the two drawings share no cell at rest — nothing measured, never a seam that held); then `n span(s) over k pair(s) at <fps> fps — r of d declared pair(s) read`. **The pairs**: a rig spec carries none — `rig.json` holds bones, slots and skins — so they come from @rig-c/parts' `parts.json`, named with `--parts <file or directory>` (its build's `--out`, beside `rig/`): each limb's consecutive segments (a part's `limb` record: `name`, `segment`, and below a joint `joint`), a pair named twice counting once, each part drawn by the slot of its name — the pairs @rig-c/parts' own `check` reads (rig-c/parts#503). A pair whose part has no slot of its name is `not read`, named. Without `--parts`, or with a `parts.json` that holds no limb, the line is `SEAM no neighbour pair declared — <why>; seams: []`, and where the rig's parent directory holds a `parts.json` it says so. **The floor** is @rig-c/parts' so the two tools agree: a frame's gap or ghost counts when it holds more cells than its box is long (its longer side, in cells) — thicker than one cell; a crack one cell deep is the half-opacity edge moving by less than a cell. Its own render, at the rate the folds are read at; it moves no byte of the motion or the build, and never changes the exit or the gate. A malformed `parts.json` is an input and is refused (`PARTS_JSON`).
194
208
  - `JITTER` — one line per bone a chain keys (a face or body dial bone excepted), in map order ([#236](https://github.com/rig-c/pose/issues/236)): the mean |second difference| of its local rotation as rig-c's poser posed the gated build (its world matrix in its parent's axes, so the rotate key up to a constant), against the same figure of its map pair's picture angle less the pair of the nearest keyed bone above it, in °/frame², and the ratio. Both are read on the same triples, three consecutive frames on which every pose point the two pairs need is good. A pair whose figure rounds to 0 is still, and gets no ratio. A group's jitter ratio is its bones' together; the line names the bone that moved it. A reading, not a gate.
195
209
  - `REACH` — a `reach` chain ([below](#the-map-rig-pose-map1)) whose aim departs from its own pair's angle ([#236](https://github.com/rig-c/pose/issues/236)): why (its origin sits off the anchor's at setup, a lever; and the bones above it the animation turns, or translates other than by the anchor's travel), the largest departure with its frame, the departure's mean |second difference|, and the aim's against the pair's, in °/frame², with the ratio. A reach whose bone sits on the anchor's origin with nothing above it moved is aimed along the pair and prints none. This line also names, with `--raised-by-head` or a `"by-hip"` set, a placed arm out of reach (below); the two read `REACH chain …` and `REACH set …`. A reading, not a gate.
210
+ - `REACH-ORIGIN` — one line per `reach` chain ([#282](https://github.com/rig-c/pose/issues/282)): how far its bone's origin starts from the source joint it re-aims from (the translated joint, at the translated bone's origin), at setup and the most on the frames with its frame, in rig units and as a fraction of the segment (the pair's median length, scaled). Past **0.20** of the segment it is followed by `WARN REACH_ORIGIN: …`, naming the `JITTER` line that shows the cost. 0.20 is the lever's 1.25×: the aim from an origin a fraction *f* up the pair turns about 1 ÷ (1 − *f*) times the pair's turn — measured on a synthetic trunk with its origin 0, 15, 30, 45 and 75 units up a 150-unit segment, the chest's `JITTER` read 1.00×, 1.11×, 1.25×, 1.43× and 2.00×. A map without a `reach` chain prints neither. A reading, not a gate.
196
211
  - `POSTURE` — when the map has a pelvis chain (joints `r_hip`, `l_hip`) and a `translate`: the hip line's tilt range, and the sway — the x-range of mid_hip − mid_shoulder — in rig units and in shoulder widths, the pose's beside the posed rig's. A reading, not a gate. A frame on which either hip is `inferred` is left out of the tilt. When that is every frame, as after `clean --hips-inferred`, the line is `hip tilt not read: hips inferred (declared at clean)` and the report's `posture` has `"tilt": null` with `"tiltWhy"`; on some frames only, the tilt is read over the others and the line counts the frames left out (`posture.hipsInferred`). The source is read from the clean record, or is `source not stated` when the joints carry the state and the record does not. With a `measured` record ([#86](https://github.com/rig-c/pose/issues/86), [#106](https://github.com/rig-c/pose/issues/106)), the read, interpolated, scaled and held frames are the measurement, so the tilt is read on all four, while `unmeasured` frames (`fit.corr < 0.9`) are left out and named. The line adds `tilt measured (<method>) on N frame(s): R read, I interpolated, H held (held: <spans>)` (plus `scaled` and `unmeasured` spans when present). The report's `posture` carries `tiltMeasured`. The sway reads the hips as before. When the pelvis bone was keyed from the sequence's pelvis track ([#295](https://github.com/rig-c/pose/issues/295)), the reference tilt is the track's on the frames it read and the hip line's on the frames it declined, and the line names it as the `PELVIS` line does (`reference 30.7° from silhouette (…; all 24 frames from the track)`, `posture.tiltTrack`). The track's turn also swings what the pelvis bone carries, so a second `sway:` line states that the rig's x-range includes the pelvis track's turn, how far it carries the trunk's origin, and the trunk alone — mid_shoulder about that origin — beside the reference (`posture.sway.carried`). Without a track the lines are unchanged. With `--plant-feet`, `POSTURE` also prints one `bounce` line per support span ([#107](https://github.com/rig-c/pose/issues/107), `posture.bounce` in the report): the detrended (centred ±12-frame moving mean removed) vertical standard deviation, rig/reference ratio and correlation for the hip, the neck and `eyes − neck`.
197
212
  - `SWITCH` — with `alternates` in the map ([below](#alternate-drawings-alternates)): each span on which a set of alternate drawings shows, its slots, its frames, how far the pose was from the set's key skeleton, how far each bone with a `drawn` angle was turned from it, and where each of its boundaries was moved to (`moved`, [below](#alternate-drawings-alternates)).
198
213
  - `PHASE` — one per set with a `phase` ([below](#alternate-drawings-alternates), [#286](https://github.com/rig-c/pose/issues/286)): the supporting foot it shows on, every span it showed and its frame count, and the support hand-over each span was switched on and off at (`on frame 19 r → l, off frame 31 l → r`; `none` where no foot supports). The report's `switches` row of such a set carries the same under `phase`: `{ step, side, on: { frame, from } | null, off: { frame, to } | null }`.
214
+ - `SWITCH-MIN` — one per set the minimum span held back ([#279](https://github.com/rig-c/pose/issues/279)): the runs on which it was the nearest set within the tolerance but shorter than `--switch-min`, so not shown, each with its frames, frame count and seconds (the first four, then how many more), and how many runs it was nearest on with the longest. A set that matched and was held back reads apart from one that never matched; a set with nothing held back prints no line, and the report's `switchRuns` lists every set.
199
215
  - `UNCOVERED` — with `alternates` in the map: every fold on a frame where no switch took the folding mesh off screen. This is the list of poses still to paint.
200
216
  - `RANGE` — a chain's `range` ([below](#a-joints-range-range)): a bone whose rotate key the pose asks past its range, outside every span of a set depicting its chain, is clamped at the limit; one line per run, with the frames, the seconds and the largest overflow. The demand for an alternate.
201
217
  - `ART-RANGE` — inside a set's span: a bone with a `drawn` angle in the set, turned from it past its limits — the set's `turn` for that bone (held at the limit) or, without one, its chain's `range` (the bone follows the pose). Listed as the pose asked it either way: the demand for another drawing of that set.
@@ -204,6 +220,7 @@ and what the run cannot decide for you it lists:
204
220
  - `REACH` — with `--raised-by-head`, or a `"by-hip"` set: a placed span whose target was out of the arm's reach, too far or too near. It names the frames, which way, and the shortfall in eye distances (hip widths for `"by-hip"`). The wrist was placed at the reach limit instead. This is not `ART-RANGE`: the rig's proportions were the limit, not the art's range. A too-near frame is placed at the limit like a too-far one, and graded at the placed distance.
205
221
  - `LEG-SCALE` — with `--leg-scale` ([below](#the-legs-foreshortening-and-planted-feet)): per leg segment, its straight length in px and as a share of `FEET`'s leg length, the spread of its picture length over it, the limits in px, the frames clamped at each limit, where its leg was free on clamped frames how far the clamps on that leg left the free ankle above or below the picture's (in the rig's leg at setup, thigh origin → ankle, and in `FEET`'s px; `legScale[].free` in the report, [#237](https://github.com/rig-c/pose/issues/237)), and (with `--plant-feet`) one `past the floor (support)` line per supporting run that asked under the floor ([#133](https://github.com/rig-c/pose/issues/133), `legScale[].support` in the report).
206
222
  - `DEPTH` — when the sequence carries depth ([above](#derived-depth-clean---depth)): where it came from, on how many frames the limbs were ordered front to back, and which limb was in front alone how often — `DEPTH layers from the sequence's derived depth (clean --depth run): the limbs ordered front to back on N of N frame(s); in front alone: r_arm a, l_arm b, r_leg c, l_leg d; tied in front on k (the report's layers)`.
223
+ - `DRAWORDER` — with `--draw-order depth` ([above](#draw-order-from-depth---draw-order-depth)): the keys and the spans they key, per leg drawn behind, cut to 260 characters — `DRAWORDER from depth, 6 key(s) over 3 span(s) (leg_r behind leg_l 8–17, 28–37, 48–57)` — and a line per pair not keyed, saying why.
207
224
  - `PELVIS` — when the map has a pelvis chain (one bone, joints `r_hip → l_hip`) or the sequence a pelvis track ([above](#the-pelvis-track-rig-pose-pelvis)): where the pelvis bone's rotation came from and its swing — `PELVIS: from <source>, swing N°`, or `PELVIS: from hip line (no track)` — with the frames keyed from the hip line on a track that declined them.
208
225
  - `PIVOT` — when the map translates a bone and has both legs: the translated bone's origin against its thigh roots' midpoint at setup, in units and hip widths — the pivot every hip turn swings the legs about.
209
226
  - `FEET` — when the map has both legs: one `segment` line per mapped leg segment with its picture length ÷ straight length (min · median · max), the scale `--leg-scale` would ask before its clamp, printed with or without the flag so the minimum can be read before choosing the range ([#237](https://github.com/rig-c/pose/issues/237)); the supports and jumps read off the reference ankles (planted or not), and on the posed build, outside the jumps, the lowest ankle's vertical range and each support span's worst vertical range and horizontal drift of the supporting ankle, in leg lengths.
@@ -226,8 +243,10 @@ and what the run cannot decide for you it lists:
226
243
 
227
244
  Each figure is formatted as the full line formats it, from the same record. Refusals print the same in every mode, and the exit code and the gate do not change with it; `--verbose` with `--summary` is a usage error.
228
245
 
229
- `--report <file>` writes all of it as JSON (`rig-pose-report/1`): `spec`, `rigc` (`{ version, entry }`: the rig-c that gated it), `rig`, `animation`, `inputs` (consumed rig spec, map and pose files, each with invocation-relative `path` and exact-byte `sha256`; a folder rig resolves to its actual rig.json; these three identities do not claim to bind optional base motion, sets or images), `frames`, `duration`, `frameTimes` (the exact motion-key time for each pose frame; never FPS inferred from duration), `render` (the separate renderer sampling domain, `{ fps, frames }` from frame-set metadata, or `null` when not measured), `cleaned` (the sequence's `cleaned` record, [above](#clean-first-rig-pose-clean)), `options` (every figure the run used, `switchTolerance`, `switchMin`, `switchAt`, `legScale`, `plantFeet`, `stance`, `floor` and `raisedByHead` included; `reduceKeepPlanted: true` only with `--reduce-keep-planted`), `scale`, `scaleFit` (the `SCALE` reading: `segment`, `leg`, `hip`, `outside`, `travel`; `null` without a scale), `holds`, `short`, `endOn`, `bends`, `flips`, `gate` (rig-c's gate summary, a string), `landings`, `jitter`, `reach`, `posture`, `folds`, `seams`, `seamPairs`, `body` (`standing`, `read`, `over45`, `framesOver45`, and with `body.yaw` or `body.sets` in the map, `yaw` and `sets`), `legScale`, `pivot`, `pelvis` (the `PELVIS` reading, [above](#the-pelvis-track-rig-pose-pelvis): `{ source, trackSource, bone, chain, swing, track, hipLine, declined }` — `source` the track's or `"hip line"`, `trackSource` the sequence's `pelvisSource` or `null`, `swing` the peak-to-peak of the bone's rotate keys in degrees, `track` and `hipLine` the frames each keyed, `declined` the frames the track declined as `[first, last]` pairs; `null` when the map has no pelvis chain and the sequence no track), `feet` (`planted`, `legLength`, `straight`, `rigLeg`, `segments`, `supports`, `jumps`, `jumpEvents`, `steps`, `changes`, `lowered`, `floor`, `shifts`, `blend`, `closure`, and the posed `reading`; `straight` is each side's 95th-percentile picture length hip → ankle in px and `rigLeg` each side's rig leg at setup (thigh origin → ankle) in world units, both `null` without both legs; `segments` holds one `{ chain, bone, joints, straight, ratio: { min, median, max }, why? }` per mapped leg segment, read with or without `--leg-scale` (`straight` and `ratio` `null`, and `why` said, where it was not measured), [#237](https://github.com/rig-c/pose/issues/237); a jump is an inclusive `[first, last]` pair of frames; `lowered` holds `{ frames, was, now, cause? }` runs (`cause`, where `now` is `null`: `released` or `higher`), present only with `--plant-feet` on a sequence not declared in place ([#113](https://github.com/rig-c/pose/issues/113)); `floor` is the world y every support span was planted on and `shifts` holds one `{ side, frames, shift }` per support span in world units, `null` and empty without `--plant-feet`; `blend` is `{ bounds, runs: [{ side, frames, k: { min, max } }], over: [{ side, frames, needed, left }] }`, the supporting leg stretched on the blend frames, `null` without `--plant-feet`; `closure` is `{ drift, frames }`, the hip's x drift closed over an in-place loop, `null` unless the sequence is `"in-place"` and `--plant-feet` is given), `stance` (`tolerance`, `release`, `minSeconds`, `bounds`, `runs`: `{ state, side, frames, maxHeight, maxDepth?, k, absorbed?, moved? }` (`state` is `both`, `lifted` or `below`; `maxDepth`, on a `below` run, is the free ankle's largest distance below the supporting one in leg lengths; `moved`, on a `lifted` or `below` run, the frames whose free ankle a `--leg-scale` clamp moved off the pose's height, as `legScale[].free` is written, [#237](https://github.com/rig-c/pose/issues/237)), `over`: `{ side, frames, needed, left }`; `null` without `--stance`), `shear`, `idleDropped`, `idleKeptOut` and `merged` ([below](#the-idle-what-the-retarget-drops)), `reduce` (the tolerances, each row of `REDUCE` and `ankleDrift`, [below](#few-keys---reduce)), and the demand lists. The fields a run is graded by:
230
- With a sequence that carries depth ([#281](https://github.com/rig-c/pose/issues/281), [above](#derived-depth-clean---depth)) the report also carries `layers`, one `{ frame, t, order, depth }` per frame: `order` the limbs front to back as layers, each an array of the limbs that share it (`[["r_arm"], ["l_arm", "r_leg"], ["l_leg"]]`), and `depth` each ordered limb's depth in px, + toward the camera. A sequence without depth writes no `layers` key, and its report is what it was.
246
+ `--report <file>` writes all of it as JSON (`rig-pose-report/1`): `spec`, `rigc` (`{ version, entry }`: the rig-c that gated it), `rig`, `animation`, `inputs` (consumed rig spec, map and pose files, each with invocation-relative `path` and exact-byte `sha256`; a folder rig resolves to its actual rig.json; these three identities do not claim to bind optional base motion, sets or images), `frames`, `duration`, `frameTimes` (the exact motion-key time for each pose frame; never FPS inferred from duration), `render` (the separate renderer sampling domain, `{ fps, frames }` from frame-set metadata, or `null` when not measured), `cleaned` (the sequence's `cleaned` record, [above](#clean-first-rig-pose-clean)), `options` (every figure the run used, `switchTolerance`, `switchMin`, `switchAt`, `legScale`, `plantFeet`, `stance`, `floor` and `raisedByHead` included; `reduceKeepPlanted: true` only with `--reduce-keep-planted`; `reduceChains` only with a per-chain `--reduce`), `scale`, `scaleFit` (the `SCALE` reading: `segment`, `leg`, `hip`, `outside`, `travel`; `null` without a scale), `holds`, `short`, `endOn`, `bends`, `flips`, `gate` (rig-c's gate summary, a string), `landings`, `jitter`, `reach`, `posture`, `folds`, `seams`, `seamPairs`, `body` (`standing`, `read`, `over45`, `framesOver45`, and with `body.yaw` or `body.sets` in the map, `yaw` and `sets`), `legScale`, `pivot`, `pelvis` (the `PELVIS` reading, [above](#the-pelvis-track-rig-pose-pelvis): `{ source, trackSource, bone, chain, swing, track, hipLine, declined }` — `source` the track's or `"hip line"`, `trackSource` the sequence's `pelvisSource` or `null`, `swing` the peak-to-peak of the bone's rotate keys in degrees, `track` and `hipLine` the frames each keyed, `declined` the frames the track declined as `[first, last]` pairs; `null` when the map has no pelvis chain and the sequence no track), `feet` (`planted`, `legLength`, `straight`, `rigLeg`, `segments`, `supports`, `jumps`, `jumpEvents`, `steps`, `changes`, `lowered`, `floor`, `shifts`, `blend`, `closure`, and the posed `reading`; `straight` is each side's 95th-percentile picture length hip → ankle in px and `rigLeg` each side's rig leg at setup (thigh origin → ankle) in world units, both `null` without both legs; `segments` holds one `{ chain, bone, joints, straight, ratio: { min, median, max }, why? }` per mapped leg segment, read with or without `--leg-scale` (`straight` and `ratio` `null`, and `why` said, where it was not measured), [#237](https://github.com/rig-c/pose/issues/237); a jump is an inclusive `[first, last]` pair of frames; `lowered` holds `{ frames, was, now, cause? }` runs (`cause`, where `now` is `null`: `released` or `higher`), present only with `--plant-feet` on a sequence not declared in place ([#113](https://github.com/rig-c/pose/issues/113)); `floor` is the world y every support span was planted on and `shifts` holds one `{ side, frames, shift }` per support span in world units, `null` and empty without `--plant-feet`; `blend` is `{ bounds, runs: [{ side, frames, k: { min, max } }], over: [{ side, frames, needed, left }] }`, the supporting leg stretched on the blend frames, `null` without `--plant-feet`; `closure` is `{ drift, frames }`, the hip's x drift closed over an in-place loop, `null` unless the sequence is `"in-place"` and `--plant-feet` is given), `stance` (`tolerance`, `release`, `minSeconds`, `bounds`, `runs`: `{ state, side, frames, maxHeight, maxDepth?, k, absorbed?, moved? }` (`state` is `both`, `lifted` or `below`; `maxDepth`, on a `below` run, is the free ankle's largest distance below the supporting one in leg lengths; `moved`, on a `lifted` or `below` run, the frames whose free ankle a `--leg-scale` clamp moved off the pose's height, as `legScale[].free` is written, [#237](https://github.com/rig-c/pose/issues/237)), `over`: `{ side, frames, needed, left }`; `null` without `--stance`), `shear`, `idleDropped`, `idleKeptOut` and `merged` ([below](#the-idle-what-the-retarget-drops)), `reduce` (the tolerances, each row of `REDUCE` and `ankleDrift`, [below](#few-keys---reduce)), and the demand lists. The fields a run is graded by:
247
+ With a sequence that carries depth ([#281](https://github.com/rig-c/pose/issues/281), [above](#derived-depth-clean---depth)) the report also carries `layers`, one `{ frame, t, order, depth }` per frame: `order` the limbs front to back as layers, each an array of the limbs that share it (`[["r_arm"], ["l_arm", "r_leg"], ["l_leg"]]`), and `depth` each ordered limb's depth in px, + toward the camera. With `--draw-order depth` ([#304](https://github.com/rig-c/pose/issues/304)) it carries `drawOrder`: `{ source: "depth", pairs, keys, spans }`. `pairs` gives per pair `{ a, b, limbs, slots, keyed, tie }` (and `why` when not keyed), `keys` the timeline with each key's `frame`, and `spans` every span of one order `{ behind, front, frames, keyed }` (`keyed` false where the setup order already draws it so). The options carry `drawOrder: "depth"`. A run without the flag writes neither. A sequence without depth writes no `layers` key, and its report is what it was.
248
+
249
+ With a map that has a `reach` chain ([#282](https://github.com/rig-c/pose/issues/282)) the report also carries `reachOrigin`, one `{ chain, bone, joints, anchor, setup, posed: { max, frame, t }, segment, ofSegment, warn, threshold }` per reach chain in map order: the `REACH-ORIGIN` line's figures. `setup` and `posed.max` are the distance from the bone's origin to the translated bone's (where the rig puts the source joint), in rig units; `segment` the reached length; `ofSegment` the larger distance ÷ `segment`; `warn` whether it is past `threshold` (0.2). A map without one writes no `reachOrigin` key.
231
250
 
232
251
  Report event objects with a pose `frame` or inclusive `frames` also carry exact `t` or `times` from `frameTimes`; existing `seconds` remain span lengths, with additive `spanSeconds`. Holds preserve their frame-index `from`/`to`. Existing scan joint/chain identities remain beside map-derived `bone` (a unique mapped target) or `bones` (all actual mapped targets); no joint-name heuristic chooses a target. `feet.jumps` stays an array of original frame pairs; `feet.jumpEvents` adds matching `{ frames, times }` objects. Fold, uncovered and seam rows preserve their discrete `times` and gaps, declare `domain: "render"`, and use the separate root `render` domain, never fabricated nearest pose-frame indices. The run hashes map/pose from the exact buffers passed to their readers, snapshots the resolved rig file before its gate, and rechecks all three before user-output writes. The writer validates coordinates before output writes and never changes motion keys. The switch diagnostic `moved.frame` is a selected-to-keyed pair with signed `frames`/`seconds` displacement; it is preserved as diagnostic data, not reinterpreted as an inclusive interval. An explicitly supplied uniform writer clock requires both positive `fps` and seconds offset `frame0`; canonical pose input does not yet declare FPS, so the retarget producer always writes exact `frameTimes`.
233
252
 
@@ -247,6 +266,7 @@ Report event objects with a pose `frame` or inclusive `frames` also carry exact
247
266
  The demand lists:
248
267
 
249
268
  - `switches: [{ set, chains, slots, hides, offsets, from, to, frames: [first, last], distance: { max, median }, artTurn: { <bone>: { max, median } }, moved: { on, off } }]` — one per span shown; `from` is the first frame's time, `to` the time the base art comes back (the animation's end when the span runs to it), `distance` in degrees over the frames shown, `artTurn` |wrap(target − drawn)| in degrees per bone the set has a `drawn` angle for. `moved.on` is the boundary the span starts on (null from the first frame), `moved.off` the one the base art or the next set comes back on (null to the end), each `{ frame: [selected, keyed], frames, seconds, window: [first, last], speed: { old, new } }`: signed frames and seconds, the frames it could move to, and the speed in degrees per frame on the old and the new frame. A boundary between two sets is the same entry in both.
269
+ - `switchRuns: [{ set, nearest: [{ frames: [first, last], count, seconds }], suppressed: [{ frames: [first, last], count, seconds }] }]` — one per set without a `phase`, in the map's order ([#279](https://github.com/rig-c/pose/issues/279)); `[]` when the map has none. `nearest` holds the runs of frames on which the set was the nearest within `--switch-tolerance` (inside its `frames` window), before bridging, hysteresis and the minimum span; `suppressed` the runs the minimum span dropped as shorter than `--switch-min` where they stood, on frames the set did not show in the end. `count` is the run's frames; `seconds` runs from the first frame's time to the next frame's (the animation's end for the last frame), the length `--switch-min` is compared with. A set with `phase` is not selected by distance and has no row.
250
270
  - `uncovered: [{ slot, attachment, chains, times }]` — every entry of `folds` on the times its slot still shows that mesh, with the map's chains that move it (the deepest: the arm, not the trunk it hangs from). `rigc render --folds` reads only what the pose shows, so a mesh swapped out or hidden by a switch is not read on those frames at all; `uncovered` is the fold times less the switched ones.
251
271
  - `endOn` — above, under the short hold.
252
272
  - `turns: [{ line, joints, frames, seconds, minFraction, reversed }]` — `line` is `hip`, `shoulder` or `eye`.
@@ -371,6 +391,8 @@ The retarget writes one key per pose frame on every mapped bone. That is faithfu
371
391
  - A scaled bone's `scalex` track is thinned within 5e-5 of its setup length: half of the scale landing check's 1e-4, so a reduced curve cannot fail that check.
372
392
  - A mapped bone under a scaled bone (keyed through its parent's posed matrix by `--leg-scale`) is solved again through its parent as reduced. Its rotate is then thinned with the error read as the world aim it lands on, and its `scalex` with the error read as the world length it lands on. Its world error is its own fit error, as down any other chain.
373
393
  - Each ankle is held to the plant's tolerance plus the most the reduction may move it: the hip's tolerance (with `--reduce-hip`), plus the leg from the hip to the ankle (each bone's setup offset from its parent and the last bone's length) times the largest scale asked, times the angle tolerance in radians plus 5e-5. The ankle's `LAND` line prints that tolerance, and at the end of the run `REDUCE ankle drift allowed: bone "…:tip" X units = Y leg lengths on any one frame` prints the same figure, split `(plant … + reduce …)` (`reduce.ankleDrift` in the report, `{ bone, side, plant, reduce, units, legLengths }`, the row's `units` the landing's `tolerance` itself), with the slide between two frames it lets through (2X) — so a slide bar is read against the reduction before it is run, not after ([#240](https://github.com/rig-c/pose/issues/240)). On the wave with `--leg-scale 0.8,1.05 --plant-feet --reduce 0.5 --reduce-hip 0.5`, that is 1.97 units = 0.0132 leg lengths a frame, 0.0263 between two.
394
+ - **Per chain: `--reduce <deg>,<chain>=<deg>,…`** ([#283](https://github.com/rig-c/pose/issues/283)). The first item is every chain's tolerance; each `<chain>=<deg>` after it gives a chain the map names its own, on each of its bones (the smaller, when two named chains list one bone). One tolerance on every bone trades the ankle's slide against the trunk's jitter; this holds the legs tight while the trunk is smoothed. A chain the map does not have is refused by name (`POSE_REDUCE`, beside the chains it has), and a malformed item, a tolerance outside (0, 180) or a chain named twice by the command line. Each bone's `LAND` line holds it to 0.01° plus its chain's tolerance, #116's trunk smoothing keeps each bone in its own, and each ankle's allowance above is taken at the largest tolerance of the bones from the hip down to it. The run line and the `REDUCE` total name each chain's tolerance, each bone reduced within its chain's says so (`— within its chain's 0.2°`), and each `ankle drift allowed` line names the tolerance its leg was reduced within. The report carries them as `reduce.chains` and `options.reduceChains` (`{ chain: deg }`) and `reduce.ankleDrift[].legTolerance`, written only when given; without them a run's bytes are unchanged. Measured on the tests' synthetic travelling walk (`examples/walk`'s cycle three times, 72 frames, the swinging ankle 12 px fore and aft, on the wave's rig with the walk's map and its trunk given `reach`, `--plant-feet --leg-scale 0.8,1.05`), against slide (the FEET worst horizontal drift) ≤ 0.005 leg lengths and jitter (the worst `JITTER` ratio) ≤ 1.1: `--reduce 0.2` gives 0.0042 and 1.62, `--reduce 3` 0.0547 and 1.01; with `--reduce-keep-planted`, 0.0000 and 1.62 at 0.2, 0.0000 and 1.42 at 3 (the supporting leg's seam where its kept frames meet its reduced ones); `--reduce 3,leg_r=0.2,leg_l=0.2` gives 0.0042 and 0.99.
395
+ - **A parent held to its tightest child** ([#320](https://github.com/rig-c/pose/issues/320)). Under a per-chain `--reduce`, every mapped bone above a planted or aimed chain the command names is reduced within that chain's tolerance when its own is looser (the tightest such chain, when several hang below it). A chain is planted when it carries an ankle the landing check holds, aimed when the map gives it an `aim` other than `"x"`; a bent chain (an arm, a leg with no ankle check) holds nothing above it. The lever: a turn error θ on a bone moves a chain below it by θ·L (L the distance from the bone's origin to the chain's first bone) and turns it by θ. The reduction refits the chain against the turn, so its aim stays its own, but the move it cannot undo, and the ankle allowance counts L at the leg's own degrees — θ·L ≤ θ_leg·L is θ ≤ θ_leg whatever L is. An aimed chain is refitted, and on the trunk smoothed, against its parent's error, which held to the chain's degrees never asks it to absorb more than its own tolerance. Each held bone prints `REDUCE held: bone "hip" (chain "pelvis") reduced within 0.2°, not 3° — for chain "leg_r" (a planted ankle; L = 15 units to bone "leg_r0"), …`, its `REDUCE` row ends `— held to 0.2° for chain "leg_r", chain "leg_l" below it`, its `LAND` line takes 0.01° plus the held tolerance, and the report carries `reduce.held` (`[{ bone, chain, from, to, for: [{ chain, by, bone, lever }] }]`, `by` is `ankle` or `aim`), written only when a bone was held. A plain `--reduce` names no chain, so nothing is held and its bytes are unchanged. #116's trunk smoothing is kept only where it leaves every bone it manages within that bone's own tolerance; past it, the fitted keys stand and the run prints `REDUCE smoothing (rig-c/pose#116) from bone "chest" down not kept: …` (`reduce.unsmoothed`, `[{ root, bone, worst, tolerance }]`, written only then). Measured on the tests' synthetic walk with a drawn pelvis tilt (`examples/walk/make-pelvis.ts`, drawn, read by the silhouette, cleaned with `--loop`, on the wave's rig with the walk's map and a pelvis chain on `hip`, `--plant-feet`): `--reduce 3,leg_r=0.2,leg_l=0.2` without the hold missed both ankles (0.697 and 0.677 units against an allowance of 0.628); with it, bone `hip` is held to 0.2° and the ankles land at 0.248 and 0.232, the head at 2.63° within 3.01°, the same motion bytes as a hand `pelvis=0.2` entry.
374
396
  - **`--reduce-keep-planted`** (only with `--reduce` and `--plant-feet`, refused by name otherwise; [#251](https://github.com/rig-c/pose/issues/251)) adds every frame of each support span to the frames kept regardless, on every mapped bone from the hip down to that side's ankle (rotate, and `scalex` where scaled). A kept frame is keyed at its target, so on its support frames the planted ankle lands as dense but for the hip: its `LAND` line there is `ankle on its support frames (kept dense)`, held to the plant's tolerance plus `--reduce-hip`'s (0 without it), and off them `ankle off its support frames`, held to the allowance above. `reduce.ankleDrift[].support` carries the tighter figure (`{ reduce, units, legLengths }`, `reduce` being the hip's share; `null` for a side that never supports). The tolerance has no separate rounding term: on the tests' synthetic walk (72 frames, six support spans) the supporting ankle on its support frames sits 1.22e-3 units from the dense run's, against the plant's 0.0253. Measured on that walk with `--reduce 1`, the FEET slide (the worst horizontal drift of a supporting ankle over a support span) goes from 0.0226 leg lengths to 0.00002, the dense run's; with `--reduce-hip 0.5` beside it, 0.0048, since the hip is not kept. The price is the keys: the supporting leg keeps one per support frame (the walk's four leg rotate tracks, 47 → 135 keys).
375
397
  - When `--leg-scale` is active, each trunk bone (`chest` and `head`, not an ancestor of any scaled bone) has its kept knots and Bézier handles smoothed within `deg` of its dense world aim to minimise the mean absolute second difference of its evaluated local angle and its child's compensated local angle, holding any limb children within 0.03° of their fitted world aim (rig-c/pose#116).
376
398
 
@@ -430,7 +452,7 @@ Which bones carry which joints. You write it for your rig:
430
452
  - A chain's bone *k* carries the segment from joint *k* to joint *k*+1, so it lists one joint more than bones; each bone descends from the one before (a control bone between them is fine). body-18 has no toe and no hand point, so a leg ends at the ankle and an arm at the wrist: a rig's foot (or hand) bone below them stays out of `bones`, unkeyed, turning with its parent. A chain one joint short whose last joint is an ankle or a wrist is refused (`MAP_CHAIN_JOINTS`) naming that case and the bone to leave out ([#240](https://github.com/rig-c/pose/issues/240)).
431
453
  - `aim`, one entry per bone: `"x"` (default) — the bone's own x axis is the segment; right for a limb link @rig-c/parts turned along its limb. A point — the direction from this bone's origin to that point; right for a trunk bone, whose own axis parts leaves at 0°. `"a>b"` — the direction from point a to point b; right for a head, turned by its eye line: `{ "bones": ["head"], "joints": ["r_eye", "l_eye"], "aim": ["eye_r>eye_l"] }`, or for a pelvis, turned by its hip line: `{ "name": "pelvis", "bones": ["hip"], "joints": ["r_hip", "l_hip"], "aim": ["leg_r0>leg_l0"] }`.
432
454
  - A **point** of the rig is a bone's origin (`name`), the end of its x axis (`name:tip`), or the midpoint of two of those (`"a+b"`: `"sleeve_r0+sleeve_l0"` is the shoulders' midpoint). An aim's points must be the bone itself or descend from it.
433
- - `reach: true`, on a one-bone chain from the translated joint (a trunk from `mid_hip`): the bone is aimed from where its origin is posed — the hip moved and turned — at where the pose puts the segment's end relative to mid_hip, scaled. Without it, a tilted pelvis carries the chest's origin sideways and the trunk, parallel to the pose's, misses the shoulders. Its aim is one point, from the bone's own origin: `{ "name": "trunk", "bones": ["chest"], "joints": ["mid_hip", "mid_shoulder"], "aim": ["sleeve_r0+sleeve_l0"], "reach": true }`. The chain must descend from the translated bone. **It is not free** ([#236](https://github.com/rig-c/pose/issues/236)): the aim is taken from the bone's posed origin at the pair's end, so an origin partway up the pair is a lever that turns the bone by more than the pair turns — on the example wave's trunk, its origin half way up its 150-unit reach, 2.00× the pair's mean |second difference|, carried into the head and arms that ride it — and a turning hip adds its own turn on top. It pays where a pelvis chain turns the hip, which otherwise carries the chest's origin off the pose's line; on a level hip, the trunk aimed along the pair keeps the pair's jitter and misses the shoulders only by its origin's offset swung through the trunk's lean. `JITTER` and `REACH` print what it cost.
455
+ - `reach: true`, on a one-bone chain from the translated joint (a trunk from `mid_hip`): the bone is aimed from where its origin is posed — the hip moved and turned — at where the pose puts the segment's end relative to mid_hip, scaled. Without it, a tilted pelvis carries the chest's origin sideways and the trunk, parallel to the pose's, misses the shoulders. Its aim is one point, from the bone's own origin: `{ "name": "trunk", "bones": ["chest"], "joints": ["mid_hip", "mid_shoulder"], "aim": ["sleeve_r0+sleeve_l0"], "reach": true }`. The chain must descend from the translated bone. **It is not free** ([#236](https://github.com/rig-c/pose/issues/236)): the aim is taken from the bone's posed origin at the pair's end, so an origin partway up the pair is a lever that turns the bone by more than the pair turns — on the example wave's trunk, its origin half way up its 150-unit reach, 2.00× the pair's mean |second difference|, carried into the head and arms that ride it — and a turning hip adds its own turn on top. It pays where a pelvis chain turns the hip, which otherwise carries the chest's origin off the pose's line; on a level hip, the trunk aimed along the pair keeps the pair's jitter and misses the shoulders only by its origin's offset swung through the trunk's lean. **The trade** ([#282](https://github.com/rig-c/pose/issues/282)): `reach` lands the trunk on the pose's end point from wherever its origin is posed, and pays in turn the pose does not have, more the farther the origin starts from the source joint; `reach: false` follows the measured angle — the pair's own turn and jitter — and lets the end point miss by the origin's offset. A trunk whose origin sits well up the pair on a hip that does not turn is the case to leave it off. `JITTER` and `REACH` print what it cost, and `REACH-ORIGIN` prints where it starts, with a `WARN` past 0.20 of the segment.
434
456
  - `bend`: every joint inside a chain is a hinge. `"rest"` (default) takes the side it may bend from the rig's rest pose and refuses a rest that is straight (under 0.5°); `"ccw"`/`"cw"` state it; `"free"` skips the check. Both bend refusals (`MAP_BEND_SIDE`, `POSE_BEND_ONE_WAY`) name `"free"` on the chain they refuse, and when it is right — the warning below ([#238](https://github.com/rig-c/pose/issues/238)).
435
457
  - `nod: false`, on the chain whose joints carry the eye line (`r_eye → l_eye`, [#241](https://github.com/rig-c/pose/issues/241)): the head keys no nod translate, and nothing is coupled into its rotate or the face pitch; the `NOD` line says so. Absent or `true`, the nod is read ([above](#what-it-does)). `nod` on a chain with no eye line, or anything but a boolean, is refused (`MAP_NOD`). It is a key of the chain rather than a flag because the nod is that chain's reading: the map is where a rig's author says what its head does, and a flag would have to name the chain it turns off.
436
458
  ⚠️ **Seen from the front, an elbow's side is not a joint limit.** An elbow flexes toward the camera, and its picture falls on either side of the upper arm depending on how the arm is turned. On a real 13 s front-facing dance (`dance-a`, 313 frames, RTMPose), the right elbow sat on the "wrong" side of the rig's rest on 165 frames (median 23°), and the left on 200 (median 29°, up to 179° with the hands behind the head). Every one was a real elbow bend. The knees strayed at most 8.8° and 14.7°, which is pose noise around a straight leg. So for front-facing material, arms go `"free"`. Legs on `"rest"` with `--bends clamp` hold only while the knees stay near straight. On `dance-b` (a second dance with deeper knee bends, rig-c/pose#27), that setting straightened the left knee for most of the dance: `leg_l1` was 48.3° off at p90, against 0.0–0.3° with free legs. Leave legs `"free"` unless the dance keeps its knees straight.
@@ -479,7 +501,7 @@ Bone rotation cannot carry every pose: an arm behind the head, a forearm toward
479
501
  - `phase` (optional, [#286](https://github.com/rig-c/pose/issues/286)): `"left-step"` or `"right-step"`, the stepping (swinging) foot a gait set is drawn for and named by ([below](#alternate-drawings-alternates)). left-step: the left foot is stepping; shown while the right foot supports. A right-step drawing mirrors it: the right foot is stepping, and it shows while the left foot supports. Refused with any other value, or beside `frames` (`MAP_ALT_PHASE`).
480
502
  - Each name resolves against the built rig and the map, and a miss is refused by name, listing what exists: `MAP_ALT_SLOT`, `MAP_ALT_ATTACHMENT`, `MAP_ALT_HIDES`, `MAP_ALT_CHAIN`, `MAP_ALT_JOINT`, `MAP_ALT_POSE`, `MAP_ALT_DRAWN`, `MAP_ALT_OFFSET` (a bone that is not a bone of the set's chains), `MAP_ALT_PLACE`, `MAP_ALT_SYMMETRIC`, `MAP_ALT_SUPPORT`, `MAP_ALT_ANCHOR` and `MAP_ALT_PHASE`.
481
503
 
482
- **When one shows.** Per frame and per set, the distance is the largest difference, in degrees, between a segment of its chains as the retarget asked for it (after holds, the short hold, the flip guard and the hinge check; before any range clamp) and the same segment in its key skeleton, with a foreshortened segment (`SHORT`) weighted at `0.5 ×` (`SHORT_SWITCH_WEIGHT`, [#180](https://github.com/rig-c/pose/issues/180)). On each frame the nearest set within `--switch-tolerance` (default 25°) shows, ties going to the map's order; once entered, a set stays active until its distance exceeds `--switch-tolerance + 15°` (`SWITCH_LEAVE_HYSTERESIS`, [#180](https://github.com/rig-c/pose/issues/180)) for at least `--switch-min` (default 0.25 s) or a competing set with a lower distance enters within `--switch-tolerance`; a run of one set shorter than `--switch-min` is dropped and the base art shows there. Sets compete only with those they share a slot (shown or hidden) or an offset bone with, so a drawing of each arm can show at once; two that compete never overlap.
504
+ **When one shows.** Per frame and per set, the distance is the largest difference, in degrees, between a segment of its chains as the retarget asked for it (after holds, the short hold, the flip guard and the hinge check; before any range clamp) and the same segment in its key skeleton, with a foreshortened segment (`SHORT`) weighted at `0.5 ×` (`SHORT_SWITCH_WEIGHT`, [#180](https://github.com/rig-c/pose/issues/180)). On each frame the nearest set within `--switch-tolerance` (default 25°) shows, ties going to the map's order; once entered, a set stays active until its distance exceeds `--switch-tolerance + 15°` (`SWITCH_LEAVE_HYSTERESIS`, [#180](https://github.com/rig-c/pose/issues/180)) for at least `--switch-min` (default 0.25 s) or a competing set with a lower distance enters within `--switch-tolerance`; a run of one set shorter than `--switch-min` is dropped and the base art shows there. The runs it dropped are not silent: the `SWITCH-MIN` line names each set held back with the runs and their lengths, and the report's `switchRuns` lists, per set, every run it was nearest on and every run the minimum suppressed ([#279](https://github.com/rig-c/pose/issues/279)). Sets compete only with those they share a slot (shown or hidden) or an offset bone with, so a drawing of each arm can show at once; two that compete never overlap.
483
505
 
484
506
  **A gait set: `phase`.** A walk's drawings are painted per step side, and which one is right depends on which foot is planted, not on which key skeleton is nearest: on a walk the nearest-angle set can be the wrong side for half a stride ([#286](https://github.com/rig-c/pose/issues/286); measured on a synthetic walk in place, the two sets without `phase` showed one drawing on all 168 frames, 84 of them on the wrong support). A set with a `phase` is named by the foot that is stepping — left-step: the left foot is stepping; shown while the right foot supports — and is not selected by distance: it shows exactly on its supporting foot's spans, the `feet.supports` the `FEET` line prints, and switches on the hand-over — no tolerance, no `--switch-min`, no hysteresis, no bridging, and `--switch-at peak` does not move its boundaries. On a frame where no foot supports (a jump, a released step) neither shows and the base art does. The supports are read with or without `--plant-feet` (with it, from the lower ankle, [#113](https://github.com/rig-c/pose/issues/113)); a run that reads no feet — a map without both legs, a pose without both hips and ankles — is refused naming the sets (`PHASE_FEET`). Sets that compete (a common slot or offset bone) must all declare a `phase` or none, and at most one of each side (`PHASE_GROUP`): nothing would decide between a phase set and an angle set, or two drawings of one step. Sets without `phase` are selected as above, byte for byte.
485
507
 
package/cli.ts CHANGED
@@ -189,6 +189,12 @@ retarget
189
189
  holds the upper arm the forearm is re-aimed at the wrist. ART-RANGE: a forearm still past
190
190
  its turn, or a held arm leaving the wrist > 0.01 eye distances short of its placed point
191
191
  (the shortfall listed). RAISED lines (default: off; rig-c/pose#62)
192
+ --draw-order depth key the legs' draw order from the sequence's depth (clean --depth; rig-c/pose#304): on
193
+ each frame the leg whose depth reads farther from the camera is drawn behind the other,
194
+ a tie (equal depths) holding the order before, in rig-c's drawOrder timeline. The legs'
195
+ slots must fill one unbroken run of the setup draw order (else the pair is not keyed and
196
+ the line names the slot between); a set's slot goes with the leg it depicts. Refused on
197
+ a sequence without depth (DRAWORDER_NO_DEPTH). DRAWORDER lines (default: off)
192
198
  --idle <name> the rig's animation read as its idle (default: idle). Every idle track the new
193
199
  animation does not key, on any channel, is listed as IDLE-DROPPED (a blink, a
194
200
  breath); one it keys is IDLE-KEPT-OUT, naming the track that keys it, and so is a
@@ -210,7 +216,11 @@ retarget
210
216
  --plant-feet and --stance, a scaled bone's scalex is thinned within 5e-5 of its setup
211
217
  length, a bone under one is re-solved through its parent as reduced, and each ankle
212
218
  is held to what the reduced bones above it may move it by (rig-c/pose#94), printed
213
- at the end as "ankle drift allowed" in units and leg lengths (rig-c/pose#240)
219
+ at the end as "ankle drift allowed" in units and leg lengths (rig-c/pose#240).
220
+ --reduce <deg>,<chain>=<deg>,… gives a map chain its own tolerance, the first item every
221
+ other chain's (rig-c/pose#283): legs held tight against slide while the trunk is smoothed,
222
+ e.g. --reduce 3,leg_r=0.2,leg_l=0.2; each LAND line and ankle allowance follows its
223
+ chain's tolerance, and the run line, the REDUCE lines and the report name them
214
224
  --reduce-hip <units> with --reduce, thin the hip's translate keys too, its world origin within units
215
225
  of the dense keys (default: the hip stays dense)
216
226
  --reduce-keep-planted with --reduce and --plant-feet, keep every frame of each support span on the
@@ -373,7 +383,7 @@ function legScaleOf(f: Map<string, string>): [number, number] | 'off' {
373
383
  const RETARGET_ALIASES = { sequence: 'pose', animation: 'name' } as const;
374
384
 
375
385
  function retargetArgs(argv: string[]): RetargetArgs {
376
- const f = parseFlags(argv, ['rig', 'map', 'pose', 'out', 'name', 'person', 'base-motion', 'images', 'min-score', 'smooth', 'flip', 'short', 'bends', 'bend-tolerance', 'switch-tolerance', 'switch-min', 'switch-at', 'profile', 'build', 'reference', 'render', 'parts', 'report', 'camera', 'leg-scale', 'idle', 'merge', 'reduce', 'reduce-hip', 'stance', 'floor'], ['coco', 'plant-feet', 'carry-idle', 'raised-by-head', 'reduce-keep-planted', 'verbose', 'summary'], RETARGET_ALIASES);
386
+ const f = parseFlags(argv, ['rig', 'map', 'pose', 'out', 'name', 'person', 'base-motion', 'images', 'min-score', 'smooth', 'flip', 'short', 'bends', 'bend-tolerance', 'switch-tolerance', 'switch-min', 'switch-at', 'profile', 'build', 'reference', 'render', 'parts', 'report', 'camera', 'leg-scale', 'idle', 'merge', 'reduce', 'reduce-hip', 'stance', 'floor', 'draw-order'], ['coco', 'plant-feet', 'carry-idle', 'raised-by-head', 'reduce-keep-planted', 'verbose', 'summary'], RETARGET_ALIASES);
377
387
  if (f.has('verbose') && f.has('summary')) throw new Usage('--verbose and --summary are both given; --verbose prints every line in full and --summary one line per measure, so give one');
378
388
  if (f.has('reference') && !f.has('build')) throw new Usage(`--reference needs --build: the record it goes in is written beside the build (${PROVENANCE_FILE})`);
379
389
  const bends = f.get('bends') ?? 'refuse';
@@ -382,6 +392,8 @@ function retargetArgs(argv: string[]): RetargetArgs {
382
392
  if (switchAt !== 'peak' && switchAt !== 'first') throw new Usage(`--switch-at is "${switchAt}"; it must be peak or first`);
383
393
  const profile = f.get('profile') ?? 'spine';
384
394
  if (profile !== 'spine' && profile !== 'spine-html') throw new Usage(`--profile is "${profile}"; it must be spine or spine-html`);
395
+ const drawOrder = f.get('draw-order');
396
+ if (drawOrder !== undefined && drawOrder !== 'depth') throw new Usage(`--draw-order is "${drawOrder}"; it must be depth (the order is keyed from the sequence's depth, rig-c/pose#304)`);
385
397
  const name = f.get('name') ?? 'dance';
386
398
  if (!/^[A-Za-z0-9_.-]+$/.test(name)) throw new Usage(`${argv.includes('--name') || !argv.includes('--animation') ? '--name' : '--animation'} is "${name}"; an animation name here is letters, digits, _ . -`);
387
399
  return {
@@ -420,6 +432,7 @@ function retargetArgs(argv: string[]): RetargetArgs {
420
432
  merge: f.get('merge'),
421
433
  ...reduceOf(f),
422
434
  output: f.has('verbose') ? 'verbose' : f.has('summary') ? 'summary' : 'default',
435
+ ...(drawOrder !== undefined ? { drawOrder: 'depth' as const } : {}),
423
436
  };
424
437
  }
425
438
 
@@ -440,7 +453,7 @@ function floorOf(f: Map<string, string>): { floor?: number } {
440
453
  }
441
454
 
442
455
  /** --reduce and --reduce-hip (rig-c/pose#32): off unless given; the hip only with the bones. --reduce-keep-planted (rig-c/pose#251): only with both --reduce and --plant-feet. */
443
- function reduceOf(f: Map<string, string>): { reduce?: number; reduceHip?: number; reduceKeepPlanted?: true } {
456
+ function reduceOf(f: Map<string, string>): { reduce?: number; reduceChains?: Record<string, number>; reduceHip?: number; reduceKeepPlanted?: true } {
444
457
  if (f.has('reduce-keep-planted')) {
445
458
  const missing = ['reduce', 'plant-feet'].filter((x) => !f.has(x));
446
459
  if (missing.length > 0) throw new Usage(`--reduce-keep-planted is given without ${missing.map((x) => `--${x}`).join(' and ')}; it keeps the support spans --plant-feet reads at dense keys inside the reduction --reduce makes`);
@@ -449,9 +462,22 @@ function reduceOf(f: Map<string, string>): { reduce?: number; reduceHip?: number
449
462
  if (f.has('reduce-hip')) throw new Usage('--reduce-hip is given without --reduce; the hip is thinned with the bones, not alone');
450
463
  return {};
451
464
  }
452
- const reduce = num(f, 'reduce', 0, (n) => n > 0 && n < 180, 'degrees in (0, 180)');
453
465
  const keep = f.has('reduce-keep-planted') ? { reduceKeepPlanted: true as const } : {};
454
- return f.has('reduce-hip') ? { reduce, reduceHip: num(f, 'reduce-hip', 0, (n) => n > 0, 'rig units > 0'), ...keep } : { reduce, ...keep };
466
+ // rig-c/pose#283: "<deg>,<chain>=<deg>,…" — the first item every chain's tolerance, then a map chain's own.
467
+ const [head, ...rest] = f.get('reduce')!.split(',');
468
+ const reduce = rest.length === 0 ? num(f, 'reduce', 0, (n) => n > 0 && n < 180, 'degrees in (0, 180)') : Number(head);
469
+ if (rest.length > 0 && (head === '' || !(reduce > 0 && reduce < 180))) throw new Usage(`--reduce is "${f.get('reduce')}"; its first item "${head}" must be degrees in (0, 180), every chain's tolerance, before any <chain>=<deg>`);
470
+ const chains: Record<string, number> = {};
471
+ for (const item of rest) {
472
+ const m = /^([^=\s]+)=(.*)$/.exec(item);
473
+ if (m === null) throw new Usage(`--reduce is "${f.get('reduce')}"; "${item}" is not <chain>=<deg> (a chain the map names, then its tolerance in degrees)`);
474
+ if (Object.hasOwn(chains, m[1]!)) throw new Usage(`--reduce is "${f.get('reduce')}"; chain "${m[1]}" is given twice`);
475
+ const v = Number(m[2]);
476
+ if (m[2] === '' || !Number.isFinite(v) || !(v > 0 && v < 180)) throw new Usage(`--reduce is "${f.get('reduce')}"; chain "${m[1]}" is "${m[2]}", and it must be degrees in (0, 180)`);
477
+ chains[m[1]!] = v;
478
+ }
479
+ const per = rest.length === 0 ? {} : { reduceChains: chains };
480
+ return f.has('reduce-hip') ? { reduce, ...per, reduceHip: num(f, 'reduce-hip', 0, (n) => n > 0, 'rig units > 0'), ...keep } : { reduce, ...per, ...keep };
455
481
  }
456
482
 
457
483
  function compareArgs(argv: string[]): CompareArgs {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rig-c/pose",
3
- "version": "0.39.0",
3
+ "version": "0.41.0",
4
4
  "description": "Puts motion from a body-18 pose sequence onto a rig built with rig-c: bone keys in rig-c's motion format, checked through rig-c's gate before anything is written. A CLI for agents that cannot see the rig.",
5
5
  "type": "module",
6
6
  "bin": {