npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rig-c/pose

v0.35.0

Published

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.

Readme

@rig-c/pose

Puts motion from a pose onto a rig. The motion is joint positions over time: from a video, a dataset, or points placed by hand. The rig is one rig-c built. A body-18 pose sequence becomes bone keys in rig-c's motion format, and the keys are checked before anything is written.

0.x is the prototype. The format, the command and the output can change in any release until 1.0.

Part of the rig-c toolchain: core compiles and gates a rig, parts builds one from a painting, play plays it, and pose moves it.

Try it

examples/wave is in the repository, not the npm package: clone the repository, or copy its four files. It runs with nothing else: a bones-only stick rig (no art), a 3-second wave made from joint angles (make-pose.ts writes it), and the map that joins them.

rig-pose retarget --rig examples/wave/rig --map examples/wave/map.json --sequence examples/wave/pose.json --out wave.motion.json --build wave-build

pose.json is already cleaned (make-pose.ts passes it through clean). It prints the gate's verdict and where every mapped bone landed, then writes wave.motion.json, its report wave.motion.report.json and the gated build. rigc preview --candidate wave-build plays it in the Spine Web Player. A rig with no art shows only its bones.

What the rig needs

The map names chains: each limb as bones that descend from one another, parent to child. That means a thigh with its shin below it, and an upper arm with its forearm below it, and likewise for the trunk and the head. A rig whose arm is one bone, or that has no leg bones, cannot take a pose: the map is refused by name (MAP_*). @rig-c/parts builds such a rig from a painting's keypoints with propose --keypoints. A rig drawn by hand needs the same chains.

Clean first: rig-pose clean

rig-pose clean --pose dance.scan.json --out dance.pose.json [--loop] [--legs 5] [--rest 7] [--hips-inferred] [--hold off] [--hold-speed 0.015] [--hold-reach 0.03]

