rig-parts 1.2.0 → 1.3.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
@@ -306,8 +306,10 @@ These are limits of the approach, stated so nobody reads more into a green run:
306
306
  layer that is in front of another in one place and behind it in another is drawn
307
307
  right in the still (the painting's own pixels are projected onto it) and wrong once
308
308
  it moves.
309
- - **No expression or lip-sync.** The mouth is one layer; there is no mouth-shape set
310
- and no expression axis.
309
+ - **No expression or lip-sync, and no jiggle.** The mouth is one layer; there is no
310
+ mouth-shape set and no expression axis, and nothing here proposes, keys or judges a
311
+ soft region. An expression that moves the face's parts, and a jiggle, can be built on
312
+ the rig it writes ([below](#what-can-be-built-on-top)); lip-sync cannot.
311
313
  - **Occluded pixels are See-through's synthesis**, not the artist's. How much of a rig
312
314
  that is, is below.
313
315
  - **No success rate is claimed.** Ten characters have been measured stage by stage
@@ -331,6 +333,25 @@ These are limits of the approach, stated so nobody reads more into a green run:
331
333
  measures is a hanging strand on a headwear or earwear layer: each gets a pendulum
332
334
  chain, and a strand it cannot chain is named in a note rather than left stiff.
333
335
 
336
+ ### What can be built on top
337
+
338
+ This package makes no expression and no jiggle, but the rig it writes is the material for
339
+ both, and the config has a field for each. [docs/AUTHORING.md](docs/AUTHORING.md) has a recipe
340
+ for each — a config fragment, the `check` lines it passes with, and its limit:
341
+
342
+ - [**Expressions and other named animations**](docs/AUTHORING.md#recipe-expressions-and-other-named-animations):
343
+ the face's parts are bones; animations written over them in rig-c's motion-spec shape and
344
+ named by `motion.animations_from` are built and gated beside the idle on every build. One
345
+ attachment per slot, so a part moves, turns and scales but is never swapped for another
346
+ image; `check` measures the idle alone.
347
+ - [**A soft region driven by physics**](docs/AUTHORING.md#recipe-a-soft-region-driven-by-physics)
348
+ (a bust, the hips): a bone the config authors, a contour-mode region that hands it the patch,
349
+ and a `physics` constraint on it; `examples/sample` carries one. Under the sample's slow
350
+ 4 s idle the bust moves at most 0.066 rig px and the hips not at all: the spring answers
351
+ faster keys on the body. The sample's `hop`, a named animation keying only `hip`, moves the
352
+ bust up to 6.583 and the hips up to 4.432 rig px; the region's `band` caps how far a patch may
353
+ go before the texture-stretch ceiling, and `check` measures the idle alone.
354
+
334
355
  ### How much of a rig the model painted
335
356
 
336
357
  `parts.json` splits each part's opaque pixels (`opaque_px`) into `visible_px` — no
package/cli.ts CHANGED
@@ -326,14 +326,18 @@ usage:
326
326
  <out>/render/recomposite_rig.png and <out>/render/${ERROR_MAP_FILE} (the error
327
327
  map: uncovered error px red, covered error px blue, the rest the painting in
328
328
  light grey). Reads config.seethrough.head_box and
329
- .resolution and config.assemble.rig_scale, .plan, .extend_below_crop and
329
+ .resolution and config.assemble.rig_scale, .plan, .extend_below_crop,
330
330
  .patches — extra parts cut from the painting itself over a rig-pixel box
331
331
  (alpha "silhouette": the painting's figure inside it; "box": all of it),
332
332
  drawn "back", "front" or {"before": <plan part>}, recorded in parts.json
333
- as from "painting:<name>" and counted 100 % source — and no rig section:
333
+ as from "painting:<name>" and counted 100 % source — and .cuts — a plan
334
+ part's pixels inside a rig-pixel polygon taken into a new part, drawn the
335
+ same way, recorded with the plan part's from — and no rig section:
334
336
  bones, meshes, regions and motion need not exist yet, because propose
335
337
  drafts them from these parts (AUTHORING §4, the table).
336
- Prints one line per part, the seam override counts, the \`pixels:\` totals
338
+ Prints one line per part, one \`cut:\` line per part a cut split (its
339
+ opaque pixels = what it kept + what each cut took), the seam override
340
+ counts, the \`pixels:\` totals
337
341
  (opaque = visible + occluded; taken from the painting; visible but not
338
342
  projected), and \`recomposite vs source\` (mean |d| and % within 8 over the
339
343
  mean channel; error px: max channel > 40; uncovered: of those, where no
@@ -435,7 +439,9 @@ usage:
435
439
  shared, so a character whose idle keys it, or whose constraint names it,
436
440
  is refused. The plate is a region on bone "${PLATE}" under
437
441
  root, drawn first, its image at canvas (0, 0). One idle holds every
438
- character's tracks. Refused by name, every problem at once: an id
442
+ character's tracks; an animation beside a character's idle (its
443
+ config's motion.animations_from) rides along as "<id>:<name>",
444
+ prefixed as the idle is and scheduled by nothing. Refused by name, every problem at once: an id
439
445
  repeated, empty or holding ":" (or "/", "\\", a leading "."); a build
440
446
  missing a file or whose own check is not green; characters built at
441
447
  different rig_scale; a character whose parts' boxes, placed, leave the
package/docs/AUTHORING.md CHANGED
@@ -82,9 +82,11 @@ read by no CPU stage).
82
82
  | `assemble.plan` | assemble; also the list every `meshes`/`regions` key must come from | **proposed** by `assemble --propose-plan`. `[part name, "full" \| "head", See-through tag]`, in draw order back to front. Names are free; roles come from the tag |
83
83
  | `assemble.extend_below_crop` | assemble | **proposed** with the plan: `{part, run, tag}` — a head-run part that reaches the bottom of the head crop is continued from a full-run layer, whole connected components |
84
84
  | `assemble.patches` | assemble; `meshes`/`regions` keys may name them too | **authored**, optional: `[{name, box, alpha, draw}]` — an extra part cut from the **painting** itself, for figure no See-through layer holds (a hem both runs dropped). `box` is `[x0, y0, x1, y1]` in **rig** pixels, `x1`/`y1` exclusive — the space of `parts.json` and `recomposite_rig.png`, where the hole is found; `alpha` is `"silhouette"` (the painting's figure silhouette inside the box, the one `--seam silhouette` uses) or `"box"` (the whole box); `draw` is `"back"`, `"front"` or `{"before": "<plan part>"}`. A patch is always a region: its bone is `regions.<name>`, in the one place every region's bone lives, and a `meshes` entry for it is refused. See §5 for how to place one |
85
+ | `assemble.cuts` | assemble; `meshes`/`regions` keys may name the new parts too, and must (each is a part like any other) | **authored**, optional (issue #170): `[{from, into, polygon, draw, overlap}]` — a plan part's layer cut by a polygon the author draws: the pixels of plan part `from` whose **centre** lies inside `polygon` (even-odd; a centre exactly on an edge is outside) leave `from` and make the new part `into`, colour and alpha, so every pixel of the layer ends in exactly one of the two. `polygon` is `[[x, y], …]`, at least three points, non-negative integer **rig** pixels — the space of `parts.json` and of the patches' boxes. The file that shows the layer in those coordinates is `assemble`'s own `rig/parts/<from>.png`, placed at its `parts.json` `x`, `y` (the PNG's pixel `(i, j)` is rig pixel `(x + i, y + j)`), or `render/recomposite_rig.png`, the whole rig 1:1; the contact sheets (`sheet`) are scaled and carry no coordinates. `draw` is `"back"`, `"front"` or `{"before": "<plan part>"}`, as for a patch (to keep the setup pose, put it beside `from`: `{"before": "<from>"}`, or before the plan part after it). `from` must be a plan part (`CONFIG_NAME_RESOLVES`; a patch or another cut's piece is refused), `into` a new name (`CONFIG_PART_UNIQUE`), the polygon three or more integer points (`CONFIG_FIELD_TYPE`). The piece's `parts.json` record carries `from`'s `<run>:<tag>` and its own counts, each split from the layer's by where the pixel lies; it is a mesh or a region under its own name, and the coverage rule holds it like a plan part. What the config cannot know the stage refuses: a polygon point past the rig (`ASSEMBLE_CUT_INSIDE`), two cuts of one part sharing a pixel (`ASSEMBLE_CUT_OVERLAP`), a cut that takes none of the layer's opaque pixels or leaves `from` none (`ASSEMBLE_CUT_PIXELS`). `overlap` is required, a non-negative integer of rig pixels with no default (`CONFIG_FIELD_TYPE`): the **band** — the polygon's pixels within `overlap` px of its edge by Chebyshev distance (the polygon's mask less its erosion by a `2 * overlap + 1` square) — stays in `from` as well as going to the piece, so the piece's resampled edge has art under it; the counts stay a strict partition and the band is the base's extra copy, printed on the `cut:` line. At `0` the two abut and `check`'s seam bar judges the edge between them (`CHECK_SEAM_WITHIN_BAR`, §5). A band only covers the edge at rest: a piece that swings well clear of where it was painted shows the hole it leaves, and a thin piece's band shows a ghost of the piece — an authored fill for what lies under a piece is issue #193 |
85
86
  | `bones` | rig; `propose --from-config` | **proposed** by `propose`, then corrected. A single bone `{name, parent, at, tip?}` or a chain `{chain, parent, points, tip}` whose links are named `<chain>0 … <chain>n`. Parents come before children. In `rig.json` each link is **turned along its chain** — `rotation` is the direction from its origin to the next link's (the last link's to `tip`) and `length` that distance (issue #73), so a physics constraint added downstream finds a lever; under `--idle-keys ctl` the link's `<link>_ctl` carries the turn and the length and the link sits at local rotation 0 beneath it. A single bone is never turned; one whose parent is a link is turned back upright |
86
87
  | `meshes.<part>` | rig | **proposed**, then corrected. Exactly one of `grid` (the lattice: cell size, px — what `propose` writes), `contour` (the outline mode, authored; the next row and "A contour mesh" below) or `auto` (the automatic mode, authored; "An automatic mesh" below) — two, or none, is `CONFIG_MESH_MODE`. In every mode: `r` (added to every distance before weighting: `w = 1/(d + r)²`), `segments` (a chain name, a bone name with a `tip`, or `[bone, [x0,y0], [x1,y1]]`), and, **authored**, optional, `exponent` (issue #161): a number above 0 that replaces the 2 in that rule, `w = 1/(d + r)^exponent` — a larger one hands each vertex more fully to its nearest bone; absent is 2 and changes no byte, a value that is not a number above 0 is `CONFIG_FIELD_TYPE` at `meshes.<part>.exponent`, and a declared one is echoed as `exponent` in the mesh's `mesh_report.json` row; the exponent is the distance rule's, so declared beside `rule: "heat"` it is refused (`CONFIG_FIELD_TYPE` at `meshes.<part>.exponent`). The segment list is the one authored decision about a layer: which bones may pull it. The slot's bone is the first segment's. Optional `rule` (issue #161): `"distance"` — the rule above, and what an absent `rule` means (every byte is the same) — or `"heat"`, bone heat over the part's own silhouette, in every mode: the lattice's art (alpha above 8) with holes filled, or for `contour` and `auto` the silhouette their outline is traced from (that mode's art — alpha above 8 for `contour`, alpha 1 and above for `auto` — less any island a declared `stray` left out, holes filled, grown by its `margin`); each bound bone's sources are the silhouette pixels whose centre lies within 0.75 px of its segment (a pixel two bones reach goes to the nearer, a tie to the first listed), held at 1 for that bone and 0 for the others; the field is solved by row-major Gauss–Seidel over-relaxation to a sweep change under 1e-7 within 20000 sweeps; a vertex reads the nearest silhouette pixel and takes the same cap and floor (and `regions`) as the distance rule; `r` is not read. The mesh's `mesh_report.json` row gains `heat` (the constants, each bone's sources and sweeps, the residual). Any other value is `CONFIG_FIELD_TYPE`; a bound bone with no source pixel is `RIG_HEAT_BONE_SOURCE`, an island no bone has a source in `RIG_HEAT_ISLAND_SOURCE`, a solve past its sweep bound `RIG_HEAT_CONVERGED` — none is weighted 0 |
87
88
  | `meshes.<part>.contour` | rig | **authored**, optional (issue #84): `{tolerance, margin, spacing, budget?, stray?, regions?}`. `tolerance` — rig-c's Douglas–Peucker tolerance on the traced outline, px, 0 or more; `margin` — how far the silhouette grows before it is traced, px: 0, or 1 or more (a pixel joins when its centre lies within `margin` of an art pixel's centre; a value above 0 and below 1 adds nothing and is refused, `CONFIG_FIELD_TYPE`); `spacing` — the background interior spacing, px, above 0; `budget` — the most vertices the mesh may have (refused above it, nothing thinned); `stray` — the largest island, in art pixels, that may be left out of the mesh (absent: none is); `regions` — local deformation regions, each `{name, shape, bone, spacing, band}` with `shape` `"circle"` (`cx, cy, r`) or `"polygon"` (`points`, 3 or more): `bone` is the region's control bone (any bone `bones` declares), `spacing` the interior spacing inside the region and its band, `band` the width the bone's weight falls across. Every length and position is **rig px**, like every other in the config; the part image is the rig less `(x − 4, y − 4)` (its box less the pad), a translation, so a length is the same number in both. A region's `cx`, `cy`, `r`, `band` and `points` are multiples of 1/256 px (`CONFIG_FIELD_TYPE` otherwise); region names are unique per mesh (`CONFIG_REGION_NAME_UNIQUE`). The alpha threshold is not a field: art is alpha above 8, the lattice's and `check`'s |
89
+ | `meshes.<part>.ribs` | rig | **authored**, optional (issue #188), in the `contour` and `auto` modes: `{<chain>: {stations}, …}`, each key a chain this mesh's `segments` name. Each link of the chain gets a row of vertices across the part at its origin and at `stations` points evenly between its origin and its end (`stations`, a whole number, 0 or more, required; a chain of L links carries L × (stations + 1) ribs) — so a strand can fold where its chain bends; the rule is "Ribs along a chain" under "A contour mesh". Absent places none and changes no byte. Refused: a lattice mesh with ribs (`CONFIG_RIBS_MODE` — its vertices are its grid's corners), a key that is not one of the mesh's chains (`CONFIG_NAME_RESOLVES`), an object naming no chain or a `stations` that is not a whole number from 0 (`CONFIG_FIELD_TYPE`); at the rig stage `CONTOUR_RIB_ART`, `CONTOUR_RIB_LINK`, `CONTOUR_RIB_CROSSING`, `CONTOUR_RIB_EDGE` (the contour refusals). The mesh's `mesh_report.json` row gains `ribs` — the declaration, the ribs placed and the vertices they hold — and its contour report (the source's, in `auto`) lists each rib's vertices |
88
90
  | `regions.<part>` | rig | **proposed**: the bone a rigid part rides. Every plan part is exactly one of a mesh or a region (`CONFIG_PART_ATTACHED`) |
89
91
  | `motion.duration` | rig; check (the loop is measured at this time) | proposed as 4 s; a whole number of 1/12 s ticks, because `check` renders at 12 fps |
90
92
  | `motion.tracks` | rig | **proposed**, then tuned. Single `{bone, prop, amp, period, phase, base?}` or chain `{chain, amps, period, phase, lag}` — one amplitude per link, link `i` at phase `phase + lag·i`. Every `period` must divide `duration` (`CONFIG_PERIOD_DIVIDES_DURATION`). A bone property is keyed by one track (`CONFIG_BONE_PROPERTY_KEYED_ONCE`): a chain keys `rotate` on every link, the blink's `eyes` group `scaley` and its `brows` group `translatey` on every member, so a single track on any of those is a second track on it — rigc would refuse it one stage later, `animation "idle" has two tracks on eye.scaley`. A chain link is turned along its chain (the `bones` row), so a `translatex`/`translatey` key on a bone whose parent is a turned link, or a `scale`/`shear` key on a turned link, would move or stretch along the link and not along the picture's axis: the rig stage refuses it (`RIG_KEY_FRAME_UNTURNED`); `rotate` is keyed on any bone |
@@ -364,6 +366,42 @@ contour refusal in a run is listed together; nothing falls back to the lattice:
364
366
  | `CONTOUR_PART_HAS_ART` | no pixel above alpha 8 | re-run assemble |
365
367
  | `CONTOUR_PARAMETER` | a parameter `src/contour.ts` cannot run at: a margin above 0 and below 1 px (it would add no pixel), a spacing whose keep radius (half of it) snaps to 0, a polygon whose band folds it, a part over 32768 px | the named field |
366
368
  | `RIG_CONTOUR_REGIONS_OVERLAP` | a vertex two regions both reach (`g > 0`); the detail names the vertex and both regions | move the regions apart or narrow a band |
369
+ | `CONTOUR_RIB_ART` | a rib's point (a link origin or a station) is not on the part's art (its pixel at or below the threshold) or not inside the outline, or a point between its ends falls within 1/256 px of the outline — a rib that leaves the art | move the chain onto the part, or leave the chain out of `ribs` |
370
+ | `CONTOUR_RIB_LINK` | a chain link shorter than the outline's `tolerance` (or of no length), or a joint where the chain turns straight back on itself; the detail names the chain, the link and the tolerance field | fewer, longer links, or a lower `tolerance` |
371
+ | `CONTOUR_RIB_CROSSING` | two ribs cross, touch or share an end (both named, with their ends) | fewer `stations`, or a chain that bends less sharply inside a wide part |
372
+ | `CONTOUR_RIB_EDGE` | a rib's consecutive vertices could not be joined by an edge (no edge across them could be flipped) — a guard: the rule keeps every other point off a rib | report it |
373
+
374
+ **Ribs along a chain** (`meshes.<part>.ribs`, issue #188). A long thin part that hangs from a
375
+ chain — a tie, a strand of hair, a scarf end — bends at the chain's joints, and a mesh with no
376
+ row of vertices across the strand there cannot fold at the joint: the bend is taken up by
377
+ whatever triangles happen to span it. For each chain `ribs` names, `src/contour.ts`
378
+ (`placeRibs`) places, in part-image px:
379
+
380
+ - **where**: on each link k (from `points[k]` to `points[k + 1]`, the last to the tip) a rib at
381
+ its origin and at `points[k] + m/(stations + 1) · (end − origin)`, m = 1..`stations`; the tip
382
+ carries none;
383
+ - **across**: along n = (−d_y, d_x)/|d|, d the link's direction — at a joint between two links
384
+ the sum of their unit directions, the bend's bisector;
385
+ - **clipped to the art**: the point must be on the part's art (its pixel above the mode's
386
+ threshold) and inside the outline; from it the line runs both ways to the first point where it
387
+ meets the outline the mode traced (art, holes filled, grown by `margin`, simplified at
388
+ `tolerance`), and those two points, on the 1/256 px grid, are the rib's ends. Each end joins
389
+ the hull on the edge it lands on (or is that edge's vertex), moved off the edge by at most
390
+ 1/512 px; coverage, overshoot and self-intersection then read the outline with them;
391
+ - **between**: the point itself (so the joint is a vertex), and each half from an end to the
392
+ point cut into ⌈half / spacing⌉ equal parts at the mode's background `spacing`
393
+ (`auto.source.spacing` in the automatic mode);
394
+ - **kept a row**: no other interior point is kept within half a spacing of a rib, every
395
+ consecutive pair of a rib is made an edge of the triangulation (edges across it are flipped
396
+ until it is one) and locked, as an outline edge is — so `nonDelaunayEdges` does not count it.
397
+
398
+ Nothing reads a weight: every vertex is weighted as any other at its point. The rib on a strand
399
+ painted inside a wider layer runs across the whole opaque run there, sleeve and body included —
400
+ cutting the strand out is #170's. In the automatic mode every rib vertex is added to
401
+ `protect.vertices` (after the author's, as written) and each rib is sent to rig-c's
402
+ `reduceMesh` as a named line (`lines`, rig-c 2.33.0; `maxDeviation` 0), so the reduction keeps
403
+ every rib vertex and its post-pass flips no rib edge; the result's measurement carries one
404
+ `MQ_LINE_DEVIATION` row per rib.
367
405
 
368
406
  **The window between coverage and overshoot**, measured in #106 (`tools/contour_survey.ts`, five
369
407
  generated shapes, margins 0, 1, 1.5, 2, 2.5, 3): the smallest margin that covered every art
@@ -844,6 +882,123 @@ boundary stays held, and an edge from the region to a vertex further out than it
844
882
  that crosses the region with no point of it inside to split at — is still stopped by name
845
883
  (`AUTO_MESH_ACCEPTED`, the detail naming P16).
846
884
 
885
+ ### Recipe: expressions and other named animations
886
+
887
+ The package proposes no expression and keys nothing but the idle; what it gives is a rig whose
888
+ face is bones (`eye_r`, `eye_l`, `brow_r`, `brow_l`, `mouth` on `sample`) and one door for
889
+ animations you write over them, `motion.animations_from` (the row above, issue #183). Write a
890
+ file in rig-c's motion-spec shape beside the config and name it:
891
+
892
+ ```jsonc
893
+ // config.json
894
+ "motion": { "duration": 4.0, "tracks": [ … ], "blink": { … },
895
+ "animations_from": "expressions.json" }
896
+
897
+ // expressions.json — the bones the config declares, keyed as rig-c keys them
898
+ { "spec": "rigc-motion/1",
899
+ "animations": {
900
+ "surprised": { "duration": 0.5, "tracks": [
901
+ { "bone": "brow_r", "property": "translatey", "keys": [ { "t": 0, "v": [0] }, { "t": 0.25, "v": [2] }, { "t": 0.5, "v": [0] } ] },
902
+ { "bone": "brow_l", "property": "translatey", "keys": [ { "t": 0, "v": [0] }, { "t": 0.25, "v": [2] }, { "t": 0.5, "v": [0] } ] },
903
+ { "bone": "mouth", "property": "scaley", "keys": [ { "t": 0, "v": [1] }, { "t": 0.25, "v": [1.4] }, { "t": 0.5, "v": [1] } ] } ] } } }
904
+ ```
905
+
906
+ `build` writes `surprised` into `rig/motion.json` after `idle`, gates it with the rig, and
907
+ `check` names it on a line of its own (`examples/sample` carries one, `animations.json` with `hop`, the
908
+ recipe below). With exactly this file beside `examples/sample`'s config in its place,
909
+ `build` printed (this tree, rig-c as `package.json` pins it):
910
+
911
+ ```
912
+ [rig] animations beside the idle, from …/expressions.json as written: surprised
913
+ [check] beside the idle: 1 animation(s), surprised — built and gated by rigc with the rig, not measured: every bar here reads the idle
914
+ [check] check: PASS; 9 of 9 bar(s) measured, 0 skipped
915
+ ```
916
+
917
+ A bone the rig lacks is rigc's refusal at the rig stage, in rigc's words (`RIG_RIGC_GREEN`); a
918
+ fault in the file itself is the loader's (the row above names each one).
919
+
920
+ **Limits.** Every slot holds one attachment, the part's own image, so an expression moves,
921
+ turns and scales the face's parts and never swaps one for another: there is no second mouth
922
+ shape to key, and no lip-sync. `check` measures the idle alone: an animation beside it has
923
+ passed rigc's gate and nothing else — no seam, stretch or hole bar has read it.
924
+
925
+ ### Recipe: a soft region driven by physics
926
+
927
+ A jiggle (a bust, the hips) is a patch of one part that lags behind the body. The package
928
+ proposes none and judges none; what it gives is the three pieces to build one, all in the
929
+ config. `examples/sample` carries a worked one (issue #183, PR #187), and its config is the
930
+ fragment below, cut to the bust:
931
+
932
+ ```jsonc
933
+ "bones": [ …,
934
+ { "name": "chest_jiggle", "parent": "chest", "at": [419, 320] }, … ],
935
+ "meshes": { "topwear": {
936
+ "contour": { "tolerance": 1, "margin": 1, "spacing": 18, "stray": 4,
937
+ "regions": [ { "name": "bust", "shape": "circle", "cx": 419, "cy": 320, "r": 28,
938
+ "bone": "chest_jiggle", "spacing": 6, "band": 16 } ] },
939
+ "r": 8, "segments": [ ["chest", [419, 223], [419, 437]], ["hip", [419, 437], [419, 557]] ] } },
940
+ "constraints": [
941
+ { "type": "physics", "name": "bust_phys", "bone": "chest_jiggle",
942
+ "x": 0.5, "y": 1, "inertia": 0.25, "strength": 120, "damping": 0.92, "mass": 1, "mix": 1 } ]
943
+ ```
944
+
945
+ - **A bone the config authors**, under the bone the patch rides on. `propose` writes no such
946
+ bone and no constraint; it is yours, and nothing keys it — the constraint moves it.
947
+ - **A region in a `contour` (or `auto`) mesh** that hands the patch to that bone: weight 1
948
+ inside the shape, falling to 0 across `band` (*A contour mesh*, above). A `grid` mesh carries
949
+ no region, so the part moves from the lattice to the outline mode first; `sample` traced
950
+ `topwear` at its former grid spacing (18) and `bottomwear` at 28. Place the shape on the
951
+ patch alone: on `sample` one circle at the hip centre reached the clasped hands, painted
952
+ inside `bottomwear`, so the hips are two side circles (`hip_r`, `hip_l`) on one bone.
953
+ - **A `physics` constraint** on that bone in `constraints`, in rig-c's shape; every field but
954
+ the bone is rigc's to accept.
955
+
956
+ `build` on `sample` as tracked (PR #187; the values above are the ones tuned for its `hop`, issue #183):
957
+
958
+ ```
959
+ check: PASS; 9 of 9 bar(s) measured, 0 skipped
960
+ loop physics: 2 physics constraint(s) left out of the loop's frames, reported, not judged — "bust_phys" on bone "chest_jiggle" (rig.json), "hip_phys" on bone "hip_jiggle" (rig.json)
961
+ loop: idle 49 frame(s) at 12 fps, f0000 vs f0048 (t = 4s): max |d| 0 (0 required)
962
+ ```
963
+
964
+ The `loop physics:` line is the loop bar saying what it did not read (§7, *loop*): rig-c's
965
+ render resets physics at frame 0 and steps it, so the idle's last frame differs from its first
966
+ by whatever the spring is still doing; the bar is read off the same rig with every physics
967
+ constraint left out, and the constraints left out are listed, reported and not judged
968
+ (`check.json`'s `loop_physics`, PR #184). It is not a measure of the jiggle.
969
+
970
+ **Limits.** The idle drives the spring, and a slow idle barely moves it. Measured on `sample`
971
+ (PR #187, rig-c `render --geometry`, the physics build against the same build with
972
+ `constraints` removed): under its 4 s breath (`chest` `translatey` amplitude 1.3 px), the bust
973
+ moves at most 0.155 rig px (idle frame 3, t 0.25 s); the hips move 0.000 at all 49 frames,
974
+ because the idle keys `chest` and never `hip`, so a child of `hip` receives no motion. A spring
975
+ of strength 100 trails a parent moving at about 2 px/s by the order of speed over its angular
976
+ frequency, about 0.2 px (PR #187, from the definition): no setting makes a 4 s breath read as a
977
+ jiggle (with the values tracked now, `bust_phys` inertia 0.25, strength 120, damping 0.92, the
978
+ idle moves the bust at most 0.066 rig px, idle frame 2). Faster keys on the parent bone — a hop,
979
+ a turn, in a named animation (the recipe above) or a consumer's own — are what the spring
980
+ answers. `sample`'s `animations.json` holds one, `hop` (1 s: `hip` `translate` y down 5, up 24,
981
+ landing at -6 at 0.45 s, settling at 0.55 s; nothing keys the jiggle bones): rendered at 30 fps
982
+ against the same build without its constraints, the bust moves at most 6.583 rig px and the hips
983
+ (`hip_phys` inertia 0.2, strength 160, damping 0.88, mass 1.5) at most 4.432 (frame 13, the fall
984
+ into the landing), and both overshoot after it, 3.38 and 1.38 rig px (frames 21 and 22;
985
+ `docs/evidence/sample-hop.png`). No bar here judges the jiggle, and two limits follow from that:
986
+
987
+ - **The region's `band` caps the displacement.** The patch moves as one piece and the band is
988
+ where the mesh stretches to follow it, so physics values that carry the patch further than
989
+ its band absorbs squeeze or stretch the band's triangles past the texture-stretch ceiling.
990
+ `check` reads the idle alone and does not see it; check's own reading applied to `hop`'s
991
+ geometry with PR #187's values (inertia 0.5, strength 100, damping 0.85, mass 1, a bone
992
+ displacement of 9.85 rig px against bands of 16 and 12) gives
993
+ `CHECK_TEXTURE_STRETCH: mesh "bottomwear" triangle 13 (vertices 5 183 6), edge 6-5, idle frame 13 — the edge is 0.233 times its rest length (max(ratio, 1/ratio) 4.29); <= 1.926544 is required`
994
+ and `topwear` 2.597; with the values tracked now it reads 1.697 (`topwear`) and 1.566
995
+ (`bottomwear`). Tune the constraint against the animation that drives it, not the idle.
996
+ - **An added animation can move the idle's figures.** The render's viewport is fitted over
997
+ every animation, and the idle frames share it, so an animation that travels further than the
998
+ idle redraws the idle at another size: adding `hop` to `sample` narrowed the idle render from
999
+ 201 to 198 px wide and moved `check.json`'s `BREATH_VISIBLE` `torso_heat_mean` from 7.8 to
1000
+ 7.705, every status unchanged, although `check` reads the idle alone.
1001
+
847
1002
  ## 4. The command order
848
1003
 
849
1004
  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.
@@ -978,7 +1133,7 @@ proposal in as the steps above say (`RL01`); every step must exit 0.
978
1133
  | --- | --- | --- | --- |
979
1134
  | `layers` | the table, then the `WARN` lines | every tag the plan will need has opaque pixels; one `face` in the head run; `0 WARN line(s)` | a run whose layer PNG is not its box's size (`LAYERS_PNG_MATCHES_BBOX`); a `WARN PLAN_LAYER_…` line (§6, *Plausibility*) |
980
1135
  | `sheet` of both runs | the tile list (and the sheet, if you can see) | eyes, irises, lashes and brows as left/right pairs in the head run | a head box that cut off an ornament: move `head_box`, re-run the head crop |
981
- | `assemble` | one line per part, the `pixels:` totals (opaque = visible + occluded; taken; visible but not projected), then `recomposite vs source: mean \|d\|, within 8, error px > 40, uncovered error px`, then `uncovered holes (8-connected): N` and the largest five as `uncovered hole K: <px> px at x,y wxh (between "<part>" <px> px, …)`; and look at `render/recomposite_error_rig.png`. Between the part lines and the totals, one `fringe pushed back: "<part>" <px> px, where the painting shows "<holder>" <px> px, …` line per part whose fringe was cleared (below) | on the examples: `sample` 0.83 / 98.0 % / 4,359 / 1,185, 259 holes, the largest 94 px; `demo` 2.34 / 95.8 % / 10,340 / 1,564, 453 holes, the largest 70 px (default rule) — many slivers along part edges, no hole a region could hide | one large hole: part of the figure is in no layer — a plan entry is missing, hair left the head crop sideways (the demo's `hair_back` is taken from the full run for that reason), or See-through split one garment into two and left the space between them in neither (a skirt as two trouser legs); the `between` parts say where. When neither run holds it at all, an `assemble.patches` entry cuts it from the painting (below) |
1136
+ | `assemble` | one line per part, the `pixels:` totals (opaque = visible + occluded; taken; visible but not projected), then `recomposite vs source: mean \|d\|, within 8, error px > 40, uncovered error px`, then `uncovered holes (8-connected): N` and the largest five as `uncovered hole K: <px> px at x,y wxh (between "<part>" <px> px, …)`; and look at `render/recomposite_error_rig.png`. Between the part lines and the totals, one `cut: "<part>" opaque <n> = "<part>" <kept> + "<piece>" <taken> …` line per part an `assemble.cuts` entry cut (below), then one `fringe pushed back: "<part>" <px> px, where the painting shows "<holder>" <px> px, …` line per part whose fringe was cleared (below) | on the examples: `sample` 0.83 / 98.0 % / 4,359 / 1,185, 259 holes, the largest 94 px; `demo` 2.34 / 95.8 % / 10,340 / 1,564, 453 holes, the largest 70 px (default rule) — many slivers along part edges, no hole a region could hide | one large hole: part of the figure is in no layer — a plan entry is missing, hair left the head crop sideways (the demo's `hair_back` is taken from the full run for that reason), or See-through split one garment into two and left the space between them in neither (a skirt as two trouser legs); the `between` parts say where. When neither run holds it at all, an `assemble.patches` entry cuts it from the painting (below) |
982
1137
  | `propose` | `note:` lines, `LINT` lines, the `coverage` lines, `landmarks.png`, `basis.json` | no LINT line: every chain link lies on its mesh's art, and the hip is below the chest and the figure's top quarter; the `coverage:` summary says how many bones that verdict covers — a `not checked` bone was read by no check (its line says why), and a `ratio` or `fallback` bone in `basis.json` is a guess | a link off the art (a bone on the background) — move it onto the layer; `LINT hip at [x, y] is not below chest at [x, y]: …` or `LINT hip at [x, y] is above 0.25 of the figure height (figure y T..B, so hip y must be at least L): a hip at the shoulders` — move `hip` down to the waist (and `chest` between it and the neck); a `hanging strand … -- no chain proposed` note — that strand hangs stiff until you add a chain down the x and rows it names (§3); a `no blink: …` or `blink without brows: …` note — the idle will not blink (or its brows will not drop), because no part came from an `eyewhite` (or `eyebrow`) layer; the note names the tags looked for; a `no eyewhite part for eye_<s> …` note — the iris and lash parts it names ride `head` and do not blink, because no `eyewhite-<s>` part made that side's eye bone: take the eyewhite from the other run, or keep them on `head`; a `no face part: face box derived from …` note (always the first) — `head`, `neck` and every face-height scale come from a box guessed off the hair and neck (§3): check `head` and `neck` on `landmarks_head.png` and move them; when it adds `the blink shuts the eyes over no face part`, read `BLINK_NO_HOLE` after `check` — an eye with no face under it can open a hole (the `sample` with only its face removed: 587 px). Under `--keypoints`: the `lint rule set:` line first (joints, or screen and why), then the joint notes — `missing: the rule "…" placed bone X instead` is a bone still guessed; `not used — …` names why an arm's joints did not place its sleeve; `LINT chest at … is not between the neck joint …`, `LINT hip at … is not past chest …`, `LINT hip at … is nearer the neck joint …` and `LINT <chain><i> at … does not advance from <s>_shoulder toward <s>_wrist` are the joint-frame lines: move that bone between, past or along the joints they name |
983
1138
  | `compare` | one line per pair (origin, parent, tip, length, direction — or SKIP and why), then the per-row summary, the unmapped bones of each side, `required:`, each side's roles and the `controls:` line | proposal against the config you corrected: the rows you changed and no others. Config against the `rig.json` `build` writes for it: measured on both examples, every origin within 6.19e-7 px (the rig's offsets are written to 6 places) and every chain link's tip and length within 4.99e-4 px (`length` is written to 3), printed to three places as `0.000` (a signed figure as `+0.000` or `-0.000`); `parent … is the right's ancestor at depth 2, <bone>_ctl between` for every keyed mesh bone, no `DIFFERENT` and no `NOT MAPPED` | `parent DIFFERENT: left a, right b` — a bone hangs from another parent than the other side's; `frames not related` — a rig with no stage (`skeleton.width`/`height`), two configs at different `rig_scale`: the distance rows SKIP, so declare the frame in a `--map` file if the author knows it; `FAIL STRUCTURE_REQUIRED_PRESENT` — a bone the map requires is missing through its pairs |
984
1139
  | `rig` (inside `build`) | one line per mesh: vertices, triangles, bones, influences, `cover`; the `bones` line; the `idle keys` line; then rigc's gate lines (with A15's declared SKIP under `--idle-keys direct`); in `rig.json`, each chain link's (or its `_ctl`'s) `length` and `rotation` | `cover 1.00000` on every mesh, both gate summaries `0 failed` (the compile and the packed pages); every link's `length` the distance to the next link and `rotation` its direction (Spine degrees, counter-clockwise, y up, local to the parent), every offset under it — a child bone's `x, y`, a weight's bind `x, y`, a region's `x, y` — in that turned frame, and a region on a link carrying `rotation` that turns it back upright. Nothing moved: `flattenRig` (`src/rig.ts`) turns every offset back and gives the unturned numbers | `RIG_LATTICE_ONE_LOOP`: change that mesh's `grid` |
@@ -1179,9 +1334,31 @@ leave a resampled edge between them in the render. The patch is config, so every
1179
1334
  and read `uncovered error px` again: a patch covering `n` of the uncovered error
1180
1335
  pixels lowers it by exactly `n` (the selftest's `AS17` plants that and counts it).
1181
1336
 
1337
+ **Cutting a piece out of a layer.** When one See-through layer holds two things that
1338
+ must move apart — a scarf end painted into the sweater's `topwear`, both arms in one
1339
+ `handwear` — add an `assemble.cuts` entry (§3): open `rig/parts/<part>.png` from an
1340
+ earlier `assemble`, add its `parts.json` `x`, `y` to a pixel's column and row to get
1341
+ rig pixels, and draw the polygon round the piece. The stage prints, between the part
1342
+ lines and the `fringe pushed back` lines, one line per cut part in the `pixels:`
1343
+ line's shape, counted when the cut is made (before the fringe push-back and the seam
1344
+ pass, which the records are counted after):
1345
+
1346
+ cut: "topwear" opaque 134452 = "topwear" 118973 + "scarf" 15479; band 593 px held by both
1347
+
1348
+ Give the piece a `meshes` entry over the bones that should carry it, and take those
1349
+ bones out of the part it left: on the public `scarf`, the scarf end `robe_r` runs down,
1350
+ cut out of `topwear` into its own mesh on `robe_r` (and `robe_r` dropped from `topwear`'s
1351
+ segments) leaves every `topwear` vertex still at wind 6 where the uncut mesh moved
1352
+ one by 101.5 px [observed, `docs/evidence/scarf-cut.png`]. Where the cut runs through
1353
+ opaque art, `overlap` decides the seam: at 0 the pieces abut, each edge is resampled
1354
+ against transparency, and on that cut the seam bar read 170 px over 40 against 50
1355
+ allowed (`CHECK_SEAM_WITHIN_BAR`); at 1 it read 8, at 2 it read 3, at 4 it read 2
1356
+ (the uncut build reads 2) [observed].
1357
+
1182
1358
  `parts.json` holds one record per part, in draw order: the plan's, with each patch
1183
- where its `draw` puts it (`"back"` patches first in their own order, each
1184
- `{"before": X}` patch immediately behind `X`, `"front"` patches last). Its counts:
1359
+ and each cut's piece where its `draw` puts it (`"back"` patches first in their own
1360
+ order, then `"back"` pieces; each `{"before": X}` patch, then each such piece,
1361
+ immediately behind `X`; `"front"` patches last, then `"front"` pieces). Its counts:
1185
1362
 
1186
1363
  | field | counts |
1187
1364
  | --- | --- |
@@ -1197,7 +1374,8 @@ where its `draw` puts it (`"back"` patches first in their own order, each
1197
1374
 
1198
1375
  A part's `from` is `<full|head>:<tag>` — the See-through run and tag, which every
1199
1376
  role (`propose`) and every judgement-line region (`check`) is read from — or, for a
1200
- patch, `painting:<its own name>`. **A patch is 100 % source**: every pixel is the
1377
+ patch, `painting:<its own name>`; a cut's piece carries the `<run>:<tag>` of the part
1378
+ it was cut from, so two records may share one. **A patch is 100 % source**: every pixel is the
1201
1379
  painting's, so it is all visible and all taken — `visible_px`, `projected_core_px`
1202
1380
  and `source_px_taken` equal `opaque_px`, and `occluded_px`,
1203
1381
  `visible_not_projected_px`, `refused_drift_px` and `merged_px` are 0. The reader
@@ -1316,10 +1494,11 @@ See-through with another seed.
1316
1494
  | `CONFIG_FILE_PRESENT`, `CONFIG_IS_JSON` | no such file, or not JSON | `--config` |
1317
1495
  | `CONFIG_KEY_KNOWN`, `CONFIG_KEY_RETIRED`, `CONFIG_FIELD_PRESENT`, `CONFIG_FIELD_TYPE` | an unknown key (a retired one says what replaces it), a missing one, a wrong type; a project's own record under a plain key, or an annotation (`note`, `<name>_note`) holding something other than a string | the named field; a record goes under a key beginning `x-` (any JSON, read by nothing), a remark under `note` or `<name>_note` (a string) — §3 |
1318
1496
  | `CONFIG_HEAD_BOX_SQUARE` | `seethrough.head_box` is not a non-empty square | `head_box` — take `propose --head-box`'s |
1319
- | `CONFIG_TAG_KNOWN`, `CONFIG_PART_NAME`, `CONFIG_PART_UNIQUE` | a plan entry names no v3 tag, a part name that is not a file name, or a part or layer twice — a patch named like a plan part or another patch included | `assemble.plan`, `assemble.patches` |
1320
- | `CONFIG_NAME_RESOLVES` | a parent, segment, region, track, blink member, blink still part or bone, extend part or patch `draw.before` names nothing declared above it; or a constraint's `bones`, `target`, `source` or `bone` names a bone `bones` does not declare (the detail names the constraint and lists the bones that exist) | the named reference, or the declaration it needs |
1497
+ | `CONFIG_TAG_KNOWN`, `CONFIG_PART_NAME`, `CONFIG_PART_UNIQUE` | a plan entry names no v3 tag, a part name that is not a file name, or a part or layer twice — a patch named like a plan part or another patch, and a cut's `into` named like a plan part, a patch or another cut's, included | `assemble.plan`, `assemble.patches`, `assemble.cuts` |
1498
+ | `CONFIG_NAME_RESOLVES` | a parent, segment, region, track, blink member, blink still part or bone, extend part, patch or cut `draw.before`, or cut `from` names nothing declared above it (a cut's `from` must be a plan part); or a constraint's `bones`, `target`, `source` or `bone` names a bone `bones` does not declare (the detail names the constraint and lists the bones that exist) | the named reference, or the declaration it needs |
1321
1499
  | `CONFIG_BONE_UNIQUE` | a bone or chain declared twice, or `root` declared | `bones` |
1322
- | `CONFIG_PART_ATTACHED` | a plan part with neither or both of a mesh and a region; a patch with a mesh, or with no region | `meshes` / `regions` |
1500
+ | `CONFIG_PART_ATTACHED` | a plan part or a cut's piece with neither or both of a mesh and a region; a patch with a mesh, or with no region | `meshes` / `regions` |
1501
+ | `CONFIG_RIBS_MODE` | `meshes.<part>.ribs` on a lattice (`grid`) mesh: its vertices are its grid's corners, which no row can move (issue #188) | put the part in `contour` or `auto`, or remove `ribs` |
1323
1502
  | `CONFIG_PATCH_BOX` | an `assemble.patches` box with `x1 <= x0` or `y1 <= y0` | that patch's `box` (`x1`, `y1` are exclusive) |
1324
1503
  | `CONFIG_AMPS_MATCH_CHAIN` | a chain track's `amps` is not one per link | that track's `amps` |
1325
1504
  | `CONFIG_PERIOD_DIVIDES_DURATION` | a period that is not a whole fraction of the idle — the loop could not close | that track's `period`, or `motion.duration` |
@@ -1363,7 +1542,10 @@ See-through with another seed.
1363
1542
  | `ASSEMBLE_PART_OPAQUE` | a part ended with no opaque pixel | drop the entry, or take the tag from the other run |
1364
1543
  | `ASSEMBLE_PATCH_BOX_INSIDE` | a patch's box reaches past the rig (the painting times `rig_scale`; the detail gives the size) | that patch's `box` — it is in rig pixels, not painting pixels |
1365
1544
  | `ASSEMBLE_PATCH_OPAQUE` | a patch takes no pixel: a `"silhouette"` patch whose box holds none of the figure | move the box onto the figure, or `"alpha": "box"` |
1366
- | `ASSEMBLE_COUNTS_ADD_UP` | a part's visible and occluded counts do not add up to its opaque pixels, or a projected pixel is not visible — an assembler bug, not an input problem | report it with the part named; nothing was written |
1545
+ | `ASSEMBLE_CUT_INSIDE` | a cut's polygon has a point past the rig (the detail gives the points and the size) | that cut's `polygon` — it is in rig pixels, not painting pixels |
1546
+ | `ASSEMBLE_CUT_OVERLAP` | two cuts of one part share a rig pixel (the detail gives the count and the first, `x,y`) | one of the two polygons: a pixel goes to one part |
1547
+ | `ASSEMBLE_CUT_PIXELS` | a cut takes none of its part's opaque pixels (alpha above 8), or a part's cuts leave it none | draw the polygon over the art; to move the whole layer, rename the plan part instead |
1548
+ | `ASSEMBLE_COUNTS_ADD_UP` | a part's visible and occluded counts do not add up to its opaque pixels, or a projected pixel is not visible, or a cut part's pieces do not add up to it — an assembler bug, not an input problem | report it with the part named; nothing was written |
1367
1549
 
1368
1550
  ### propose
1369
1551
 
@@ -1842,6 +2024,13 @@ What is composed:
1842
2024
  duration. Constraints are carried in character order with their
1843
2025
  `invariants.detached`; `invariants.idleDrivesMeshes` once, when any character
1844
2026
  declares it.
2027
+ - **Every animation beside a character's idle** — what `motion.animations_from`
2028
+ wrote into its `motion.json` (§3) — rides along as `<id>:<name>` after the idle,
2029
+ its tracks' bones, groups and easings prefixed as the idle's are and every other
2030
+ value as written; the scene schedules none of them, they are the consumer's to
2031
+ play. Each is read with the idle's own key lists, so a key compose does not know
2032
+ how to prefix is `SCENE_BUILD_FIELD_KNOWN`, as it is on the idle, and a track of
2033
+ one that keys `root` is `SCENE_ROOT_SHARED` (issue #183).
1845
2034
  - **Gated as `rig` gates its own:** `rigc build --profile spine-html --pack`, the
1846
2035
  compile and the packed pages on disk, in a scratch directory; `--out` receives
1847
2036
  `rig/` (`rig.json`, `motion.json`, `images/`) and `scene.json` only when it is
package/docs/STABILITY.md CHANGED
@@ -105,13 +105,16 @@ part name, `[i]` an entry of a list):
105
105
  - **generation.sampler** — required: `steps`, `cfg`, `sampler`, `scheduler`
106
106
  - **generation.control** — required: `skeleton`, `strength`, `end_percent`; optional: `model`
107
107
  - **seethrough** — required: `resolution`, `steps`, `seed`, `offload`; optional: `head_box`
108
- - **assemble** — required: `rig_scale`, `plan` (from plain `assemble` on); optional: `extend_below_crop`, `patches`
108
+ - **assemble** — required: `rig_scale`, `plan` (from plain `assemble` on); optional: `extend_below_crop`, `patches`, `cuts`
109
109
  - **assemble.extend_below_crop[i]** — required: `part`, `run`, `tag`
110
110
  - **assemble.patches[i]** — required: `name`, `box`, `alpha`, `draw`
111
111
  - **assemble.patches[i].draw**, when an object — required: `before`
112
+ - **assemble.cuts[i]** — required: `from`, `into`, `polygon`, `draw`, `overlap`
113
+ - **assemble.cuts[i].draw**, when an object — required: `before`
112
114
  - **bones[i]**, a single bone — required: `name`, `parent`, `at`; optional: `tip`
113
115
  - **bones[i]**, a chain — required: `chain`, `parent`, `points`, `tip`
114
- - **meshes.&lt;part&gt;** — exactly one of `grid`, `contour`, `auto`; required in every mode: `r`, `segments`; optional in every mode: `exponent`, `rule`
116
+ - **meshes.&lt;part&gt;** — exactly one of `grid`, `contour`, `auto`; required in every mode: `r`, `segments`; optional in every mode: `exponent`, `rule`; optional with `contour` or `auto`: `ribs`
117
+ - **meshes.&lt;part&gt;.ribs.&lt;chain&gt;** — required: `stations`
115
118
  - **meshes.&lt;part&gt;.contour** — required: `tolerance`, `margin`, `spacing`; optional: `budget`, `stray`, `regions`
116
119
  - **meshes.&lt;part&gt;.contour.regions[i]** — required: `name`, `shape`, `bone`, `spacing`, `band`, and `cx`, `cy`, `r` for a circle or `points` for a polygon
117
120
  - **meshes.&lt;part&gt;.auto** — required: `source`, `sourceBounds`, `targets`, `influences`, `budget`, `minArtSamples`, `motion`; optional: `protect`, `regions`, and the three Stage B opt-ins `boundaryRuns`, `retriangulate`, `removalOrder`
@@ -152,7 +155,7 @@ every number) are AUTHORING §3's tables, and the loader refuses any other by na
152
155
  | --- | --- | --- |
153
156
  | `parts.json`, `parts/<name>.png` | `assemble`, `build` | one record per part: `from`, the box `x`, `y`, `w`, `h` (rig px), the pixel counts `src/parts.ts` documents (`opaque_px`, `visible_px`, `occluded_px`, `projected_core_px`, `source_px_taken`, `visible_not_projected_px`, `refused_drift_px`, `merged_px`, `seam_override_px`, and `fringe_pushed_back` when not empty); `rig_size`, `scale_rig_per_source`, `ghost_px`, `recomposite` |
154
157
  | `rig/rig.json`, `rig/motion.json`, `rig/images/` | `rig`, `build`, `compose` | rig-c's rig spec and motion spec, `spec` `rigc-rig/1` and `rigc-motion/1`: the format is rig-c's, and this package writes only what rig-c's parser accepts |
155
- | `rig/mesh_report.json` | `rig`, `build` | one row per mesh: `part`, `vertices`, `triangles`, `hull`, `bones`, `max_influences`, `mean_influences`, `art_coverage`, then `grid` (a lattice); `mode` "contour" with `params`, `contour`, `regions`; or `mode` "auto" with `settings`, `source`, `result`, `residuals`, `worst_residual`, `worst_region`, `motion_amplitude`, `termination`, `weights`, `regions`, `deformation`, `quality_report` (rig-c's `mesh-quality-report/1` document whole), `motion_report`, and, only when they apply, `stray_cleared` (after `source`, when `source.stray` is declared), `skinning_residual` and `replay` (`AutoMeshReport` in `src/rig.ts`); in every mode, `exponent` when the mesh declares one (after `grid`, or after `mode`) |
158
+ | `rig/mesh_report.json` | `rig`, `build` | one row per mesh: `part`, `vertices`, `triangles`, `hull`, `bones`, `max_influences`, `mean_influences`, `art_coverage`, then `grid` (a lattice); `mode` "contour" with `params`, `contour`, `regions`; or `mode` "auto" with `settings`, `source`, `result`, `residuals`, `worst_residual`, `worst_region`, `motion_amplitude`, `termination`, `weights`, `regions`, `deformation`, `quality_report` (rig-c's `mesh-quality-report/1` document whole), `motion_report`, and, only when they apply, `stray_cleared` (after `source`, when `source.stray` is declared), `skinning_residual` and `replay` (`AutoMeshReport` in `src/rig.ts`); in every mode, `exponent` when the mesh declares one (after `grid`, or after `mode`); `ribs` when a contour or auto mesh declares them (after `exponent`, or after `mode`), and then `contour.ribs` (`source.contour.ribs` in the automatic mode) |
156
159
  | `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), `loop_physics` after `loop_max_diff` when the rig declares a physics constraint, then the judgement lines (`BREATH_VISIBLE`, `BLINK_NO_HOLE`, `CHAIN_LAG`, `TIP_OVER_ROOT`, `STILL_REGIONS_DARK`, `TEXTURE_STRETCH`), `RECOMPOSITE_HOLES`, `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 |
157
160
  | `proposal.json`, `render/landmarks*.png` | `propose` | the config's four rig sections and `notes` |
158
161
  | `basis.json` | `propose` | spec `spine-parts-basis/1` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rig-parts",
3
- "version": "1.2.0",
3
+ "version": "1.3.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": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: rig-parts
3
- description: Turn one anime character painting and its See-through layer decomposition into a verified 2D character rig (Spine 4.3 skeleton data) — measured parts, weighted meshes, bone chains and a looping idle — with rig-parts, which gates everything through rig-c before it is written. Use for a request to rig or animate a single character painting, to run or read rig-parts layers, sheet, assemble, propose, rig, check, loop or build, or to write or fix a rig-parts config.json. Not for cutting art into parts by hand, for authoring a rig from loose PNGs (that is the rigc skill), for expressions or lip-sync, or for running See-through itself.
3
+ description: Turn one anime character painting and its See-through layer decomposition into a verified 2D character rig (Spine 4.3 skeleton data) — measured parts, weighted meshes, bone chains and a looping idle — with rig-parts, which gates everything through rig-c before it is written. Use for a request to rig or animate a single character painting, to run or read rig-parts layers, sheet, assemble, propose, rig, check, loop or build, or to write or fix a rig-parts config.json. Not for cutting art into parts by hand, for authoring a rig from loose PNGs (that is the rigc skill), for lip-sync or swapping a part's image, or for running See-through itself.
4
4
  license: MIT
5
5
  compatibility: Requires Bun 1.2 or later. The tool is the npm package rig-parts (bunx rig-parts, or bun add -d rig-parts); rig-c is installed with it. See-through is run separately.
6
6
  ---
@@ -75,6 +75,13 @@ stage writes are the whole interface.
75
75
  `assemble.patches` entry (a rig-pixel box, `alpha: "silhouette"`, drawn
76
76
  `"back"` and reaching under its neighbours, its bone in `regions`) and assemble
77
77
  again; never add a part to `rig/` or `parts/` by hand (AUTHORING §5).
78
+ One layer holding two things that must move apart (a scarf end inside
79
+ `topwear`, both arms in one `handwear`): add an `assemble.cuts` entry — a
80
+ polygon in rig pixels read off `work/rig/parts/<part>.png` plus its
81
+ `parts.json` `x`, `y`, a new name, a `draw`, an `overlap` (the band the part
82
+ keeps under the piece's edge; 1 px closed the seam on scarf) — and give the piece its own
83
+ `meshes` entry; the `cut:` line gives the split. At overlap 0 a cut through
84
+ opaque art leaves a seam `check` reads (AUTHORING §5).
78
85
  A seated or reclining figure: add `--keypoints <file>` (body-18 joints in
79
86
  painting px, each observed, occluded or missing; AUTHORING §3, *A posed
80
87
  figure*) so the neck, hip, chest and sleeves follow the pose and LINT reads
@@ -132,11 +139,13 @@ share is synthesis where the painting was there to be taken.
132
139
  a GPU or the network.
133
140
  - Give a layer two depths: a layer in front of another in one place and behind it in
134
141
  another is right in the still and wrong in motion.
135
- - Expressions, lip-sync, or any animation but one looping `idle`.
142
+ - Propose, key or judge any animation but the looping `idle`, or lip-sync: every slot
143
+ holds one attachment, so nothing swaps a part's image. Expressions and a jiggle are
144
+ yours to build on top (below).
136
145
  - Write animated WebP: `loop` writes GIF and APNG.
137
146
  - Know what the art does not tag: a swinging element painted inside another layer,
138
147
  hair of an unfamiliar shape, whether an accessory swings. Those are yours to add.
139
- - Promise a success rate. Two public characters and eight private ones check green;
148
+ - Promise a success rate. Three public characters and eight private ones check green;
140
149
  that is all that has been measured.
141
150
 
142
151
  ## Where to read next
@@ -144,6 +153,15 @@ share is synthesis where the painting was there to be taken.
144
153
  [docs/AUTHORING.md](https://github.com/firejune/rig-parts/blob/main/docs/AUTHORING.md)
145
154
  first — every config field, the command order, what to read after each stage, every
146
155
  refusal. It is in the installed package at `node_modules/rig-parts/docs/AUTHORING.md`,
147
- the copy that matches the version you run. A `RIG_RIGC_GREEN` or `CHECK_RIGC_GREEN`
156
+ the copy that matches the version you run. Two recipes there cover what this package
157
+ leaves to you:
158
+
159
+ - An expression or any other named animation: AUTHORING §3, *Recipe: expressions and other
160
+ named animations* (`motion.animations_from`; built and gated with the rig, not measured).
161
+ - A jiggle (a bust, the hips): AUTHORING §3, *Recipe: a soft region driven by physics* (a
162
+ config-only bone, a `contour` region on it, a `physics` constraint; `examples/sample`
163
+ carries one, and its slow idle barely moves it).
164
+
165
+ A `RIG_RIGC_GREEN` or `CHECK_RIGC_GREEN`
148
166
  line quotes rig-c's own assertion; rig-c's guide for those is
149
167
  `node_modules/rig-c/docs/AUTHORING.md`, and its `rigc` skill covers the compiler.