@rig-c/parts 1.18.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,7 +37,7 @@ config beside the values it replaced: the two outer skirt chains were moved out
37
37
  against 1.4725; moved, 2.425). One input-stage edit: <code>hair_back</code> is taken
38
38
  from the full run, because the twin tails leave the head crop sideways (a plan edit).
39
39
  <code>check</code> printed, verbatim:<br/>
40
- <code>51 assertions: 25 measured (25 passed, 0 failed), 26 skipped, 0 not in profile "spine-html"</code><br/>
40
+ <code>50 assertions: 24 measured (24 passed, 0 failed), 26 skipped, 0 not in profile "rig-play"</code><br/>
41
41
  <code>loop: idle 41 frame(s) at 12 fps, f0000 vs f0040 (t = 3.333333333333333s) with the rig's 29 physics constraint(s) left out (named on the `loop physics:` line; rendered with them, the ends need not meet — `build --loop --lead-in &lt;n&gt;`): max |d| 0 (0 required)</code><br/>
42
42
  <code>seam: setup pose at 654x1216, scale 0.9438: mean |d| 0.147 (&lt;= 1.0), 0 px over 40, 0.00 rig px (the count / s²; &lt;= 50), 0 px over 80 (reported)</code><br/>
43
43
  <code>pack: skeleton.png 1051x972, 23 region(s), 96.9% covered, padding 2, page edges free, shape polygon</code><br/>
@@ -250,7 +250,9 @@ rig-parts assemble --source painting.png --full layers/full --head layers/head -
250
250
  # -> a large red hole neither run holds? add an assemble.patches entry (cut from the painting) and assemble again
251
251
  rig-parts propose --parts work/rig --source painting.png --out work
252
252
  # -> proposal.json, basis.json (what each bone rests on) and render/landmarks.png; per-bone coverage lines;
253
- # correct it, copy bones/meshes/regions/motion into config.json
253
+ # correct it, copy bones/meshes/regions/limbs/motion/constraints/turn into config.json, each it wrote
254
+ # (limbs only with --keypoints, constraints only with --cloth, turn only with --full/--head;
255
+ # a limbs block left out is refused next)
254
256
  rig-parts propose --parts work/rig --source painting.png --out work --from-config config.json
255
257
  # -> LINT lines; exit 1 while any remain
256
258
  rig-parts build --config config.json --source painting.png --full layers/full --head layers/head --out out --loop
@@ -265,6 +267,12 @@ proposal pasted uncorrected stops at the last step, naming its `bottomwear` on
265
267
  `CHECK_TIP_OVER_ROOT` — the correction its tracked config records there, the two outer
