@rig-c/parts 1.19.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
@@ -763,7 +777,7 @@ const TAKES = {
763
777
  layers: { valued: ['--export', '--template', '--canvas'], switches: [] },
764
778
  sheet: { valued: ['--source', '--layers', '--out', '--cell', '--cols'], switches: [] },
765
779
  rig: { valued: ['--config', '--parts', '--out', '--idle-keys', '--page-edges', '--pack-shape'], switches: [] },
766
- 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'] },
767
781
  compare: { valued: ['--left', '--right', '--map'], switches: [] },
768
782
  check: { valued: ['--rig', '--out', '--parts', '--source', '--page-edges', '--pack-shape', '--requirements'], switches: ['--seam-pairs', '--keep-stills'] },
769
783
  loop: { valued: ['--frames', '--out'], switches: ['--palette'] },
@@ -1124,6 +1138,10 @@ function cmdPropose(args: string[]): number {
1124
1138
  if (ruleArg !== undefined && flags.has('--from-config')) return usage('--grid-rule chooses the drafted spacing; --from-config proposes nothing');
1125
1139
  if (ruleArg !== undefined && !(GRID_RULES as readonly string[]).includes(ruleArg)) return usage(`--grid-rule ${ruleArg}; one of ${GRID_RULES.join(', ')} is required`);
1126
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;
1127
1145
  const partsDir = existsSync(partsArg) && statSync(partsArg).isFile() ? dirname(partsArg) : partsArg;
1128
1146
  if (!existsSync(source)) {
1129
1147
  throw new PartsError([{ code: 'PROPOSE_SOURCE_PRESENT', object: source, detail: 'no such file; the painting is drawn under the overlay' }]);
@@ -1151,7 +1169,7 @@ function cmdPropose(args: string[]): number {
1151
1169
  }
1152
1170
  const cmpPath = flags.get('--compare');
1153
1171
  const cfg = cmpPath === undefined ? null : loadConfig(cmpPath);
1154
- const { proposal: drafted, basis } = proposeWithBasis(P, joints, wristSnap, gridRule);
1172
+ const { proposal: drafted, basis } = proposeWithBasis(P, joints, wristSnap, gridRule, cloth);
1155
1173
  const fullRun = flags.get('--full');
1156
1174
  const headRun = flags.get('--head');
1157
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
@@ -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
@@ -283,3 +283,23 @@ hip*). No other command changed, and no byte any build writes moves.
283
283
 
284
284
  What to do: write a hip a skirt hides as `occluded` with its estimate. A file that
285
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).
package/docs/STABILITY.md CHANGED
@@ -53,7 +53,7 @@ defaults are the help's too, and none is guessed from the art.
53
53
  | `comfy` | `seethrough`: `--image`, `--out`; optional `--resolution`, `--steps`, `--seed`, `--prefix`, `--offload`, `--lama`, `--nf4`, `--host`, `--wait`, `--timeout`, `--poll` |
54
54
  | `assemble` | `--propose-plan` with `--source`, `--full`, `--config`, optional `--head`; or `--source`, `--full`, `--config`, `--out`, optional `--head` (without it, one run: issue #359), `--seam` (near-white, silhouette), `--project` (core, visible) |
55
55
  | `propose` | `--head-box` with `--full`, `--canvas` |
56
- | `propose` | `--parts`, `--source`, `--out`; optional `--compare` or `--from-config` (one of them), `--keypoints`, `--person` and `--wrist-snap` (each with `--keypoints`), `--full`, `--head` (the turn control's depths; not with `--from-config`), `--grid-rule` (`figure` or `part`, the drafted lattice's spacing; not with `--from-config`) |
56
+ | `propose` | `--parts`, `--source`, `--out`; optional `--compare` or `--from-config` (one of them), `--keypoints`, `--person` and `--wrist-snap` (each with `--keypoints`), `--full`, `--head` (the turn control's depths; not with `--from-config`), `--grid-rule` (`figure` or `part`, the drafted lattice's spacing; not with `--from-config`), `--cloth` (a config whose `cloth` block is the recipe, issue #364; not with `--from-config`) |
57
57
  | `compare` | `--left`, `--right`; optional `--map` |
58
58
  | `rig` | `--config`, `--parts`, `--out`; optional `--idle-keys` (ctl, direct), `--page-edges` (pot, free), `--pack-shape` (rect, polygon) |
59
59
  | `check` | `--rig`, `--out`; optional `--parts`, `--source`, `--page-edges`, `--pack-shape`, `--requirements`, `--seam-pairs`, `--keep-stills` |
@@ -112,7 +112,7 @@ The fields, object by object (a bold path is where the object sits; `<part>` is
112
112
  part name, `[i]` an entry of a list):
113
113
 
114
114
  <!-- stable:config -->
115
- - **config** — required by the full loader, in front of rig and build: `key`, `assemble`, `bones`, `meshes`, `regions`, `motion`; optional: `generation`, `seethrough`, `limbs`, `constraints`, `turn`, `scene`
115
+ - **config** — required by the full loader, in front of rig and build: `key`, `assemble`, `bones`, `meshes`, `regions`, `motion`; optional: `generation`, `seethrough`, `limbs`, `constraints`, `turn`, `scene`, `cloth`
116
116
  - **generation** — required: `checkpoint`, `loras`, `trigger`, `identity`, `sampler`, `costume`, `negative_extra`, `style`, `negative_pose`, `latent`, `seed`; optional: `pose`, `control`
117
117
  - **generation.loras[i]** — required: `name`, `strength`; optional: `strength_clip`
118
118
  - **generation.sampler** — required: `steps`, `cfg`, `sampler`, `scheduler`
@@ -147,6 +147,10 @@ part name, `[i]` an entry of a list):
147
147
  - **meshes.&lt;part&gt;.auto.regions[i]** — required: `name`, `shape`, `maxEdgeLength`, `transition`, `grade`, `minArtSamples`, and `cx`, `cy`, `r` for a circle or `points` for a polygon; optional: `bone`, `band` (both or neither; with neither the region is density only)
148
148
  - **meshes.&lt;part&gt;.auto.boundaryRuns** — required: `maxVertices`
149
149
  - **meshes.&lt;part&gt;.auto.stimulus**, or each entry of it when a list — required: `chain`, `degrees`
150
+ - **cloth.&lt;tag&gt;**, the tag bottomwear, back hair or front hair (issue #364; read by propose --cloth only) — required: `chains`, `links`, `wind`, `gravity`; optional: `split`, `length_ratio`, `strength`, `damping`, `alternate_damping`, `translate`, `swing` (on bottomwear only)
151
+ - **cloth.&lt;tag&gt;.split** — required: `at`, `into`
152
+ - **cloth.&lt;tag&gt;.strength** — required: `trunk`, `branch`, `edge`
153
+ - **cloth.&lt;tag&gt;.swing** — required: `scales`
150
154
  - **regions.&lt;part&gt;** — a bone name
151
155
  - **motion** — required: `duration`, `tracks`; optional: `blink`, `animations_from`
152
156
  - **motion.tracks[i]**, a single track — required: `bone`, `prop`, `amp`, `period`, `phase`; optional: `base`
@@ -180,7 +184,7 @@ every number) are AUTHORING §3's tables, and the loader refuses any other by na
180
184
  | `check/check.json` and the rest of `check/` | `check`, `build`, `compose` | the reference's fields in its order (`gate_spine_html_green`, `rigc_entry`, `pack_mode`, `loop_max_diff`, the seam figures — `seam_mean`, `seam_px_over_40`, `seam_px_over_80` and, from issue #216, `seam_frame_scale`, the frame scale the bar divides the count by), `loop_physics` after `loop_max_diff` when the rig declares a physics constraint, `seam_border` after the seam figures when `parts.json` declares a scene (issue #242), `seam_pairs` after the seam figures when the seam bar fails or `--seam-pairs` asks (each row's picture under `check/seam/`; `--keep-stills` leaves the setup-pose still and its `frames.json` under `check/setup_still/`), then the judgement lines (`BREATH_VISIBLE`, `BLINK_NO_HOLE`, `CHAIN_LAG`, `TIP_OVER_ROOT`, `STILL_REGIONS_DARK`, `TEXTURE_STRETCH`), `ANIMATIONS_DO_NOT_FOLD` after them when `motion.json` holds an animation, the idle included — on every rig with an idle since issue #333 (issue #320: `status`, `animations`, `fps`, `physics_left_out`, `folding`, `folding_undeclared`, `declared_not_folding`, `bar`), `FEET_STAY_LEVEL` after it when a `footwear` part rides a bone that hangs from another and `motion.json` holds an animation (issue #348: `status`, `reason` on a SKIP only, `animations`, `fps`, `physics_left_out`, `bar_degrees`, `bar`, `feet` — each `part`, `bone`, `hangs_from`, `status`, `frames_posed`, `worst`), `LIMB_ONE_BONE` after it (and after `STRESS_OFF_CHAIN` where that is written) when `parts.json` holds a limb segment's part (issue #349: `status`, `parts` — each `part`, `limb`, `bone`, `status`, `others`), `RECOMPOSITE_HOLES` (its first figure `declared_scene` when `parts.json` declares a scene), `SETUP_POSE_VS_SOURCE`, `skipped`, a `requirements` block under `--requirements`, and `PASS`; `check/build/skeleton.json`, `.atlas` and `.png` are the packed artifact rig-c writes |
181
185
  | `layers.json`, `<index>_<name>.png` | `layers --export` | the wrapper form `layers` reads (issue #359): `width`, `height`, then `layers` — each `name`, `filename`, `left`, `top`, `right`, `bottom`, `depth_median` (the input's own; `null` for a PSD, the manifest's order then being the draw order) — drawn layers in draw order, then the hidden-art layers; a PNG's name is its index and its layer's name, and the manifest, not the file name, says which layer it is |
182
186
  | the template `.psd` | `layers --template` | one empty layer per name `templateLayerNames` (`src/layers.ts`) holds, bottom first; the same canvas writes the same bytes |
183
- | `proposal.json`, `render/landmarks*.png` | `propose` | the config's four rig sections, `turn` with `--full`/`--head`, and `notes` |
187
+ | `proposal.json`, `render/landmarks*.png` | `propose` | the config's four rig sections, `constraints` with `--cloth` (issue #364: a physics constraint per tree link named `<bone>_physics`, a dangling end's `<bone>_translate`, and a skirt swing's transforms `<swing>_<tree>`), `turn` with `--full`/`--head`, and `notes` |
184
188
  | `basis.json` | `propose` | spec `spine-parts-basis/1` |
185
189
  | `config.json`, `mesh_draft.json` | `mesh-draft` | `config.json` is the config read, byte for byte, but for each drafted part's `meshes` entry; `mesh_draft.json` is spec `rig-parts-mesh-draft/1`: `rules`, `lattice_check`, `draft_check`, `figure_overdraw`, and per lattice part `part`, `mode`, `lattice`, `candidate` (with `bridges` — `units`, `joins` of `span` and `area_px2`, `area_px2`, `overdraw`, and `faint` only when an island was left out as faint — only when the candidate is a bridged one, issue #326), `delta` (under `--more-triangles` only), `readings`, `gate` (each line's `reading`, `pass`, `text`, `margin`), `note`; and `not_compared` (issue #307), only when the config holds an `auto` or `contour` entry: per such entry, in config order, `part`, `mode` and `note` — the part was neither drafted nor compared, and a config of lattices alone writes no such key |
186
190
  | the scene report | `compose` | spec `spine-parts-scene-report/1` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rig-c/parts",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "description": "AI-authored 2D character parts from one anime painting and its See-through layer decomposition: measured parts, weighted-mesh rig specs, verified through rig-c before anything is written. A CLI for agents that cannot see the image.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -61,7 +61,7 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "ag-psd": "^31.0.2",
64
- "rig-c": "^2.47.1"
64
+ "rig-c": "^2.54.0"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@esotericsoftware/spine-core": "4.3.13",
@@ -84,9 +84,11 @@ under that.
84
84
  `work/render/recomposite_error_rig.png`; a large red hole between two parts is a
85
85
  gap See-through left (a skirt split into two legs), fixed in the plan or the layers,
86
86
  not in the rig. Then `rig-parts propose --parts work/rig
87
- --source <painting> --out work` → copy `bones`, `meshes`, `regions`, `motion`
88
- from `proposal.json` into the config. `assemble` needs the plan and none of
89
- those four; do not stub them to get past it (AUTHORING §4 says what each step
87
+ --source <painting> --out work` → copy `bones`, `meshes`, `regions`, `limbs`,
88
+ `motion`, `constraints`, `turn` from `proposal.json` into the config, each it
89
+ wrote (`limbs` only with `--keypoints`, `constraints` only with `--cloth`, `turn`
90
+ only with `--full`/`--head`; a `limbs` block left out is refused at the next step). `assemble` needs the plan and none of
91
+ those sections; do not stub them to get past it (AUTHORING §4 says what each step
90
92
  requires). If an `uncovered hole` stays large with the plan right — a piece
91
93
  of the figure neither run holds, red in `recomposite_error_rig.png` — add an
92
94
  `assemble.patches` entry (a rig-pixel box, `alpha: "silhouette"`, drawn
@@ -108,6 +110,18 @@ under that.
108
110
  again, and read each `limb cap:` line for the pixels a cap is missing. A clean
109
111
  LINT does not show a seated hip is right; the overlay does. Never write a joint you did not get from an
110
112
  estimator or the painting: say `missing` and let the rule stand in.
113
+ A skirt or hair that must answer the scene (wind, the body moving) rather than
114
+ loop a keyed swing: add a `cloth` block to the config — one recipe per tag,
115
+ `bottomwear`, `back hair`, `front hair`: `chains`, `links`, `wind` and `gravity`
116
+ as `[root, tip]` (multipliers of the consumer's environment, tip >= root), and
117
+ optionally `split`, `strength`, `damping`/`alternate_damping`, `translate`,
118
+ and on the skirt `swing.scales` (AUTHORING §3, *Recipe: cloth and hair*) —
119
+ and propose with `--cloth config.json`; copy its `constraints` too. Every
120
+ number is yours but `length_ratio` and `strength`, whose defaults the note
121
+ names. Weights across neighbouring trees are not drafted (issue #378). No
122
+ stop at the legs is drafted: on the recipe's fixture the environment alone
123
+ pushed no hem through a thigh, but the consumer's hip motion can
124
+ (rig-c/core#1413).
111
125
  6. Correct against `render/landmarks.png` and `note:` lines; a
112
126
  `coverage … not checked` line is a bone no check read (the line says why),
113
127
  and `basis.json` beside the proposal says what each bone rests on — check
package/src/build.ts CHANGED
@@ -70,7 +70,7 @@ import {
70
70
  splitSchedule,
71
71
  untestedIntervals,
72
72
  } from './autoreplay.ts';
73
- import { besideIdleLine, FOLD_LINE, type FoldsLine, LEVEL_LINE, type LevelLine, LIMB_LINE, type LimbLine, STRESS_LINE, buildGateLines, causeLines, IDLE_FPS, IDLE_MAX_PX, maxRgbDiff, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, JUDGEMENT_LINES, type JudgementLine, packedBuildArgs, packedBuildLabel, type PackMode, type PackShape, type PageEdges, readFrameSet, rigcFailed, rigcIdentity, rigcRanClause, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_DIR, SEAM_MEAN_BAR, seamPairText, seamRigPxText, SOURCE_LINE, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK, STILL_DIR } from './check.ts';
73
+ import { besideIdleLine, FOLD_LINE, type FoldsLine, LEVEL_LINE, type LevelLine, LIMB_LINE, type LimbLine, STRESS_LINE, buildGateLines, causeLines, IDLE_FPS, IDLE_MAX_PX, maxRgbDiff, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, GATE_PROFILE, JUDGEMENT_LINES, type JudgementLine, packedBuildArgs, packedBuildLabel, type PackMode, type PackShape, type PageEdges, readFrameSet, rigcFailed, rigcIdentity, rigcRanClause, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_DIR, SEAM_MEAN_BAR, seamPairText, seamRigPxText, SOURCE_LINE, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK, STILL_DIR } from './check.ts';
74
74
  import { type Cut, type Limb, loadConfig, loadConfigAndAnimations, loadEarlyConfig } from './config.ts';
75
75
  import { failLine, PartsError, type Problem, refuseIfAny } from './errors.ts';
76
76
  import type { MeshQualityReport, MotionSchedule } from 'rig-c/mesh';
@@ -365,7 +365,7 @@ function gateRun(label: string, rigc: RigcRunner, args: string[]): GateRun {
365
365
  * written anywhere, so packing them was work nobody read. No render is asked
366
366
  * of rigc by either build.
367
367
  */
368
- export const REFERENCE_BUILD_ARGS: readonly string[] = ['--profile', 'spine-html'];
368
+ export const REFERENCE_BUILD_ARGS: readonly string[] = ['--profile', GATE_PROFILE];
369
369
 
370
370
  /** The reference build's label, as the rig stage prints it. */
371
371
  export const REFERENCE_BUILD_LABEL = `build ${REFERENCE_BUILD_ARGS.join(' ')}`;
package/src/check.ts CHANGED
@@ -30,7 +30,7 @@
30
30
  *
31
31
  * | output | what |
32
32
  * | --- | --- |
33
- * | `build/` | `rigc build --profile spine-html --pack --page-edges free --pack-shape polygon` (or `pot`, `rect`, when the caller names them; {@link PAGE_EDGES}, {@link PACK_SHAPES}) — the packed atlas is the final artifact |
33
+ * | `build/` | `rigc build --profile rig-play --pack --page-edges free --pack-shape polygon` (or `pot`, `rect`, when the caller names them; {@link PAGE_EDGES}, {@link PACK_SHAPES}) — the packed atlas is the final artifact |
34
34
  * | `gate_spine-html.txt` | that build's gate lines, verbatim — the one gate: rigc's `build` runs it once over the compile and once over the packed pages on disk, and `spine-html` holds every rule `spine` measures (selftest `CH09`) |
35
35
  * | `idle_frames/` | `rigc render --animation idle --fps 12 --max 640 --geometry`: `frames.json` + `idle/f*.png` + `idle/geometry.json` (each mesh's skinned vertices, which `TEXTURE_STRETCH` reads, every attachment's posed triangles, which `TIP_OVER_ROOT` and both halves of `STILL_REGIONS_DARK` read since issues #118 and #123, and every bone's world transform, which the face half reads its head bone from) |
36
36
  * | `contact.png` | rigc's own contact sheet of that render, copied out |
@@ -304,9 +304,17 @@ export interface PackMode {
304
304
  /** Both defaults, {@link DEFAULT_PAGE_EDGES} and {@link DEFAULT_PACK_SHAPE}. */
305
305
  export const DEFAULT_PACK_MODE: PackMode = { pageEdges: DEFAULT_PAGE_EDGES, packShape: DEFAULT_PACK_SHAPE };
306
306
 
307
+ /**
308
+ * The profile every rigc build this package runs is gated under. rig-c 2.50.0
309
+ * renamed `spine-html` to `rig-play` and runs the same rules under it; the old
310
+ * name is accepted only until a later minor release and prints a deprecation
311
+ * line on stderr (selftest `CK20` holds every stage's build to the new name).
312
+ */
313
+ export const GATE_PROFILE = 'rig-play';
314
+
307
315
  /** The packed build's rigc arguments after `--out <dir>`, as every stage that packs passes them. */
308
316
  export function packedBuildArgs(mode: PackMode): string[] {
309
- return ['--profile', 'spine-html', '--pack', '--page-edges', mode.pageEdges, '--pack-shape', mode.packShape];
317
+ return ['--profile', GATE_PROFILE, '--pack', '--page-edges', mode.pageEdges, '--pack-shape', mode.packShape];
310
318
  }
311
319
 
312
320
  /** The packed build as a label: the rigc command line that was run, after `rigc`. */