rig-parts 1.63.0 → 1.64.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/docs/AUTHORING.md CHANGED
@@ -1016,7 +1016,7 @@ still judges the mesh before the rig stage publishes anything.
1016
1016
 
1017
1017
  `segments` is required in this mode too, but names bones directly: first the
1018
1018
  accepted slot bone, then any other weighted bones. All weight bones must be
1019
- listed. A declared stopped link may be named by its emitted `<link>_d` name; no other unknown name resolves by suffix. Chain shorthand and coordinate segments are refused here. These names
1019
+ listed. A declared stopped link may be named by its emitted `<link>_d` name; no other unknown name resolves by suffix (`RIG_EXPLICIT_BINDING` refuses a raw stopped link that `stops` would rewrite). When the part is also in `turn.depths` (issue #740), state `segments`, `weights` and any island `bone` in those pre-turn bone names; `turn` wraps them into `turn_<part>_<bone>` at zero offset (`x: 0, y: 0`) after the stop-binding check, leaving the bone-local bind coordinates `(x, y, weight)` unchanged. Chain shorthand and coordinate segments are refused here. These names
1020
1020
  are bindings, not a recipe to generate geometry. Do not supply `r`, `grid`,
1021
1021
  `contour`, `auto`, `influence`, `exponent`, `rule`, `ribs`, `influences` or `stray`
1022
1022
  beside `explicit` (`CONFIG_EXPLICIT_MODE`). `gather: false` retains its usual
@@ -3841,7 +3841,7 @@ says why each requirement exists and what breaks without it.
3841
3841
  | <a id="RIG_LAYOUT_CONFLICT"></a>`RIG_LAYOUT_CONFLICT` | layout declarations share a moving slot, use a moving slot as a target, target themselves, form a reciprocal/chained pair, or partially overlap a shared target block; both field paths are printed | move each participating slot in at most one placement, and share only a stationary target block |
3842
3842
  | <a id="RIG_STILL_ROW_INSIDE_PART"></a><a id="RIG_STILL_ROW_CLEAR"></a><a id="RIG_STILL_PIECES_HAVE_ART"></a><a id="RIG_STILL_NAME_FREE"></a>`RIG_STILL_ROW_INSIDE_PART`, `RIG_STILL_ROW_CLEAR`, `RIG_STILL_PIECES_HAVE_ART`, `RIG_STILL_NAME_FREE` | a `motion.blink.still` row that is not strictly inside the part, that crosses art (a cut through art changes the render even at rest), that leaves one piece with no art, or whose `<part>_still` slot name another part already has | that entry's `row` — a row with no art between the crease and the lash line — or rename the other part |
3843
3843
  | <a id="RIG_SET_COVERS_BONE"></a>`RIG_SET_COVERS_BONE` | `assemble.alternates[i].drawn.<bone>` (issue #476): a subset set in `parts.json` `alternates` (`parts` present) states a `drawn` world angle for a bone none of its covered slots ride; the detail names the bone, the set, and the covered bones | remove the uncovered bone from that set's `drawn`, or add the part that rides it to `parts` |
3844
- | <a id="RIG_EXPLICIT_BINDING"></a>`RIG_EXPLICIT_BINDING` | the declared stops or turn would change the accepted payload's bindings | transfer the accepted final emitted bone names and local coordinates, or use a generated mesh; no normalization is applied |
3844
+ | <a id="RIG_EXPLICIT_BINDING"></a>`RIG_EXPLICIT_BINDING` | the declared stops would change the accepted payload's bindings | transfer the accepted final emitted bone names and local coordinates, or use a generated mesh; no normalization is applied |
3845
3845
  | <a id="RIG_EXPLICIT_IMAGE_HASH"></a>`RIG_EXPLICIT_IMAGE_HASH` | the decoded part RGBA differs from `explicit.image_sha256`; both digests are named | restore the accepted image or transfer its matching accepted payload and guard; nothing is written |
3846
3846
  | <a id="RIG_EXPLICIT_REGION_HASH"></a>`RIG_EXPLICIT_REGION_HASH` | the canvas or placed part rectangle differs from `explicit.region_sha256`; both digests are named | restore the accepted region or transfer a payload bound to this placement; nothing is written |
3847
3847
  | <a id="RIG_EXPLICIT_ISLAND_COUNT"></a>`RIG_EXPLICIT_ISLAND_COUNT` | `explicit.islands.length` differs from the non-dust 4-connected art island count of `parts/<part>.png`; the missing or extra `<part>__island<N>` slot names and island pixel counts are named | supply one `explicit.islands` entry per non-dust art island (`<part>`, `<part>__island2`, …); nothing is written |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rig-parts",
3
- "version": "1.63.0",
3
+ "version": "1.64.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": {
package/src/rig.ts CHANGED
@@ -2774,6 +2774,35 @@ export function buildRig(
2774
2774
  // from the drawn link `<link>_d` above it, and slots and weights on a stopped link bind `<link>_d`.
2775
2775
  const rebound = frontSlots.length === 0 ? slots : [...slots, ...frontSlots];
2776
2776
  const stops = cfg.stops === undefined ? null : applyStops(cfg.stops, chains, bones, rebound, skin, (original, emitted) => layoutAlias(explicitLayout.plan, original, emitted));
2777
+ for (const [part, mesh] of Object.entries(cfg.meshes)) if ('explicit' in mesh) {
2778
+ const defaultBone = typeof mesh.segments[0] === 'string' ? mesh.segments[0] : mesh.segments[0][0];
2779
+ if (mesh.explicit.islands !== undefined) {
2780
+ mesh.explicit.islands.forEach((isl, i) => {
2781
+ const att = skin[isl.slot]?.[isl.slot];
2782
+ const slotEntry = slots.find((s) => s.name === isl.slot);
2783
+ const expectedBone = isl.bone ?? defaultBone;
2784
+ const weightsChanged =
2785
+ att !== undefined &&
2786
+ 'type' in att &&
2787
+ JSON.stringify(att.weights) !== JSON.stringify(isl.weights.map(row => row.map(w => ({ bone: w.bone, x: w.x, y: w.y, weight: w.weight }))));
2788
+ const boneChanged = slotEntry !== undefined && slotEntry.bone !== expectedBone;
2789
+ if (weightsChanged || boneChanged) {
2790
+ fail('RIG_EXPLICIT_BINDING', `config.meshes.${part}.explicit.islands[${i}].weights`, 'the declared stops would change these accepted bindings; supply the accepted final emitted bone names and bind coordinates, or use a generated mesh; no payload is normalized');
2791
+ }
2792
+ });
2793
+ continue;
2794
+ }
2795
+ const att = skin[part]?.[part];
2796
+ const slotEntry = slots.find((s) => s.name === part);
2797
+ const weightsChanged =
2798
+ att !== undefined &&
2799
+ 'type' in att &&
2800
+ JSON.stringify(att.weights) !== JSON.stringify((mesh.explicit.weights ?? []).map(row => row.map(w => ({ bone: w.bone, x: w.x, y: w.y, weight: w.weight }))));
2801
+ const boneChanged = slotEntry !== undefined && slotEntry.bone !== defaultBone;
2802
+ if (weightsChanged || boneChanged) {
2803
+ fail('RIG_EXPLICIT_BINDING', `config.meshes.${part}.explicit.weights`, 'the declared stops would change these accepted bindings; supply the accepted final emitted bone names and bind coordinates, or use a generated mesh; no payload is normalized');
2804
+ }
2805
+ }
2777
2806
  // ---- the turn control (issue #251) -------------------------------------
2778
2807
  // Only when the config has `turn`: every other build adds nothing here, so no byte moves.
2779
2808
  if (cfg.turn !== undefined) refuseIfAny(turnRiderProblems(cfg.turn, cfg, parts.parts));
@@ -2795,30 +2824,6 @@ export function buildRig(
2795
2824
  const att = skin[row.part]?.[row.part];
2796
2825
  if (att !== undefined && 'type' in att) row.bones = meshBoundBones(att);
2797
2826
  }
2798
-
2799
- for (const [part, mesh] of Object.entries(cfg.meshes)) if ('explicit' in mesh) {
2800
- const defaultBone = typeof mesh.segments[0] === 'string' ? mesh.segments[0] : mesh.segments[0][0];
2801
- if (mesh.explicit.islands !== undefined) {
2802
- mesh.explicit.islands.forEach((isl, i) => {
2803
- const att = skin[isl.slot]?.[isl.slot];
2804
- const slotEntry = slots.find((s) => s.name === isl.slot);
2805
- const expectedBone = isl.bone ?? defaultBone;
2806
- const weightsChanged =
2807
- att !== undefined &&
2808
- 'type' in att &&
2809
- JSON.stringify(att.weights) !== JSON.stringify(isl.weights.map(row => row.map(w => ({ bone: w.bone, x: w.x, y: w.y, weight: w.weight }))));
2810
- const boneChanged = slotEntry !== undefined && slotEntry.bone !== expectedBone;
2811
- if (weightsChanged || boneChanged) {
2812
- fail('RIG_EXPLICIT_BINDING', `config.meshes.${part}.explicit.islands[${i}].weights`, 'the declared stops or turn would change these accepted bindings; supply the accepted final emitted bone names and bind coordinates, or use a generated mesh; no payload is normalized');
2813
- }
2814
- });
2815
- continue;
2816
- }
2817
- const att = skin[part]?.[part];
2818
- if (att !== undefined && 'type' in att && JSON.stringify(att.weights) !== JSON.stringify((mesh.explicit.weights ?? []).map(row => row.map(w => ({ bone: w.bone, x: w.x, y: w.y, weight: w.weight }))))) {
2819
- fail('RIG_EXPLICIT_BINDING', `config.meshes.${part}.explicit.weights`, 'the declared stops or turn would change these accepted bindings; supply the accepted final emitted bone names and bind coordinates, or use a generated mesh; no payload is normalized');
2820
- }
2821
- }
2822
2827
  refuseIfAny(problems);
2823
2828
  const name = `${cfg.key}_painting`;
2824
2829
  const rig: RigSpec = {