266
268
  skirt chains moved (issue #227, [its README](examples/demo/README.md)); on `scarf`,
267
269
  since issue #262, it builds green.
270
+ On `demo`, which tracks the pose file it was painted on, the block runs again with
271
+ `--keypoints` on both `propose --parts` lines (`RL03`): the proposal then also drafts
272
+ `limbs`, the step's list carries them, and the run ends where `RL01` ends. The joints
273
+ are that pose file scaled to the painting, not read off it, so this shows what the
274
+ loop carries, not where the joints are. The list as it stood before issue #375, four
275
+ sections, stops at `--from-config` naming every limb part (`RL04`).
268
276
 
269
277
  `propose` is deliberately not a step of `build`: the proposal is a draft to correct
270
278
  against its overlay, and a config with bones is `build`'s input. `rig`, `check` and
package/cli.ts CHANGED
@@ -19,7 +19,7 @@ import { DEFAULT_PROJECT_RULE, DEFAULT_SEAM_RULE, HOLES_LISTED, PROJECT_RULES, t
19
19
  import { assembleStage, build, checkStage, ERROR_MAP_FILE, loopStage, readRuns, readSource, rigStage, suppliedCheckFlag } from './src/build.ts';
20
20
  import { ACCEPTABLE_BARS, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, findRigc, PACK_SHAPES, rigcRunnerAt, type PackShape, PAGE_EDGES, type PageEdges, PARTS_HOME_SENTENCE, RIGC_GEOMETRY_VERSION, type RigcRunner, NO_PARTS_SENTENCE, SEAM_MEAN_BAR, SOURCE_SENTENCE, SEAM_PX_BAR, SEAM_PX_LEVEL, SPINEBOY_YARDSTICK, TEXTURE_STRETCH_CEILING } from './src/check.ts';
21
21
  import { ComfyClient, resolveHost, runPainting, runSeeThrough } from './src/comfy/index.ts';
22
- import { type CharacterConfig, loadConfig, loadEarlyConfig, loadPaintConfig } from './src/config.ts';
22
+ import { type CharacterConfig, loadClothConfig, loadConfig, loadEarlyConfig, loadPaintConfig } from './src/config.ts';
23
23
  import { failLine, guidePointer, PartsError } from './src/errors.ts';
24
24
  import { GUIDE_DOC, type GuideAnswer, guideRule, guideSection } from './src/guide.ts';
25
25
  import { proposeHeadBox } from './src/headbox.ts';
@@ -197,6 +197,20 @@ usage:
197
197
  reference's rule, a tenth of the part's short side clamped to 5..36.
198
198
  r is the part rule's under both.
199
199
 
200
+ rig-parts propose … [--cloth <config.json>]
201
+ Draft cloth and hair as trees that answer the world (issue #364), from
202
+ the config's "cloth" block — a recipe per tag, bottomwear, back hair
203
+ and front hair (AUTHORING §3, *Recipe: cloth and hair*). Each tagged
204
+ piece hangs "chains" trees of "links" links from its attachment bone
205
+ (hip, or head), forked by "split", the links shortening toward the tip;
206
+ proposal.json's "constraints" puts a physics constraint on every link,
207
+ its strength by trunk, branch or edge, its wind and gravity response
208
+ on a ramp from the declared root value to the tip value (multipliers
209
+ of the consumer's environment — no direction is drafted), and drafts no
210
+ idle key on them. A bottomwear recipe's "swing" drives each tree's
211
+ first link from one shared pendulum. A recipe that drafts nothing is
212
+ refused (PROPOSE_CLOTH_DRAFTED). Without --cloth nothing changes.
213
+
200
214
  rig-parts propose --parts <dir> --source <painting.png> --out <dir> --from-config <config.json>
201
215
  Draw the config's CURRENT bones instead (<out>/render/landmarks_config.png
202
216
  and _head) and LINT them (by the joints too, given --keypoints), with the
@@ -262,7 +276,7 @@ usage:
262
276
  written into motion.json as read, for rigc's gate to judge (AUTHORING
263
277
  §3). --parts is the
264
278
  directory holding parts.json and parts/<name>.png. The result is built
265
- through rig-c (profile spine-html, packed with --page-edges and
279
+ through rig-c (profile rig-play, packed with --page-edges and
266
280
  --pack-shape as for check; rigc's build gates the compile and the packed pages on disk) in a
267
281
  scratch directory first, and --out receives images/*.png, rig.json,
268
282
  motion.json and mesh_report.json only when it is green. Prints one line
@@ -323,7 +337,7 @@ usage:
323
337
  builds); an "idle" in it is not.
324
338
  ${PARTS_HOME_SENTENCE}; ${NO_PARTS_SENTENCE}.
325
339
  Both are only read. Into --out:
326
- build/ (rigc build --profile spine-html --pack --page-edges <value>
340
+ build/ (rigc build --profile rig-play --pack --page-edges <value>
327
341
  --pack-shape <value>: the packed atlas is the artifact), gate_spine-html.txt (the gate lines
328
342
  verbatim), idle_frames/ (rigc render --animation idle --fps 12 --max 640
329
343
  --geometry: the frames and geometry.json, the skinned vertices and
@@ -617,7 +631,7 @@ usage:
617
631
  another character without its prefix (SCENE_NAME_PREFIXED); a plate not
618
632
  of the canvas size; idles of different durations; a key in a build's
619
633
  rig or motion compose does not know. The result is gated through
620
- rig-c as rig gates its own (build --profile spine-html --pack, the
634
+ rig-c as rig gates its own (build --profile rig-play --pack, the
621
635
  compile and the packed pages on disk); --out receives rig/ (rig.json,
622
636
  motion.json, images/) and ${SCENE_REPORT_FILE} (spec "${SCENE_REPORT_SPEC}":
623
637
  every setting, each character's offset, scale, bounds and shift (and what it
@@ -644,10 +658,11 @@ usage:
644
658
  (every channel above 235) or unpainted (alpha 8 or less) ground
645
659
  connected to the edge; the figure is the rest; a gap is one background
646
660
  pixel on the line between two joints. PAINTING_BACKGROUND_PLAIN (a
647
- border pixel that is not ground), PAINTING_JOINTS_STATED (a wrist, hip,
648
- knee or ankle not observed), PAINTING_JOINT_ON_FIGURE (an observed
649
- joint on the background), PAINTING_ARM_CLEAR (no gap from a wrist to
650
- its hip), PAINTING_HANDS_APART (none between the wrists),
661
+ border pixel that is not ground), PAINTING_JOINTS_STATED (a wrist,
662
+ knee or ankle not observed, or a hip neither observed nor occluded
663
+ with a position), PAINTING_JOINT_ON_FIGURE (an observed joint, or an
664
+ occluded hip, on the background), PAINTING_ARM_CLEAR (no gap from a
665
+ wrist to its hip), PAINTING_HANDS_APART (none between the wrists),
651
666
  PAINTING_LEGS_APART (none between the knees, or the ankles). Prints the
652
667
  figure, every joint and every gap with its count; writes nothing.
653
668
  docs/PAINTING.md says why each one matters. Exit 0 PASS, 1 refused.
@@ -762,7 +777,7 @@ const TAKES = {
762
777
  layers: { valued: ['--export', '--template', '--canvas'], switches: [] },
763
778
  sheet: { valued: ['--source', '--layers', '--out', '--cell', '--cols'], switches: [] },
764
779
  rig: { valued: ['--config', '--parts', '--out', '--idle-keys', '--page-edges', '--pack-shape'], switches: [] },
765
- propose: { valued: ['--parts', '--source', '--out', '--from-config', '--compare', '--full', '--head', '--canvas', '--keypoints', '--person', '--wrist-snap', '--grid-rule'], switches: ['--head-box'] },
780
+ propose: { valued: ['--parts', '--source', '--out', '--from-config', '--compare', '--full', '--head', '--canvas', '--keypoints', '--person', '--wrist-snap', '--grid-rule', '--cloth'], switches: ['--head-box'] },
766
781
  compare: { valued: ['--left', '--right', '--map'], switches: [] },
767
782
  check: { valued: ['--rig', '--out', '--parts', '--source', '--page-edges', '--pack-shape', '--requirements'], switches: ['--seam-pairs', '--keep-stills'] },
768
783
  loop: { valued: ['--frames', '--out'], switches: ['--palette'] },
@@ -1123,6 +1138,10 @@ function cmdPropose(args: string[]): number {
1123
1138
  if (ruleArg !== undefined && flags.has('--from-config')) return usage('--grid-rule chooses the drafted spacing; --from-config proposes nothing');
1124
1139
  if (ruleArg !== undefined && !(GRID_RULES as readonly string[]).includes(ruleArg)) return usage(`--grid-rule ${ruleArg}; one of ${GRID_RULES.join(', ')} is required`);
1125
1140
  const gridRule = (ruleArg ?? DEFAULT_GRID_RULE) as GridRule;
1141
+ // Issue #364: the cloth and hair recipe, read from a config's cloth block; without it nothing changes.
1142
+ const clothArg = flags.get('--cloth');
1143
+ if (clothArg !== undefined && flags.has('--from-config')) return usage('--cloth drafts trees while proposing; --from-config proposes nothing');
1144
+ const cloth = clothArg === undefined ? null : loadClothConfig(clothArg).cloth;
1126
1145
  const partsDir = existsSync(partsArg) && statSync(partsArg).isFile() ? dirname(partsArg) : partsArg;
1127
1146
  if (!existsSync(source)) {
1128
1147
  throw new PartsError([{ code: 'PROPOSE_SOURCE_PRESENT', object: source, detail: 'no such file; the painting is drawn under the overlay' }]);
@@ -1150,7 +1169,7 @@ function cmdPropose(args: string[]): number {
1150
1169
  }
1151
1170
  const cmpPath = flags.get('--compare');
1152
1171
  const cfg = cmpPath === undefined ? null : loadConfig(cmpPath);
1153
- const { proposal: drafted, basis } = proposeWithBasis(P, joints, wristSnap, gridRule);
1172
+ const { proposal: drafted, basis } = proposeWithBasis(P, joints, wristSnap, gridRule, cloth);
1154
1173
  const fullRun = flags.get('--full');
1155
1174
  const headRun = flags.get('--head');
1156
1175
  const prop =
package/docs/AUTHORING.md CHANGED
@@ -114,9 +114,10 @@ read by no CPU stage).
114
114
  | `motion.blink` | rig | **proposed**: `{t, eyes, brows, squash, brow_drop}`; the whole blink, `t` to `t + 0.364` s (`t + 0.314` s without brows: the brows' keys are the ones that end last), must fit inside the idle (`RIG_BLINK_INSIDE_IDLE`). The `eyes` group's `scaley` squashes every part on the eye bones about the bone's origin, the eyewhite's centre. Optional: a figure with no `eyewhite-r`/`-l` part has no eye bone, so `propose` writes no `blink` and notes `no blink: no eyewhite part (looked for: eyewhite-r, eyewhite-l), …`; with eyes and no `eyebrow-r`/`-l` part it writes the blink without `brows` and `brow_drop` and notes `blink without brows: …`. `brows` and `brow_drop` are stated together or not at all (`CONFIG_BLINK_BROWS_PAIRED`), and a group that names no bone is refused (`CONFIG_BLINK_GROUP_MEMBERS`) — rigc would refuse it one stage later, `group "eyes" declares no members` — as is a group that names one bone twice (`CONFIG_BLINK_GROUP_UNIQUE`; rigc: `group "eyes" names member "eye" twice`). An `irides-*` or `eyelash-*` part on a side with no eyewhite has no eye bone to ride: `propose` places it on `head` as a region, which the blink does not move, and notes `no eyewhite part for eye_r (looked for: eyewhite-r): iris (head:irides-r) is placed on head as a region, …`. The timing is fixed, not a field (table below) |
115
115
  | `motion.blink.still` | rig | **proposed** only for a lash `propose` notes (below), then checked: `{<part>: {row, bone}}` — the rows of that region part above `row` (rig px, y down) are drawn by a second slot `<part>_still` on `bone` and do not blink; rows from `row` down keep the part's slot and blink. The part must be a region on a bone `eyes` names, `bone` one it does not (`CONFIG_STILL_OFF_THE_BLINK`), and `row` a row of the part with no art across its whole width (`RIG_STILL_ROW_CLEAR`) |
116
116
  | `motion.animations_from` | rig; check (named; read for folds) | optional, never proposed (issue #183): a path, relative to the config file's directory, to a file in rig-c's motion-spec shape, `{"spec": "rigc-motion/1", "animations": {"<name>": {"duration": …, "tracks": [...]}, …}}` — an expression, a gesture, anything beside the idle. On every `rig` and `build` each of its animations is written into `rig/motion.json` after `idle`, as read: nothing added, re-keyed or judged here, and each is rigc's to accept or refuse at the rig stage's gate, in rigc's words. It may name what `motion.json` already holds — the bones `bones` declares, the easings `shut` and `open`, the groups `eyes` and `brows` when the blink writes them — and nothing else from the file reaches it. The loader refuses, every problem at once: no such file or one that cannot be read (`CONFIG_FILE_PRESENT`), text that is not JSON (`CONFIG_IS_JSON`), a file that is not an object, a `spec` other than `"rigc-motion/1"`, an `animations` that is not a table of objects (`CONFIG_FIELD_TYPE`), any other key — rig-c's `easings`, `groups`, `setup`, `physics`, `mix`, `archetype` and `cut` included, which would be read and not written (`CONFIG_KEY_KNOWN`), an animation named `idle` (`CONFIG_ANIMATION_NAME_FREE`), and a key written twice in one object of the file, which JSON would fold into its last value without a word (`CONFIG_KEY_UNIQUE`). `check` names the others on one line (`beside the idle: …`) and reads each for folds, with the idle — `ANIMATIONS_DO_NOT_FOLD` (§7, issues #320, #333); every other bar reads the idle. Absent, `motion.json` holds the idle alone, byte for byte as before, and `check` reads that idle for folds |
117
- | `constraints` | rig (handed to rig-c as `rig.json`'s `constraints`); `compare` and the coverage lines (roles) | **authored**, optional; `propose` writes none. A list of constraints in **rig-c's own rig-spec shape** (`RigConstraint`: `ik`, `transform`, `path`, `physics`, `slider` — rig-c's `src/rig.ts` documents every field), in the order rigc applies them. The loader checks only what this package owns: each entry is an object with a `type` rigc names (`CONFIG_CONSTRAINT_TYPE_KNOWN`) and a non-empty `name`, unique within its kind as rigc finds constraints (`CONFIG_CONSTRAINT_NAME_UNIQUE`; an ik and a transform may share one); every bone it names — `bones`, an ik's `target`, a transform's `source`, a physics or slider constraint's `bone` — is a bone `bones` declares, a chain link as `<chain><i>` (`CONFIG_NAME_RESOLVES`); the bone a constraint follows does not sit under a bone it drives (`CONFIG_CONSTRAINT_TARGET_DETACHED`). Every other field (`mix`, `softness`, `properties`, a path's `slot`, a slider's `animation`) and every value is rigc's to accept or refuse at the rig stage's gate, in rigc's words (`RIG_RIGC_GREEN`) — an ik's `bones` as a shape too: from rig-c 2.15.0 rigc refuses an ik over more than two bones, or over a pair whose second bone is not the first's child (issue #103; this loader refused both itself until then). A note and an `x-` record may ride on an entry; the rig stage leaves them out of what it hands rigc. Under the default `--idle-keys ctl` a two-bone ik whose child link the idle keys is split by that link's control, and rigc refuses the pair: run `rig --idle-keys direct` or `build --idle-keys direct` (the rig stage adds that sentence, naming the flag on the command that ran, to rigc's line; `build` forwards it to its rig stage, issue #95). Each bone a constraint follows is declared to rigc as `invariants.detached` from every bone it drives, which rigc's gate rule `A25` checks |
117
+ | `constraints` | rig (handed to rig-c as `rig.json`'s `constraints`); `compare` and the coverage lines (roles) | **authored**, optional; `propose` writes none, but with `--cloth` (issue #364), where it writes the recipe's physics and swing transforms (*Recipe: cloth and hair that answer the world*, below). A list of constraints in **rig-c's own rig-spec shape** (`RigConstraint`: `ik`, `transform`, `path`, `physics`, `slider` — rig-c's `src/rig.ts` documents every field), in the order rigc applies them. The loader checks only what this package owns: each entry is an object with a `type` rigc names (`CONFIG_CONSTRAINT_TYPE_KNOWN`) and a non-empty `name`, unique within its kind as rigc finds constraints (`CONFIG_CONSTRAINT_NAME_UNIQUE`; an ik and a transform may share one); every bone it names — `bones`, an ik's `target`, a transform's `source`, a physics or slider constraint's `bone` — is a bone `bones` declares, a chain link as `<chain><i>` (`CONFIG_NAME_RESOLVES`); the bone a constraint follows does not sit under a bone it drives (`CONFIG_CONSTRAINT_TARGET_DETACHED`). Every other field (`mix`, `softness`, `properties`, a path's `slot`, a slider's `animation`) and every value is rigc's to accept or refuse at the rig stage's gate, in rigc's words (`RIG_RIGC_GREEN`) — an ik's `bones` as a shape too: from rig-c 2.15.0 rigc refuses an ik over more than two bones, or over a pair whose second bone is not the first's child (issue #103; this loader refused both itself until then). A note and an `x-` record may ride on an entry; the rig stage leaves them out of what it hands rigc. Under the default `--idle-keys ctl` a two-bone ik whose child link the idle keys is split by that link's control, and rigc refuses the pair: run `rig --idle-keys direct` or `build --idle-keys direct` (the rig stage adds that sentence, naming the flag on the command that ran, to rigc's line; `build` forwards it to its rig stage, issue #95). Each bone a constraint follows is declared to rigc as `invariants.detached` from every bone it drives, which rigc's gate rule `A25` checks |
118
118
  | `turn` | rig (adds the turn control) | **proposed** only with `propose --full`/`--head` (issue #251), then corrected: `{pivot, depths}`. `depths` holds, per part, the See-through layer's `depth_median` the part came from (larger = further back), read from the run the part was assembled from; a `painting:` patch has none and is not carried. A rider (issue #271) — a part from an `eyewhite`, `irides`, `eyelash`, `eyebrow`, `nose` or `mouth` layer — is drafted at the `face` part's depth, 0.001 nearer when parts.json draws it after the face and 0.001 further when before: See-through makes no depth of its own for the eye group (it cuts them from one depth map of the combined eyes) and clamps nose and mouth to the face's median − 0.001, and the two runs' own medians for these layers disagreed by up to 0.47 and swapped their order against the face. Every other layer — ears, headwear and earwear included — keeps its own median. With no `face` part, or more than one, the riders keep their own and a note says so. One note lists each part's depth and where it came from; a carrier the other run places more than 10 % of `pivot − nearest` away gets a note naming both values and the run read — the two runs are not reconciled. `pivot` is the depth that stays put; `propose` drafts the mean of the parts' depths weighted by `opaque_px`, the one depth at which the painted pixels' centroid does not slide under the dial. The rig stage adds a bone `turn` under `root` at its origin that nothing keys — its local x is the dial, in rig px — and carries each part by `(pivot − depth) / (pivot − nearest depth)` of it: the nearest part all of it, a part behind the pivot the other way, a part at the pivot not at all. Each part rides leaf twins (`turn_<part>_<bone>`, an identity child of every bone its slot and weights bind) moved by one `transform` constraint (`turn_<part>`, after the config's own); the idle's keys, the `<bone>_ctl` controls and a physics constraint stay on the bones, and the dial moves none of them. At rest: with `turn` declared, no pixel differs by more than 1 level at dial 0 (not byte-identical; the cause is float noise in the identity child bones, and the constraints add nothing); without `turn`, byte-identical. Neither field has a default (`CONFIG_FIELD_PRESENT`); the pivot must lie behind the nearest part (`CONFIG_TURN_PIVOT_BEHIND_NEAREST`). Absent, no control is added and no byte of any output moves |
119
119
  | `scene` | assemble (the recomposite, its holes and the figure's mask); check, through `parts.json`'s `scene` block (`RECOMPOSITE_HOLES`, the seam) | **authored**, optional; `propose` writes none (issue #242). `{}` declares the painting a scene: the figure and its surroundings in one image, See-through's full run holding only the figure. With it, `assemble` reads the recomposite figures, the hole list and the `uncovered:` lines inside the figure — the rig pixels where any full-run layer's alpha is above 8 (`OPAQUE_ALPHA_ABOVE`, the cut every layer's painted pixels are read by) — each line starting `inside the figure (config.scene: …, <n> of <m> rig px)`; writes that figure as `figure_rig.png` (white on black) beside the recomposite, and a `scene` block in `parts.json`; flags error pixels on the error map inside it only (outside, the painting grey); and `check` leaves out of the seam every frame pixel whose composite sample reaches the painting's outermost row or column, saying how many (`seam_border` in `check.json`, and on the seam line). It takes no field: the figure and its threshold are derived. A key in it other than a record or an annotation is `CONFIG_KEY_KNOWN`. Absent, no byte of any output moves |
120
+ | `cloth` | `propose --cloth` only; no other stage reads it, so it moves no byte of any build | **authored**, optional (issue #364): one recipe per tag, `{"bottomwear": {…}, "back hair": {…}, "front hair": {…}}`, each `{chains, links, wind, gravity, split?, length_ratio?, strength?, damping?, alternate_damping?, translate?, swing?}`. `chains` — trees across the piece (for `back hair`, per side of the eye axis), a whole number, 1 or more; `links` — links from root to tip on every path, 1 or more; `split` — `{at, into}`: the first `at` links are the trunk, which forks into `into` (2 or more) branches; `at` below `links` (`CONFIG_CLOTH_SPLIT_BEFORE_TIP`); `length_ratio` — a path's last link over its first, above 0, default 0.5; `strength` — `{trunk, branch, edge}`, each above 0 and below the spring's stability bound (`CONFIG_CLOTH_SPRING_STABLE`), default 100 / 100 / 50; `damping` — in [0, 1], absent: rig-c's default; `alternate_damping` — the odd trees' damping, in [0, 1], beside `damping` and different from it (`CONFIG_CLOTH_ALTERNATE_DIFFERS`); `wind`, `gravity` — `[root, tip]`, each 0 or more, tip at or above root (`CONFIG_CLOTH_RESPONSE_GROWS_TO_TIP`); `translate` — the x and y mix of a dangling end's flutter, above 0 and at most 1; `swing` — `bottomwear` only, `{scales}`, one positive scale per tree (`CONFIG_CLOTH_SWING_SCALE_PER_TREE`). The two defaults are the recipe's own, measured on its synthetic fixture (the recipe below). The recipe below says what each field drafts |
120
121
 
121
122
  **A scene target is an ordinary bone.** A target the scene places — a point a hand
122
123
  reaches for, a face something turns toward — is a single bone parented to `root`,
@@ -822,7 +823,7 @@ unreduced source on the rig's idle and passes the author's bounds (step 4). The
822
823
  4. **Motion** — the rig stage builds the rig twice through rigc's gate: as written (the
823
824
  **candidate**, the reduced mesh, packed as `--out` receives it) and with that one attachment
824
825
  swapped for the unreduced source — its UVs, triangles and hull as `reduceMesh` was handed them,
825
- its own weights bound the same way (the **reference**, `build --profile spine-html` without
826
+ its own weights bound the same way (the **reference**, `build --profile rig-play` without
826
827
  `--pack`: the same gate over the compile, and nothing reads a reference's pages). rig-c's
827
828
  `compareMeshesInMotion` (`rig-c/meshcompare`) poses both model documents on the idle
828
829
  exactly as `check` renders it — `idle` at 12 fps over its duration, physics reset at 0 and
@@ -991,7 +992,7 @@ wall time goes to standard error, not into the table).
991
992
  | `boundaryRuns` | optional, `{ maxVertices }`, a whole number 2 or more, no default: each removal pass first tries to replace a run of 2 to `maxVertices` consecutive source-hull vertices with one chord, as one step held to every declared row (below) |
992
993
  | `retriangulate` | optional, `"delaunay"`: once the reduction ends, the kept vertices are re-triangulated by Delaunay flips, taken whole only when every declared row still passes (below) |
993
994
  | `removalOrder` | optional, `"deformation-load"`: the single removals are tried in ascending predicted load instead of ascending source index (below) |
994
- | `stimulus` | optional (issue #196), `{ "chain", "degrees" }` or a non-empty list of them: a bend the motion gate measures the part against, for a part no idle key deforms (*A part only physics moves*, after the physics recipe). `chain` is a chain this mesh's `segments` name, each named once; `degrees` a number above 0. Each link of the chain is turned by +`degrees` and by −`degrees` about its joint — a `rotate` key on the link, so the chain curls — in two animations of duration 0 (`stimulus <chain> +<degrees>`, `stimulus <chain> -<degrees>`) that only the comparison's builds carry: the reference and a candidate built from the written rig with them added (`build --profile spine-html` without `--pack`, one more gated build per part); `rig/motion.json` never holds them. Each is walked at time 0, right after the physics reset, so the pose is the declared bend and no constraint has moved it; the idle is walked as before, so where idle keys also move the part both are measured, against the one `motion` bound. The acceptance loop's probes walk them too, as `selection` beside the grid frames. The row echoes `stimulus` (chain, degrees, links, the two animation names); absent, nothing is added and every byte is the same. A chain the segments do not name is `CONFIG_NAME_RESOLVES`, `degrees` that is not a number above 0 `CONFIG_FIELD_TYPE`, both at the field; an animation of `motion.animations_from` already called by one of the two names is `AUTO_MESH_MOTION_INPUT`. The amplitude (*The allocation rows*) is still derived from the idle alone |
995
+ | `stimulus` | optional (issue #196), `{ "chain", "degrees" }` or a non-empty list of them: a bend the motion gate measures the part against, for a part no idle key deforms (*A part only physics moves*, after the physics recipe). `chain` is a chain this mesh's `segments` name, each named once; `degrees` a number above 0. Each link of the chain is turned by +`degrees` and by −`degrees` about its joint — a `rotate` key on the link, so the chain curls — in two animations of duration 0 (`stimulus <chain> +<degrees>`, `stimulus <chain> -<degrees>`) that only the comparison's builds carry: the reference and a candidate built from the written rig with them added (`build --profile rig-play` without `--pack`, one more gated build per part); `rig/motion.json` never holds them. Each is walked at time 0, right after the physics reset, so the pose is the declared bend and no constraint has moved it; the idle is walked as before, so where idle keys also move the part both are measured, against the one `motion` bound. The acceptance loop's probes walk them too, as `selection` beside the grid frames. The row echoes `stimulus` (chain, degrees, links, the two animation names); absent, nothing is added and every byte is the same. A chain the segments do not name is `CONFIG_NAME_RESOLVES`, `degrees` that is not a number above 0 `CONFIG_FIELD_TYPE`, both at the field; an animation of `motion.animations_from` already called by one of the two names is `AUTO_MESH_MOTION_INPUT`. The amplitude (*The allocation rows*) is still derived from the idle alone |
995
996
 
996
997
  **A bound declared absent.** `maxOvershoot` and `maxUndercut`, in `sourceBounds` and in
997
998
  `targets.artFit`, each take a number of px 0 or more or `null`. `null` means *measured and
@@ -1500,6 +1501,122 @@ is the fix.
1500
1501
  201 to 198 px wide and moved `check.json`'s `BREATH_VISIBLE` `torso_heat_mean` from 7.8 to
1501
1502
  7.705, every status unchanged, although `check` reads the idle alone.
1502
1503
 
1504
+ ### Recipe: cloth and hair that answer the world
1505
+
1506
+ Issue #364. Cloth and hair move **by physics, not by keyed animation** (the owner's
1507
+ direction, 2026-10-13), so that a rig in a scene answers the scene — the body under
1508
+ it moving, a gust from the side. The config's `cloth` block declares the recipe per
1509
+ tag, and `propose --cloth <config.json>` drafts it; the proposal is copied into the
1510
+ config as always, `constraints` with the rest. Without `--cloth` nothing the proposer
1511
+ writes changes. The recipe makes the rig **respond**; driving it — a hip key, a walk,
1512
+ the wind — is the consumer's, and the recipe drafts no key and no stimulus.
1513
+
1514
+ ```jsonc
1515
+ "cloth": {
1516
+ "bottomwear": { "chains": 4, "links": 3, "split": { "at": 1, "into": 2 },
1517
+ "damping": 0.85, "alternate_damping": 0.7,
1518
+ "wind": [2, 10], "gravity": [5, 30], "translate": 0.2,
1519
+ "swing": { "scales": [1.3, 1, 1, 1.3] } },
1520
+ "back hair": { "chains": 2, "links": 3, "split": { "at": 1, "into": 2 }, "wind": [1, 8], "gravity": [4, 24] },
1521
+ "front hair": { "chains": 1, "links": 3, "wind": [1, 6], "gravity": [2, 12], "translate": 0.2 }
1522
+ }
1523
+ ```
1524
+
1525
+ (The selftest's fixture recipe, `fixtures/cloth.ts`: values chosen to exercise every
1526
+ rule, not advice. It leaves `length_ratio` and `strength` out, so the recipe's own
1527
+ defaults are what is built and measured below.)
1528
+
1529
+ What each rule drafts (`src/cloth.ts` holds them):
1530
+
1531
+ | rule | drafts | from |
1532
+ | --- | --- | --- |
1533
+ | where | `bottomwear`: from `hip`, the root row 20 px below hip's y (the keyed chains' row), the tip row 4 px above the part's bottom. `back hair` that reaches below the shoulders: from `head`, per side of the eye axis (twin tails are a side each), the eye line to 4 px above its bottom. `front hair`: each strand that hangs below the chin (the lock rule's), from `head`, 0.22 face heights above the eye line to 5 px below the strand; the fringe's three columns keep their two links and their places | the keyed chains' own rows, which these replace |
1534
+ | trees | tree k of `chains` owns the cell k/N .. (k+1)/N of the art's width in every row, and its links sit at the cell's centre in the 24-row band there, so a piece that widens fans its trees. With `split`, the trunk's tip is the fork, and branch b of `into` follows the centre of its share of the cell. Chains are named `<prefix><k>_` (trunk) and `<prefix><k><a, b, …>_` (branches): `skirt`, `hairback_r`/`hairback_l`, `lockt` | `chains`, `links`, `split` |
1535
+ | lengths | link j's length is the first's times q^j, q = `length_ratio`^(1/(links − 1)), the whole spanning root row to tip row: the links shorten toward the tip | `length_ratio` (default 0.5, below) |
1536
+ | stiffness | a trunk link takes `strength.trunk`, a branch link `strength.branch`, and the edge — the outermost branch on each outer side of the piece (both sides of a skirt or a strand; the outer side of each back-hair side), or without `split` the outermost tree — `strength.edge`; a fringe column is a trunk, its outer two the edge. **Strength does not fall along a chain**: a tip reads soft because each link turns on top of every link above it, so the motion accumulates down the chain at one strength. Only the edge is declared softer | `strength` (default trunk 100, branch 100, edge 50, below) |
1537
+ | response | link j of an n-link path takes `wind` and `gravity` at j/(n − 1) of the way from the root value to the tip value; a single-link path takes the tip value. Each is a scalar **multiplier** of the environment the consumer sets (spine-core's skeleton `windX, windY`, `gravityX, gravityY`; rig-c 2.52.0's live track `setEnvironment`, rig-play 1.10.0's `environment()`): no direction is drafted | `wind`, `gravity` |
1538
+ | out of phase | the odd trees take `alternate_damping`, the even ones `damping` | `damping`, `alternate_damping` |
1539
+ | flutter | the last link of every path gets a second physics constraint, `<bone>_translate`, moving x and y by `translate`, with the link's stiffness and damping and **no** wind or gravity response. spine-core scales a translate spring's response by the skeleton's reference scale (100) and a rotation's by the link's length over it, so one response on both pushes an end about 100/length times further than it turns it (the table below) | `translate` |
1540
+ | swing | `bottomwear` only: a bone `<prefix>_swing` under `hip` at the centre of the root row, one link long, straight down, with physics (the trunk's stiffness, the root's response); each tree's first link takes the swing's rotation times its scale — a transform `<swing>_<k>`, local source and target, additive, the swing's setup rotation in hip's frame (−90: hip is drafted unturned) subtracted — and no physics of its own | `swing.scales` |
1541
+
1542
+ Every link but a swing-driven first link gets `<bone>_physics` (`rotate` 1), and no
1543
+ idle key: the drafted idle keys none of the trees. A recipe that drafts nothing —
1544
+ its tag on no part, or `back hair` that ends above the shoulders (a bun) — is refused,
1545
+ `PROPOSE_CLOTH_DRAFTED`.
1546
+
1547
+ **Every spring is stable at its step (A23's inequality).** Per step spine-core does
1548
+ `v += −strength·x·step·massInverse`, `x += v·step`, `v *= d` with d = damping^(60·step);
1549
+ in (x, v·step) that is the matrix [[1 − k, 1], [−d·k, d]] with
1550
+ k = strength·massInverse·step², and both eigenvalues are inside the unit circle exactly
1551
+ when **0 < k < 2(1 + damping^(60·step))**. The recipe sets no `fps` or `mass`, so step is
1552
+ 1/60 and massInverse 1 (rig-c's defaults), and a damping left out is rig-c's 0.85. The
1553
+ loader refuses a strength, declared or default, at or past the bound at any damping the
1554
+ recipe runs at (`CONFIG_CLOTH_SPRING_STABLE`). The defaults against it:
1555
+
1556
+ | strength | k = strength/3600 | bound at damping 0.85 (rig-c's default) | at 0.7 | at 0 (the lowest) | at 1 |
1557
+ | --- | --- | --- | --- | --- | --- |
1558
+ | 100 (trunk, branch) | 0.027778 | 3.7 | 3.4 | 2 | 4 |
1559
+ | 50 (edge) | 0.013889 | 3.7 | 3.4 | 2 | 4 |
1560
+
1561
+ **The defaults are the recipe's own, measured here** on the cloth fixture
1562
+ (`fixtures/cloth.ts`: a skirt of four trees on a swing, twin-tail back hair of two trees
1563
+ a side, a lock and the fringe; its recipe above; rig-c 2.47.1's build and `check`, and
1564
+ for the environment rig-c's live track, `setEnvironment`, which rig-c has from 2.52.0,
1565
+ read with 2.53.0). Candidates, each with the others at the
1566
+ default (`TEXTURE_STRETCH` severity, ceiling 1.927; then, under wind (1, 0) against
1567
+ calm (0, 0), gravity (0, 1) throughout, each held three idle cycles, the largest vertex
1568
+ displacement of `hair_back`, `skirt` and `hair_front`, and how far the origin of the last
1569
+ link of the right side's outer edge branch, `hairback_r0a_1`, moves against that of the
1570
+ inner branch beside it, `hairback_r0b_1`, in rig px):
1571
+
1572
+ | candidate | TEXTURE_STRETCH | wind (1, 0) vs calm: hair_back / skirt / hair_front | edge / inner branch |
1573
+ | --- | --- | --- | --- |
1574
+ | **default: strength 100 / 100 / 50, length_ratio 0.5** | **1.382** | **10.299 / 3.390 / 2.119** | **5.608 / 3.846** |
1575
+ | edge 100 (no softer edge) | 1.125 | 8.126 / 2.480 / 1.091 | 3.857 / 3.846 |
1576
+ | edge 25 | 2.062, **FAIL** | 14.355 / 4.931 / 4.014 | 8.333 / 3.846 |
1577
+ | length_ratio 1 (equal links) | 1.516 | 13.677 / 4.133 / 2.721 | 5.208 / 3.348 |
1578
+ | length_ratio 0.25 | 1.217 | 7.841 / 2.762 / 1.731 | 5.513 / 4.083 |
1579
+
1580
+ The rule the defaults were chosen by: the edge visibly softer than the branch beside it
1581
+ (edge 100 makes them one), every bar green (edge 25 stretches the texture past the
1582
+ ceiling), and among the ratios that do shorten the links toward the tip (1 does not) the
1583
+ one that answers the wind most. The figures are this fixture's; on other art the same
1584
+ reading, `check` and a gust, is how to tune them.
1585
+
1586
+ The flutter's split, measured on the same fixture (`TEXTURE_STRETCH` severity, the
1587
+ translate mix at 0.2 / 0.5 / 1): as drafted, no response on the translate, 1.382 /
1588
+ 1.388 / 1.398; given the link's own wind and gravity, 1.65 / 3.233 (**FAIL**) /
1589
+ 3.814 (**FAIL**).
1590
+
1591
+ **What moves them.** Physics answers motion of the bones above it and the environment.
1592
+ The drafted idle keys `chest` (the breath) and the head chain, never `hip`, and the
1593
+ recipe adds no key: under the idle alone a skirt's trees move only by the environment.
1594
+ `rigc render` and `check` pose under spine-core's default, wind (1, 0) and gravity
1595
+ (0, 1), so on the fixture `check` reads `check: PASS`, `TEXTURE_STRETCH` 1.382,
1596
+ `TIP_OVER_ROOT` ratio 15.06 (root 0.095, tip 1.438 rig px), `CHAIN_LAG` PASS,
1597
+ `ANIMATIONS_DO_NOT_FOLD` 0 folding meshes (that bar leaves physics out), and
1598
+ `rigc render --folds` on the same build **with** all of its physics constraints reads 0 of 3
1599
+ meshes folding at 12, 24 and 60 fps. Under an explicit environment set through the live
1600
+ track's `setEnvironment` — wind (1, 0) for one idle cycle, then (−1, 0) for one, then
1601
+ calm (0, 0) for one, gravity (0, 1) throughout, on one looping track — the frame at the
1602
+ end of each wind phase, against the calm frame at the same idle time, moves `hair_back`
1603
+ by up to 10.396 and 10.188, `skirt` 3.390 and 3.382, and `hair_front` 2.131 and 2.104 rig
1604
+ px, either way alike; held three cycles each from a fresh track, 10.299 / 10.277,
1605
+ 3.390 / 3.382 and 2.119 / 2.115.
1606
+
1607
+ **Not in this recipe.**
1608
+
1609
+ - **Weights across neighbouring chains** (rig-c/core#1415's per-vertex weights, a vertex
1610
+ following the links of several trees): issue #378.
1611
+ - **A stop at the legs** is not needed for this recipe. On a copy of the fixture with two
1612
+ thighs drawn behind the skirt, under the environment alone (wind (1, 0), then (−1, 0),
1613
+ then calm) no skirt link that clears the thighs at rest crosses into one: 0 crossings.
1614
+ A consumer's hip motion can make the hem cross: on the same copy, under a hip sway
1615
+ applied to it through the live track (±8 degrees and ±12 px, a 2 s period), skirt links
1616
+ cross on 224 (calm) and 228 (wind (1, 0)) of 360 steps. That case is rig-c/core#1413's; until it lands, a stop is the
1617
+ author's own constraint network in `constraints`. When `--keypoints` drafts leg chains,
1618
+ a note says so.
1619
+
1503
1620
  ## 4. The command order
1504
1621
 
1505
1622
  Two steps are external: the See-through runs (by any route; the optional `comfy seethrough` adapter is only a client for a ComfyUI box). Everything else is this tool.
@@ -1518,6 +1635,7 @@ early loader does not read it.
1518
1635
  | `layers` | 1 and 4 `inputs`, 6 `assemble --propose-plan` | `key`, `seethrough`, `assemble.rig_scale` | `seethrough.head_box`, for `--propose-plan` only (`ASSEMBLE_FIELD_PRESENT`) |
1519
1636
  | `assemble` | 7 `assemble` | `key`, `seethrough`, `assemble.rig_scale`, `assemble.plan` | `seethrough.head_box` (`ASSEMBLE_FIELD_PRESENT`) |
1520
1637
  | `full` | 8 `propose --from-config`, `propose --compare`, `compare` (a side that states a `key`), `rig`, 9 `build` | `key`, `assemble.rig_scale`, `assemble.plan`, `bones`, `meshes`, `regions`, `motion` | for `build`, the `assemble` row's too: its assemble stage reads through that loader after this one |
1638
+ | `cloth` | 8 `propose --cloth` (issue #364) | `key`, `cloth` | every other key must still be one a config knows; only `cloth` is vouched for. Every other loader vouches for `cloth` too when it is present |
1521
1639
 
1522
1640
  A missing field is `CONFIG_FIELD_PRESENT` naming it; a missing `generation` names
1523
1641
  the fields the block holds, and a missing `assemble.plan` names
@@ -1582,7 +1700,13 @@ full loader before its assemble stage writes anything.
1582
1700
  no later stage can see — then
1583
1701
  `rig-parts propose --parts work/rig --source inputs/painting.png --out work` →
1584
1702
  `work/proposal.json` and `work/render/landmarks.png` (+ `_head`). Copy
1585
- `bones`, `meshes`, `regions` and `motion` into the config. Then read what
1703
+ each of `bones`, `meshes`, `regions`, `limbs`, `motion`, `constraints` and `turn` it
1704
+ wrote into the config: `limbs` is written only with `--keypoints`, `constraints`
1705
+ only with `--cloth` and `turn` only with `--full`/`--head` (`PROPOSAL_CONFIG_SECTIONS` in `src/propose.ts` is the list,
1706
+ and the selftest's `TY18` holds this sentence to it). A `limbs` block left out
1707
+ is refused at step 8 by the name of every part it cuts: `CONFIG_NAME_RESOLVES`
1708
+ on each limb part the meshes or regions attach, `CONFIG_PART_ATTACHED` on the
1709
+ layer it would have cut (`RL04`). Then read what
1586
1710
  was looked at. After the LINT summary, one `coverage <bone> [<role>]:` line
1587
1711
  per bone says `checked, clean` (and which checks read it), `checked, LINT`,
1588
1712
  or `not checked` and why — its role (a `control` binds nothing and may sit
@@ -2086,7 +2210,7 @@ skip); the pose costs 14.8 against 15.3 us per frame (reference-demo) and 8.3 ag
2086
2210
  (reference-sample), the difference all in `updateWorldTransform`; the rendered idle frames
2087
2211
  differ by at most one level in a channel. The default stays `ctl`: the examples'
2088
2212
  `expected/rig.json` and `motion.json` are the reference's output, and the
2089
- declaration is in the rig spec only, so `rigc validate <build> --profile spine-html`
2213
+ declaration is in the rig spec only, so `rigc validate <build> --profile rig-play`
2090
2214
  run on a `direct` build (it has no rig spec to read) refuses A15 once per keyed
2091
2215
  bone. No stage runs `validate`: the gate is `rigc build`, which reads the rig spec
2092
2216
  and its declaration, and is green on both (`RG19`).
@@ -2369,6 +2493,11 @@ See-through with another seed.
2369
2493
  | <a id="CONFIG_CONSTRAINT_NAME_UNIQUE"></a>`CONFIG_CONSTRAINT_NAME_UNIQUE` | two `constraints` entries of one kind share a `name`; rigc finds a constraint by its kind and its name, so an ik and a transform may share one | rename one |
2370
2494
  | <a id="CONFIG_CONSTRAINT_TARGET_DETACHED"></a>`CONFIG_CONSTRAINT_TARGET_DETACHED` | the bone an ik follows (`target`) or a transform reads (`source`) is one of the bones that constraint drives, or sits under one (the detail gives the line of parents) — driving them would move what they follow. This package's refusal: rig-c 2.15.0 gates such a rig green when nothing declares the parentage (measured on the selftest's rig fixture, issue #103) | a bone beside the chain; a scene target is a single bone parented to `root` (§3) |
2371
2495
  | <a id="CONFIG_TURN_PIVOT_BEHIND_NEAREST"></a>`CONFIG_TURN_PIVOT_BEHIND_NEAREST` | `turn.pivot` is at or in front of the nearest part's depth: the law `(pivot − depth) / (pivot − nearest)` has no value there, and in front of it every part travels against the dial | a pivot above the smallest depth in `turn.depths` |
2496
+ | <a id="CONFIG_CLOTH_SPLIT_BEFORE_TIP"></a>`CONFIG_CLOTH_SPLIT_BEFORE_TIP` | a `cloth` recipe's `split.at` is at or past its `links`: the trunk would leave no link for the branches (issue #364) | `split.at` below `links`, or leave `split` out |
2497
+ | <a id="CONFIG_CLOTH_RESPONSE_GROWS_TO_TIP"></a>`CONFIG_CLOTH_RESPONSE_GROWS_TO_TIP` | a `cloth` recipe's `wind` or `gravity` is `[root, tip]` with tip below root: the response grows from the root to the tip, where the motion accumulates down the chain | that pair |
2498
+ | <a id="CONFIG_CLOTH_ALTERNATE_DIFFERS"></a>`CONFIG_CLOTH_ALTERNATE_DIFFERS` | a `cloth` recipe's `alternate_damping` without `damping`, or equal to it: neighbouring trees are declared out of phase, and an alternate that alternates nothing declares nothing | `damping` beside it, a different value, or leave `alternate_damping` out |
2499
+ | <a id="CONFIG_CLOTH_SWING_SCALE_PER_TREE"></a>`CONFIG_CLOTH_SWING_SCALE_PER_TREE` | `swing` on a tag other than `bottomwear`, or `swing.scales` not one scale per tree (`chains`) | one scale per tree, in tree order across the skirt |
2500
+ | <a id="CONFIG_CLOTH_SPRING_STABLE"></a>`CONFIG_CLOTH_SPRING_STABLE` | a `cloth` recipe's strength — declared, or the default 100 / 100 / 50 — whose spring is unstable at a damping the recipe runs at (`damping`, `alternate_damping`, or rig-c's 0.85 when `damping` is left out): k = strength·massInverse·step² at or past 2(1 + damping^(60·step)), step 1/60 and mass 1 (rig-c's defaults; the recipe sets neither). The detail gives k and the bound | a lower strength, or a higher damping |
2372
2501
  | <a id="CONFIG_TURN_BONE_FREE"></a>`CONFIG_TURN_BONE_FREE` | the config has `turn` and `bones` declares a bone named `turn`, the control the rig stage adds | rename that bone |
2373
2502
 
2374
2503
  ### inputs
@@ -2391,8 +2520,8 @@ says why each requirement exists and what breaks without it.
2391
2520
  | --- | --- | --- |
2392
2521
  | <a id="PAINTING_SOURCE_PRESENT"></a><a id="PAINTING_SOURCE_DECODES"></a>`PAINTING_SOURCE_PRESENT`, `PAINTING_SOURCE_DECODES` | the painting is missing or not a PNG | the path |
2393
2522
  | <a id="PAINTING_BACKGROUND_PLAIN"></a>`PAINTING_BACKGROUND_PLAIN` | a border pixel is neither near-white nor unpainted: scenery, a floor, a gradient or a vignette reaches the edge, or the figure is cut by the frame; the detail counts them and names the first | a painting on a plain light background with the whole figure inside the frame (PAINTING.md, *Background*) |
2394
- | <a id="PAINTING_JOINTS_STATED"></a>`PAINTING_JOINTS_STATED` | a wrist, hip, knee or ankle is `occluded` or `missing` in the keypoint file: the gap rules read those eight joints, and a hand or foot the painting does not show is a limb nothing can rig | a painting that shows it; or the joint, if the estimator missed one the painting shows |
2395
- | <a id="PAINTING_JOINT_ON_FIGURE"></a>`PAINTING_JOINT_ON_FIGURE` | an observed joint's pixel is background | the keypoint file (measured on this painting?), or the painting |
2523
+ | <a id="PAINTING_JOINTS_STATED"></a>`PAINTING_JOINTS_STATED` | a wrist, knee or ankle is `occluded` or `missing` in the keypoint file, or a hip has no position: the gap rules read those eight joints, and a hand or foot the painting does not show is a limb nothing can rig. A hip may be `occluded` (a skirt hides it on most clothed figures, issue #372) when its arm's gap is **well defined**: it carries a position — inside the image, which the keypoint reader holds (`KEYPOINTS_POSITION_INSIDE`), so the wrist-to-hip line's every sample is a pixel of the painting — and that pixel is figure (`PAINTING_JOINT_ON_FIGURE`). A hip `occluded` with no position, or `missing`, is refused: there is no line to read | a painting that shows it; or the joint, if the estimator missed one the painting shows; for a hip under a skirt, `occluded` with its estimated position (PAINTING.md, *An occluded hip*) |
2524
+ | <a id="PAINTING_JOINT_ON_FIGURE"></a>`PAINTING_JOINT_ON_FIGURE` | an observed joint's pixel, or an occluded hip's, is background. For an occluded hip the detail adds that the arm's gap would be the estimate's own background, not one between the arm and the body | the keypoint file (measured on this painting?), or the painting; an occluded hip's estimate, moved onto the figure it is hidden under |
2396
2525
  | <a id="PAINTING_ARM_CLEAR"></a>`PAINTING_ARM_CLEAR` | the line from a wrist to the hip of its side crosses no background: the arm is painted against the body. Hands that meet close a loop round the torso, so this fires with `PAINTING_HANDS_APART` on both sides | a painting with the arm held away from the body (PAINTING.md, *Arms*) |
2397
2526
  | <a id="PAINTING_HANDS_APART"></a>`PAINTING_HANDS_APART` | the line between the two wrists crosses no background: hands clasped or touching | a painting with the hands apart and open (PAINTING.md, *Hands*) |
2398
2527
  | <a id="PAINTING_LEGS_APART"></a>`PAINTING_LEGS_APART` | the line between the knees, or between the ankles, crosses no background: the legs are one column there (a long skirt over the knees is refused here too) | a painting with the legs apart and a visible gap at the knees and the ankles (PAINTING.md, *Legs*) |
@@ -2447,6 +2576,7 @@ says why each requirement exists and what breaks without it.
2447
2576
  | <a id="PROPOSE_FACE_PRESENT"></a>`PROPOSE_FACE_PRESENT` | no part comes from a `face` layer, and the face-less fallback cannot derive a face box either: it needs the head run's `neck` and at least one head-run `front hair`/`back hair` part whose top is above the neck's, and the message names the half that is missing. With both, `propose` does not refuse — it derives the box and says so in its first note (§5) | `assemble.plan`: take the face, or the head run's hair and neck |
2448
2577
  | <a id="PROPOSE_CUT_FROM_RECORDED"></a>`PROPOSE_CUT_FROM_RECORDED` | two parts share one layer and none of them records `cut_from`: a `parts.json` an `assemble` before 1.6.0 wrote with `assemble.cuts`. The records are in draw order, so which is the plan part and which the piece cannot be read off the file; the message names the parts and the layer. Every other reader still takes the file | `--parts`: re-run assemble (1.6.0+) to record `cut_from` |
2449
2578
  | <a id="PROPOSE_LIMB_PARTS"></a>`PROPOSE_LIMB_PARTS` | `parts.json` holds parts `config.limbs` cut at their joints (issue #349); the limbs are drafted from the parts before the cut, which the rules read by layer | `--parts` a build assembled with the config's `limbs` left out; keep the block the config holds (its caps and ranges are the author's). `propose --from-config` reads the config's bones on either build |
2579
+ | <a id="PROPOSE_CLOTH_DRAFTED"></a>`PROPOSE_CLOTH_DRAFTED` | with `--cloth` (issue #364): the config's `cloth` block holds a recipe for a tag the proposer drafts no chain on — no part has that tag, or every `back hair` part ends above the shoulders (a bun bone, no chain) | leave that recipe out, or assemble the layer it is for |
2450
2580
  | <a id="PROPOSE_ACCESSORY_BODY"></a>`PROPOSE_ACCESSORY_BODY` | an accessory has nothing above its pendant rows to hang its bone on | that part's plan entry, or author its bones by hand |
2451
2581
  | <a id="PROPOSE_TURN_RUN_GIVEN"></a><a id="PROPOSE_TURN_LAYER_PRESENT"></a><a id="PROPOSE_TURN_DEPTH_PRESENT"></a>`PROPOSE_TURN_RUN_GIVEN`, `PROPOSE_TURN_LAYER_PRESENT`, `PROPOSE_TURN_DEPTH_PRESENT` | with `--full`/`--head` (issue #251): a part came from a run whose layers were not given, the run given holds no layer of the part's tag, or its layer carries no `depth_median` while another part's layer carries one; a run with no depth on any part's layer (a PSD, or an export of one) is not refused — the turn is skipped, and the note says `turn: SKIP` and names the run (issue #359) | give the run the parts were assembled from, as the wrapper's `layers.json` |
2452
2582
  | <a id="BASIS_RECORDS_EVERY_BONE"></a><a id="BASIS_NAMES_RESOLVE"></a><a id="BASIS_KIND_KNOWN"></a><a id="BASIS_KIND_HOLDS"></a>`BASIS_RECORDS_EVERY_BONE`, `BASIS_NAMES_RESOLVE`, `BASIS_KIND_KNOWN`, `BASIS_KIND_HOLDS` | `propose --keypoints`'s record of how it placed each bone, `basis.json`, does not hold to the proposal it was written with: a proposed bone with no record, one recorded twice or out of the proposal's order; a part, bone or frame quantity it names that is not there; a placement kind outside the known ones; or a placement that does not hold what its kind says (a joint placement with no joint, a non-joint one with a joint, a derived one with no bone, a constant that is not finite). Nothing is written | a proposer bug, not an input problem: report it with the line |
@@ -3152,7 +3282,7 @@ What is composed:
3152
3282
  play. Each is read with the idle's own key lists, so a key compose does not know
3153
3283
  how to prefix is `SCENE_BUILD_FIELD_KNOWN`, as it is on the idle, and a track of
3154
3284
  one that keys `root` is `SCENE_ROOT_SHARED` (issue #183).
3155
- - **Gated as `rig` gates its own:** `rigc build --profile spine-html --pack`, the
3285
+ - **Gated as `rig` gates its own:** `rigc build --profile rig-play --pack`, the
3156
3286
  compile and the packed pages on disk, in a scratch directory; `--out` receives
3157
3287
  `rig/` (`rig.json`, `motion.json`, `images/`) and `scene.json` only when it is
3158
3288
  green. Then `check` runs over `rig/` into `check/`, as on any rig it did not
package/docs/MIGRATION.md CHANGED
@@ -262,3 +262,44 @@ and writes nothing; no other command changed, and no byte any build writes moves
262
262
  | the README's input bullet for the painting | one sentence | the requirements, linked to `docs/PAINTING.md`, which ships in the package |
263
263
 
264
264
  What to do: nothing. Run `painting` on a new painting before See-through.
265
+
266
+ ### And `painting` reads an occluded hip (issue #372)
267
+
268
+ A skirt hides the hips on most clothed figures, and the keypoint file says so with
269
+ `occluded`. `painting` refused that state, which taught a writer to call an
270
+ estimated hip `observed`. It now reads an occluded hip when the arm's gap from it is
271
+ well defined: the hip carries a position (inside the image, as the keypoint reader
272
+ holds every position) and that position's pixel is figure (PAINTING.md, *An occluded
273
+ hip*). No other command changed, and no byte any build writes moves.
274
+
275
+ | change | before | after |
276
+ | --- | --- | --- |
277
+ | a hip `occluded` with a position, on the figure | `PAINTING_JOINTS_STATED` (exit 1) | read: its arm's gap is measured from it, and the run passes or fails on the gaps |
278
+ | a hip `occluded` with a position, on the background | `PAINTING_JOINTS_STATED` | `PAINTING_JOINT_ON_FIGURE`, as an observed joint there is |
279
+ | a hip `occluded` with no position, or `missing` | `PAINTING_JOINTS_STATED` | the same rule; the detail now says a hip may be occluded with a position |
280
+ | a wrist, knee or ankle `occluded` | `PAINTING_JOINTS_STATED` | unchanged; the detail adds that only a hip may be occluded |
281
+ | the `joints:` line | `joints: <n> observed, <m> on the figure` | the same when no hip is occluded; otherwise `joints: <n> observed, <k> occluded with a position (<hips>), <m> on the figure` |
282
+ | a gap line read from an occluded hip | — | ends ` (<hip> occluded, read at its position)` |
283
+
284
+ What to do: write a hip a skirt hides as `occluded` with its estimate. A file that
285
+ wrote it `observed` reads the same gaps as before.
286
+
287
+ ### And cloth and hair that answer the world (`propose --cloth`, issue #364)
288
+
289
+ A config may hold a `cloth` block — one recipe per tag, `bottomwear`, `back hair` and
290
+ `front hair` (AUTHORING §3, *Recipe: cloth and hair that answer the world*) — and
291
+ `propose --cloth <config.json>` drafts that tag's pieces as trees of physics links
292
+ in place of the keyed chains. Without `--cloth` nothing the proposer writes moves;
293
+ `rig` and `build` do not read `cloth` at all, so no byte any build writes moves
294
+ either (the examples' `expected/` and every existing control are unchanged).
295
+
296
+ | change | before | after |
297
+ | --- | --- | --- |
298
+ | `cloth` in a config | refused, `CONFIG_KEY_KNOWN` | vouched by every loader (`CONFIG_FIELD_*`, and the `CONFIG_CLOTH_*` rules of AUTHORING §6), read by `propose --cloth` only |
299
+ | `propose --cloth <config.json>` | a usage error, flag not taken (exit 2) | the recipe's trees, their `constraints` in proposal.json, one note per piece; a recipe that drafts nothing, `PROPOSE_CLOTH_DRAFTED` (exit 1) |
300
+ | `proposal.json` | no `constraints` | `constraints` after `motion`, with `--cloth` only |
301
+
302
+ What to do: nothing. To draft a skirt or hair that the consumer's wind and gravity move,
303
+ add a `cloth` block and propose with `--cloth`; copy each of `bones`, `meshes`, `regions`,
304
+ `limbs`, `motion`, `constraints` and `turn` the proposal wrote into the config, as before
305
+ (`constraints` is the one `--cloth` adds).