retarget reads only a sequence that clean wrote (#67). Where the video's camera moves, the order is scan → clean → retarget (#80): a zoom step is a jump of the picture, and clean's spike and smoothing steps would read it as motion of the body, so tools/scan.py takes the camera out of the raw sequence first and clean reads what it writes. A pose model reads each frame alone, so a hanging wrist moves ±20 px a frame and a cloak-hidden leg jumps 100+ px, and every key carries that shake. clean takes it out once, at the sequence, where it can be inspected. A sequence without the record is refused by name (POSE_NOT_CLEANED), and the message is the clean command to run on that file. There is no flag to skip the step. Windows of 1 (--legs 1 --rest 1) are a legitimate run that smooths nothing, and they are recorded as such.

The steps, in this order, each printed with its figures:

  1. Spikes. A joint more than 0.2 trunk lengths from its 5-frame median takes the median. The trunk is neck → mid-hip, the median over the clip. Each replacement is listed by frame and joint (SPIKE). Without --loop, a clip end cuts the window of the first and last two frames. There the median is a neighbour's value, and a joint moving steadily would read as a spike. Those frames are not tested, and the SPIKES line and the record name them. Reflecting the window does not help: the median of [x2, x1, x0, x1, x2] is still x1.
  2. Occlusion. A joint whose state is not observed (or inferred), or whose score is under 0.3, takes its 3-frame median (OCCLUDED).
  3. Smoothing. A quadratic Savitzky–Golay per joint: the legs (both hips, knees and ankles) over --legs frames (default 5), every other joint over --rest (default 7). A window is 1 (none) or odd and at least 5. 3 is refused because a quadratic through 3 points passes through all three. Without --loop, the window is cut at the ends. Where fewer than three frames have a position, the degree drops to fit the ones that do.
  4. Floor. Only with --loop: the whole figure moves in y by one constant, so that the lowest ankle over the cycle is where it was before smoothing. A loop is also written with "travel": "in-place" (#92, the TRAVEL line): one cycle of a gait, repeated, does not travel, whatever its supporting foot does in the picture (below).
  5. Hold (#91). A joint that has stopped is written at one position (HOLD), because detector noise on a still joint is not motion, and no smoothing window removes it without also smearing the movement into the stop. Below.

A median is taken per coordinate (x and y apart), over the window's frames on which the joint has a position, its own frame included, whatever its state. An even count takes the mean of the middle two. --loop says the sequence is one cycle, such as a gait from strike to strike. Every window then wraps, the last frame running into the first, so nothing is untested; a cycle shorter than its widest window is refused (CLEAN_LOOP). Nothing is invented. A joint with no position on a frame gets none: retarget holds it and prints a HOLD line. States and scores are written as they were read. --coco reads COCO-17 (below) and converts it first. --person picks the person when there is more than one, and only that person is written.

The output carries the record, here dance-a's from the table below:

"cleaned": { "spec": "rig-pose-clean/1",
  "params": { "loop": false, "legs": 5, "rest": 7 },
  "spikes": { "found": [], "untested": [0, 1, 311, 312] },
  "jitter": { "before": 7.91, "after": 4.62 } }

jitter is the mean |second difference| of y, in px/f², over every joint and every three consecutive frames that have a position, wrapping with --loop. It is measured on the sequence as read and on the cleaned positions. retarget prints the record as its CLEAN line and copies it into --report.

--hips-inferred (#63) declares that the hips are not seen: under a skirt or a cloak the pose model still places them, but where it puts them is its guess. After the steps, r_hip and l_hip are marked inferred on every frame where they have a position (INFERRED), and the record's params carry "hipsInferred": { "by": "declared" } — the person running clean said so. Positions and scores are unchanged, so retarget's keys are the same with the flag and without it. What changes is the POSTURE line, which does not read a hip tilt off inferred hips (below), and the CLEAN line, which ends hips inferred (declared at clean).

The stillness hold (#91, #108, #109) runs on the cleaned positions, after the smoothing and the floor, joint by joint — except the five face joints (nose, r_eye, l_eye, r_ear, l_ear), which are held as one group (all of them or none, over the span where all five pass, #109), so a piecewise hold does not turn the head. Stillness is a property of a joint (or of the face as a whole), so a hand that has stopped is held while the elbow beside it still settles. A joint's speed on a frame is its movement from the frame before, in trunk lengths, taken as a 5-frame median. A hold of a joint is a span of at least 0.5 s (the first frame's time to the end of the last) on every frame of which its median speed is under --hold-speed (default 0.015 trunk lengths a frame), and over which it stays within --hold-reach (default 0.03 trunk lengths) of its own span median. Speed alone cannot tell a slow drift from stillness, because a drift accumulates. For each joint (and for the face group together) the span is chosen greedily, left to right: from a still frame it grows one frame at a time while the reach holds on the grown span, its median read again each time, and stops at the first frame that breaks it. If what it reached lasts 0.5 s it is a hold and the search resumes after it; if not, the search moves one frame on. A still run not held by that greedy pass that lasts at least 0.5 s and fails the reach only on one interior run of at most 6 frames stays held around that excursion (#108), provided the held frames themselves last at least 0.5 s and stay within the reach of their own median: the excursion is kept (blended from its cleaned positions toward the span median over the 6 frames from either held side), not erased, and the joint is not released for the whole span. A span does not cross a loop's seam.

On every held frame of a hold the joint takes its per-coordinate median over the span. It blends in linearly over the 6 frames before it (the 6th stays as cleaned, the frame before the span is ⅚ of the way) and out over the 6 frames after it, mirrored. A blend frame inside another hold of the same joint is not touched; where two blends of one joint meet, the larger weight wins. Where one hold of a joint ends on frame k and the next starts on k + 1 + g, with a gap g of under 6 frames (a seam: the reach broke while the joint was still moving slowly, or a few frames between two holds failed a test), the two medians differ, and the joint would jump between them. A seam cross-fades over 6 + g frames, symmetric about the gap's centre: frames k − 2 … k + 3 + g take 1/(2(6 + g)), 3/(2(6 + g)), … of the way from the first median to the second (for g = 0: 1/12, 3/12, … 11/12, the blend's sixths placed symmetric about the seam), so no frame moves more than 1/(6 + g) of the step. The cross-fade replaces the blends on the gap's frames. Each seam is printed (SEAM) and recorded with the joint, the second hold's first frame, the step in trunk lengths and the gap. A hold that carries a cross-fade or an excursion has a non-zero jitter after: the ramp's or excursion's own curvature, printed as (ramp) or (excursion), not noise. A joint that fails keeps its cleaned track. A joint with no position on a frame gets none, and states and scores are kept.

Each hold is printed (HOLD, joint by joint, with its largest speed and reach, and its excursion when one was kept) and recorded with the joint's jitter inside it, before and after. Where some joints are held, every run of the same frames (the held spans joined where they overlap or touch) on which a joint has no hold is named with the test it fails there and its figure (UNHELD, and the record's unheld): speed (its largest median speed), else reach (its largest distance from its median over the run), else short (it is still, but for under 0.5 s; the figure is its seconds), else span (it passes all three, and its spans broke elsewhere).

"params": { "loop": false, "legs": 5, "rest": 7, "hold": { "speed": 0.015, "reach": 0.03, "minSeconds": 0.5, "blend": 6 } },
"holds": [{ "joint": "nose", "frames": [first, last], "excursion"?: [first, last], "jitter": { "before": before, "after": 0 } }, …],
"seams": [{ "joint": "neck", "frame": second hold's first frame, "step": trunk lengths, "gap": frames }, …],
"unheld": [{ "joint": "l_elbow", "frames": [first, last], "test": "reach", "value": figure }, …]

The CLEAN line names, for each region, the joints held over which frames and the frames not held with their test and figure and the seams cross-faded (HOLD frames a–b: held nose, neck, … a–b; seams cross-faded: neck f (step …); not held: l_elbow c–d (reach … > 0.03)), or says no HOLD. --hold off turns the step off: the positions are those of a clean without it, and the record carries "hold": "off" and none of holds, seams and unheld. A record written before #91 names none of them, and is read as it was.

Measured hips: tools/pelvis.py (#86, not shipped) reads the pelvis line where the eye reads it under a skirt, off the skirt's waistband, and runs after clean: scan → clean → pelvis → retarget. It takes the video and a sequence clean wrote without --hips-inferred. It refuses by name, writing nothing:

  • a sequence with no cleaned record (PELVIS_NOT_CLEANED);
  • one whose hips are already marked (PELVIS_HIPS_INFERRED);
  • a frame count other than the video's (PELVIS_FRAMES);
  • a clip on which no frame gives a reading (PELVIS_NO_READING).

On each frame it reads the top edge of the dark region at the waist. The region is set by the hips and the frame's trunk length. The tool keeps the edge's flattest high run, since a flaring skirt's sides are steep and are not the pelvis line, and fits a line to it.

A frame gives no reading, declined with the reason, when:

  • a wrist lies in front of the waist (the picture under it is not the skirt's dark);
  • the edge is too broken or too far from a line;
  • the reading is over 25° from level, or a lone outlier: further, per frame, than the misread guard allows from both the read before it and the reading after it.

The tool never draws a line through a hand in front. If two reads lie close in time on either side (at most 1 s apart), a declined frame takes the linear interpolation between them. Over the clip's unheld read frames (or all read frames when fewer than two are unheld), the tool fits a zero-intercept gain (fit.gain) and correlation (fit.corr) between the detector's hip angle and the read waistband angle (#106). On a declined run longer than 1 s (or at the clip's start or end), when fit.corr >= 0.9 the frame takes the detector's hip angle scaled by fit.gain (scaled); when fit.corr < 0.9 the detector line does not track the waistband on that clip, so the run is marked unmeasured (keeping the detector's hip pair, refused nowhere, and named on the line).

Both hips are marked inferred on every frame and turned about their midpoint to the frame's angle, keeping the midpoint and the width. The record says which frame is which: "hipsInferred": { "by": "measured", "method", "fit"?: { "frames", "gain", "corr" }, "read", "interpolated", "scaled"?: [[first, last], …], "held": [[first, last], …], "unmeasured"?: [[first, last], …], "declined": [{ "frames": [first, last], "why" }] }. Every frame is in exactly one of read, interpolated, scaled, held and unmeasured, and declined lists exactly the non-read frames. Runs are frame indices inside the sequence, increasing and disjoint. A malformed record is refused by name (POSE_CLEANED).

tools/pelvis_selftest.py is its synthetic check. A planted wrist on a hand in front of the waist reads "covered", and with the hand moved away the edge's angle is read again. The misread guard, the interpolation, the detector-gain scaling on long covered spans (#106, with --hold-covered as its planted mutant) and the unmeasured path (r < 0.9) are checked frame by frame. Its cover mutant (--no-cover-check) must read a line through the hand, and the check asserts that it does.

Measured on the reference fixtures (#67's bars):

| Clip | Run | Jitter reduction | Against | | --- | --- | --- | --- | | dance-a, 13 s non-loop | defaults | −42 % (7.91 → 4.62 px/f²) | hand-cleaned reference: 4.62; positions within 0.071 px | | walk-a, one gait cycle | --loop | −66 % | hand-cleaned reference (within 0.001 px/f² outside the arms) | | run-a, one gait cycle | --loop | −53 % | hand-cleaned reference | | walk-b, 90 frames, held out | defaults | −67 % | the bar: −30 % | | run-b, 90 frames, held out | defaults | −75 % | the bar: −30 % |

A retarget of the cleaned dance-a fixture finds the same jumps (FEET) as the same run on the hand-cleaned reference; its hip rotate sd over frames 94–175 is 4.574, against 4.58.

What it does

rig-pose retarget --rig out/rig --map map.json --sequence dance.pose.json --out motion.json --animation dance

Names (#243). --sequence and --animation are the names the rest of the toolchain gives these inputs (rig-c/exam's grader, rigc render --animation); --pose and --name, this command's first names, are the same flags. Both of a pair given with the same value is accepted; with different values the run is refused before anything is read, naming both (--pose "a.json" and --sequence "b.json" are both given and differ; --sequence is another name for --pose, so give one). Either spelling writes the same bytes.

Paths (#239). --rig is the directory @rig-c/parts writes, <out>/rig/: it reads rig.json and the motion.json beside it. Naming the rig.json itself reads no motion spec, so a rig that needs its own animations — a slider applies one — is red before anything is added, and the RIG_GATE_BASE line says the motion.json beside it was not read and names the directory form (or --base-motion). When the gate is green without it, the run goes on and --out carries none of the rig's own animations, so a MOTION line says the same thing, in every output mode (#250). A directory --out, --report or --camera names that is not there yet is made once the run is green, and a made directory <dir> line says so and which paths it holds; a red run makes none, and a file where a directory has to be made is refused (ARG_PATH). The report is always written: without --report it goes beside the motion as <out>.report.json (motion.json → motion.report.json), and its wrote line says it was the default. A report that is already at that path is replaced, and the wrote line names when the replaced one was last written (its mtime, ISO 8601), so a second run to the same path is never silent. None of this changes the motion's bytes.

For every frame of the pose and every bone the map names, the segment that bone carries (an upper arm: shoulder → elbow) points somewhere in the picture. The bone is turned so its segment points the same way: the picture angle, flipped into rig-c's y-up world, minus the bone's angle at the rig's setup pose (read through rig-c's own poser), less the turn of the mapped bone above it — because a rotate key in rig-c adds to the setup rotation and a child turns with its parent. The travel of the pelvis midpoint, scaled by a length measured on both the rig and the pose, moves the hip. When a head chain carries the eye line (r_eye → l_eye), the eyes' vertical travel against neck in the reference, scaled into the rig's units, keys a vertical translate nod channel on the head bone (#111) clamped within ±2% of the setup neck length (head − neck, #181), while on sequences where that clamp acts, the clamped vertical nod (clampedY) is coupled in phase into face.pitch's dial (+2.8°/unit, mapped through face.pitch.range) and the head bone's rotate track (+1.6°/unit, #190, #205; gains calibrated against the hand-polished reference p1). How the nod's baseline is read (#241): on a sequence declared "travel": "in-place" it is frame 0, with no window, no clamp and so no coupling (#242); otherwise it is a ±0.5 s running median of the eyes' height against the neck, averaged again over the same window, and the nod is the eyes' height less it. On a sequence clean --loop wrote (its cleaned.params.loop) and not declared in place, that window wraps, the last frame running into the first, as clean's own windows do; cut at the clip's ends instead, the two ends are read against one-sided baselines, and the coupling carries the difference into the head's rotate. On the tests' synthetic loop (#242's walk, 7 cycles of 24 frames, the eyes bobbing 3 px twice a cycle, travel deleted), the wrapped window puts cycle 7 on cycle 1 to 0.000° and 0.000 units; cut at the ends it was 1.057° on the rotate and 0.661 units on the nod. "nod": false on the eye-line chain (below) keys no nod and couples none. The NOD line says which of these happened. The result is one animation in rig-c's motion format (rigc-motion/1): a rotate track per mapped bone, a translate track on the hip (and on the head when the reference nods), one key per pose frame — and, when the map lists alternate drawings, an attachment track per slot they switch (and a translate track per bone a set offsets).

Nothing is written until every check passes:

| Check | Fails with | | --- | --- | | rig-c's gate is green with the rig as it is, and again with the animation in (rigc build) | RIG_GATE_BASE, RIG_GATE, then rigc's own FAIL lines | | every mapped bone lands on the angle the pose asks for, on every frame — the gated build is posed by rig-c's poser and measured (0.01° / 0.01 units); with --leg-scale, every scaled bone's x axis (and the trunk's y axis when trunk foreshortening is keyed, #172) on its asked scale (0.0001 of its setup length), and with either leg flag each ankle where the keys put it (0.01 units + 0.0001 × its setup distance from the hip) | POSE_LANDS: the bone, the worst error, the frame | | elbows and knees bend only the way they can: at every joint inside a chain, the inner angle stays on the side of the rest pose's | POSE_BEND_ONE_WAY: one line per hinge — the chain, the joint, how many frames in how many runs, the frame range, the worst angle and its frame, and the remedy ("bend": "free" for front-facing material, below); --verbose prints the per-frame refusals before it | | every point the map needs is good on at least one frame | POSE_POINT_NEVER_GOOD | | the map resolves against the rig: bones exist, chains descend, aims descend | MAP_*, naming both sides |

and what the run cannot decide for you it lists:

  • CLEAN — what rig-pose clean did to the sequence: its windows, spikes, jitter before → after, and the stillness hold counted — holds, joints, regions and their frames, the joint spans not held, the seams (#238). The per-joint record is the sequence's cleaned (holds, unheld, seams), which --report writes whole; --verbose prints it on the line.
  • MOTION — --rig named a rig.json with a motion.json beside it that was not read, and the gate passed anyway (#250): the file, and the two forms that read it. Printed under --summary too.
  • RIGC — the rig-c version and entry that gated and posed the run (also rigc in the report): a later rig-c can change the bytes.
  • SCALE — when the map translates a bone: the figure scale, scale.rig over the median picture length of scale.pose, and (#240) that median as a share of the segment's 95th percentile — under 1, the median frame foreshortens the segment and the factor is that much larger than the segment at its longest gives. Then a bands line: the rig-to-picture ratio of the legs (hip → ankle: the rig's at setup over the picture's 95th percentile, to over its median, either side) and of the hips (thigh roots apart at setup over r_hip → l_hip's 95th percentile, to over its median), each within or outside — within when the rig's length carried into the picture by the factor (rig ÷ factor) lies between the picture's median and 95th percentile give or take one picture px, so a band the picture never widened is not missed by a rounding the picture cannot show (the wave re-cleaned with the hold on reads a factor of 1.00001 against a hip band of exactly 1.00000); an outside both bands line when the factor lies outside both — the scale pair sizes the figure as neither its legs nor its hips do; and a hip travel line, the translated joint's x and y range, the pose's in px beside the keyed bone's in units, each also in leg lengths (legs: the two sides' mean, the picture's 95th percentile and the rig's at setup) and hip widths (hips: the picture's 95th percentile, the rig's thigh roots at setup). A reading (scaleFit in the report); nothing it prints changes a key. The wave: leg 0.99698–0.99698 and hip 1.00000–1.00000, both within; no outside both bands line.
  • HOLD — a joint missing, or scored under --min-score, holds its last good value; one line per span, with the frames and the reason.
  • SHORT — a segment shorter in the picture than --short of its median length (with --short <fraction>; off by default) has its angle there interpolated, not read; one line per span, with the chain, the segment, the frames and the lowest fraction. Below.
  • END-ON — a SHORT run lasting at least 0.15 s: the limb points at the camera, a pose the rig cannot draw.
  • FLIP — a segment that turns more than --flip degrees in one frame (with --flip <deg>; off by default) is spread over the frames around the jump; one line per spread, with the chain, the segment, the jump, the frames and h. Below.
  • BEND — with --bends clamp, a hinge bent the wrong way is straightened on that frame instead of refusing; one line per hinge with the count, the frame range and the worst angle (one per frame with --verbose; every frame in the report's bends).
  • FOLD — rigc render --folds over the new animation: every mesh that folds, and when. A reading, not a gate.
  • 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): 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).
  • JITTER — one line per bone a chain keys (a face or body dial bone excepted), in map order (#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.
  • REACH — a reach chain (below) whose aim departs from its own pair's angle (#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.
  • 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, #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. With --plant-feet, POSTURE also prints one bounce line per support span (#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.
  • SWITCH — with alternates in the map (below): 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).
  • 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.
  • RANGE — a chain's range (below): 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.
  • 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.
  • RAISED — with --raised-by-head (below): one line per span of a set that draws an arm raised, per chain. It gives the frames and the median eye distances (the rig's in units, the reference's in px). It gives the wrist's offset from the head centre in eye distances: the median of the reference's, of where the wrist was placed, of the rig's on the pose's angles, and of the rig's as keyed. It gives the worst |rig − reference| before and after, the worst |after − placed|, and the frames whose upper arm the set's turn held. RAISED none when the flag is given and no such set showed.
  • BY-HIP — with a set's "place": "by-hip" (below): the RAISED line against the hip, one per span of such a set per chain it places. The unit is the hip width, the offset is from the mid-hip. BY-HIP none when the map has such a set and none of its spans showed.
  • 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.
  • LEG-SCALE — with --leg-scale (below): 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), and (with --plant-feet) one past the floor (support) line per supporting run that asked under the floor (#133, legScale[].support in the report).
  • 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.
  • 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); 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.
  • STANCE — with --stance (below): a unit line first, naming the tolerance's unit (FEET's leg length, each side's median hip → ankle) in px and as a share of the straight leg (the 95th percentile hip → ankle) (#237); one line per run of frames planted both (with the stretch keyed, its largest k), lifted, or past the tolerance below (the pose's height kept, #114 — printed only where no --leg-scale clamp moved the free ankle; otherwise the frames it moved and how far, #237), with the free side, the frames and the tolerance in leg lengths and px; and one per run whose stretch fell past its bounds (past the bounds), with the k the floor needed, the bound it had and how far the foot was left from it; and, where the leg's own range set a lower bound below 0.92, one bound from the leg's own range line with each side's lowest.
  • SHEAR — with --leg-scale: every scaled bone and every bone under one, how far its art is sheared and how long its world axes are against setup. A reading. Where the shear prints above 0.00°, the line names where it comes from (#240): a bone that inherits normal from a scaled parent (or one under a scaled bone) takes its parent's unequal axes into its art (it inherits "normal" from scaled "…"), and "inherit": "noScale" on that bone in the rig spec is the fix — it still turns with its parent, and rig-pose keys it through its parent (#153); onlyTranslation removes the shear too and also stops it turning. On the tests' bent knee under a scaled thigh, the shin's 5.27° is 0.00° with noScale.
  • IDLE, IDLE-DROPPED, IDLE-KEPT-OUT, MERGE — the rig's idle against the new animation, and what --carry-idle and --merge put back (below).
  • REDUCE — with --reduce (below): per bone, its keys before and after and its largest error, with the frame; then the total. At the end of the run, one ankle drift allowed line per ankle (#240): the tolerance its LAND line held it to, in units and in its side's rig leg at setup, split into the plant's and the reduction's, and the two-frame slide that allows; with --reduce-keep-planted, the same for its support frames (the plant's plus the hip's), or that its side never supports.
  • NOD — with a chain carrying the eye line (#241): the head bone and chain, the keyed translate's y range in units over the frames, the window its baseline was read over (none (in-place), or ±h frames wrapped on the loop or cut at the clip's ends), and outside in place the clamp (±2% of the neck's setup length) with the frames it acted on and the coupling that follows: NOD bone "head" (chain "head", r_eye → l_eye): translate y -0.400 … +0.400 units over 168 frame(s); window ±12 frame(s) (0.5 s) running median, detrended, wrapped on the loop; clamp ±0.400 units (2% of the neck's setup length) acted on 140 frame(s), so the clamped nod is coupled in phase into bone "head"'s rotate (+1.6°/unit) on every frame. When no nod is keyed it says why instead: "nod": false, a translate on the bone (the map's or a set's offsets), a scaled bone above it, no good neck, no scale, or eyes that do not move against the neck (the example wave).
  • FACE — with face in the map (#138, #173): per-frame yaw and pitch read from nose against the eye midpoint (r_eye + l_eye) and, when present, the ear midpoint (r_ear + l_ear), de-rolled by the eye line, then compressed into each configured axis's [min, max] range (scale and correlation r), and, when face.yaw.sets (or face.sets) is declared, every yaw-switched set's entered spans, swap count, and any hides slot keyed by another track, written as face in --report.
  • BODY — per-frame body yaw read from the camera-normalised shoulder width (r_shoulder + l_shoulder) and hip width (r_hip + l_hip) projected perpendicular to the trunk against the sequence's standing width (yaw ≈ acos(w / w_standing), #173): its overall and opening (frames 0–47) range, every span with |yaw| ≥ 45°, and, when body.yaw or body.sets is declared in the map, its knee-mapped dial keys and every yaw-switched set's entered spans, swap count, and any hides slot keyed by another track, written as body in --report.
  • TURN — a line across the body (hips, shoulders, eyes) shorter in the picture than 0.5 of its median length, or reversed, for at least 0.15 s: the body or the head turned. A reading of the pose, made whether or not the map uses the line; it changes no key. The demand for a turned-body drawing, as END-ON is for a foreshortened limb.

How much it prints (#238). By default the lines above, with the CLEAN line's hold record counted and a hinge's wrong-way frames in one line. --verbose prints every line in full, as the run did before #238: the CLEAN line with the record, a BEND line per frame, and before each collapsed POSE_BEND_ONE_WAY its per-frame refusals. --summary prints the run line, one line per measure and the wrote lines — the lines two runs are compared on:

  • GATE — the GATE line.
  • FEET — the posed build's two FEET lines' figures: the lowest ankle's vertical range, and the supporting ankle's worst vertical range and horizontal drift over the support spans; not measured without both legs in the map.
  • LIFT — the STANCE runs counted (absorbed runs are inside their neighbours): lifted, planted both and below, each in runs and frames, the highest lifted run, and the runs past the bounds; not read (no --stance) without it.
  • JITTER — the JITTER lines: how many bones, the worst ratio and its bone, the still pairs and the bones not measured.
  • LAND — the LAND lines: how many, and per kind (aim, origin, scale, ankle) the count, the worst and its bone and frame, and the tolerance.

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.

--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), 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, 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; 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); 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), over: { side, frames, needed, left }; null without --stance), shear, idleDropped, idleKeptOut and merged (below), reduce (the tolerances, each row of REDUCE and ankleDrift, below), and the demand lists. The fields a run is graded by: 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.

| field | each entry | | --- | --- | | holds | { point, from, to, source, why }: a joint held over frames from–to at frame source's value | | bends | { chain, joint, frame, t, angle, side, rest }: a hinge found bent the wrong way (angle in degrees, Spine's sense; side ±1, the side it may bend) | | endOn | { chain, bone, joints, frames, seconds, minFraction }: a limb pointing at the camera for ≥ 0.15 s | | landings | { bone, worst, frame, kind, tolerance, on? }: where rig-c's poser put each bone, against the target. kind is angle (degrees), origin or foot (world units), or scale (a fraction of the setup length). With --reduce-keep-planted each foot is two rows, on: "support" (the frames its side supports) and on: "off-support" (the rest), each with its own tolerance (#251); a side with no frame in one of them has no row for it. A run writes only when every worst ≤ tolerance | | jitter | { bone, chain, joints, against, rig, pair, ratio, triples, reach }: the JITTER line's figures, one per bone a chain keys. rig and pair are mean |second difference| in °/frame², against the keyed bone above whose pair the pose's local angle is read against (null at the top), ratio = rig ÷ pair (null when the pair is still: its figure rounds to 0), triples how many three-frame runs both were read over, reach whether the chain has "reach" | | reach | { chain, bone, joints, anchor, offset: { units, ofLength }, above: [{ bone, property, chain }], departure: { max, frame, t, meanD2 }, aimD2, pairD2, ratio }: the REACH line's figures, one per reach chain whose aim departs from its pair; empty when none does. offset is the bone's setup distance from the anchor's origin (units, and a fraction of the reached length), above the bones over it the animation turns (rotate, with the chain that turns it) or translates other than by the anchor's travel (translate), departure the aim − the pair's angle (largest |·| in degrees, its frame, and its mean |second difference|), aimD2 and pairD2 the two angles' mean |second difference| over every frame, ratio = aimD2 ÷ pairD2 | | folds | { slot, attachment, times }: every mesh rigc render --folds saw fold, at those seconds; null when it did not read | | seams | { pair, kind, frames: [first, last], times: [first, last], maxPx, domain: "render" }: one per consecutive span of one kind (gap or ghost) over the one-cell floor, per declared pair ("<a>\|<b>"), in the pairs' order, gap before ghost; frames are the seam render's frame indices (rigc's f<index>.png) and times rigc's seconds, maxPx the most cells on a frame of the span. [] when none opens, when no pair is declared, and when none was read — seamPairs says which. Always present: the writer refuses a report without it (REPORT_SEAMS) | | seamPairs | { pair, declared, status, opens, maxPx, underFloor, reason? }: one per declared pair. status is read, skip (rigc read no band: nothing measured) or unread (not rendered; reason says why); opens the render frames over the floor, underFloor those rigc named that were one cell thick, declared where parts.json declared it. [] when no pair is declared. Always present (REPORT_SEAMS) |

The demand lists:

  • 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.
  • 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.
  • endOn — above, under the short hold.
  • turns: [{ line, joints, frames, seconds, minFraction, reversed }] — line is hip, shoulder or eye.
  • range: [{ chain, bone, frames, seconds, maxOverflow, key, limits }] — key is the rotate key the pose asked for on the frame of the largest overflow, before the clamp.
  • artRange: [{ set, chain, bone, frames, seconds, maxOverflow, turn, limits, clamped, shortfall?, anchor? }] — turn is the signed art turn the pose asked on that frame; clamped says whether the set's turn held the bone there. shortfall (with --raised-by-head, or a "by-hip" set) marks a held upper arm whose re-aimed forearm left the wrist short of its placed point: the most, in eye distances (in hip widths with anchor: "hip"), with turn and maxOverflow the upper arm's on that frame.
  • raised: [{ set, chain, frames, seconds, eyes: { rig, reference }, reference, placed, before, after, worst: { before, after, placed }, short, upperHeld, reach: { min, max, farthest } }] — with --raised-by-head. reference, placed, before and after are the wrist's offset from the head centre on each frame of the span, [x, y] in eye distances, x right and y up. eyes holds each side's median eye distance over the span. worst.before and worst.after are the largest |rig − reference| in x and in y; worst.placed is the largest |after − placed|. short lists the REACH runs, each { frames, how, shortfall: { max, median } }, where how is far, near or missed. upperHeld lists the frames whose upper arm the set's turn held. reach gives |l0 − l1|, l0 + l1 and the farthest target from the shoulder, in world units.
  • byHip: [{ set, chain, frames, seconds, hips: { rig, reference }, reference, placed, before, after, worst: { before, after, placed }, short, upperHeld, reach: { min, max, farthest } }] — Present only when a set of the map declares place; a run without one writes no byHip key. Each span of a "place": "by-hip" set on each chain it places (#93): raised's fields against the hip, the offsets from the mid-hip in hip widths, hips each side's median hip width over the span, short in hip widths.

Runs in turns, range and artRange merge across a gap of one frame, as endOn does; seconds runs from the first frame's key time to the end of the last frame.

--smooth <n> is an alias kept since #67. The smoothing is clean's (--legs, --rest), so retarget prints --smooth is carried by clean; ignored and smooths nothing: --smooth 7 writes the same bytes as --smooth 1. The three paragraphs below were measured before #67, when retarget smoothed with a cubic Savitzky–Golay over --smooth frames. They are kept as the record of why the smoothing moved to the sequence.

--smooth 7 flattens the pelvis's rebound. Measured on dance-a with planted feet and leg scaling (#47): the rebound — the hip's peak upward acceleration at the bottom of each bounce, rig ÷ reference, median over a window, bar ≥ 0.7 — reads 0.40–0.52 with --smooth 7 in every configuration measured (0.461 and 0.501 in the two windows, where --smooth 1 reads 0.648 and 0.942). So the pelvis is read at --smooth 1, and --smooth 7 is not advised for it. What follows is the earlier advice, for the limbs.

--smooth 7 at 24 fps, for the limbs. It was measured on a 13 s dance (dance-a) on a @rig-c/parts rig, read back from the gated build: the motion's timing did not move (speed cross-correlation lag 0 on the wrists, ankles and hip), the frame-to-frame second difference dropped 2.5× on the wrists, 1.7–2.1× on the ankles and 1.5× on the hip, and the hip's 95th-percentile speed fell to 0.85×. The criterion set beforehand, a 2× drop on every point, was not met on the hip. That criterion was flawed: the hip's second difference is mostly real motion, while the filter cuts white noise 4.6×. A measure that isolates noise is a later card, and the default stays off until it passes.

The flip guard. A pose model can put a fast sweep of a limb into one frame — under motion blur it takes the elbow or the wrist for another point — and the rig then whips the limb round between two keys. Position smoothing cannot remove a step like that. So after holds, and before the hinge check, every segment the map aims (not a reach, which is aimed later from posed positions) is read frame to frame: a turn greater than --flip <deg> from frame k−1 to frame k is spread. For the smallest h ≥ 1 that keeps every one-frame turn at or under the threshold, frames k−h … k+h−1 are replaced by the straight line from frame k−h−1 to frame k+h, the turn's own way round. It is centred, so the step happens when the pose put it, over 2h frames, and both ends keep the pose's angles. A window that would cross the first or last frame stops there and the line says so; two windows that overlap become one. --flip off keeps every turn. The report lists each spread under flips.

The threshold was fixed by measuring first (#15). On dance-a (313 frames, RTMPose), the one-frame turns of the picture angle of 10 segments — both upper arms, forearms, thighs and shins, the eye line and the hip line, 3,120 turns — run p50 1.0°, p90 5.0°, p99 23.1°, p99.5 33.4°, p99.9 78.2°, max 148.6°. 90° is the p99.9 rounded up past a margin, and marks the 3 turns above it (0.1%): both forearms between frames 181 and 182 (148.6° and 114.4°, a motion-blurred stretch) and the left forearm between 53 and 54 (99.2°, the forearm pointing at the camera). None of them is a spike that comes back: each keeps the direction the limb was already turning, and the new angle holds — a real sweep put into one frame. Holding the last angle would be wrong a frame later, so the guard spreads the step instead. With it, on the @rig-c/parts rig #11 measured, the largest one-frame turn of any guarded bone in the gated build, posed by rig-c, is 78.2° (148.6° without it), and every bone still lands.

Re-measured with the guard on, against the guarded unsmoothed run, on #11's criteria (power above 6 Hz down ≥ 2× on every point, timing lag 0, p95 speed ≥ 0.85×): no window passes. --smooth 7 comes closest — the left wrist's power drops 1.99× and the hip's p95 speed is 0.847× — so --smooth stays off by default, and so does the guard: the rule posted before the run made a default for either one depend on a window passing. With a 90° flip threshold on dance-a, no bone of the gated build turns more than 90° in one frame (without it, 148.6°), and every bone still lands.

The short hold. A segment pointing at the camera is short in the picture, and its picture direction is then detector noise: the rig turns the bone to follow it. So after holds, and before the flip guard, every mapped segment is measured on every frame (not a reach) — its picture length, between the same two points its angle is read from, over its own median length across the pose. On a frame under --short <fraction> its angle is not read from the pose: it is interpolated, the short way round, from the last frame at or above the cutoff to the next one, and held flat at the first or last frame of the pose. A horizontal line across the body from a left joint to its right twin (a>b, such as the hip line or the eye line) also holds its last confident angle and interpolates across the span whenever its picture length falls under --short or its 2D reading reverses sign within one frame past --flip (when the left and right joints cross in 2D during a full side turn, #184); its hold is printed as HOLD chain "..." and is not counted toward limb END-ON runs. The flip guard, the hinge check and the keys all see the held angle. --short off (the default) reads every frame unless an across-body line reverses past --flip. The report lists each span under short (chain, bone, segment, first and last frame, the lowest fraction, and across / reversed when across-body).

The cutoff was fixed by measuring first (#22), over both upper arms, forearms, thighs and shins. On dance-a (24 fps, 2,504 segment-frames) the fraction's 0.5th percentile is 0.51, its 5th 0.82, and 7 segment-frames (0.28%) are under 0.35; on dance-b (a 16 s sequence at 30 fps, 3,936 segment-frames) they are 0.27 and 0.75, and 34 (0.86%). Every frame under 0.35 was looked at, and was one of two kinds:

  • end-on, lasting several frames — the limb really points at the camera: on dance-b the right upper arm folded across the chest (frames 395–401, 0.09–0.25), and the right thigh and shin of a knee raised at the camera (365–370, 0.16–0.34); on dance-a the left upper arm with the elbow at the camera (frames 30–37, 0.27–0.35, S2 below);
  • blur collapse, lasting 1–3 frames — a fast forearm whose elbow and wrist the detector both puts on the blurred hand (eleven spans on dance-b).

Both have a meaningless direction; only the first is a pose the rig cannot draw. 0.35 is the smallest round value that takes in every end-on frame (the highest is 0.35, on dance-a) without reaching normal motion (the 5th percentiles are 0.75 and 0.82). A run under the cutoff — spans merged across a gap of one frame — lasting ≥ 0.15 s (3.6 frames at 24 fps, 4.5 at 30) is the end-on kind: it excludes every blur collapse above (at most 3 frames, 0.10 s at 30 fps) and keeps every end-on span (6–8 frames). The run prints it as END-ON and the report lists it under endOn (chain, bone, segment, frames, seconds, the lowest fraction): it is the list of moments that need an alternate drawing (#6).

Re-checked on a @rig-c/parts fixture rig with the short hold, the flip guard and clamped bends. On dance-b the three spreads the flip guard made out of end-on segments are SHORT holds now, not FLIPs: right upper arm 399–400, right thigh 368–369, right shin 368–371. Of the flip guard's other spreads only one is left, the left forearm at 322–323; every other one fell on frames under the cutoff and is a SHORT hold instead. No guarded bone of the gated build turns more than 81.4° in one frame (85.1° with the flip guard alone), and every bone lands. endOn there lists the right upper arm (395–401) and the right shin (365–370). It does not list the right thigh, which is under 0.35 on one frame only (369, 0.16). The plan before the run expected the thigh too, and that expectation was wrong. On dance-a endOn lists exactly S2's left upper arm (30–37); the three FLIPs are #15's, the largest one-frame turn is 78.2°, and every bone lands. When dance-b's pelvis is mapped (r_hip → l_hip), its side-on turn (frames 463–491, body yaw +79.5°) foreshortens the hip line to 0.17 of standing and crosses r_hip.x past l_hip.x on frames 482–484 (+177.4° / −179.4°); holdAcross (#184) holds hip.rotate across the unconfident span ([−2.2°, +2.6°] over 463–491, 0 FLIPs) without listing horizontal lines as limb END-ON. The default stays off: a default needs its own criterion and a third sequence.

The legs: foreshortening and planted feet

A knee bending toward the camera is seen from the front only as a shorter leg, and a rig with rigid legs cannot lower its hip without lifting its feet (#47). Two flags, both off by default:

--leg-scale <min,max> scales each mapped leg segment (two of one side's hip, knee and ankle) along its own x axis — a scalex key — by its picture length on the frame over its straight length, the larger of the two sides' 95th-percentile picture lengths for that segment over the sequence (#148, weighted by the segment's share of the two legs' mean setup bone length when the rig's setup legs differ by more than 0.1 % so an asymmetrically drawn rig places both ankles at the sequence's relative height, #225; on a non-in-place sequence longer than 25 frames, each frame's segment length is normalised by a continuous rate-limited figure-scale signal figureScaleOf derived from posture- and crouch-invariant segment measures — max(r_thigh + r_shin, l_thigh + l_shin) plus the mid_hip → mid_shoulder trunk segment held at each support span's 95th-percentile straight length and ramped linearly across non-support airborne gaps into a larger incoming support span when the 5-series consensus moves past ±2 %, never vertical ankle.y − nose.y — with frame-to-frame step ≤ 1 % and frozen spans ≥ 5 frames held at their median, so neither camera zoom, a forward trunk bend nor a landing crouch reads as the figure changing size or steps the denominator on landing, and --camera <file> writes that track as rig-pose-camera/1 without changing motion.json, #159, #161, #163, #172, #226, #231), clamped to [min, max]. When the camera-normalised trunk (neck → mid_hip or mid_shoulder → mid_hip) foreshortens or extends by more than 4 % from its standing length, the trunk bone is keyed in scaley (for a +y-aimed trunk, scalex for +x) clamped to the same [min, max] bounds, carrying the trunk and head's depth bend while keeping the aimed rotation (#172). The bounds are <min,max> with 0 < min ≤ 1 ≤ max. A segment the map aims other than "x" is refused (LEGS_MAP). LEG-SCALE lists each segment's frames clamped at either limit with the unclamped asked scale range on each clamped run (legScale[].clamped in --report, { frames, times, limit, asked: { min, max } }, #225).

**Its un