Skip to content
rig-cPublic

About

Puts motion from a pose — joint positions over time, from a video, a dataset or hand-placed points — onto a rig rig-c built: a body-18 pose sequence retargeted to bone keys, checked before it is written.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

@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, with its record retarget.json beside it (below). rigc preview --candidate wave-build plays it in the Spine Web Player. A rig with no art shows only its bones.

Choosing the approach

How far a retarget can carry a figure depends on the figure and on how intense the motion is (#361):

Figure Mild motion (walk, idle, gesture) Intense motion (run, dance)
A designed character retarget, plus frame parts where a frame reads wrong part animation: replacement parts or frame parts swapped in on the frames that need them, sprite-like, placed with bind (#360) and alternates; not one drawing bent through large angles
A mannequin or a super-deformed (SD) figure retarget full retarget, end to end: its proportions survive large rotations

The exams use the mannequin. Expecting film-smooth intense motion from a designed character's single set of drawings is the wrong goal. The measured reason is that no procedural lever produced what the drawing lacked:

  • Back-kick (#357): on a run's back-kick frames only a drawn fold reads as the leg leaning back. The thigh's foreshortening and an end-on hold do not.
  • Knee up (#359): a thigh swinging toward the camera keeps its screen direction, so selection by direction alone can never pick a knee-up drawing. The card was parked in favour of drawing that frame part.
  • Pelvis below the cover (#342): a garment hides the tilt. The reader had to move below the cover, to the hem and the thigh roots, and a thigh drawn in front of the garment reads as its hem.
  • Draw order (#358, closed without a change): on the run reference the crossing back foot is a near-end-on shin drawn at full length, not a shin drawn in the wrong order. No draw-order rule fixes a missing drawing.

The record of every lever tried on the way to this table, with its measurement, is docs/JOURNEY-gait.md.

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] [--depth walk|run]

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; rig-pose pelvis, below, supersedes it — it reads only a dark skirt on a non-white ground and turns the hip pair, where the command reads line art and light garments and writes a track of its own) 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.

The pelvis track: rig-pose pelvis

rig-pose pelvis --pose dance.scan.json --frames frames/ --out dance.pelvis.json [--mode silhouette|waistband|below-cover] [--person <id>]
rig-pose pelvis --pose dance.scan.json --pelvis-keys pelvis.keys.json --out dance.pelvis.json

A pose detector keeps little of a drawn pelvis tilt (#288): on a frontal walk drawn with the pelvis line in every key, its hips kept 4.5° of a 30.5° swing (correlation +0.27), and clean and retarget carried that faithfully, so the tilt was lost at the scan. The drawing has it, so pelvis reads it off the drawing and writes it into the sequence as its own track, which clean smooths and retarget keys. The order is scan → pelvis → clean → retarget: a sequence that already carries a cleaned record is refused (PELVIS_AFTER_CLEAN), since clean must smooth the track, and so is one that already carries a track (PELVIS_TRACK).

--frames is a folder of PNG frames, one per sequence frame, taken in file-name order and of the sequence's size (PELVIS_FRAMES, PELVIS_FRAME). There is no video decoding: extract the frames first. The hip keypoints place the reading — where the hips are, and which of them is the figure's right — but their angle is not read. Two readers, both on the hip span (the columns between the two hip keypoints):

  • silhouette (the default): the straightest edge of the drawn figure across the hip span, near the hips' height. An edge pixel is a figure pixel (a channel more than 64 from the background colour, the median of the frame's border) beside a background pixel, so a drawn outline and a filled shape both have one. Every straight line within ±40° of level and one hip width of the hips is scored by the edge pixels within 1 px of it; the best is read when its pixels cover at least 60 % of the span's columns, and fitted by least squares (one 3-MAD rejection pass) within 2.5 px rms. It reads a drawn pelvis line, or the edge of a drawn hip.
  • waistband: the top edge of a garment covering the hips (a skirt, shorts). The garment's colour is the median at the hips' midpoint, and must not be the background's. In each column of the span, the first row from 1.5 hip widths above the hips where three pixels in a row carry it (within 40 per channel) is the top edge, and the line is fitted through those tops on the same terms. The colour must hold on its patch: at least 75 % of the patch's pixels carry its median (#293). A dithered or paletted garment (a 216-colour GIF, whose channels alternate between palette steps pixel by pixel) does not, so its pixels are read through the mean of a 5×5 window, which does. Hips on the band's top edge give a patch that is half garment and half ground, so the patch is also tried one and two patch radii (0.3 hip widths) below the hips, and the scan then runs down to that patch. The first patch that holds and is not the background gives the colour, trying each height as is and then through the window mean. If none holds, the midpoint's median is read as before. The PELVIS line counts the frames read through the window mean and those whose colour was found below the hips.
  • below-cover (#342): for a garment whose top is hidden under another — a sweater's hem lying over a skirt's waistband hangs level whatever the pelvis does, so the band-top reader reads the cover. It reads what the cover cannot hide, every colour through the 5×5 window mean, a pixel being garment when it is nearer the garment's colour than each leg's (sampled 0.7 of the way hip → knee) and the ground's: the hem (in each column of the span, the last garment row before three that are not; a line fitted through them), the thigh roots (along each hip → knee, where the garment stops for 3 px; the line from the right root to the left), and the waistband's band top only when the ground lies above it. The tilt is the mean of the cues; fewer than two, or two more than 8° apart, decline the frame by name (too few cues below the cover, cues disagree). A CUES line per frame prints every cue and the hip keypoints' line and sign beside the fit's; the hips are reported, not fitted (a detector's hips keep a fraction of a drawn tilt), and a HIPS-SIGN line names a frame where their sign is opposite the fit's. On the synthetic walk with shorts turned with the drawn pelvis under a level sweater (sweater), every frame reads within 1.5° of the drawn tilt; the waistband reader reads the sweater's level hem there. Known limit: a thigh drawn in front of the garment (raised toward the camera) is read as the hem — its outline over the cloth looks like the hem's edge, and the hem and that thigh's root agree on it — so those frames must be declined by hand (set their pelvisTilt to null, or leave them out of --pelvis-keys). Reading them is #349.

Both pass over the forearms: pixels within 0.15 hip widths of a segment elbow → wrist (1.5 times that of a wrist, the hand) are not read, and a waistband top right under a forearm is not taken. A frame neither reader can read is declined: its pelvisTilt is null — marked, not guessed — and a DECLINED line names the frames and the reason (the pelvis blank, no garment, no straight edge across 60 % of the span, the hips side-on or missing). The PELVIS line counts the frames read and declined and gives the swing read.

--pelvis-keys is the hand override where neither reader applies: rig-pose-pelvis-keys/1, { "spec": "rig-pose-pelvis-keys/1", "keys": [ { "frame", "deg" } ], "note"? }. Frames are the sequence's and strictly increase, deg is in pelvisTilt's sense; the tilt is linear between keys, the short way round, and held before the first and after the last. A frame may be fractional (#294): a scan interpolated from drawn keys puts them between its frames — k × 4.75 for 8 keys over 38 frames — and a key rounded onto the grid moves up to half a frame and bends the tilt between. On that synthetic case (24 fps, a known tilt sampled at the keys' times), the tilt written on every frame is the keys' linear interpolation within 0.0005° (the writer's rounding), where the same keys rounded to whole frames miss it by up to 1.19° (test/pelvis.test.ts). A key outside the sequence's span, [0, frames − 1], is refused by name. Fractional frames stay /1: they are a wider value of the same key, and a reader before #294 refuses them by name ("frame" is number 4.75; it must be a frame index in [0, N]) rather than misreading them. Anything malformed is refused by name (PELVIS_KEYS), and the source is "keys". --person picks the person when there is more than one, and only that person is written.

The track is pelvisTilt on every frame and pelvisSource ("silhouette", "waistband", "below-cover" or "keys") on the sequence, written as rig-pose-sequence/1.1 (below). pelvisTilt is Spine degrees — counter-clockwise, y up — of the direction from the figure's right hip to its left, the angle retarget reads off r_hip → l_hip: about 0 for a figure facing the camera, about ±180 facing away.

clean smooths pelvisTilt as its own track, not re-read from the hips: the quadratic Savitzky–Golay over the legs' window (--legs, the hips' window), circular with --loop, on the frames that carry a reading — a null stays null. It prints a PELVIS line with the largest change, and records "pelvis": { "window" } in the record's params (the CLEAN line ends pelvisTilt smoothed as its own track over N frames). A sequence with a track whose record lacks it — the track added after clean — is refused by name (POSE_PELVIS).

retarget keys the map's pelvis chain — one bone, joints r_hip → l_hip (either way round) — from the track on every frame it reads, and from the hip line on the frames it declined; such a bone is not held across the hip pair's foreshortening (#184), since the pair no longer sets its angle. The PELVIS line says which: PELVIS: from <source>, swing N° (N the peak-to-peak of the bone's rotate keys), with the frames on the hip line named; PELVIS: from hip line (no track) when the sequence has none; PELVIS: from hip line (the <source> track read none of the N frames) when it read nothing; and PELVIS: not keyed — … when the sequence has a track and the map no pelvis chain. The report carries it as pelvis. A sequence without a track keys exactly what it keyed before.

On the synthetic walk with a drawn tilt (examples/walk/make-pelvis.ts, the hip keypoints keeping 0.15 of it), posed by rig-c's poser from the gated build, the pelvis swings 30.67° against the drawn 30° (1.02×, r = 1.000) from the silhouette track, and 4.51° (0.15×) from the hip line without it; under a garment band whose top edge turns 24° peak to peak out of phase with the hips, the waistband track gives 23.76° (0.99×, r = 1.000 against the band's edge); with the pelvis painted out, no frame is read and the motion is the hip line's, byte for byte (test/pelvis.test.ts). The same band reduced to the 216-colour palette by a 4×4 ordered dither (the dithered variant) is read on 24 of 24 frames, giving 23.95° (1.00×, r = 0.999 against the band's edge). With the hip keypoints also moved up onto the band's edge (dithered-edge) it is read on 24 of 24, giving 23.94° (1.00×, r = 0.999); the reader without the change reads none of either.

Derived depth: clean --depth

rig-pose clean --pose run.scan.json --out run.pose.json --loop --depth run

A sequence carries x and y only, so nothing downstream can say which limb is nearer the camera (#281). Part of it is in the picture: a segment drawn shorter than its rest length leans out of the picture plane. With --depth walk|run (off by default; without it the output is byte for byte what it was), clean writes on every frame, for each limb segment whose two joints are placed, "depth": { "<segment>": { "ratio", "sign", "source": "derived" } }, read on the cleaned positions. The segments are r_upper_arm, r_forearm, r_thigh, r_shin and the same on the left, each from its joint nearer the trunk to the farther one.

  • ratio is the segment's drawn length ÷ its rest length. The rest length is, per kind of segment, the larger of the two sides' 95th-percentile picture lengths over the clip: the straight length --leg-scale reads (#148). A segment drawn foreshortened on more than 5 % of the clip on both sides has a rest length shorter than its own, and its ratios read high: on the synthetic run below, the forearms (elbows bent 70° forward through the whole stride) rest at 41.05 px against their true 60.

  • sign is +1 when the segment's far end is nearer the camera than its near end, −1 when it is farther, and 0 when the ratio is within the flat band, 0.95–1.05, or above it (drawn longer than rest is not a lean). cos 18.2° = 0.95, so a segment within about 18° of the picture plane reads flat; the card set the same band for its flat fixture. Which way a foreshortened segment leans the length cannot say, so the body says it, "forward" being the way the trunk faces:

    segment leans why
    thigh forward a hip flexes forward, and extends back too little to foreshorten past the band
    shin back the owner's walk statement (#336): the lifted foot is behind the supporting leg. A knee folds the shin back, in a walk and in a run
    upper arm with the opposite leg in a gait each arm swings with the other side's leg: forward when the other thigh is the more foreshortened, back when its own is, forward (a shoulder flexes forward) when neither is
    forearm forward an elbow bends toward the back of the body, so the forearm folds forward

    The thigh's row is the owner's run statement (#336): the lifted leg is in front, its knee protruding. The table is the same for both gaits, so --depth walk and --depth run write the same signs; the gait the flag records decides which joint --draw-order depth orders the legs by (below).

    The trunk faces the camera (forward is +) when its left shoulder and hip are drawn right of its right ones, away (forward is −) when they are drawn left, and is side-on when the shoulders and hips are drawn, on average, less than 0.1 trunk lengths (neck → mid-hip) wide: forward is then across the picture, so every sign on that frame is 0 — undecided, not guessed.

  • source is "derived". "measured" is for a 3D detector, which may fill the same field: it is read, never written here. clean carries a measured depth through as it was read (DEPTH measured depth … carried through), and refuses --depth on one (CLEAN_DEPTH), since a derived reading would replace a measurement. A sequence carrying derived depth is refused without --depth (CLEAN_DEPTH): its depth was read off positions the clean moves.

The record says it: "depth": { "gait" } in params, and the CLEAN line ends depth derived per segment (gait <gait>). The DEPTH line gives the rest lengths, the band, the segment-frames toward, away and flat, and the frames the trunk faced the camera, faced away and was side-on. The sequence is written as rig-pose-sequence/1.2 (below).

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.

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). Nothing is keyed from it unless --draw-order depth asks (below): without the flag the motion is byte for byte the same with and without depth.

On the synthetic fixtures (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 and under --depth walk, as the generator draws them, and the two files differ only in the gait they record; on the frontal walk in place, the 28 shin-frames the generator draws away from the camera (the lifted foot behind) 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 48 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.

Draw order from depth: --draw-order depth

rig-pose retarget --rig rig/ --map map.json --sequence run.pose.json --out motion.json --name run --draw-order depth

With --draw-order depth (off by default; without it nothing changes) retarget keys the legs' draw order from the sequence's depth (#304), so a walk's lifted foot is drawn behind the supporting leg and a run's lifted leg, its knee forward, in front (#336). 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. A key's t is half a frame before the frame it shows on (0 on the first frame), as the switches' attachment keys are: a key exactly on a frame time is a tie, and the render breaks it a frame late on about half of them (#325).

  • 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. Each forearm against the trunk is keyed from a limb layout (below).
  • The rule. On each frame the leg whose depth 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 a tie, the depth a flat chain can hold there. 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.
  • The joint the gait names (#336, the owner's two statements). On a sequence cleaned with --depth walk a leg's depth is its ankle's (the hip at 0, plus the thigh's, plus the shin's), the tie √(1 − 0.95²) × (thigh + shin) of the rest lengths (46.83 px on the fixtures): in a walk the lifted foot is behind the supporting leg. With --depth run it is its knee's (the thigh's), the tie √(1 − 0.95²) × thigh (24.96 px): in a run the lifted leg is in front, its knee protruding. The limb mean (layers, above) says neither: a lifted leg's thigh forward and shin back cancel in it, and a walk whose thighs stay inside the band leaves it at most half a shin (35 px on the fixtures), under its tie of √(1 − 0.95²) × (thigh + shin ÷ 2) (35.89 px). A measured depth names no gait and keeps the limb mean and its tie (#304).
  • 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.
  • A walk cleaned before #336 (#336). A derived depth whose foreshortened shin leans the way the trunk faces carries the sign table before #336, where a walk's lifted foot read forward; the ankle would draw that foot in front. The flag refuses it by name (DEPTH_RECLEAN), listing the frames, until it is cleaned again with this version. The table since leans the shin back in both gaits, so a record it wrote never matches. A run's signs did not change, so a run cleaned by an earlier release is read as it is, and so is a walk with no foreshortened shin (its signs read the same under both tables). Measured depth is not checked. The file format does not change.

On #281's fixtures, retargeted onto a limb-cut rig whose legs overlap (the left leg drawn first; test/draworder.test.ts):

  • The walk (by the ankle): each of the 36 frames with one leg lifted draws that leg behind; the order changes on frames 3, 15, 27 and 39, where a lifted foot first goes back past the tie, and holds through the double support between. The generator orders the true ankles on 16 of the 48 frames, and the keyed order agrees on all 16.
  • The run (by the knee): the order changes on frames 0, 10, … 50, where the other foot lands. The generator's swing thigh flexes forward from toe-off (#343), so on each of the 42 frames with one leg lifted the lifted knee is in front of the supporting one, its thigh leaning forward past the band, and that leg is drawn in front. The generator orders the true knees on 42 of the 60 frames, and the keyed order agrees on all 42. On frame 12, on a rig that draws the right leg first, none of the 8,771 pixels the lifted right leg draws shows the standing leg, against 2,205 in the setup order.
  • Read otherwise: by the limb mean or by the ankle, the run draws the lifted leg behind on 21 of its 42 lifted frames; by the limb mean or by the knee, the walk keys nothing (the setup order) and draws the lifted right leg in front on its 18 frames. Knees planted 10 px apart, inside the knee tie, hold the order before; without the tie they swap it.

The forearms against the trunk: a limb layout

With --draw-order depth, each forearm is also keyed in front of or behind the trunk (#346), when the rig carries the layout @rig-c/parts writes for it and the map binds it.

  • The layout. rig-parts rig (parts 1.56.0 and later) writes layout.json beside rig.json when the config declares a limb layout, for example "layout": { "parts": ["forearm_r"], "before": { "part": "torso_r" } } on the arm: { "spec": "rig-parts-layout/1", "rig": { "file": "rig.json", "sha256" }, "sets"?: { "file": "sets.json", "sha256" }, "setup_slots", "placements": [{ "limb", "parts", "before" | "after", "slots", "target_slots" }] }. Each placement is one group, its slots drawn immediately before or after a target block. retarget reads it only with the flag. It must be bound to the rig.json bytes it reads (LAYOUT_RIG_SHA), to the sets.json beside it, or to none when there is none (LAYOUT_SETS_SHA), and its setup_slots must be the rig's slots in order (LAYOUT_SETUP). The producer's adjacency is not taken as proof: each block is read again in the rig's compiled draw order, and one that is not an unbroken run next to its target is refused (LAYOUT_ADJACENCY).
  • The binding. The map names each group's depth source in layout (below): "layout": [{ "limb": "arm_r", "parts": ["forearm_r"], "depth": "r_forearm", "against": "trunk" }], limb and parts the placement's own. The supported sources are r_forearm and l_forearm, against "trunk" (the shoulders' and hips' plane). Any other is refused when the map is read (MAP_LAYOUT). A placement the map does not bind is refused by name (LAYOUT_UNBOUND): no source is taken from a name, a nearest bone or a zero depth. A binding that no placement has is refused (LAYOUT_BINDING_UNKNOWN), and so is a binding with no layout beside the rig (LAYOUT_MISSING). A group's slots must hang on the bone the map gives the segment, or under it, and no target slot may be a limb's (LAYOUT_BINDING_BONES).
  • The rule. On each frame, a forearm's depth is what its segment adds to the wrist over the elbow: sign × rest × √(1 − ratio²) px, + toward the camera. It is drawn in front of its target where that depth is toward the camera by at least the tie, and behind it otherwise. A frame where the segment is not named keeps the setup order. The tie is the legs', scaled to what is read: √(1 − 0.95²) × the forearm's rest length. The forearm's own lean is read, not the wrist's depth from the shoulder, because the upper arm's derived sign is a gait's reading, not a measurement. The group and its target change places in their run. Keys are written where the whole order changes, half a frame before the frame they show on, and merged with the legs' keys into one timeline. A group whose run overlaps a keyed leg pair's is not keyed, and the line says why. One DRAWORDER line per group names its source, target, tie and the frames in front. The report's drawOrder adds layout ({ path, sha256 }) and groups, one { limb, parts, depth, against, setup, slots, target, keyed, tie, front, back, frames } per group.
  • One target per group. parts refuses two placements that share a slot (RIG_LAYOUT_CONFLICT). So both forearms against one torso slot cannot be declared: each needs a target block of its own (in the fixtures, the torso is cut into two halves). On a rig with a single torso slot, only one forearm can be placed until rig-c/parts#657 lets placements share a target; pose does not work around it.

On #281's fixtures, retargeted onto the walk's figure with a torso cut into two halves, each forearm drawn immediately behind its half at setup (test/forearms.test.ts), compared with the generator's true depth. A forearm is truly in front where the mean of its elbow's and wrist's forward depth is above the trunk's plane, the same in every view:

  • The frontal run: each forearm's depth says in front on 48 of the 60 frames (tie 13.17 px). Both are keyed in front on 48/48 and keep the setup order on the other 12/12. The legs' order is the same as without a layout. Every frame truly in front by the tie is drawn in front (27/27). By the sign of the truth alone, 42/42 frames truly in front are drawn in front, and 12/18 frames truly behind are drawn behind. The other 6 are 3.3–3.8 px behind the trunk, inside the tie, with the elbow swung back while the forearm leans forward.
  • The run seen turned 40° (#358's camera): 27/27 frames truly in front by the tie are drawn in front. By sign, 39/42 truly in front and 15/18 truly behind agree; the 3 behind are 3.8 px behind, inside the tie.
  • The frontal walk: 4/4 frames truly in front by the tie are drawn in front. By sign, 14/38 truly in front and 10/10 truly behind agree. The other 24 lean forward less than the tie.
  • The walk seen turned: no forearm frame passes the tie, and the motion is byte for byte the one written without a layout. Its 4 frames truly in front by the tie (about 20 px) are drawn behind, because seen turned the forearm is foreshortened less. The rule errs toward the setup order, never forward.
  • No frame in any of the four is drawn in front while truly behind by more than the tie. Without the flag, a layout and a binding change no byte of the motion or the report.

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, or a directory that cannot be written into, is refused (ARG_PATH) before anything is written; --build and --render make their own and are held to the same refusal. clean --out, pelvis --out, convert --out and compare --report/--sheet do the same (#284). 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.
  • REACH-ORIGIN — one line per reach chain (#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.
  • 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. When the pelvis bone was keyed from the sequence's pelvis track (#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, 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).
  • PHASE — one per set with a phase (below, #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 }.
  • SWITCH-MIN — one per set the minimum span held back (#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.
  • SWITCH-TURN — one per set kept off frames by its art turn (#355): the runs on which its key was within the tolerance but a bone it shows by its drawing unaided turned more than --switch-tolerance from its drawn angle, so it was not a candidate there, and what showed instead. A set whose art never turned past the tolerance prints no line.
  • SWITCH-BIND — one per set with a bind (#360, below): the frames it is bound on as runs, the bind they came from, and on how many of them it shows (shown on N/N).
  • 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).
  • DEPTH — when the sequence carries depth (above): 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).
  • DRAWORDER — with --draw-order depth (above): the keys and the spans they key, per leg drawn behind, cut to 260 characters — DRAWORDER from depth by the knee (run; tie 24.96 px), 6 key(s) over 3 span(s) (leg_r behind leg_l 0–9, 20–29, 40–49), naming the joint and its tie for a keyed pair (#336) — and a line per pair not keyed, saying why.
  • PELVIS — when the map has a pelvis chain (one bone, joints r_hip → l_hip) or the sequence a pelvis track (above): 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.
  • 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; 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: { 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; 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: With a sequence that carries depth (#281, above) 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) it carries drawOrder: { source: "depth", pairs, keys, spans }. pairs gives per pair { a, b, limbs, slots, keyed, tie } (by, "knee" or "ankle", when the sequence's gait names the joint the pair was read at, #336, the tie then that joint's; and why when not keyed), keys the timeline with each key's frame (the frame it first shows on; t there is that frame's time, as everywhere in the report, and the motion's key sits half a frame before it), 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.

With a map that has a reach chain (#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.

With a map that binds a set (#360, bind) the report also carries switchBinds, one { set, frames: [[first, last], …] } per set with a bind in map order: its bound frames as inclusive runs, increasing and disjoint, the cycle form expanded (the SWITCH-BIND line's). Each switches row of a bound span carries bound: true. A map without one writes no switchBinds key.

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. endOnHeld: { <bone>: [[first, last], …] }, only when there are any (#355): per bone the set draws, the runs of frames it was end-on on (under --short), held at its drawn angle. 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.
  • 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); [] 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; turned (#355) the runs on which its key was within the tolerance and a bone it shows by its drawing unaided turned past the tolerance from its drawn angle, so it was not a candidate (before bridging, hysteresis and the minimum span, like nearest). 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; nor has a set with bind.
  • 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 build's record: retarget.json

--build <dir> keeps the gated build — rigc's own files, byte for byte as rigc wrote them — and beside them one more file, retarget.json (rig-pose-retarget/1, #270): what made the build. It is written once the kept build is green and the inputs are re-read unchanged, and a RECORD line says so (RECORD retarget.json written beside the build (reference: <path> | none given)). Its keys, in this order:

{
  "spec": "rig-pose-retarget/1",
  "pose": { "version": "<this package's version>" },
  "rigc": { "version": "<the rig-c that gated the run, as the RIGC line names it>" },
  "inputs": {
    "rig": { "path": "<--rig as given>", "sha256": "<the rig spec's bytes>" },
    "map": { "path": "<--map as given>", "sha256": "<the map's bytes>" },
    "sequence": { "path": "<--sequence as given>", "sha256": "<the sequence's bytes>", "source": "<its \"source\", verbatim>" }
  },
  "flags": { "name": "<the animation>", "minScore": 0.3, "...": "every retarget option as resolved — the report's options, in their order" },
  "reference": "<--reference as given>"
}
  • Paths are as typed. Each path is the argument as given on the command line — not resolved, not made relative, not normalised. Each sha256 is the hex digest of the file's bytes the run read (the same bytes the report's inputs hash). --rig given as a directory or as a rig.json hashes the same file: the rig spec, rig.json, that rigc builds from. A motion.json read beside it (or --base-motion), a sets.json and the images are not in the hash, as they are not in the report's.
  • reference is the author's, and only when given. --reference <path> (with --build; without it the run is refused: --reference needs --build: the record it goes in is written beside the build) names what the motion was made to match — the video, say — and the record carries it exactly as typed. It is echoed, never read: the path is not checked to exist. Without --reference the record has no reference key — not null, and nothing is derived from the sequence's source or its path. (compare --reference is another flag.)
  • Deterministic. No clock, no host name, no absolute path the user did not type: the same inputs and flags write the same bytes into any build directory. 2-space JSON, a trailing newline.

rigc review is the intended reader (rig-c's later card): it reads the build and, through this file, what made it.

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 unit is the segment's own straight length, not a leg length (#237). min and max are shares of the segment's 95th-percentile picture length, which LEG-SCALE prints in px beside its share of FEET's leg length (the median hip → ankle --stance is in); the two are different units and different percentiles. FEET prints every mapped segment's picture length ÷ straight (min · median · max) with or without --leg-scale, so the fold the clamp would meet can be read first. A free leg clamped at the floor is drawn longer than the picture's, so its ankle hangs lower (or, past the ceiling, higher) than the picture's: on the clamped frames of a free leg LEG-SCALE prints how far, against the same leg posed at the picture's scale on every segment, the supporting leg as keyed in both, in the rig's leg at setup (legScale[].free: { side, frames, count, above, below }, above/below as { units, legLengths } or null). The card's 0.8,1.05 (#47) was read off a dance whose leg picture length ran 0.86–1.18 of its median; a walk folds its swing leg further (on the tests' synthetic lift, a free leg folded to 0.69 under a 0.85 floor hangs 0.1591 leg lengths below the picture's), so read the FEET segment minimum before taking it.

rig-c's scalex multiplies the bone's x axis, and a child with the normal inherit mode takes its parent's whole world matrix. So under a thigh scaled by s, a shin at angle φ in the thigh's axes points at atan2(sin φ, s·cos φ), and is itself scaled by |(s·cos φ, sin φ)|. A child (or its intermediate control bone) with noScale or noScaleOrReflection (#153) still has its direction carried through the parent's scaled matrix, so its angle is skewed the same way, while rig-c normalises that direction to unit length so the parent's scale is not inherited. The rotate key turn − turn above assumes unit scale. A mapped bone under a scaled bone is therefore keyed through its parent's posed matrix: a rotate key k and a scalex key s' put its world x axis on its target at the scale asked (under noScale, s' is the bone's own asked scale s directly). On dance-a the unit-scale formula missed the shins by up to 2.4°, their length by 0.16 of setup and the ankles by 38 units; solved, every bone lands. A normal child's art is still sheared by its parent's scale, and a bone under it (a foot) is squashed along the shin: SHEAR reads both.

--plant-feet places the hip so the supporting foot stays where it took support, through the posed legs at the scale the pose asked for (when a segment asks for a scale below --leg-scale's clamp floor, the clamp does not lift the hip by what it withheld, #107; on a supporting leg, the segment's scalex follows the asked scale down to the bound #95 derives from the leg's own range so the supporting ankle stays on #85's floor while the free leg keeps the configured clamp, and any frame clamped at #95's bound lifts the hip by the remainder so the ankle never sinks, listed as LEG-SCALE past the floor (support), #133), instead of moving it by the pose's mid_hip travel:

  • The supporting foot is the one whose reference ankle is lower in the picture. It changes only when the other ankle has been at least 0.02 leg lengths lower for at least 0.1 s (timed as END-ON runs are), and once confirmed takes support from the frame it crossed that margin (#102); a leg length is each side's median picture length hip → ankle, and the switch compares against their mean.
  • Frontal views (#280). Seen from the front, a foot's depth is drawn as height: the nearer foot is the lower one, and it may be the one in the air. So every clip is also read by a second signal, the ankle's vertical speed: a planted foot moves with the ground — still under a fixed camera, or sliding back at the ground's speed in a gait drawn in place. Each side's ground speed is the median of its ankle's vertical step over the frames outside every jump (a walking foot is down on more than half of them), and a foot is down on a frame when the step into it or the step out of it departs from that speed by less than 0.01 leg lengths (the re-plant's stillness); both feet can be down. When speed and height disagree by more than depth can draw, height wins (#369): a foot whose ankle is 0.25 leg lengths or more above the other's is in the air whatever its speed, so the vertical speed never reads it down and the lower ankle supports. Between two feet on the floor a frontal view draws only a step's depth as height — 0.14 leg lengths on the tests' frontal walk below — and that is the only disagreement the speed is there to settle; within 0.25 the speed decides as below. Near the top of a run's swing the lifted foot is nearly still while the landed foot still settles, and a run's stances are short, so its median step is not the ground's: on a frontal run (two flights a stride) the speed read the lifted foot down 0.36 leg lengths above the planted one on the tests' run and 0.72 on a frontal run reference, planted it, and hoisted the body onto it. On the tests' frontal run carried on a floor (make-depth.ts's ground: 10 strides of 20 frames, each foot down on 4, two flights of 6) the lower foot supports on every one-foot frame, the planted foot sits on the floor to 0.0000 leg lengths, and the hip's vertical range is 0.900 of the sequence's; read by speed alone, the lifted foot supported on 40 frames, the planted foot sat 0.094 leg lengths off the floor and the hip's range was 1.69 of the sequence's. The two signals contradict on a frame where the lower ankle (by 0.02 leg lengths) moves while the higher moves with the ground. A clip is read as frontal when that happens on more than 5 % of the frames outside the jumps and its stride shows in depth: on each side the ankle's range against its hip (5th to 95th percentile) is larger in y than in x — a side view's stride is in x, and its stance ankle rises and falls with the leg's angle, so there the vertical speed does not decide. 5 % sits below the shortest contradiction a frontal walk makes (one frame a step: 2 of the 24 frames of a 1 s cycle, 8.3 %) and above every fixture the tests read in depth (0 %); the tests' synthetic frontal walk contradicts on 18 of 96 frames (18.8 %). On a frontal clip each frame's support is the vertical speed's: the one foot down; on frames both are down, the foot that landed, so support passes on the frame it lands (as #102 takes it from the landing frame); ankle height's only where neither foot moves with the ground. A plant then ends where the other foot lands, so no step releases one and #113's and #119's readings are not made. On a frontal clip FEET prints two support: lines — the frames contradicted and their share against 5 %, then the ankle ranges — and one stride line per support span: the signal that decided its frames, the frames both feet were down, and what ankle height alone would have read there, e.g. stride l 8–15: ankle vertical speed on 8 frame(s) (both feet down on 3); ankle height alone would read r on 2 of them. On any other clip ankle height decided as before and nothing is printed, in any output mode, so such a run's output is as it was. On the tests' synthetic frontal walk (an 8-drawing cycle on twos interpolated to 24 fps, both feet down on 4 of the 8 drawings, the nearer foot lower, the ankles moving 17 and 23 px in y) the feet read down are its contact table on all 96 frames, where ankle height alone supported a foot in the air on 16. The third signal the card names, the sole's distance to a floor line, is not read: no floor line in the picture can be declared (--floor is a world y, placed from the support it would have to decide).
  • Contact keys, by hand (#303, the route for drawn references that #313 asked for). A drawn frontal walk can defeat every reading above: its nearer foot is drawn lower, so ankle height names the foot in the air, and a slow walk can step under the vertical speed's 0.01-leg-length stillness, so #280 never reads it as frontal. A single floor line in the picture does not help either: seen from the front the ground is a band, not a line (#303's reading). The artist's key sheet already carries the contact table, so --contact-keys <file> takes it: rig-pose-contact-keys/1, { "spec": "rig-pose-contact-keys/1", "keys": [ { "frame", "side" } ], "note"? }, side one of l, r, both, none. Frames are the sequence's, may be fractional (a key sheet timed off the frame grid, as --pelvis-keys, #294), and strictly increase within [0, frames − 1]. A key holds until the next one, and the last to the clip's end: contact is a state, not an angle, so there is nothing between l and r to interpolate. A fractional key takes effect on the first frame at or after it; frames before the first key are not covered and keep the reading without keys. The precedence is contact keys, then the vertical speed (#280, on a frontal clip), then ankle height: the keys are applied last, over whatever the readings decided, on every frame they cover. l or r is that foot; none is flight, no foot supports; both is the foot that landed, as #280 hands over on landing: the other side from the l or r key before the run, or, where the run starts the cover or follows a none key (flight), the side the next l or r key names (the side that stays down after). Those neighbours are read from the keys, so a key that reaches no whole frame still counts: l@0, none@1.1, both@1.2, l@3 holds l on frame 2 (#338). The support spans and changes are then rebuilt from the per-frame support, as #280's are; a step, #113's lowered or #119's tracked run is kept only where it starts before the cover, cut at the frame before it (a step's re-plant inside the cover is not read), and jumps are kept as read. FEET prints one support: contact keys line (the keys, the frames they cover, how many frames they changed, the flight they declare, the frames before the first key) and one stride line per support span naming what decided its frames and what the reading without keys would have named, e.g. stride l 19–37: of 19 frame(s), decided by contact keys 19; the reading without keys would name r on 4 of them; these stride lines stand in for #280's. The report gains a contactKeys key (the file's path and SHA-256, the key count, the first covered frame, the runs changed) and options.contactKeys, only with the flag. On the tests' slow synthetic frontal walk (8 drawings a stride, each held 4.75 frames at 24 fps, keyed from its third drawing on), the support equals the keys on all 142 covered frames, where the reading without keys misses them on 16; the 10 frames before the first key keep that reading (test/contactkeys.test.ts). A malformed file, a side outside the four, a frame outside the span or one not after the key before it is refused by name (CONTACT_KEYS), every problem in one run, and so are keys on a map whose legs give no support to decide. Without the flag nothing is read and the output is as it was.
  • The new support is held where it is on the frame it takes over, and the hip blends in y from the old derivation to the new over 3 frames while taking the new derivation's x immediately (#102), so the planted foot does not slide across the blend.
  • A jump — both reference ankles and mid_hip more than 0.05 leg lengths above their rolling floor, the 95th-percentile picture y over ±1.0 s around the frame, for at least 2 frames, starting from the first immediately preceding push-off frame where both ankles are already above their rolling floor while mid_hip lags (#142) — releases the plant: the hip follows the pose's travel from where it was, and re-plants on landing (blended the same way). At a walking hand-over the front foot has not landed yet while the back ankle lifts its heel and the hip rises 0.051–0.065 leg lengths; when the front foot lands while the back ankle stays above its floor, a back ankle that holds its height in y (< 0.01 leg lengths a frame for 2 frames) is on a raised heel rather than in flight and is not read as a jump (#101). The body has to lift: an ankle's picture y drifts with depth, and under one floor for the whole sequence the subject in dance-a standing still (frames 0–29 and 277–312, mid_hip moving 10 and 14 px) read as jumping, 152–155 of 313 frames in all.
  • In place (#92). On a sequence with "travel": "in-place", the supporting foot of a figure that does not travel moves back in the picture while the body stays. Holding it where it planted turns that slide into the body's travel, so the hip walks off and never comes back. Under the declaration each plant's anchor follows its own ankle's picture x, scaled into the rig, from the frame it planted, and keeps its y on the one floor. No step releases a foot, since a foot that follows its track has no plant to drag. Every foot may take support, and the lower one does, by the rules above. The loop is then closed: the hip's x on frame i moves by −drift · i / (n − 1), where drift is its x on the last frame less its x on the first. The last frame's hip x is then the first's, and the loop carries no net travel. The y is the floor's and is not touched. FEET prints the drift closed, in place of the steps line, and the report's feet.closure carries { drift, frames } (null otherwise). On the tests' synthetic in-place loop (40 frames, each supporting ankle sliding back 2 px a frame, the hips still), the hip moves less than 10⁻⁹ px. The same joints without the declaration put it 68 px forward over the loop. With a slide of 0.04 leg lengths a frame, both feet are released as stepping and never re-plant without the declaration, and the supports alternate with it. Jumps are read as before. What planting is and is not needed for (#242): the declaration is the sequence's, so the support reading (no step releases a foot), the camera-zoom normalisations and the head nod (no detrend window, no clamp) take it with or without --plant-feet. Planting is needed only for what acts on a planted hip: the anchor following its ankle's track and the loop closure, since without a plant there is no anchor to follow and the hip is the pose's own travel. Without --plant-feet the run says so in a TRAVEL line: sequence declares travel: in-place but --plant-feet is off — its supports and the head nod are read in place as with it; the loop closure is --plant-feet's (it closes the planted hip), so the hip follows the pose's own travel: x +0.00 units from the first frame to the last, not closed (the figure is the hip's measured x drift; and the map translates no bone when nothing is translated). On the tests' synthetic walk in place (7 cycles of 24 frames, the supporting ankle sliding up to 0.09 leg lengths a frame, the eyes bobbing 3 px twice a cycle), without --plant-feet the supports are 15 spans covering all 168 frames (the seam splits the first) and the head's rotate key and nod are the same on cycle 1 and cycle 7 to 0.001; reading the declaration only under --plant-feet, as before, they were 2 spans covering 3 frames, and the head's cycle 7 differed from cycle 1 by 1.057° and 0.661 units. That head figure came from the nod's detrend window cut at the clip's ends, which #241 wraps on a loop, so it no longer reproduces under that reading either.
  • A step releases the plant too: while the supporting ankle moves more than 0.03 leg lengths a frame, the other foot takes support if it is planted (not itself released or moving), and otherwise the hip follows the pose's travel. The released ankle may support again once it has moved less than 0.01 leg lengths a frame for 2 frames. On dance-a the plant held the supporting ankle through a real step (frames 176–181, 579 → 718 px) and threw the hip sideways.
  • Read from the lower ankle (#113, #119). On a sequence not declared "in-place", a foot whose ankle is at least 0.02 leg lengths higher in the picture than the other's never takes support: not when a plant starts, and not when a step releases the other foot. Before, a step handed support to the only foot free to take it, even a folded, lifted one, and the hip was then placed under a foot in the air. A supporting foot that the other ankle has been that much lower than for 0.1 s hands support to it when it may take it, as before; when the lower foot may not (it is released and not yet still, or moving), the supporting foot is released, and the hip follows the pose's travel until a foot may take support. On a frame after a jump or a step's release where the only foot free under #113 is the higher one (cause: "higher"), the lower foot supports tracked, not planted (#119): the hip puts it on #85's floor and follows its scaled ankle track (FEET tracked, feet.tracked) until it has been still for 2 frames (< 0.01), when the normal plant takes over at that position. Steps and jumps are read as before, and under "in-place" #92's reading stands. FEET support read from the lower ankle names every run of frames whose support differs from the reading before #113, the side before and after (none: no foot supports), and for each none why: released, the supporting foot let go because the lower ankle could not take support (released by a step, or moving), or the only foot free to take support was the higher one. The report's feet.lowered holds them as { frames, was, now, cause } (absent in place). On the tests' synthetic step under a lifted leg (the lifted ankle 0.2 leg lengths up, the grounded one stepping 0.05 leg lengths on frame 10 and still from 11), the lifted leg took support on frames 10–11 before; now frames 10–11 track the lower foot on the floor (feet.tracked) and the grounded one is planted from 12, when it is still again.
  • One floor (#85). A 2D rig has no depth, so its floor is one line, and a planted foot is on it. The picture disagrees: a step toward the camera puts the foot lower in the frame, and a camera residual puts a whole span higher, so a plant held where the picture puts each span leaves the floor stepping between spans. Every support span's supporting ankle is therefore moved in y onto one floor, the hip and everything above with it. A span's planted height is the world y (on the rig, posed by rig-c) its supporting ankle would be held at; the floor is the median of the spans' planted heights, or --floor <y> (world units, y up; only with --plant-feet, exit 2 without it). Each span's shift is floor − its planted height. A change of support still blends the hip over the 3 frames, now between two spans on the floor, and a planted foot does not leave the floor: on each blend frame the supporting leg is stretched along its chain about its root, by the factor --stance uses, k = (root y − floor y) / (root y − ankle y), held within [0.92, 1.08], its lower bound on the frame the supporting leg's own range as --stance's (#95; where that went below 0.92, a FEET bound from the leg's own range line, the report's feet.blend.range, and each blend run's lower). A frame that needs more is held at the bound, the rest left to the blend, and listed FEET past the bounds with the k needed and how far the foot was left from the floor. An ankle already within 0.01 units (the landing check's origin tolerance) is not stretched. The stretched scales are scale targets, so the landing check holds them; --stance then stretches the free leg, a different chain, to the supporting ankle. A jump follows the pose's travel from the hip it left, which is on the floor, carried linearly from take-off so the landing frame meets the floor and clamped so no frame inside the flight puts a foot below it (#103), and is held the same way over its first 3 planted frames, with the flight's carry held across the landing blend so it fades out with the blend weights rather than dropping on the first frame after landing (#147). --stance is unchanged: it stretches the free leg to the supporting ankle, which is on the floor. On the tests' synthetic stick, three spans 0, 6 and 1 px apart in the picture (0, 9 and 1.5 units on the rig) plant within 0.0001 units of one floor. With the shift forced to 0 they stay 9 units apart; without the stretch, the supporting ankle leaves the floor by 6.75 units on the first blend frame.
  • A loop's planted heights (#285). Support passes on the frame the incoming ankle first reads 0.02 leg lengths lower than the outgoing one, and each span's planted height is read through that hand-over from the span before it, so every hand-over steps the planted height the same way: over identical cycles the heights descend as a staircase while the picture's floor stays where it is. Two things keep a loop's floor a loop's. Under --leg-scale the outgoing plant, on the hand-over frame and the blend after it, holds its ankle with its leg at the scale the pose asked on its last supporting frame: by the hand-over frame the picture already draws that leg shortened by the lift, and posing it so read the lift as the floor dropping under the incoming foot. And on a sequence declared "in-place" the heights' drift over the cycles is closed before the floor is read from them, as the hip's x is: the first and the last span of the side with more spans (not counting one starting on frame 0) are a whole number of cycles apart, and the height difference between them is taken out linearly by each span's first frame, so a span and the span a cycle later have one height. A sequence with fewer than two such spans has nothing to close. On the tests' synthetic run in place (10 cycles of 19 frames, the swinging thigh foreshortened so the foot leaving the floor rises 0.08–0.12 leg lengths in the picture on its first frame off it) with --leg-scale 0.4,1.05, the shifts ranged over 2.4047 leg lengths, cycle 10's planted heights lay 323.99 units below cycle 1's, and every hand-over's blend needed a stretch past 0.92 (20 runs past the bounds); now the shifts range over 0.0033 leg lengths, cycle 10's heights are cycle 1's to under 0.0001 units, and the blend stretches by 0.9957–0.9979, the same at --leg-scale 0.8,1.05 and as without --leg-scale. Without --leg-scale the same run's shifts ranged over 0.0695 leg lengths, 9.37 units from cycle 1 to cycle 10, and now over 0.0033.
  • A loop's floor (#285). On an in-place loop the floor is the median of the spans' planted heights after their drift is closed, and the closure is anchored at frame 0: every cycle is read at the first cycle's level. Before 0.38 the floor was the median of the uncorrected staircase, so a body can sit higher by a constant between versions; that constant is this definition, not drift.
  • FEET lists the supports, the jumps and the steps (each with the side, its frames, who took support and where it re-planted; in place, the loop's closure instead), and with --plant-feet the floor: its world y and where it came from, each span's shift in units and in that side's rig leg at setup (thigh origin → ankle), and the shifts' range, then one line per run of blend frames whose supporting leg was stretched (blend held on the floor) and per run past the bounds. The report carries them under feet (supports, jumps, steps, changes, floor, shifts, blend, closure).
  • --stance <leg lengths> (only with --plant-feet, which it is refused without; off by default) plants both feet when the free one is near the floor (#60). Its unit is FEET's leg length, each side's median picture length hip → ankle — not --leg-scale's straight length, a 95th percentile — and STANCE's unit line prints it in px and as a share of the straight leg (#237). The card's 0.07 was measured on a dance, on a span where both shoes stood flat (dance-a, below); keep --stance below the lifted-foot threshold the material is graded at, since a lifted foot within the tolerance is planted: a walk graded with a 0.04 threshold plants its lifted feet under --stance 0.07. On a frame with a supporting side (so outside every jump), the free ankle's height above the supporting one is read in the picture, in the free side's leg length — the leg length FEET uses. The tolerance is a distance (#114): within it, above the supporting ankle or below it, the frame is planted both: every bone of the free leg's chain has its scalex key multiplied by k = (root y − floor y) / (root y − ankle y) on the posed rig, the root being the chain's first bone's origin and the floor the supporting ankle, so the foot reaches the floor. A free ankle within the tolerance below the supporting one is planted too: its k is under 1, since a foot slightly under the floor is the same fault as one floating above it. A free ankle past the tolerance below the supporting one is not a stance but a support error (the foot read as supporting is the one in the air), so that frame is never planted both and keeps the pose's height; it is named on a STANCE past the tolerance below line with the side, the frames and the largest distance, and read "below" in the report's stance.runs. Before #114 the gap was signed, so any free ankle below the supporting one counted as within, and a lifted foot read as supporting had the grounded leg shrunk up to it. On the tests' synthetic stance, a free ankle 0.5 leg lengths below is read below and not planted; one 0.03 below is planted with k under 1. k is held within [0.92, 1.08], except that the lower bound comes from the leg's own range in the sequence (#95): per side, r = the leg's picture length hip → ankle on the frame ÷ its straight length (the 95th percentile of that length, as --leg-scale reads a segment's), and the frame's lower bound is min(0.92, r), so the rig leg may fold as far as the picture folds it on that frame and never less far than 0.92. The upper bound stays 1.08, since a picture leg does not read longer than straight. A sequence whose legs never fold below 0.92 keeps exactly [0.92, 1.08]. Where the range set a bound below 0.92 on a planted-both frame, one STANCE bound from the leg's own range line gives each side's lowest, as does the report's stance.range. A frame past its bound is listed past the bounds with the bound it had, held at the bound and not forced. Past the tolerance above, the foot is lifted and keeps the pose's height — unless a --leg-scale clamp drew that leg at another length on the frame, which moves its ankle: STANCE then says on how many of the run's frames the height was kept, and on which frames, and by how much, the clamp moved it (stance.runs[].moved, #237). The state is read with hysteresis and a shortest run (#87), on the distance: a planted foot leaves the stance when its distance from the supporting one goes past the tolerance, and one past it is planted again only under 0.8 of it (both figures are on the STANCE line). A change of state takes effect only when its condition holds for 0.1 s (by the frames' times). A shorter crossing with the other state on both sides of it keeps their state and is listed absorbed with its frames and the height that crossed (absorbed: true in the report's stance.runs, beside the run that took it in). One that runs to the end of a support span is taken, since nothing of the other state follows it. A threshold read frame by frame turned noise at the threshold into a pop: one frame at 0.0708 against a 0.07 threshold dropped the stretch for one frame and the foot jumped up and back. The stretched scales are the scale targets, so the landing check holds them. Measured on dance-a, frames 94–175 outside the jumps: the free ankle's height above the supporting one on the posed build went from median 18.3 and p90 25.4 units to 0.0 and 0.0. On dance-b (492 frames at 30 fps) the 29 frames labelled by eye as both feet flat, outside the jumps, read 0.0 and 0.0. Of the 6 labelled one foot lifted, 5 stay above the tolerance; the sixth (frame 360) was 0.042 leg lengths up in the picture, within the tolerance, and so planted; without --stance it stood 6.5 units up, also under the tolerance (22.6 units there). That is 83 %, under the 90 % the card asked.
  • The map needs one chain ending at each ankle whose last bone is aimed "x" (the ankle is that bone's tip), and a translate the legs hang from (LEGS_MAP). A range, or a set's turn or offsets, on a bone between the hip and an ankle is refused (LEGS_RANGE): the plant is derived before the range clamp, and a clamp after it would move the planted foot.

Both are checked on the gated build: every scale lands, each ankle lands where the keys put it, and the hip's origin where the plant put it. The figures were fixed in #47 before dance-a was measured; the defaults stay off until they pass.

The idle: what the retarget drops

A rig's motion spec usually has an idle — a blink, a breath, hair and hem sway — and the new animation keys none of it (#43, item 2). The run reads that animation (--idle <name> names another; naming one the spec lacks is IDLE_NAME) track by track against the new one, channel by channel — translate poses translatex and translatey, a group track poses each of its members, a slot's rgba its rgb and alpha:

  • IDLE-DROPPED — a track the new animation keys on none of its channels, and that turns no mapped bone (below): its target, property, keys and the idle's duration. The report lists them under idleDropped (target, members, property, keys, duration, carried, notCarried).
  • IDLE-KEPT-OUT — never carried (idleKeptOut), with the reason: a track the new animation keys itself, naming the track that keys it; or a rotate, scale or shear (any axis) of a bone above a mapped bone — between mapped bones, or above the first one (the map's chains and its translated bone) — naming the mapped bones below it: the retarget's keys hold every unmapped bone at its setup pose, so such a track would turn them off the angles the pose asks. A translate of such a bone is carried: it moves the bones under it and turns none. The exception is a translate of a bone above the translated bone (the hip), which is kept out because the retarget places that bone's origin. (Folding a carried parent's motion into the mapped bones' keys is a later card.) The idle's drawOrder, events, ik, transform, deform and sequence timelines are not tracks, and are listed here too.

--carry-idle (off by default: a blink keyed over a set's art may not suit it, and that is the author's call) loops every dropped track into the new animation: the idle's keys repeated every idle duration and cut at the new animation's end, with the value interpolated there.

  • A key's ease or curve describes the segment to the next key, so a loop is built key by key. Where the idle's last key is at its duration and its first at 0, the two are one key at each seam (the next cycle's first; if their values differ, the line says where the loop jumps, as a player looping the idle does). Where the last key is earlier, it is keyed stepped, so it holds to the cycle's end as it does in the idle. Where the first key is later than 0, each later cycle opens with a stepped setup key: 0 for a bone's translate, shear and rotate, 1 for its scale, the slot's setup attachment; for any other property such a track is not carried, and the line says why.
  • A raw curve states absolute times, so a repeated one is shifted with its key. The segment the cut falls in is split there: linear and stepped as they are, a Bézier (a raw curve, or a named easing turned into one as rigc does) by de Casteljau, so the part kept is the same cubic and the last key carries neither ease nor curve. rig-c draws a Bézier as Spine's polyline, so a split segment and the idle's whole one sit off the cubic, and off each other, by that alone (measured on the test's eased segment: the idle's own up to 2.6e-3, the carried part up to 9.3e-4).
  • A track with lag or stagger, or whose values are per-member maps or models, is not carried, and the line says so.

Every bone still has to land with the carried tracks in. A carried translate above the translated bone moves its origin off the pose's travel, and the run refuses with POSE_LANDS, naming the carried tracks above the bone that missed.

On dance-a (with --carry-idle) the idle has 26 tracks. Kept out: the four arm rotates, which the dance keys, and a control bone's scalex above the chest, the neck's rotate and a head control's rotate, which sit above the mapped chest, head and arms — carried, they missed the chest, the head and the arms by 0.16°–1.37°. Carried: the other 19, the blink (an eyes group's scaley) and the breath (the chest control's translatey) among them. The gate is green, every bone lands (worst 0.00167°), and rig-c's poser shows the blink at every cycle (scaleY 0.12 at 2.37 s, 5.70 s, 9.08 s and 12.41 s, against the idle's at t mod 3.333 s within 8.9e-6) and the breath's offset (5.02 at 0.833, 4.167, 7.5, 10.833 s; 0 at 2.5, 5.833, 9.167, 12.5 s; 1.035 at the cut, 13 s).

--merge <file> merges a timed track fragment into the new animation, at absolute times from its start — blinks read off the video, for example:

{ "spec": "rig-pose-tracks/1", "tracks": [ { "group": "eyes", "property": "scaley", "keys": [ { "t": 2.3, "v": [1] }, { "t": 2.37, "v": [0.12] }, { "t": 2.45, "v": [1] } ] } ] }

Each track is a rigc-motion track. One posing a channel the new animation (or a carried idle track, or an earlier fragment track) already keys is refused by name (MERGE_CONFLICT): a fragment adds a track, it never replaces one. So is a key before 0 or past the animation's duration (MERGE_TIME), and a file of any other shape (MERGE_SPEC). What the track means — its target, property and values — is rigc's to judge: its gate checks every merged track like any other (RIG_GATE). MERGE lists each track merged; the report lists them under merged.

Few keys: --reduce

The retarget writes one key per pose frame on every mapped bone. That is faithful, but nobody can edit it by hand. --reduce <deg> (off by default) thins each mapped bone's rotate track to a few keys with curves, so that every bone's world aim stays within deg of the dense keys on every pose frame. --reduce-hip <units> does the same for the hip's translate, holding its world origin within units; without it, the hip stays dense.

  • Keys stay on the pose's own frame times. Between two kept keys, the span is linear if a straight line is within the tolerance. Otherwise it gets one raw Bézier curve, with its handles at the span's thirds and its values fitted by least squares. The error is measured the way rig-c's poser evaluates a curve (its 10-piece polyline). A span still over the tolerance gets a key at its worst frame, and both halves are fitted again.
  • Errors do not pile up down a chain. Bones are fitted top-down, and each bone is fitted against its dense key less the error its nearest mapped parent already carries. So a bone's world error is its own fit error.
  • Kept regardless: the first and last frame; the frames either side of a switch boundary, on the chains the switch changes; and the first and last frame of every FLIP spread, SHORT span and RANGE run, on that bone.
  • The landing check holds the dense targets with the tolerance added (0.01° + deg; 0.01 + units), and it is posed by rig-c as always.
  • On dance-a (313 frames, 11 mapped bones): --reduce 1 --reduce-hip 1 takes the rotate keys from 3443 to 588 (5.86×), 296 of the spans curved, and the hip's translate keys from 313 to 96. The gate is green. rig-c's poser lands every bone within 0.9998° of the dense target (head) and the hip within 0.901 units. At 0.5° the keys drop 3.92×; at 2°, 8.38×.
  • It runs last, on the final keys (#94): after --plant-feet's plant, its one floor, the blend and --stance stretches and the in-place closure. Their keys are reduced like any other, and every landing check is run again on the reduced motion.
    • 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.
    • 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.
    • 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). 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.
    • Per chain: --reduce <deg>,<chain>=<deg>,… (#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.
    • A parent held to its tightest child (#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.
    • --reduce-keep-planted (only with --reduce and --plant-feet, refused by name otherwise; #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).
    • 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 (#116).

Exit codes: 0 written, 1 refused (every reason printed, nothing written), 2 bad invocation. rig-pose --help has every flag.

The pose: rig-pose-sequence/1

@rig-c/parts' keypoints.json (spine-parts-keypoints/1) extended to frames — the same vocabulary, one more level:

{ "spec": "rig-pose-sequence/1",
  "space": { "units": "picture-px", "origin": "top-left", "y": "down" },
  "width": 1080, "height": 1920,
  "source": "what produced the file",
  "frames": [
    { "t": 0, "people": [ { "id": "a", "joints": {
        "neck":       { "state": "observed", "at": [540, 410], "score": 0.93 },
        "r_shoulder": { "state": "observed", "at": [470, 430], "score": 0.91 },
        "r_wrist":    { "state": "missing" } } } ] } ] }
  • t is seconds and strictly increases; the animation's keys are at t − t₀.
  • Joints are body-18 (nose neck r_shoulder r_elbow r_wrist l_shoulder l_elbow l_wrist r_hip r_knee r_ankle l_hip l_knee l_ankle r_eye l_eye r_ear l_ear); r/l are the subject's sides. state is observed, occluded or missing, as in the keypoints file, or inferred (#63): a position the detector supplied but did not see. An inferred joint carries at (one without it is refused, POSE_JOINT_AT), and retarget keys it as it keys observed. An unlisted joint is missing.
  • Two differences from the keypoints file: the unit is picture-px, and at may lie outside the picture (a hand leaves the frame; the score says how much to believe it).
  • More than one person: --person <id>.
  • The pelvis track (#288) is the minor rig-pose-sequence/1.1: a pelvisTilt on every frame (beside t and people; Spine degrees in (−180, 180] of the direction r_hip → l_hip, or null where the reading declined the frame) and pelvisSource on the sequence ("silhouette", "waistband", "below-cover" or "keys"), written by rig-pose pelvis (above). A sequence carrying them holds one person; a track on some frames only, a source without a track, or a track without a source is refused by name (POSE_PELVIS). The writer writes the minor the content needs — /1.1 with a track, /1 without — so a sequence without one stays readable by a reader of /1; a /1 file carrying a 1.1 key is refused naming the minor (POSE_SPEC). The spec string is what the file records of the version that wrote it: a file of a newer minor than this reader's is read, and each key it does not know is refused with the minor the file says it was written as (… the file says it was written as "rig-pose-sequence/1.3", a newer minor than this reader's "rig-pose-sequence/1.2" — read it with a rig-pose that reads that minor). A reader of /1 (rig-pose 0.37 and before) refuses a /1.1 file by its spec (POSE_SPEC, naming "rig-pose-sequence/1.1") and its keys (POSE_KEYS).
  • Depth (#281) is the minor rig-pose-sequence/1.2: a depth on every frame (beside t and people), { "<segment>": { "ratio", "sign", "source" } } — the segments r_upper_arm, r_forearm, r_thigh, r_shin, l_upper_arm, l_forearm, l_thigh, l_shin, each present where its two joints are placed on the frame ({} where none is); ratio a number ≥ 0, sign −1, 0 or 1, source "derived" (clean --depth, above) or "measured" (a 3D detector). A minor includes the keys of the minors before it, so a sequence with depth and a pelvis track is /1.2, and one with the track alone stays /1.1. A sequence with depth holds one person; depth on some frames only, a malformed entry, an unknown segment, derived and measured depth together, and derived depth without the cleaned record's "depth": { "gait" } (or that record without derived depth on every frame) are refused by name (POSE_DEPTH); a /1 or /1.1 file carrying depth is refused naming the minor (POSE_SPEC).
  • travel (optional, #92) is the clip's frame of reference. Its one value is "in-place": a gait filmed without the figure travelling, such as a treadmill or reel walk, or a loop. clean --loop writes it, and it may be written by hand on any sequence. Any other value is refused by name (POSE_TRAVEL). Without it the figure's travel in the picture is its travel. It is a property of the sequence, not of a flag (#242): with or without --plant-feet, retarget reads the supports in place, skips the camera-zoom normalisations (a loop has no zoom, and their rolling windows do not wrap) and reads the head nod without its detrend window or clamp. Only the following foot and the loop closure need --plant-feet (below).
  • score is whatever the pose model gives. RTMPose's goes above 1, so --min-score is a cut on that model's scale, not a probability.

From COCO-17 — the layout most pose models and datasets use — rig-pose clean --coco in.json --out pose.json, which converts and cleans in one step. rig-pose convert --coco converts only, and its output still needs clean. retarget --coco is refused (POSE_NOT_CLEANED), naming the clean --coco command. The input is { "format": "coco17-sequence/1", "width", "height", "source", "frames": [{ "t", "keypoints" }] }, keypoints in COCO's order as 17 [x, y, score] or 51 numbers, or null for a frame with nobody. The mapping: COCO's left_*/right_* → body-18's l_*/r_* (both are the subject's sides), a score of 0 → missing, and body-18's neck, which COCO lacks, is the shoulders' midpoint scored the lower of the two (OpenPose's definition).

The camera (#63). A sequence may carry an optional camera, one { "s", "tx", "ty", "inliers" } per frame: the picture's scale and translation against frame 0's, so a point p₀ of frame 0's picture appears at s · p₀ + (tx, ty) on that frame. Its joints are then already normalised by it, p′ = (p − t) / s, and inliers is how many background features the fit kept. s must be > 0, tx and ty finite, inliers an integer ≥ 0, and there is one entry per frame; anything else is refused by name (POSE_CAMERA). The retarget does not apply it again: it prints a CAMERA line with the range of s and the three frames where it changes fastest. With --plant-feet and no camera, the CAMERA line says the floor may step with the picture, since a zoom or a pan in the video moves the floor with it. An optional cameraSource string says where the camera came from: "tracked", "hand-labelled, N keys" or "hand windows, floor-snapped: …". It is refused without a camera, or when it is not a non-empty string (POSE_CAMERA_SOURCE), and the CAMERA line starts with it (CAMERA tracked: …; a sequence with a camera and no cameraSource prints CAMERA (source not stated): …). tools/scan.py (not shipped) writes the normalised sequence. It reads the sequence before clean does (scan → clean → retarget): a sequence that already carries a cleaned record is refused by name (SCAN_AFTER_CLEAN), since its cleaning was done with the camera's steps still in it. The camera can come three ways, exactly one per run:

  • Tracked (--video), where the background has texture to follow. The camera is read off the video's background, and the sequence is written with "cameraSource": "tracked". A frame whose fit keeps too few background features is refused by name, and nothing is written.
  • A hand file (--camera-file), where the background cannot be tracked and the camera's values are known or judged by hand. The file is rig-pose-camera/1: { "spec": "rig-pose-camera/1", "keys": [ { "frame", "s", "tx", "ty" } ], "note"? }. Its frames are integers of the pose and strictly increase, s is > 0, and tx and ty are finite. Values are linear between keys and held before the first key and after the last. inliers is 0 on every frame, since no feature was fitted, and anything malformed is refused by name (CAMERA_FILE). The source is "hand-labelled, N keys".
  • Hand windows, snapped on the floor (--camera-windows a-b,c-d,…, #82), where the background cannot be tracked and the camera zooms in steps with a full-body figure on the floor. The windows are the frames of each zoom, by eye. Each step is measured on the floor, the one background feature every full-body clip has. An ankle is planted on a frame when it is still both ways, moving less than the retarget's stillness threshold (0.01 leg lengths a frame, in one picture leg: the clip median of hip → ankle) from the previous frame and to the next (through bun tools/supports.ts, beside the retarget's own jumps from supportsOf). Jump frames are not excluded: the detector reads them on the uncorrected picture, so they carry the very camera steps the windows remove, and an airborne ankle cannot hold still over three frames. A frame read inside a detector jump is named in the source (r 187 inside detector jump 187-193), not refused. For each window and each ankle, y before and y after are the ankle's median picture y on the 3 planted frames nearest the window on each side, searched within 15 frames and never past a neighbouring window, and its ratio about the picture centre c is (y_after − c_y) / (y_before − c_y). Where both ankles read, the step is the mean of the two, and their disagreement is always written. Where one reads, the window says which ankle alone. A window no ankle reads is refused by name (CAMERA_WINDOWS), and so are windows that are not a ≤ b inside the pose's frames, that overlap, or that leave no frame between them; nothing is written. s is 1 before the first window, multiplies by each step, is linear across a window and is held between windows, and t = c (1 − s), so no pan is read. The slow drift between windows (a push-in) is held, not fitted: that is open. The source is "hand windows, floor-snapped: a-b (r+l, l/r x.x %; r 83,84,85|97,100,104; l …), c-d (r alone; r …)" (no window for none): each window names the frames every median stood on, before and after the bar, and the CAMERA line prints it. The step moves with those frames: a 3-frame median moved ±2 % with the frames picked on a reference clip, which over three windows decided whether 37 or 107 of its 265 support frames left the floor band (#82). --plot draws both ankles' y with the windows shaded and the frames each window was read on marked, where a wrong window shows.

tools/scan_selftest.py is the synthetic check of all three.

The older name. rig-c-pose-frames/0, written by the extraction jobs this package's demos were made from, is the same file under another spec string. It has space.units "painting-px" and an fps key; each frame's t is the time read. It is read as it is.

The map: rig-pose-map/1

Which bones carry which joints. You write it for your rig:

{ "spec": "rig-pose-map/1",
  "chains": [
    { "name": "trunk", "bones": ["chest"], "joints": ["mid_hip", "neck"], "aim": ["neck"] },
    { "name": "arm_r", "bones": ["sleeve_r0", "sleeve_r1"], "joints": ["r_shoulder", "r_elbow", "r_wrist"] },
    { "name": "leg_l", "bones": ["leg_l0", "leg_l1"], "joints": ["l_hip", "l_knee", "l_ankle"] } ],
  "translate": { "bone": "hip", "joint": "mid_hip",
                 "scale": { "pose": ["mid_hip", "neck"], "rig": ["hip", "neck"] } } }
  • 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).
  • 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"] }.
  • 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.
  • 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): 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): 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.
  • 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).
  • nod: false, on the chain whose joints carry the eye line (r_eye → l_eye, #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). 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. ⚠️ 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, #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.
  • mid_hip and mid_shoulder are the midpoints of the two hips and the two shoulders.
  • translate.scale is one length measured on both: two points on the rig, two points in the pose (its median over the frames where both are good). Pick a long segment that does not foreshorten much.
  • face (optional, #138, #164, #173, #189, #190, #205): { "yaw"?: { "bone": "yaw", "range": [-15, 15], "sets"?: [{ "set": "T1", "above": 45 }] }, "pitch"?: { "bone": "pitch", "range": [-10, 15] }, "sets"?: [{ "set": "T1", "above": 45 }] } (min ≤ 0 ≤ max, min < max). Reads per-frame yaw and pitch from nose against r_eye + l_eye (and r_ear + l_ear when present — where the eye span is foreshortened below 0.85 × median(de) and the ear-chord offset xea = (mid_ears − mid_eyes) · u / da exceeds both 1.8 × |xne| and 0.15, yaw blends toward the ear chord atan(2 × xea) with weight w = clamp((0.85 × median(de) − de) / ((0.85 − 0.80) × median(de)), 0, 1) so a side gaze whose 2D nose sits near the eye midpoint reaches atan(2 × xea) below 0.80 × median(de) and exits without a step), couples the head nod channel's clamped vertical travel (clampedY, +2.8°/unit when the ±2% neck-length clamp acts on the sequence) into pitch in phase (#190, #205), maps 1:1 inside 0.8 × range ([-12, 12]° on [-15, 15]°) and compresses only the overshoot above the knee into the remaining 20% of the range so a single outlier frame does not crush the rest of the clip, and keys the named dial bone's rotate track (FACE). When sets ([{ "set": "<name>", "above": <deg> }], 0 < above ≤ 90) is declared on face.yaw.sets or face.sets, each set enters on frames where |yaw| ≥ above (or the highest above satisfied when multiple sets are declared) and leaves only when |yaw| < above − 10° (10° hysteresis), emitting stepped slot attachment keys for the set's slots and hides, each half a frame before the frame it shows on as the switches' and the draw order's keys are (#334) (resolved from alternates in the map or sets.json (rig-parts-sets/1) beside rig.json; an unknown set is refused by name as MAP_SET_EXISTS).
  • body (optional, #173): { "yaw"?: { "bone": "body_yaw", "range": [-30, 30], "sets"?: [{ "set": "T8", "above": 45 }] }, "sets"?: [{ "set": "T8", "above": 45 }] } (min ≤ 0 ≤ max, min < max). Maps the per-frame body yaw read from the camera-normalised shoulder and hip width (BODY) into range with the same 0.8 × range knee mapping as face, keying the named dial bone's rotate track, and switches any declared sets (body.sets or body.yaw.sets) with the same above / above − 10° hysteresis (overriding face sets and arm alternates on slots they share while active). Without body in the map, BODY and --report's body still report the reading while leaving every track in motion.json unchanged.
  • layout (optional, #346): [{ "limb", "parts": [...], "depth": "r_forearm" | "l_forearm", "against": "trunk" }]. It binds each group of the rig's layout.json (rig-parts-layout/1, @rig-c/parts) to its depth source, one binding per placement, matching its limb and parts exactly. It is read with --draw-order depth (above). An unsupported source or target is refused (MAP_LAYOUT).

A joint's range: range

A rig whose limbs are cut at the joints reads well only within some range of each joint's angle. A chain may state it per bone, in rotate-key degrees (the key, relative to the bone's setup rotation and to the turn of the mapped bone above it):

{ "name": "arm_r", "bones": ["sleeve_r0", "sleeve_r1"], "joints": ["r_shoulder", "r_elbow", "r_wrist"],
  "range": { "sleeve_r0": [-10, 5], "sleeve_r1": [-10, 10] } }

Outside every span of a set depicting that chain, a key past its range is clamped at the limit: the bone's target becomes the clamped world angle, and the bones below are keyed from it as usual, so they still point where the pose puts them. Bones are clamped parents first, and a reach below a clamped bone is aimed again from where the clamped bones put its origin. The landing check measures against the clamped targets. Every overflow is listed as RANGE demand. A key on its limit to within 0.001° (the writer's rounding of a key; the rest angles come from rig-c's float32 model) does not count. Inside a set's span the bone is not clamped: the set's art was painted for that pose. Its drawn angle, if the set gives one, is read against the range instead (ART-RANGE). Each name is refused if it is not a bone of the chain (MAP_RANGE), as is a range that is not [min, max] with min ≤ max.

Alternate drawings: alternates

Bone rotation cannot carry every pose: an arm behind the head, a forearm toward the camera, hands crossed in front of the body. The answer is a different drawing for that pose, swapped in. One key pose repaints every slot it moves (both arms, and the torso when the pose pulls it), so an alternate is a set of slots switched together. The map says which sets the rig has and the pose each was painted from; the run decides when each shows:

"alternates": [
  { "name": "raised_arms",
    "slots":   { "upperarm_r_raised": "raised/upperarm_r", "forearm_r_raised": "raised/forearm_r", "torso": "raised/torso" },
    "hides":   ["upperarm_r", "forearm_r"],
    "chains":  ["arm_r", "arm_l"],
    "pose":    { "r_shoulder": [400, 600], "r_elbow": [300, 450], "r_wrist": [450, 400],
                 "l_shoulder": [600, 600], "l_elbow": [700, 450], "l_wrist": [550, 400] },
    "drawn":   { "sleeve_r0": 120, "sleeve_r1": 30 },
    "offsets": { "sleeve_r1": [4, -2] } } ]

(The names, coordinates and angles above are illustrative.)

  • slots: what each slot of the set shows while it shows; each attachment must be in that slot under some skin of the rig, as rig-c built it. hides: the slots that are empty while it shows. A slot's place in the draw order is the rig's own, so a drawing that must sit in front of the torso is an attachment in a slot that is: a switch is attachment keys only, and no draw-order key is written.
  • chains: the map's chains whose pose the set depicts; it is selected by them. pose: the key skeleton the art was painted from, body-18 (or mid_hip/mid_shoulder) in picture px, y down, any scale and place — only its directions are read; every joint the listed chains use must be in it.
  • drawn (optional): each bone's world angle (Spine degrees) as painted. It changes no key unless the set declares symmetric, support or anchor (below), or the segment is end-on: otherwise, while a set shows, a bone is keyed to the pose as always, and the art turns with it. A segment end-on on a frame — under --short's cutoff of its median picture length, where its direction is noise — is held at its drawn angle while the set shows (#355). A set without phase whose key skeleton and drawn angle disagree by more than --switch-tolerance on a bone it shows by its drawing unaided is refused by name (SET_KEY_DRAWN): it is chosen by its key and shown at its drawing, so such a key shows the drawing turned that far from the pose. Unaided: the bone has no turn, and the set is not re-solved after selection (no place, support or anchor, and not an arm set under --raised-by-head); there the art is re-aimed on purpose and the key may differ from the drawing. ART-RANGE therefore reads demand within the tolerance: a drawing that a bone left by more is not shown. What it gives is a figure: the art turn, how far each bone is from the angle its art was painted at (artTurn, and ART-RANGE past the bone's range).
  • offsets (optional): a translate per bone, in its parent's axes, held while the set shows and 0 elsewhere (stepped keys), so the joints as painted are the pivots. A bone translate already keys (the hip) cannot take one.
  • place (optional): "by-hip" places a hand that meets the body by its offset from the hip (below).
  • symmetric (optional, #188, #206): true normalises a two-arm symmetric gesture set (such as hands behind the head) to its drawn symmetry centre so both elbows sit at the same world height and neither wrist extends past the head silhouette (below), ramping in and out in joint space across any adjacent bridge frames where the set's slots remain shown. Requires two 2-bone chains with drawn angles on all four bones (MAP_ALT_SYMMETRIC).
  • support (optional, #188, #206): a chain name or non-empty list of chain names of the set ("support": "arm_l" or ["arm_l"]) whose bones carry a stationary support arm (such as a waist-resting hand while the other arm gestures); while the set's slots show (loShow..hiShow), each support chain's bones anchor to drawn within ±2.5° (≤ ±3°) of the pose's delta relative to the set's reference pose (bridging in joint space across any preceding gap frames where the leaving set's slots still show), and an unplaced active raised arm in the same set clears the head/bun outward along the 2D shoulder→wrist direction (MAP_ALT_SUPPORT).
  • anchor (optional, #191): a chain name or non-empty list of chain names of the set ("anchor": "arm_l" or ["arm_l"]) whose hand is anchored to the set's drawn hand-tip-to-head offset in the head bone's local frame (MAP_ALT_ANCHOR, ANCHOR_HEAD); while the set's slots for that chain show, the target hand tip is head_posed_origin + rotate(W_drawn − H_setup, head_posed_rot − head_drawn_rot) and the 2-bone arm is solved by two-circle intersection (twoBone / reachLimit) inside the set's turn, listing any frame where turn saturates and leaves a residual hand-tip shortfall (saturated: [{ frame, residual }] on raised, printed as ; residual f46 9.68u, … on RAISED).
  • phase (optional, #286): "left-step" or "right-step", the stepping (swinging) foot a gait set is drawn for and named by (below). 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).
  • 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, MAP_ALT_PHASE and MAP_ALT_BIND.

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). 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) 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). A set is a candidate on a frame only where every bone it shows by its drawing unaided (above) is within --switch-tolerance of its drawn angle (#355): the art turn wrap(target − drawn), on the same targets as the distance, and 0 for a segment end-on there (held at its drawn angle). A frame past it falls back as a frame whose key is past the tolerance does: the nearest other set within it shows, or the base art, and the bridging and hysteresis treat it as they treat any frame past the tolerance (SWITCH-TURN, switchRuns[].turned). A set with bind is never selected this way (below). 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.

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; 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); 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.

Where a switch is keyed: --switch-at. By default (first) a boundary — the frame a set starts showing on, and the frame the base art or the next set comes back on — is where the selection above puts it: the first frame within the tolerance, often a frame where the arm is slowing into the pose, so the change of drawing can be seen on a nearly still arm. --switch-at peak moves each boundary to the frame within ±--switch-min/2 (±3 frames at 24 fps with the default 0.25 s) where the chains it changes turn fastest, so the cut lands in motion (#31). The speed on a frame is the largest |angular speed| in degrees per frame, a central difference of the target world angles (one-sided on the first and last frame), over every segment of the leaving set's and the arriving set's chains; it is read on the same targets as the distance, before the range clamp and any set's turn, which depend on where the spans are. The window is clipped so every span keeps --switch-min, a gap between two spans of one set keeps it too (the bridging holds), no boundary reaches its neighbour, and a set with frames shows only inside them. In it, a frame qualifies only if the set the move newly shows is within 2 × --switch-tolerance of its key skeleton on every frame it newly shows it on; the boundary goes to the fastest qualifying frame, and stays when none qualifies. A tie goes to the frame nearest the original, then the earlier. A boundary between two sets moves once.

The bound exists because speed alone rewarded showing art far from its pose. Unbounded, on dance-a, the peak sat mid-swing: the largest distance over the frames a set showed rose from 25° to 135° (K2), 24° to 64° (K4), 21° to 59° (K1) and 21° to 38° (K3), the sets' turn held the arms hard against the pose (ART-RANGE runs 24 → 36), and the rig snapped to a hands-low drawing two frames before the arms came down. Bounded, every set stays within 50° (the largest is 43°, K4) and the median speed at the boundaries still rises from 9.7 to 14.2°/frame, but ART-RANGE runs still rise, 24 → 31 (K4 1 → 3, K1 6 → 8, K2 6 → 7, K3 2 → 4). That is why first is the default and peak an option (#31, round 2).

Either way the SWITCH line ends with moved on …, off …: each boundary's old and new frame (the same under first), the frames and seconds it moved, the speed on both and its window, and the report carries the same under moved.

What is written. An attachment track per slot whose picture changes: the set's attachment (or null for a hidden slot) where a span starts, and the slot's setup attachment, as the compiled model states it, where it ends. A direct switch from one set to another puts the first set's slots back to setup on the second's first frame, so an extra slot that is empty at setup goes empty. Between two sets that both draw an arm behind the torso, a frame that belongs to neither set stays behind (the set nearer in hand position and segment distance, not frame count), with any adjacent bridge or SHORT ramp interpolated in joint space (parent-local angle for child bones) and any extended hold of a tight set included in chainHolds/covered so the held art is clamped and exempted from capShortSteps and range; the default front slot is for a frame no set can claim and whose arm is read in front — not a fallback (#136, #139, #162, #180, #187, #206). A switch onto frame i is keyed halfway between frames i−1 and i: a stepped key on the frame's own time fires a frame late wherever a player's accumulated time lands just below it (rig-c's docs/AUTHORING.md §4.5), and halfway both the pose's times and a renderer's i / fps see it on frame i. Before the first key a slot shows its setup attachment, so no key is written at 0 unless a span starts there. Offsets are translate keys at the same times, stepped. rig-c's gate checks these keys like any other.

TURN. The cutoff was measured on the three lines across the body before it was fixed (#6): on dance-a (13 s front-facing) their picture length never drops under 0.81 of its median and none reverses; on dance-b the hip line falls to 0.17 at frames 465–491 (side-on) and the eye line to 0.22 at 26–58 (head in profile), and 3 frames reverse. 0.5 lies between. A line is reversed when it points more than 90° from the subject's right joint to the left (front-facing, it points at 0°). The lines are read after holds, held as the map's points are even where the map does not use them; a line whose points are good on no frame is named as not measured.

A stopgap window: frames. A set may state "frames": [[first, last], …]: it shows only inside those windows, and on them it is chosen over any set within tolerance that has no window. It is for two sets whose key skeletons the angles cannot tell apart. Measured on dance-a, the hands clasped behind the back and the hands low in front differ by only 2–21°, and the wrist and hand-point scores do not separate them either; the cue that might is #33's depth state. It is recorded in the map as a stopgap, not a selection rule. A window restricts a set; it never forces one. Where the set does not show inside its window, the run says why in a FRAMES line: the stretch, and whether the pose there was past the tolerance (with the largest distance), or within it but the run was dropped.

Bound frames: bind. A set may state the frames it shows on, exactly (#360): a replacement drawing generated for frames the base art draws badly, swapped in on those frames like a sprite. frames cannot do this: a window only restricts where the key's distance may pick the set. Two forms, both inclusive pose-frame ranges:

  • explicit, "bind": [[first, last], …]: whole frame numbers ≥ 0, first ≤ last, inside the sequence. A range past the last frame is refused (SET_BIND_FRAMES).
  • cycle-relative, "bind": { "cycle": N, "at": [[first, last], …] }: each range counted from the start of every N-frame cycle from frame 0, so [a, b] binds a + kN … b + kN for every k. A range starts inside the cycle (first ≤ N − 1) and spans at most one (last − first + 1 ≤ N); a last past N − 1 runs into the next cycle, so its tail binds frames 0 … b − N at the start, the previous cycle's. A repetition that crosses the sequence's end is cut at its last frame; one past it binds nothing. A cycle bind that binds no frame of the sequence is refused (SET_BIND_FRAMES). Changing the loop's length is one number. The two forms give the same motion when they bind the same frames.

A bound set shows on exactly its bound frames, and nowhere else. On them nothing about the pose is tested: no key distance (its key skeleton, pose, still names the chains' joints but is not used, so it may be anywhere), no art-turn gate, no --switch-min, no bridging, no hysteresis, and --switch-at peak does not move its boundaries; a one-frame bind shows on its one frame. SET_KEY_DRAWN does not apply to it: its drawing is its own. It is not picked by angle outside its binds either: its key is not a claim about the drawing, so picking it by that key would show the drawing where nothing says it fits — exactly what SET_KEY_DRAWN refuses for an angle set. Bound sets take no part in forming the competing groups, so a bind never joins two groups: every group is selected exactly as without the bound sets (by distance, or by phase), and then each bound set takes its frames outright from every group it shares a slot or offset bone with, its slots and its chains. No behind-the-torso hold or bridge (#136) reaches a bound frame, so only the bound drawing shows there. A frame no bind covers is what it is without the bind: a run of another set it cuts keeps the frames either side, however short: the author owns the edges a bind cuts, and --switch-min does not re-test them. While it shows, the rest is as for any set: slots, hides, offsets, drawn (the end-on hold), turn, place, support and anchor apply on its spans, and each change is keyed half a frame before the frame it shows on, as above. A bound set beside frames or phase is refused (MAP_ALT_BIND), and so is a malformed bind. Two bound sets bound on a common frame that draw a common bone (of a chain, or an offset bone), or switch a common slot are refused naming both, the frames and what they share (SET_BIND_OVERLAP). The SWITCH line of a bound span says bound in place of its distance; SWITCH-BIND and the report's switchBinds list the bound frames. A map without bind writes the bytes it always has.

How far a set's art may turn: turn. A drawing reads right only near the angle it was painted at. A set may state, per bone with a drawn angle, how far its art may turn from it, "turn": { "sleeve_r0": [-20, 20] }. While the set shows, the bone is held within it: its target becomes drawn + clamp(wrap(target − drawn), min, max), each bone on its own world angle, so a forearm under a held upper arm keeps its own target. The landing check measures against the held angle. The overflow the pose asked for is listed as ART-RANGE. A turn on a bone the set has no drawn angle for is refused (MAP_ALT_TURN).

A raised arm placed against the head: --raised-by-head. The pose's angles put a hand where it is against the subject's own shoulder. On a rig whose shoulders sit nearer its head, or whose head is larger, the same angles put a hand raised beside the head on top of it. With --raised-by-head (off by default, #62, #179) a set places a chain by the head when its key skeleton (pose) puts the chain's wrist within 2 × eye_dist (RAISED_WRIST_EYES = 2) of the head centre (the midpoint of r_eye and l_eye, or nose when the eyes are absent from the key skeleton); a chain whose wrist sits farther keeps its shoulder-based pose angles regardless of its upper arm's drawn angle. While that set shows, the arm's wrist goes where the reference's is against the head:

  • The unit is the eye distance: on each side, its median over the span. That is the reference's |r_eye − l_eye|, and on the rig the distance between the two points the head chain's "a>b" aim names. The head centre is the eyes' midpoint on each frame. The head chain is the one carrying the eye line (r_eye → l_eye, either way round). The flag is refused without one, or with one not aimed "a>b" (RAISED_HEAD).
  • The target is rig head centre + (reference wrist − reference head centre) × (rig eye distance ÷ reference eye distance), the picture's y turned up, per frame.
  • The order of solving. The arm is solved last of the bones. By then the hip's travel, every reach, the head and every range clamp are final, and the arm moves neither the head nor its own shoulder. On each frame the rig's eye points and the shoulder are posed forward from those targets, as a reach's origin is: each rest offset is turned by the nearest mapped bone above.
  • Reach. The arm reaches the annulus about the shoulder from |l0 − l1| to l0 + l1. Here l0 runs from the upper arm's origin to the forearm's, and l1 from there to the wrist (the forearm's tip, or the point it aims at). A target outside it is replaced by the reach limit along the head-relative direction. That is the point inside the annulus, on the ray from the head centre through the target, nearest the target. Where the ray crosses the annulus twice, the nearer stretch wins, and a tie goes to the point nearer the head. A ray that never enters it gives the annulus point nearest the target. Every such frame is listed in REACH. A too-near frame is treated as a too-far one: the wrist is placed at the reach limit, and the distance the rule grades it at is the placed one. That is |placed − head centre|, not the reference's, which the arm cannot fold to.
  • The solve. Two bones on the rig's own lengths put the wrist on that point, the elbow bending to the side the pose's does. The set's turn is part of the solve. When it holds the upper arm, the forearm is re-aimed from the held elbow at the placed wrist rather than held on its own. ART-RANGE lists where the re-aimed forearm still cannot reach within turn, in two cases. One is a forearm past its own turn, held there. The other is a held upper arm whose re-aimed forearm leaves the wrist more than 0.01 eye distances short of its placed point; 0.01 is the float and rounding scale. That case is listed on the upper arm, with the shortfall in eye distances. The solved angles become the targets, which the keys and the landing check follow.
  • Frames outside those spans, and sets that draw no arm raised, are keyed as without the flag, byte for byte. A raised chain must have two bones, each aimed "x" or at one point, and the eye points must not turn with it (RAISED_CHAIN).

A hand that meets the body: "place": "by-hip". The same mismatch, pointed at the hip. On a clasped-hands set the pose's angles aim the forearms the right way, and the rig's arm lengths put the clasp lower or higher on the body than the video has it (#93). A set may ask for its hands to be placed against the hip instead, "place": "by-hip" (off by default; no flag). It is the solve above, with the head's measures swapped for the hip's:

  • The key skeleton carries both hips. A set with "by-hip" must have r_hip and l_hip in its pose: the map's author supplies them, where the hips were when the drawing was painted. A set without them is refused by name (MAP_ALT_PLACE, naming the missing hip); nothing is guessed from the chains' joints.
  • Which chains. A chain of the set is placed when it ends at a wrist (r_wrist, l_wrist) that the set's pose puts within one hip width of the mid-hip: |wrist − (r_hip + l_hip) / 2| ≤ |r_hip − l_hip|, read on the key skeleton. A set asking for "by-hip" with no such chain is refused, naming each wrist's distance in hip widths, and so is a place other than "by-hip", a pose whose hips meet, and a pose without a chain's wrist (MAP_ALT_PLACE; the missing wrist is also MAP_ALT_JOINT, as for any set).
  • The unit is the hip width: on each side, its median over the span. The reference's is |r_hip − l_hip|. The rig's is the distance between its two hip points, posed from the targets of the bones above as the eyes are: the two points a chain carrying the hip line (r_hip → l_hip, either way round) names with an "a>b" aim, a pelvis; else the origins of the first bones of the two chains starting at r_hip and at l_hip, the legs. A map with neither is refused (PLACE_HIP), and so is a span over which either median is 0 (PLACE_HIPS).
  • The target is the rig's mid-hip + (reference wrist − reference mid-hip) × (rig hip width ÷ reference hip width), the picture's y turned up, per frame. The order of solving, the reach, the two-bone solve inside the set's turn and ART-RANGE are those of --raised-by-head, with every eye distance a hip width (a held arm's shortfall too, its artRange entry marked anchor: "hip"), and the mid-hip where the head centre is.
  • Out of reach below the shoulder. The reach limit is taken on the ray from the mid-hip through the target. A hand at the hips sits on the far side of the mid-hip from the shoulder on most rigs, so that ray usually runs away from the arm and never enters its reach. The wrist then goes to the reach's point nearest the target, on the line from the shoulder (missed in REACH).
  • A chain both --raised-by-head and "by-hip" would place is refused (PLACE_BOTH). A placed chain must have two bones, each aimed "x" or at one point, and the hip points must not turn with it (PLACE_CHAIN).
  • Frames outside the set's spans, the chains it does not place, and every map without "place" are keyed as before, byte for byte. The report's byHip carries each span.

Compare: the rig's render against the video

rig-pose compare --reference ref.pose.json --render render.pose.json --map map.json \
  [--retarget-report report.json] [--reference-hands ref.wholebody.json --render-hands render.wholebody.json] \
  [--cloth-reference ref.cloth.json --cloth-render render.cloth.json --cloth-windows 3.9-7.4,7.6-11.1] \
  [--report compare.json] [--sheet compare.png]

Polishing a retarget used to mean rendering it, having a person look, and hearing what was wrong. Most of what a person names can be measured with the same pose detector on both sides (#38). compare reads two rig-pose-sequence/1 files on one time base: the reference (the source video's keypoints) and the render (the same detector run on rigc render frames of the retargeted motion). It lists the differences per body unit, worst first, each with the fix it suggests. The person is then needed only for taste. The detector stays outside rig-pose, as it does for retarget.

The figures and bars below are fixed. They were set in #38 before any render was measured, and they are grading lines for rig-c/exam. A change to one is a new comment on that issue first.

One time base. Both sequences have the same number of frames, and each frame's times differ by at most 1 ms. Render the motion at the reference's rate, frame for frame. Anything else is refused (COMPARE_FRAMES, COMPARE_TIMES, with the first frames that differ). The hands files and cloth tracks are held to the same rule against the reference (COMPARE_HANDS_FRAMES/_TIMES, COMPARE_CLOTH_FRAMES/_TIMES).

Positions are normalised per side. A position is divided by that side's own hip width: the median over its frames of |r_hip − l_hip|, in picture px. Directions are compared as Spine angles of the picture segment, |wrap(render − reference)|. Every joint with a position counts, whatever its score; only hand visibility reads scores. A frame where either side lacks a point the measure needs is not measured, and it is never over the bar.

A finding is a run of frames over a measure's bar lasting ≥ 0.15 s. A run is timed from the first frame's time to the end of the last frame, as END-ON is: 4 frames at 24 fps (0.167 s) are a finding, 3 (0.125 s) are not. Runs one frame apart merge, and the merged gap is not counted as over the bar. A finding's worst frame is the frame of its largest figure. Within a measure, findings are listed worst first.

unit measure figure, per frame bar fix class
head eye-line tilt |direction difference| of r_eye → l_eye > 15° not derivable
arms direction, per mapped arm segment |direction difference| > 20° paint inside a set's span, rig outside
arms hand visibility, per hand mean score of the hand's 21 points (or the wrist's score) on each side reference ≥ 0.6 while render < 0.4 depth (#33)
body torso length |mid_shoulder − mid_hip| ÷ that side's median the render's range < 0.5 × the reference's, over the whole run motion
body hip vertical travel mid_hip's y in hip widths the same rule as torso length motion
legs direction, per mapped leg segment |direction difference| > 20° paint inside a set's span, rig outside
legs foot plant, per ankle the ankle's speed in hip widths per frame (from the frame before) render > 0.05 while reference < 0.02 motion
cloth lag, swing ratio, overshoot, per window #34's three figures, on each side outside #34's bands around the reference's figure rig (physics)

Why these bars (#38):

  • 20° for a segment's direction. HQ's rule for a set's art is drawn ± 20°: the art may turn up to 20° from the angle it was painted at and still read. A bigger difference is an arm that looks different.
  • 0.6 and 0.4 for a hand. On dance-a, the hands in front (S4) score 0.53–0.88 and the hands hidden behind the back (S1) score 0.41–0.57. 0.6 and 0.4 sit outside that overlap, one on each side.
  • 15° for the head's tilt, 0.05 / 0.02 hip widths per frame for a sliding foot, and 0.5 × of the reference's range for a missing bounce were fixed with the others before dance-a was measured.

Units and segments. The map says which segments are compared. An arm segment joins two of one side's shoulder, elbow and wrist, and a leg segment two of one side's hip, knee and ankle. A segment's name is chain/bone. Other chains, such as the trunk, the pelvis or the head, are not compared as segments. The head is measured on its eye line whatever the map says, and the body on its torso and hip. A mapped arm or leg segment whose point has a position on no frame of one side is refused (COMPARE_JOINT_MISSING, naming the chain, the bone, the point and the side).

The fix class is given only when it can be derived:

  • An arm or leg direction. The finding's worst frame is checked against the spans in --retarget-report (its switches: each set's frames and the chains it depicts). Inside a span of a set that depicts the segment's chain, the class is paint: the drawing shown there is not this pose. Outside every such span it is rig: the art does not show the bone's direction. Without a report, a chain no set of the map depicts is rig. A chain some set depicts is not derivable, and the line names those sets.
  • Hand visibility is depth (#33): the hand is drawn behind something.
  • A foot slide or no bounce is motion.
  • Cloth is rig: the cloth is the rig's physics.
  • A head tilt has no class in #38, so the line says it is not derivable.

Hand visibility. rig-pose-sequence/1 carries body-18 only, so the hand points come from COCO-WholeBody files, one per side: --reference-hands and --render-hands. The shape is { "spec"?: "coco-wholebody-133", "fps"?, "width"?, "height"?, "source"?, "frames": [{ "t", "points": [[x, y, score] × 133] | null }] }. The subject's left hand is points 91–111 and the right hand 112–132. A frame's figure is the mean score of the hand's 21 points. A null frame (nobody found) is not measured. Without the files, the wrist's score is read instead: an absent or missing wrist reads 0, as COCO's v = 0 does. The summary line then says from wrist scores. One of the two files without the other is refused with exit 2, because the two sides must be measured the same way.

Body. Each figure is read over the whole run, so the unit gives at most one finding per measure, no bounce, spanning every frame. Its figure is the ratio of the ranges, render ÷ reference. Its worst frame, the one the sheet draws, is the reference's frame farthest from its median. The summary line always gives both ranges.

Cloth. Cloth is measured only when both cloth tracks are given, with --cloth-windows. Without them the unit reads not measured. A cloth track is HQ's rig-c-cloth-track/0: { "spec": "rig-c-cloth-track/0", "subject"?, "method"?, "source"?, "fps"?, "frames_count"?, "units"?, "hipWidth": { "median", "unit": "picture px" | "rig world units" }, "origin"?, "axes"?, "skirtSwing"?, "frames": [{ "i", "t", "hip_x", "hip_y", "hip_w", "tilt_deg", "hem_mid", "hem_l", "hem_r", "hem_l_y", "hem_r_y", "hem_band_cx", "skirt_cx", "skirt_top", "skirt_bottom" }] }, the frame keys all numbers. Every position is already divided by the subject's own median hip width, with x to the right. hip_x is the hips' midpoint and hem_mid the midpoint of the hem's leftmost and rightmost points in the lower half of the skirt (#34's hem); only those two and t are read. Any other spec, or a key outside that list, is refused (COMPARE_CLOTH). The three figures are #34's, as HQ's skirt criteria define them, ported from HQ's measuring script. On the per-frame record of dance-a they reproduce its numbers exactly (reference A 0.958 / 2.634 / 0.061, B 0.696 / 1.409 / 0.000). Read through rig-c-cloth-track/0, whose values are rounded to 5 decimals, the reference's lag in A reads 0.959.

  • Both signals are smoothed with a Gaussian, σ = 1 frame over ± 4 frames, ends padded, before differentiating (numpy's gradient).
  • lag, in frames, hem after hip positive: the peak of the normalised cross-correlation of hip-x velocity with hem-x velocity over the window, searched over −6…+12 frames and refined by a parabola.
  • swing ratio: per hip stroke, the hem's excursion over the hip's. A stroke runs from one hip reversal (a zero crossing of hip velocity) to the next and moves at least the stroke threshold. A wiggle under the threshold is dropped as a pair. The hem is read round(lag) frames after each reversal. The figure is the median over the window's strokes.
  • overshoot: per stroke, how far the hem keeps travelling in the stroke's direction after the hips turn, within half of the next stroke (or 12 frames after the last), as a fraction of the hip stroke. The figure is the median.
  • The stroke threshold is 0.2 reference hip widths: one physical size on both sides (the criteria's Revision 1), expressed in the render's own hip widths. A render track in rig world units is carried by the retarget's scale, scale.factor in --retarget-report (rig units per reference px): 0.2 × reference hip width × factor ÷ render hip width. On dance-a that is 0.2 × 128.1 × 0.54152 ÷ 92.202 = 0.1505, the criteria's own figure. Without the report it is refused (COMPARE_CLOTH_SCALE). A render track in picture px is carried by the torso, as the retarget scales the rig: 0.2 × (reference hip width ÷ reference torso) ÷ (render hip width ÷ render torso), each torso the sequence's median mid_hip → mid_shoulder. A reference track not in picture px is refused (COMPARE_CLOTH_UNITS). The CLOTH lines print both thresholds and how the render's was carried.
  • The bands, per window, around the reference's own figure: lag ± 1.0 frame; swing ratio ± 25 %; overshoot ± 0.10, absolute, floored at 0. A render figure outside its band is a finding over the window's frames. A window with no stroke on one side is listed as not measured.
  • A window is t0-t1 in seconds, and windows are named A, B, … in the order given. A window's frames are round((t − t₀) × fps) up to the end, exclusive. A window of 12 frames or fewer is refused, since the lag search reaches 12.

Output. The first line gives the frames, the duration, both hip widths, and the report the spans came from. Then come one summary line per unit (HEAD, ARMS, BODY, LEGS, CLOTH) and one FIND line per finding. A summary line gives the unit's findings and, per measure, its findings, its worst figure with the subject and frame, the frames over the bar (in runs of any length), and the bar. A FIND line gives the unit, the measure, the subject and its points, the worst figure with both sides' readings and its frame, the frames and seconds, the frames over the bar, the bar, and the fix class with the reason. With cloth, a CLOTH line per window gives both sides' figures. The run ends with the number of findings.

--report <file> writes the same as JSON (rig-pose-compare/1):

  • inputs, frames, duration, hipWidth: { reference, render }, torso: { reference, render } (medians, px);
  • rule: { findingSeconds, findingGap, timeTolerance } and bars (every bar above, by name);
  • units: [{ unit, findings, notMeasured, measures: [{ measure, subjects, source, findings, overBar, notMeasured, bar, ranges? }] }];
  • findings: [{ unit, measure, subject, points, chain, frames: [first, last], seconds, over, worst: { frame, t, figure, reference, render }, figureUnit, bar, fix, fixWhy }];
  • cloth: null, or { windows: [{ name, t, frames, reference, render, band }], stroke: { reference, render, carried } }.

Numbers are rounded to 4 decimals, once, at the writer.

--sheet <png> draws the reference skeleton beside the render skeleton at each finding's worst frame, one row per finding, with the finding's own points marked in yellow. It never draws a video frame. A cloth finding is a figure over a window, with no single frame, so it has no row. When no finding has a frame, no sheet is written and the run says so.

The control is the reference compared with itself. It must report no finding in any unit, and every summary line says 0 findings. On dance-a (313 frames, RTMPose, with its whole-body hand points and the retarget's report) it does.

Exit codes: 0 compared, whether it found anything or not, since a finding is not a failure; 1 refused, with every reason printed; 2 bad invocation.

Requirements

Bun ≥ 1.2 on PATH — rig-pose, like rig-c, runs its TypeScript sources directly. rig-c comes as a dependency. With @esotericsoftware/spine-core installed beside it, rig-c's gate runs the spine-core round trip; without it, rig-c's own gate, and the gate's report says which.

The supported surface is the rig-pose command. RELEASING.md's What 1.x promises lists every flag, report key, problem code and line tag as promised or courtesy, and why. The package exports no importable API: exports exposes only package.json (#262).

Why alternate drawings (0.2)

A flat painting turned by its bones matches each limb's direction, not its depth. On dance-a (a 13 s front-facing dance), retargeted onto a @rig-c/parts rig, five moments were found where it cannot follow. They were found by measuring the pose against the rig and checked against the video:

t (s) what the subject does why bone rotation cannot follow
S1 0.0–1.1 hands clasped behind the back depth order: the forearms go behind the body
S2 1.0–2.25 a hand to the ear, then the forearm toward the camera foreshortening (the upper arm at 0.27 of its length) and depth order
S3 3.7–7.5 hands behind the head depth order; both arm meshes fold through the whole span
S4 7.7–10.9 hands crossed in front which forearm is in front
S5 11.1–13.0 an arm thrust at the camera, then a V sign foreshortening (0.63) and the shape of the hand; the arm mesh folds

The answer, since 0.2, is a second drawing of the arm for each of those poses, shown in place of the bent mesh while the pose lasts. rig-pose chooses when to show it, keys the switch in the motion, and lists every fold no drawing covers (#6, above).

What it does not do

What 1.0 leaves out (#217). Where an open issue carries the work, it is named; where none does, the line says what to do instead.

  • Extract poses from video. The input is joint positions. How you get them is up to you: a pose model, a dataset, or by hand.
  • Turn the body. It works in 2D only. A segment pointing at the camera foreshortens, and the rig cannot: the direction is matched, the length is not. With --leg-scale, the leg segments and the trunk (#172) are the exception. A body turned past what a stretch can show is drawn by switching to a set the map declares (body.sets, #173), not by turning.
  • Front and behind, per segment. Which limb passes in front of which is not read. The keys carry no front/back slot order (#33).
  • Blinks and mouth shapes. No eyelid or mouth key is written. The face points read the head's yaw and pitch (face in the map) and nothing more. No issue carries this.
  • Fit cloth physics to the reference. A skirt's physics is the rig's own. It is not fitted to how the cloth moved in the video (#34).
  • Clean detector errors by limb length. clean reads spikes, occlusion and jitter. It does not find a joint the detector placed wrong from the limb lengths around it (#75).
  • Track the camera from the video. A zoom or a pan is read from the sequence's camera (above). Fitting it from pixels is the repository's tools/ scripts, which are not shipped. Without a camera, the picture is read as a fixed camera.
  • Hands and fingers. There are no bones for them yet (rig-c/parts#171).

Licence

MIT, see LICENSE.

About

Puts motion from a pose — joint positions over time, from a video, a dataset or hand-placed points — onto a rig rig-c built: a body-18 pose sequence retargeted to bone keys, checked before it is written.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages