rig-parts 1.73.1 → 1.74.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
@@ -2854,6 +2854,36 @@ shin's tip against the stage point it stands on.
2854
2854
  with `exponent` 6 on `legwear`, 1.6. The same dance on `demo` as tracked, whose legs ride the
2855
2855
  hip, reads 18.36.
2856
2856
 
2857
+
2858
+ ### Equal-depth units in a sampled turn
2859
+
2860
+ With `turn.roles`, `turn.depths` remains a legacy layer-order table; it does
2861
+ not override sampled unit motion. Its See-through values (larger means farther
2862
+ back) are not interchangeable with `turn.unitDepths`, whose values are calibrated
2863
+ scalar depths in rig pixels (larger means nearer the viewer).
2864
+
2865
+ To make a mirrored pair of `"unit"` parts travel equally, author equal
2866
+ `turn.unitDepths.<part>` values. A part entry wins over a bone entry, including
2867
+ zero. Otherwise the producer samples the primary surface at each unit's setup
2868
+ landmark, calibrating `pivot + sampledZ - surfaceZRef`; mirrored landmarks on an
2869
+ asymmetric depth sheet can therefore travel differently. Only when that sample
2870
+ is unavailable does it fall back to the unit's own sheet. No symmetry is inferred
2871
+ from part names or positions, and equal legacy `depths` do not establish it.
2872
+
2873
+ Equal unit depths yield the same signed translation within a frame on both yaw
2874
+ and pitch; the sign reverses between negative and positive angles. Their local
2875
+ keys compensate for the parent carrier, so compare the complete inherited pose
2876
+ when measuring screen travel. For ears, the existing behind-cheek depth check
2877
+ still applies to an explicit override; use a depth at or behind both cheeks.
2878
+
2879
+ The build report prints `WARN RIG_TURN_UNIT_DEPTH_SUPPORT` when an implicit unit
2880
+ sample lies outside the surface's source pixel-index bounds or its sampled art
2881
+ alpha is below 255. It names the unit, source surface and sheet, raw crop
2882
+ coordinate, clamped art index and support alpha. Scalar-sheet alpha is not art
2883
+ support. This warning retains the extended scalar sample, all motion arithmetic
2884
+ and existing refusal behavior; it is not a gate. Explicit `unitDepths` entries
2885
+ bypass that sample and its warning, including an explicit zero.
2886
+
2857
2887
  ## 4. The command order
2858
2888
 
2859
2889
  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.
@@ -4528,6 +4558,7 @@ and stay absent).
4528
4558
  | <a id="RIG_TURN_HEAD_BONE"></a>`RIG_TURN_HEAD_BONE` | `buildRig` found no `"head"` (or body turn root) bone when emitting 2.5D turn carriers | declare `"head"` (or the body turn root bone) in `config.bones` |
4529
4559
  | <a id="RIG_TURN_SHARED_DEPTH_SCALE"></a>`RIG_TURN_SHARED_DEPTH_SCALE` | `"surface"` parts bound to one head or body turn declare different `near` polarities or `zScale` values | use one shared `near` and `zScale` across all `"surface"` parts in the turn group |
4530
4560
  | <a id="RIG_TURN_UNIT_DEPTH_REQUIRED"></a>`RIG_TURN_UNIT_DEPTH_REQUIRED` | a `"unit"` bone has neither a `unitDepths` entry nor a readable `"surface"` depth sheet | provide a `"surface"` depth sheet or an explicit `unitDepths.<part>` value |
4561
+ | <a id="RIG_TURN_UNIT_DEPTH_SUPPORT"></a>`RIG_TURN_UNIT_DEPTH_SUPPORT` | WARN only: an implicit unit depth sample is outside the source surface pixel-index bounds or its sampled art alpha is below 255 | author an explicit calibrated `unitDepths.<part>` value when this cross-part extrapolation is unintended; existing scalar sampling and refusal behavior are retained |
4531
4562
  | <a id="RIG_TURN_EAR_DEPTH_BEHIND_CHEEK"></a>`RIG_TURN_EAR_DEPTH_BEHIND_CHEEK` | an ear bone in `config.turn.roles` has a calibrated depth at or in front of the cheek surface depth | place the ear behind the cheek surface (for example via `config.turn.unitDepths`) |
4532
4563
 
4533
4564
  ## 7. The bars `check` enforces, and what only an eye answers today
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rig-parts",
3
- "version": "1.73.1",
3
+ "version": "1.74.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/build.ts CHANGED
@@ -1528,6 +1528,7 @@ export function rigStage(input: RigStageInput, rigc: RigcRunner, scratch: string
1528
1528
  if (from !== null) log(` animations beside the idle, from ${from.file} as written: ${Object.keys(from.table).join(', ') || 'none'}`);
1529
1529
  log(faceAxisLine(rig.faceAxis));
1530
1530
  if (rig.turn !== null) log(turnLine(rig.turn));
1531
+ for (const w of rig.turnWarnings ?? []) log(` WARN ${w.code}: ${w.object} — ${w.detail}`);
1531
1532
  log(
1532
1533
  rig.idleKeys === 'ctl'
1533
1534
  ? ` idle keys ctl: ${rig.meshKeyed.length} mesh-driving bone(s) keyed by the idle, each keyed through a same-origin <bone>_ctl parent`
package/src/rig.ts CHANGED
@@ -1132,6 +1132,8 @@ export interface AttachmentSetsFile {
1132
1132
  }
1133
1133
 
1134
1134
  export interface RigOutput {
1135
+ /** Sample-support warnings for the build report only; no emitted-file field. */
1136
+ turnWarnings?: Problem[];
1135
1137
  depthReferences?: Array<{ part: string; size: [number, number]; depth: PartDepthReference }>;
1136
1138
  rig: RigSpec;
1137
1139
  /** Internal explicit-layout provenance; serialized only as an external companion. */
@@ -3653,8 +3655,10 @@ export function buildWaistPairSource(
3653
3655
  for (const [bName, bEntry] of B) {
3654
3656
  boneLandmarks.set(bName, [bEntry.x, bEntry.y]);
3655
3657
  }
3658
+ const turnWarnings: Problem[] = [];
3656
3659
  if (cfg.turn?.roles !== undefined || cfg.turn?.body !== undefined) {
3657
3660
  buildDepth25DTurn(rig, motion, {
3661
+ warnings: turnWarnings,
3658
3662
  turn: cfg.turn,
3659
3663
  parts: parts.parts,
3660
3664
  images,
@@ -3717,6 +3721,7 @@ export function buildWaistPairSource(
3717
3721
  autoMotion,
3718
3722
  faceAxis,
3719
3723
  turn: turned?.rows ?? null,
3724
+ ...(turnWarnings.length === 0 ? {} : { turnWarnings }),
3720
3725
  ...(sets === undefined ? {} : { sets }),
3721
3726
  ...(explicitLayout.plan === undefined ? {} : { layout: explicitLayout.plan }),
3722
3727
  };
package/src/turn.ts CHANGED
@@ -1478,6 +1478,8 @@ export function sampleUnitPartDepth(scalar: Raster, art: Raster, ref: PartDepthR
1478
1478
  }
1479
1479
 
1480
1480
  export interface Depth25DTurnOptions {
1481
+ /** Report-only diagnostics; never serialized into the rig or motion. */
1482
+ warnings?: Problem[];
1481
1483
  turn: TurnSpec;
1482
1484
  parts: ReadonlyArray<{ name: string; x: number; y: number; w: number; h: number; depth?: PartDepthReference }>;
1483
1485
  images: ReadonlyMap<string, Raster>;
@@ -1684,6 +1686,23 @@ function buildDepth25DTurnGroup(
1684
1686
  const padY = Math.floor((ext.height - rec.h) / 2);
1685
1687
  const lx = Math.max(0, Math.min(ext.width - 1, Math.round(pt[0] - rec.x + padX)));
1686
1688
  const ly = Math.max(0, Math.min(ext.height - 1, Math.round(pt[1] - rec.y + padY)));
1689
+ // The scalar extension has opaque alpha even where the source art
1690
+ // supplies no opaque support. Report that extrapolation, not its value.
1691
+ if (opts.warnings !== undefined) {
1692
+ const art = opts.images.get(rec.name);
1693
+ const rawX = pt[0] - rec.x, rawY = pt[1] - rec.y;
1694
+ const outside = rawX < 0 || rawY < 0 || rawX > rec.w - 1 || rawY > rec.h - 1;
1695
+ const artX = Math.max(0, Math.min(rec.w - 1, lx - padX));
1696
+ const artY = Math.max(0, Math.min(rec.h - 1, ly - padY));
1697
+ const alpha = art?.data[(artY * rec.w + artX) * 4 + 3];
1698
+ if (alpha !== undefined && (outside || alpha !== 255)) {
1699
+ opts.warnings.push({
1700
+ code: 'RIG_TURN_UNIT_DEPTH_SUPPORT',
1701
+ object: `${group.fieldPrefix}.roles.${part}`,
1702
+ detail: `unit part "${part}" (bone "${bName}") samples surface "${rec.name}" sheet "${rec.depth.image}" at raw crop [${pt[0]}, ${pt[1]}] (${outside ? 'outside' : 'inside'} source sample bounds [${rec.x}, ${rec.y}]..[${rec.x + rec.w - 1}, ${rec.y + rec.h - 1}]); sampled art [${artX}, ${artY}] has art alpha ${alpha}, expected 255 without clamping; the existing extended scalar sample is retained; author ${group.fieldPrefix}.unitDepths.${part} for an explicit calibrated depth`,
1703
+ });
1704
+ }
1705
+ }
1687
1706
  const zUnit = levelToDepthZ(ext.data[(ly * ext.width + lx) * 4], rec.depth);
1688
1707
  resolved = pyRound(pivot + (zUnit - zRef), 4);
1689
1708
  }