rig-parts 1.69.0 → 1.70.1

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
@@ -408,7 +408,7 @@ These are limits of the approach, stated so nobody reads more into a green run:
408
408
  by models the user runs on their GPU or a web demo. No weights ship here; the
409
409
  optional `comfy` adapter drives the user's own ComfyUI host and models.
410
410
  - **Legacy turns use one ordering depth per layer.** See-through also supplies per-pixel
411
- sheets, now preserved as inputs for the subsequent generator consumer (#773/#763). A layer that is in front of another in one place and behind it in another is drawn
411
+ sheets, consumed by the shipped 2.5D head-turn producer through [config.turn.roles](docs/AUTHORING.md#3-configjson-field-by-field). A layer that is in front of another in one place and behind it in another is drawn
412
412
  right in the still (the painting's own pixels are projected onto it) and wrong once
413
413
  it moves.
414
414
  - **No expression or lip-sync, and no jiggle.** The mouth is one layer; there is no
@@ -586,8 +586,11 @@ missing declared file is an error, not a fallback. The ordering median remains
586
586
  separate, and its agreement reading cannot certify an image's polarity.
587
587
 
588
588
  Assembly emits registered part sheets and references; rig emits PAD4 sheets and
589
- `depth_sheets.json`. Existing weighted meshes do not use them yet: production
590
- Core grid/contour generators and turns belong to #763. No-image builds keep the
589
+ `depth_sheets.json`. Since 1.68.0 (#777), [config.turn.roles](docs/AUTHORING.md#3-configjson-field-by-field)
590
+ uses depth sheets for 2.5D head turns with grid surfaces and `yaw_dial`/`pitch_dial`
591
+ controls; see [`CONFIG_TURN_ROLE_UNKNOWN`](docs/AUTHORING.md#CONFIG_TURN_ROLE_UNKNOWN)
592
+ and [`CONFIG_TURN_SURFACE_REQUIRED`](docs/AUTHORING.md#CONFIG_TURN_SURFACE_REQUIRED).
593
+ Authored weighted body meshes do not consume depth sheets. No-image builds keep the
591
594
  legacy output bytes. `comfy seethrough --config <file> --depth-run <full|head>`
592
595
  uses the same opt-in; its default captures art only. Standalone authored part
593
596
  references remain active declarations. See [the input contract and refusals](docs/AUTHORING.md#per-layer-depth-sheets-773).
package/docs/AUTHORING.md CHANGED
@@ -1184,6 +1184,80 @@ lattice and contour modes a mesh may declare it:
1184
1184
  "bottomwear": { "grid": 25, "r": 10, "segments": [["hip", [232, 425], [601, 425]], "skirt_r", "skirt_c", "skirt_l"], "influences": { "maxInfluences": 6, "floor": "continuous" } }
1185
1185
  ```
1186
1186
 
1187
+ **A topwear hem over a rigid bottomwear (issue #650).** Only an explicit `waist`
1188
+ declaration enables this construction. Without it, even an eligible pair keeps
1189
+ the original bindings, rigid region, graph, motion, reports and images. A distance lattice whose
1190
+ `topwear` provenance declares exactly two distinct segment bones receives a waist
1191
+ driver only when one is the explicit region binding of a `bottomwear` part and the
1192
+ other is its descendant in the declared bone graph. Part and bone spellings do not
1193
+ select this rule. More than one eligible bottomwear partner, or multiple topwear
1194
+ drivers of one bottomwear region, is `RIG_WAIST_PAIR`;
1195
+ an explicit declaration without that qualifying pair is refused by name.
1196
+ The declaration selects one pair; no other pair is changed.
1197
+
1198
+ Call the region bone H and the descendant C. The existing lattice art criterion is
1199
+ strictly alpha above 8, after any declared stray removal. The last such pixel row
1200
+ is the painted hem, rather than the padded image boundary. The lattice splits its
1201
+ original triangles at C's image-space origin, the H-only plateau and the hem row, retaining the
1202
+ original diagonal partition and rebuilding
1203
+ the outline, triangles, UVs and bindings together. At and above C's row, distance
1204
+ weights stay unchanged. Below it, the H-only plateau starts one source texel before the painted hem,
1205
+ with `t = min(1, (y - C.y) / (plateau - C.y))`. The
1206
+ uncut distance share `h` becomes `h + (1 - h) * t` for H and its complement for C;
1207
+ the declared cap and floor are then applied once. The whole last art pixel and the
1208
+ padding below it bind fully to H. `RIG_WAIST_HEIGHT` names a missing hem or a C
1209
+ origin not strictly above the plateau; `RIG_WAIST_BINDING` names a missing member of the
1210
+ declared pair. Alternate drawings use their own art and posed C origin. Heat,
1211
+ contour, automatic, influence and meshes with other binding sets keep their rules.
1212
+ The original triangle's native setup position and UV field are interpolated at
1213
+ each new point. Bind coordinates are inverted through the runtime frame, written
1214
+ with the existing six decimal places, then stored by the runtime as float32;
1215
+ UVs retain the interpolated value and the runtime's existing page-float32 step.
1216
+ This is the nearest representable field under those storage rules, not a promise
1217
+ of exact mathematical setup coordinates or universally identical raster pixels.
1218
+ Setup RGBA differences must be measured on the actual input, reporting differing
1219
+ pixels and the maximum absolute difference per channel. The waist acceptance
1220
+ allows at most one per channel. Literal whole-hem support is checked separately
1221
+ in the actual stored UV field; nominal source rows are not that proof.
1222
+ The stored-space check reads native page UVs through the promised renderer entry,
1223
+ requires integral unscaled page/source metadata at most 2048 on each edge, and
1224
+ intersects every complete last-row alpha>8 pixel cell with the UV triangles.
1225
+ Exact coverage and absence of positive non-H support are required; duplicate
1226
+ coverage cannot compensate for a hole, and an empty or missing read is refused.
1227
+ A source-coordinate rounding envelope alone is not an acceptance result.
1228
+ Paired alternate drawings retain their selected base or front slots, including
1229
+ hidden-base-slot semantics and deform target identities.
1230
+
1231
+
1232
+ Only the paired bottomwear region becomes a mesh. Its whole existing padded
1233
+ image is partitioned by the top's actual triangle arrangement. Inside that domain
1234
+ each bone receives the barycentric stored moments `w`, `w*x`, `w*y`, divided by
1235
+ the resulting positive weight to recover local coordinates; cap/floor is not
1236
+ applied again. Common vertices copy complete bindings. Outside the top domain,
1237
+ the supplied region rectangle's nodes retain H, and the arrangement connects the
1238
+ fields. The bottom's mesh report names the top driver's grid and measures the
1239
+ resulting geometry and art coverage. Alternate sets select their own paired
1240
+ field; a subset without a new bottom drawing receives a derived attachment on
1241
+ the existing bottom slot/image. A set hiding the top retains the rigid bottom.
1242
+ Explicit still pieces remain separate and retain their own binding.
1243
+
1244
+ External numeric deforms on the changed top topology are expanded in the **old
1245
+ per-influence** index space, including sparse runs, odd `offset`, odd lengths,
1246
+ `fromVertex` and null setup keys. Per-bone delta moments are transferred by the
1247
+ same old-to-new barycentric correspondence. `RIG_WAIST_DEFORM_RUN` names an old
1248
+ run outside its array; `RIG_WAIST_DEFORM_SUPPORT` refuses a nonzero moment whose
1249
+ bone the new field no longer contains. Such a moment cannot be carried by another
1250
+ bone under arbitrary motion. `RIG_WAIST_DEFORM_REBIND` refuses nonzero numeric
1251
+ offsets after stops/turn changed those local binding frames without a proven
1252
+ correspondence. A whole-attachment transform is `RIG_WAIST_DEFORM_MODEL`: evaluating
1253
+ a nonlinear model at new nodes is not proven equivalent to its old piecewise
1254
+ field. `RIG_WAIST_DEFORM_REGION` refuses an authored timeline on the original
1255
+ bottom region, which had no mesh deform array. Refusals occur when external
1256
+ animations are merged, before the build gate or output. Other attachments,
1257
+ including unrelated face deforms, retain their existing path and are not refused.
1258
+ This changes the rig's response to motion; the consumer's motion and an owner's
1259
+ viewing of the result remain separate checks.
1260
+
1187
1261
  **When to reach for it:** a part that folds where two chains meet, or at the end of a curl.
1188
1262
  The default does not move; a fold is today found by looking at a build that passes every
1189
1263
  bar, and this field is what there is to turn once one is seen.
@@ -3997,6 +4071,29 @@ says why each requirement exists and what breaks without it.
3997
4071
  | <a id="RIG_PNG_PRESENT"></a><a id="RIG_PNG_MATCHES_BOX"></a><a id="RIG_PART_HAS_ART"></a>`RIG_PNG_PRESENT`, `RIG_PNG_MATCHES_BOX`, `RIG_PART_HAS_ART` | a part's PNG is missing, the wrong size, or has no art pixel | re-run assemble |
3998
4072
  | <a id="RIG_NAME_RESOLVES"></a><a id="RIG_SEGMENT_DEFINED"></a><a id="RIG_CHAIN_POINTS"></a>`RIG_NAME_RESOLVES`, `RIG_SEGMENT_DEFINED`, `RIG_CHAIN_POINTS` | a segment names an undeclared bone, a bone with no `tip` and no next link, or a chain too short to make a link | that bone's `tip`, or write the segment out as `[bone, [x0,y0], [x1,y1]]` |
3999
4073
  | <a id="RIG_INFLUENCES_KEPT"></a>`RIG_INFLUENCES_KEPT` | a mesh's declared `influences` leaves a vertex with no bone: every capped share is under 0.03 (at or under it, for the continuous floor). The largest of k shares is at least 1/k, so this needs 34 or more kept bones; the default cap of 4 never reaches it (issue #284). The detail names the first vertex, how many more, and its largest share | declare `maxInfluences` 33 or less, or name fewer bones in `segments` |
4074
+ | <a id="RIG_LATTICE_STATION"></a>`RIG_LATTICE_STATION` | a requested lattice subdivision row is not finite | supply finite source-image row coordinates |
4075
+ | <a id="RIG_WAIST_PIVOT_SOURCE"></a>`RIG_WAIST_PIVOT_SOURCE` | paired original mesh layout/masks are absent, contact is empty/disjoint, or no bilateral lower station precedes the painted hem | retain the source layout and nonempty contiguous alpha support; do not guess a band height |
4076
+ | <a id="RIG_WAIST_PIVOT_NAME"></a>`RIG_WAIST_PIVOT_NAME` | the appended bone name exists, or the constraint name is empty | choose a new explicit bone and nonempty constraint name; do not replace a graph node |
4077
+ | <a id="RIG_WAIST_PIVOT_FRAME"></a>`RIG_WAIST_PIVOT_FRAME` | exact authored upper/lower rest frames are not unturned unit frames, or the source graph has constraints, turn or stops | retain the unconstrained original graph; authored eligibility uses exact frames, while inverse binding uses runtime frames; no epsilon or graph rewrite |
4078
+ | <a id="RIG_WAIST_COPY_SOURCE"></a>`RIG_WAIST_COPY_SOURCE` | supplied paired meshes/images or an existing upper source row are missing, a triangle crosses that row, or original upper support is lost | retain the stored source topology and an existing row above the annotated copy boundary; no replacement lattice or upper refinement |
4079
+ | <a id="RIG_WAIST_COPY_FRAME"></a>`RIG_WAIST_COPY_FRAME` | the supplied waist frame is absent, already drives the original top, has an incompatible graph/property contract or authored non-unit rest frame | supply the existing upper-parented world rotate-only frame; do not rewrite graph or motion |
4080
+ | <a id="RIG_WAIST_COPY_DEFORM"></a>`RIG_WAIST_COPY_DEFORM` | a targeted original top deform carries a nonzero numeric run or attachment transform whose per-bone moment cannot be transferred to the new waist frame | retain the original field or provide a proved composition; no silent deformation reinterpretation |
4081
+ | <a id="RIG_WAIST_COPY_COVERAGE"></a>`RIG_WAIST_COPY_COVERAGE` | a mixed-support triangle has positive source area below the copy boundary, or actual native UV/support correspondence is unavailable | retain the constrained source arrangement and native whole-area proof; inserted points alone are insufficient |
4082
+ | <a id="RIG_WAIST_PAIR"></a>`RIG_WAIST_PAIR` | a topwear has multiple eligible bottomwear partners, or a bottomwear has multiple topwear drivers | declare one unambiguous binding pair; no first-match selection |
4083
+ | <a id="RIG_WAIST_HEIGHT"></a>`RIG_WAIST_HEIGHT` | the painted hem is missing, or the upper origin does not precede its one-texel plateau | retain nonempty art and an upper origin strictly above the plateau |
4084
+ | <a id="RIG_WAIST_BINDING"></a>`RIG_WAIST_BINDING` | the uncut distance field does not retain both declared waist bones | declare both members of the eligible binding pair |
4085
+ | <a id="RIG_WAIST_GEOMETRY"></a>`RIG_WAIST_GEOMETRY` | a waist arrangement coordinate is not finite | supply finite source geometry |
4086
+ | <a id="RIG_WAIST_TOPOLOGY"></a>`RIG_WAIST_TOPOLOGY` | the arrangement has an invalid edge incidence, a pinched boundary or multiple boundary loops | supply one coherent closed mesh domain |
4087
+ | <a id="RIG_WAIST_CORRESPONDENCE"></a>`RIG_WAIST_CORRESPONDENCE` | a subdivision point lies outside the original supplied triangle domain | preserve the source triangle domain and its correspondence |
4088
+ | <a id="RIG_WAIST_SET_BINDING"></a>`RIG_WAIST_SET_BINDING` | a selected set no longer resolves the paired source meshes on their declared slots | preserve the selected top and bottom attachment identities |
4089
+ | <a id="RIG_WAIST_STORED_METADATA"></a>`RIG_WAIST_STORED_METADATA` | native atlas metadata is not integral, unscaled, size-bounded and consistent with the original drawing | read back the actual page and region metadata; require matching original dimensions and page edges at most2048 |
4090
+ | <a id="RIG_WAIST_STORED_UV"></a>`RIG_WAIST_STORED_UV` | native UVs, support or triangles are missing, nonfinite, not float32 or inconsistent | supply complete native float32 UVs with parallel support and valid triangles |
4091
+ | <a id="RIG_WAIST_STORED_EMPTY"></a>`RIG_WAIST_STORED_EMPTY` | the stored-hem drawing has no painted row | supply nonempty original art; an empty read cannot pass |
4092
+ | <a id="RIG_WAIST_DEFORM_REGION"></a>`RIG_WAIST_DEFORM_REGION` | an authored deform targets the original rigid bottomwear region | remove the invalid region deform; conversion cannot reinterpret it as a mesh timeline |
4093
+ | <a id="RIG_WAIST_DEFORM_MODEL"></a>`RIG_WAIST_DEFORM_MODEL` | an unproved whole-attachment transform targets a subdivided waist mesh | supply a representable numeric old-topology run |
4094
+ | <a id="RIG_WAIST_DEFORM_RUN"></a>`RIG_WAIST_DEFORM_RUN` | an old-topology deform run has invalid values, offsets or array bounds | supply a finite numeric run within the original per-influence array |
4095
+ | <a id="RIG_WAIST_DEFORM_REBIND"></a>`RIG_WAIST_DEFORM_REBIND` | a nonzero old local deform follows a stop or turn that changed the waist binding frames | retain an established correspondence for the rebound field before supplying nonzero local deltas |
4096
+ | <a id="RIG_WAIST_DEFORM_SUPPORT"></a>`RIG_WAIST_DEFORM_SUPPORT` | a nonzero old bone moment has no corresponding influence on the new vertex | preserve that bone support; another bone cannot carry its moment under arbitrary motion |
4000
4097
  | <a id="RIG_LATTICE_ONE_LOOP"></a>`RIG_LATTICE_ONE_LOOP` | the lattice over a part does not close into one outline even after the repair passes | that mesh's `grid` |
4001
4098
  | <a id="RIG_CONTROL_NAME_FREE"></a>`RIG_CONTROL_NAME_FREE` | a keyed, mesh-weighted bone needs `<bone>_ctl` and that name is taken | rename the declared bone |
4002
4099
  | <a id="RIG_HEAD_UP_NAME_FREE"></a>`RIG_HEAD_UP_NAME_FREE` | `config.bones` declares `head_up`, the bone the rig stage adds under `head` to carry the face's up axis (issue #250) | rename that bone |
@@ -5464,9 +5561,131 @@ actual padded drawing `size` [width,height], and its `depth` reference. A blink
5464
5561
  still split has separate lower/upper scalar slices at the same row as the art;
5465
5562
  both keep original source provenance. Its references describe inputs for Core's
5466
5563
  `generator.depth = {image, near, zScale}`; sources are provenance, not Core
5467
- fields. Existing authored weighted meshes are unchanged and do not consume the
5468
- reference yet (#763 owns generators/turns). Measurement sheets are not packed
5564
+ fields. Since 1.68.0 (#777), [config.turn.roles](#3-configjson-field-by-field) consumes
5565
+ depth sheets for 2.5D head turns with grid surfaces and `yaw_dial`/`pitch_dial`
5566
+ controls; see [`CONFIG_TURN_ROLE_UNKNOWN`](#CONFIG_TURN_ROLE_UNKNOWN) and
5567
+ [`CONFIG_TURN_SURFACE_REQUIRED`](#CONFIG_TURN_SURFACE_REQUIRED).
5568
+ Authored weighted body meshes are unchanged and do not consume depth sheets. Measurement sheets are not packed
5469
5569
  art. An authored `parts.json` depth reference remains an active standalone
5470
5570
  `rig` declaration even without config opt-in: its file/size/color/opacity
5471
5571
  guards still apply. With binding inactive there is no added reference, file or
5472
5572
  legacy-byte change. #773 adds no image generation, geometry or motion model.
5573
+ ### Explicit source-derived waist pivot stage
5574
+
5575
+ `withWaistPivot` is a pure, explicit candidate-stage operation over an already
5576
+ built paired waist. It leaves the ordinary builder unchanged. The complete top
5577
+ attachment, original bones, limb attachments, images and motion object remain
5578
+ unchanged; one bone is appended under the declared upper chain. Its translation
5579
+ is the parent-upper frame evaluated at the source band TOP centre. This is an
5580
+ explicit definition, not a claim that a spatially blended top field equals that
5581
+ single parent frame. The candidate's native readings must report their difference.
5582
+
5583
+ The source profile uses the existing alpha-above-eight masks: one contiguous
5584
+ shared column run, its central bottom upper cell edge, then the first later row
5585
+ whose bottom silhouette expands beyond both original side supports. That is the
5586
+ lower station, a conservative source boundary rather than an inferred semantic
5587
+ colour seam. The last painted bottom row is the end of a fixed linear waist-to-
5588
+ lower blend. Original triangles are split at those two rows, original UVs are
5589
+ interpolated through their original correspondence, and old stored setup points
5590
+ are inverse-bound through the fixed rest frames. The whole upper band uses the
5591
+ waist bone; the last painted hem has only lower-frame support. No coefficient,
5592
+ colour classifier, image edit or animation fitting is involved.
5593
+
5594
+ The generated core transform explicitly declares `rotate` to `rotate`,
5595
+ `mixRotate: 1`, and every translation, scale and shear mix zero. A mix without
5596
+ its matching property is dead data in the native 4.3 contract. With no declared
5597
+ destination property, the native gate rejects the constraint as
5598
+ `A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT`: it drives no property. The
5599
+ planted missing-property control requires that exact refusal for its named
5600
+ constraint as the only failed assertion; an arbitrary exit 1 or a skipped check
5601
+ does not pass. It does not build successfully and then reach a no-op angle
5602
+ probe. The positive control still reads native rotation and translation. Both
5603
+ controls carry their commands, full stdout/stderr and SHA-256 hashes in their
5604
+ case details or errors before temporary cleanup. Native gating is mandatory
5605
+ before this stage can be called a compiled candidate; it does not
5606
+ establish visual or motion acceptance. Nonzero bottom deformation remains subject
5607
+ to the original region/deform refusal; the operation adds no motion adapter.
5608
+
5609
+ The refusal rows are in the waist registry in section6. Eligibility measures
5610
+ authored unit frames with the core exact-frame evaluator; inverse binding keeps
5611
+ the core runtime-frame evaluator. No tolerance is applied to authored rotations.
5612
+ An existing constrained, turned or stopped graph remains ineligible.
5613
+
5614
+ `withWaistSharedCopy` is a separate explicit operation on a supplied pivot
5615
+ output. It preserves that output's bottom attachment, bones, constraints, limb
5616
+ attachments, art and motion. Its copy boundary is an operational annotation:
5617
+ the existing shared source columns' first bottom alpha cells, connected at
5618
+ column centres and continued at each end across the top hull. It is not a
5619
+ semantic cloth detector. The upper anchor is the last existing stored UV row
5620
+ strictly above that boundary, including its stored rounding rather than a
5621
+ nominal regenerated row. The fixed smoothstep between those source boundaries
5622
+ transfers original per-bone weights and first moments into the existing waist
5623
+ frame. Each new waist moment inverse-binds the original corresponding rest
5624
+ point; no geometry is snapped to the band's edge.
5625
+
5626
+ The lower original triangles are constrained along the boundary segments and
5627
+ existing edges. Boundary-interface points are propagated only below the upper
5628
+ anchor; original upper vertex UV/binding records and triangle fields are exact
5629
+ under the hull's required index permutation. Every triangle with positive area
5630
+ below the copy boundary must have only waist support, including side
5631
+ extensions. The native control repeats this test with actual runtime source
5632
+ UVs using exact binary arithmetic. A planted crossing triangle demonstrates why
5633
+ checking just inserted vertices is insufficient. Nonzero targeted top deforms
5634
+ and attachment transforms are refused by name until a composition into the new
5635
+ per-bone support is proved; original motion is never silently rewritten. A
5636
+ passing gate still does not establish picture, seam or motion acceptance.
5637
+
5638
+ The rigid copy support starts conservatively above that annotated boundary by
5639
+ `(source height + max absolute boundary slope * source width) * 2^-23` source
5640
+ pixels. This is a storage guard, not an authored height or fit coefficient:
5641
+ rig-c's `f32` writes the shortest decimal naming the same float32 as its input,
5642
+ and Spine retains that decimal. A complete float32 rounding cell on normalized
5643
+ UVs `[0,1]` is bounded by `2^-23`. Each actual new UV is checked with exact
5644
+ arithmetic against that source-coordinate bound, including arithmetic before
5645
+ the conversion. The boundary's slope bound then keeps the represented mixed
5646
+ triangles above the **original** boundary. Native whole-area coverage still
5647
+ tests the original boundary without tolerance; its failure remains a failure.
5648
+
5649
+ ### Opting into the accepted shared waist
5650
+
5651
+ ```json
5652
+ "waist": {
5653
+ "top": "shirt", "bottom": "skirt",
5654
+ "upper": "chest", "lower": "hip",
5655
+ "bone": "skirt_waist", "constraint": "skirt_waist_rotation"
5656
+ }
5657
+ ```
5658
+
5659
+ All six nonempty names are required. `top` names a two-bone distance lattice
5660
+ with topwear provenance; `bottom` names a rigid region with bottomwear
5661
+ provenance bound to `lower`. `upper` is the other segment bone and a descendant
5662
+ of `lower`. `bone` is a new generated name; `constraint` names its rotate-only
5663
+ constraint. Origins and the canvas come from the actual parts, including their
5664
+ existing padding. There are no authored offsets, gains or search parameters.
5665
+ `buildRig` and the `rig`/`build` commands compose the preserved pair, pivot and
5666
+ shared-copy stages. The pure `buildWaistPairSource` intermediate is for stage
5667
+ composition and controls; `buildRig` is the complete declared construction.
5668
+
5669
+ This bounded construction requires one base pair without alternate or limb-owned
5670
+ replacements, unit rest frames and an unconstrained, unturned, unstopped graph.
5671
+ Unsupported input is refused, never silently changed to a different construction.
5672
+ Imported original keys remain unchanged; nonzero targeted top deforms or tracks
5673
+ driving the generated waist frame are refused before gated emission.
5674
+
5675
+ The accepted integrated-walk artifact in
5676
+ [the final issue disposition](https://github.com/rig-c/parts/issues/650#issuecomment-6408838090)
5677
+ reported native seam ghost 0 and gap 0 over 380 samples. Its maximum raster jut
5678
+ was **7 metric pixels at frame 1 against the previous limit of 6**: the raw bar
5679
+ failed and acceptance explicitly retained that exception. The separate fixed
5680
+ emission audit at frame 57 counted 375 all-bracketed background pixels, including
5681
+ 26 in the dropping half. Native seam gap 0 is not a whole-picture no-background
5682
+ claim. These figures belong to the preserved artifact and its recorded toolchain;
5683
+ they do not certify a new toolchain, input or packaging build. No tuning to meet
5684
+ 6 pixels is part of this declaration.
5685
+
5686
+ | rule | means | change |
5687
+ | --- | --- | --- |
5688
+ | <a id="CONFIG_WAIST_BINDING"></a>`CONFIG_WAIST_BINDING` | A declaration names an absent mesh/region/bone or repeats a pair member; supply the actual distinct bound objects | supply the actual distinct bound objects |
5689
+ | <a id="CONFIG_WAIST_NAME_FREE"></a>`CONFIG_WAIST_NAME_FREE` | The generated bone name already exists; declare an unused name | declare an unused name |
5690
+ | <a id="RIG_WAIST_DECLARATION"></a>`RIG_WAIST_DECLARATION` | Actual provenance, pair bindings or drawing ownership do not match the bounded declaration; supply the declared base two-bone mesh/rigid-region pair | supply the declared base pair |
5691
+ | <a id="RIG_WAIST_MOTION"></a>`RIG_WAIST_MOTION` | Imported tracks drive the generated waist frame; retain their original authored targets | retain the original authored targets |
package/docs/STABILITY.md CHANGED
@@ -118,7 +118,7 @@ The fields, object by object (a bold path is where the object sits; `<part>` is
118
118
  part name, `[i]` an entry of a list):
119
119
 
120
120
  <!-- stable:config -->
121
- - **config** — required by the full loader, in front of rig and build: `key`, `assemble`, `bones`, `meshes`, `regions`, `motion`; optional: `generation`, `seethrough`, `limbs`, `constraints`, `stops`, `turn`, `scene`, `cloth`, `proposed_rig_scale`, `requirements` (issue #556, additive: a path beside the config that build reads in place of its requirements flag, whose behaviour is unchanged), `motion_seams`, `depth`
121
+ - **config** — required by the full loader, in front of rig and build: `key`, `assemble`, `bones`, `meshes`, `regions`, `motion`; optional: `generation`, `seethrough`, `limbs`, `constraints`, `stops`, `turn`, `scene`, `cloth`, `proposed_rig_scale`, `requirements` (issue #556, additive: a path beside the config that build reads in place of its requirements flag, whose behaviour is unchanged), `motion_seams`, `depth`, `waist`
122
122
  - **generation** — required: `checkpoint`, `loras`, `trigger`, `identity`, `sampler`, `costume`, `negative_extra`, `style`, `negative_pose`, `latent`, `seed`; optional: `pose`, `control`
123
123
  - **generation.loras[i]** — required: `name`, `strength`; optional: `strength_clip`
124
124
  - **generation.sampler** — required: `steps`, `cfg`, `sampler`, `scheduler`
@@ -189,6 +189,7 @@ part name, `[i]` an entry of a list):
189
189
  - **depth** — optional: `zScale`, `full`, `head` (#773)
190
190
  - **depth.full**, **depth.head** — optional: `layers`, `atlas` (one fallback route)
191
191
  - **depth.full.layers.&lt;layer&gt;**, **depth.head.layers.&lt;layer&gt;**, **depth.full.atlas**, **depth.head.atlas** — required: `image`, `near`, `zScale`
192
+ - **waist** — required: `top`, `bottom`, `upper`, `lower`, `bone`, `constraint` (#650; absent keeps the ordinary bindings and graph)
192
193
  <!-- /stable:config -->
193
194
 
194
195
  `TY14` holds the set of field names in backticks above to the set of names the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rig-parts",
3
- "version": "1.69.0",
3
+ "version": "1.70.1",
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": {
package/src/config.ts CHANGED
@@ -1042,7 +1042,18 @@ export type LimbLayout = { parts?: string[] } & (
1042
1042
  { before: LimbLayoutTarget; after?: never } | { after: LimbLayoutTarget; before?: never }
1043
1043
  );
1044
1044
 
1045
+ export interface WaistDeclaration {
1046
+ top: string;
1047
+ bottom: string;
1048
+ upper: string;
1049
+ lower: string;
1050
+ bone: string;
1051
+ constraint: string;
1052
+ }
1053
+
1045
1054
  export interface CharacterConfig {
1055
+ /** Explicit shared-waist construction. Absent, no pair is detected or altered. */
1056
+ waist?: WaistDeclaration;
1046
1057
  depth?: DepthConfig;
1047
1058
  key: string;
1048
1059
  note?: string;
@@ -1404,7 +1415,7 @@ export interface AssembleConfig {
1404
1415
  proposed_rig_scale?: number;
1405
1416
  }
1406
1417
 
1407
- const TOP_KEYS = ['key', 'generation', 'seethrough', 'assemble', 'limbs', 'bones', 'meshes', 'regions', 'motion', 'constraints', 'stops', 'turn', 'scene', 'cloth', 'proposed_rig_scale', 'requirements', 'motion_seams', 'depth'] as const;
1418
+ const TOP_KEYS = ['key', 'generation', 'seethrough', 'assemble', 'limbs', 'bones', 'meshes', 'regions', 'motion', 'constraints', 'stops', 'turn', 'scene', 'cloth', 'proposed_rig_scale', 'requirements', 'motion_seams', 'depth', 'waist'] as const;
1408
1419
 
1409
1420
  /**
1410
1421
  * The one partial entry point. It validates exactly what its door requires —
@@ -1436,6 +1447,7 @@ function parseEarlyConfigFrom(raw: Json, door: EarlyDoor, base: string | null):
1436
1447
  if (top === null) refuseIfAny(c.problems);
1437
1448
  const t = top as Record<string, Json>;
1438
1449
  if ('depth' in t) checkDepthConfig(c, t.depth);
1450
+ if ('waist' in t) checkWaistDeclaration(c, t.waist);
1439
1451
  if ('key' in t) c.string('config.key', t.key);
1440
1452
  if ('generation' in t) checkGeneration(c, t.generation);
1441
1453
  else if (needed.includes('generation')) {
@@ -1660,6 +1672,7 @@ function parseConfigFrom(raw: Json, base: string | null): { config: CharacterCon
1660
1672
  if (top === null) refuseIfAny(c.problems);
1661
1673
  const t = top as Record<string, Json>;
1662
1674
  if ('depth' in t) checkDepthConfig(c, t.depth);
1675
+ if ('waist' in t) checkWaistDeclaration(c, t.waist);
1663
1676
  if ('key' in t) c.string('config.key', t.key);
1664
1677
  if ('generation' in t) checkGeneration(c, t.generation);
1665
1678
  if ('seethrough' in t) checkSeeThrough(c, t.seethrough);
@@ -1676,6 +1689,11 @@ function parseConfigFrom(raw: Json, base: string | null): { config: CharacterCon
1676
1689
  if ('motion' in t) checkMotion(c, t.motion, names.bones, names.chains, 'regions' in t ? t.regions : undefined);
1677
1690
  if ('constraints' in t) checkConstraints(c, t.constraints, names.bones, names.parents);
1678
1691
  if ('stops' in t) checkStops(c, t.stops, names.bones, names.chains);
1692
+ if ('waist' in t && isPlainObject(t.waist)) {
1693
+ for (const field of ['upper', 'lower'] as const) if (typeof t.waist[field] === 'string' && !names.bones.has(t.waist[field])) c.fail('CONFIG_WAIST_BINDING', `config.waist.${field}`, `names ${JSON.stringify(t.waist[field])}; a declared bone is required`);
1694
+ for (const [field, map] of [['top', t.meshes], ['bottom', t.regions]] as const) if (typeof t.waist[field] === 'string' && (!isPlainObject(map) || !Object.hasOwn(map, t.waist[field]))) c.fail('CONFIG_WAIST_BINDING', `config.waist.${field}`, `names ${JSON.stringify(t.waist[field])}; ${field === 'top' ? 'a declared mesh' : 'a declared region'} is required`);
1695
+ if (typeof t.waist.bone === 'string' && names.bones.has(t.waist.bone)) c.fail('CONFIG_WAIST_NAME_FREE', 'config.waist.bone', `bone ${JSON.stringify(t.waist.bone)} already exists; an unused generated name is required`);
1696
+ }
1679
1697
  const attached = attachedParts(t.meshes, t.regions);
1680
1698
  if ('motion_seams' in t) c.problems.push(...motionSeamProblems(t.motion_seams, attached, 'config.motion_seams', 'CONFIG_MOTION_SEAM'));
1681
1699
  const hasAltFace = hasAlternateFaceSet(t.assemble, [...parts, ...cuts]);
@@ -4136,6 +4154,7 @@ export function loadClothConfig(path: string): { key: string; cloth: ClothRecipe
4136
4154
  if (top === null) refuseIfAny(c.problems);
4137
4155
  const t = top as Record<string, Json>;
4138
4156
  if ('depth' in t) checkDepthConfig(c, t.depth);
4157
+ if ('waist' in t) checkWaistDeclaration(c, t.waist);
4139
4158
  if ('key' in t) c.string('config.key', t.key);
4140
4159
  if ('cloth' in t) checkCloth(c, t.cloth);
4141
4160
  refuseIfAny(c.problems);
@@ -4602,3 +4621,12 @@ function checkDepthConfig(c: Check, raw: Json): void {
4602
4621
  if ('atlas' in q && isPlainObject(q.layers) && Object.keys(q.layers).length > 0) c.fail('DEPTH_SOURCE_AMBIGUOUS', path, 'declares canvas and per-layer sheets together; choose one fallback explicitly');
4603
4622
  }
4604
4623
  }
4624
+
4625
+ /** The operation has no implicit selector, fitted position or tuning coefficient. */
4626
+ function checkWaistDeclaration(c: Check, raw: Json): void {
4627
+ const fields = ['top', 'bottom', 'upper', 'lower', 'bone', 'constraint'] as const;
4628
+ const o = c.object('config.waist', raw, ['top', 'bottom', 'upper', 'lower', 'bone', 'constraint'], []);
4629
+ if (o === null) return;
4630
+ for (const field of fields) if (field in o) c.string(`config.waist.${field}`, o[field]);
4631
+ for (const [a, b] of [['top', 'bottom'], ['upper', 'lower']] as const) if (typeof o[a] === 'string' && o[a] === o[b]) c.fail('CONFIG_WAIST_BINDING', `config.waist.${a}`, `matches ${b} ${JSON.stringify(o[b])}; two distinct names are required`);
4632
+ }
package/src/mesh.ts CHANGED
@@ -336,6 +336,12 @@ export function artCoverage(mesh: Pick<LatticeMesh, 'vertices' | 'triangles'>, a
336
336
  * still not one closed outline after the passes is refused rather than handed on.
337
337
  */
338
338
  export function latticeMesh(part: string, art: Mask, grid: number, maxPasses: number = ONE_LOOP_PASSES): LatticeMesh | Problem {
339
+ return latticeMeshRows(part, art, grid, [], maxPasses);
340
+ }
341
+
342
+ /** Refine the original kept cells at supplied image rows; no new mask/bridge decision or omitted cell. */
343
+ export function latticeMeshRows(part: string, art: Mask, grid: number, rows: readonly number[], maxPasses: number = ONE_LOOP_PASSES): LatticeMesh | Problem {
344
+ if (rows.some((y) => !Number.isFinite(y))) return { code: 'RIG_LATTICE_STATION', object: `mesh "${part}"`, detail: `station rows [${rows.join(', ')}] include a non-finite coordinate; supplied finite image rows are required` };
339
345
  const lattice = latticeCells(art, grid);
340
346
  let any = false;
341
347
  for (let i = 0; i < lattice.cells.data.length && !any; i++) any = lattice.cells.data[i] !== 0;
@@ -347,7 +353,20 @@ export function latticeMesh(part: string, art: Mask, grid: number, maxPasses: nu
347
353
  };
348
354
  }
349
355
  const loop = oneLoop(lattice.cells, maxPasses);
350
- const mesh = triangulate(lattice, loop.cells, loop.passes);
356
+ let refined = lattice;
357
+ let kept = loop.cells;
358
+ const extra = rows.filter((y) => y > 0 && y < art.height && !lattice.ys.includes(y));
359
+ if (extra.length > 0) {
360
+ const ys = [...new Set([...lattice.ys, ...extra])].sort((a, b) => a - b);
361
+ kept = newMask(lattice.nx, ys.length - 1);
362
+ let originalRow = 0;
363
+ for (let j = 0; j < ys.length - 1; j++) {
364
+ while (originalRow + 1 < lattice.ny && ys[j] >= lattice.ys[originalRow + 1]) originalRow++;
365
+ kept.data.set(loop.cells.data.subarray(originalRow * lattice.nx, (originalRow + 1) * lattice.nx), j * lattice.nx);
366
+ }
367
+ refined = { ...lattice, ys, ny: ys.length - 1, cells: kept };
368
+ }
369
+ const mesh = triangulate(refined, kept, loop.passes);
351
370
  if (mesh.loops !== 1 || mesh.pinchedVertices !== 0) {
352
371
  return {
353
372
  code: 'RIG_LATTICE_ONE_LOOP',