rig-parts 1.60.0 → 1.60.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/NOTICE.md +1 -1
- package/README.md +2 -2
- package/cli.ts +9 -1
- package/docs/AUTHORING.md +4 -2
- package/docs/PAINTING.md +2 -2
- package/docs/STABILITY.md +1 -1
- package/package.json +1 -1
- package/src/build.ts +15 -1
- package/src/check.ts +154 -14
package/NOTICE.md
CHANGED
|
@@ -119,7 +119,7 @@ redistributed.
|
|
|
119
119
|
`allowDifferentLicense: false`: generated images may be used commercially and
|
|
120
120
|
the model must be credited, which this line does.
|
|
121
121
|
|
|
122
|
-
The [
|
|
122
|
+
The [rig-c/examples](https://github.com/rig-c/examples) README records
|
|
123
123
|
both, beside each character's generation record.
|
|
124
124
|
|
|
125
125
|
### Models named by the optional painting graph
|
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ The painting was generated for this repository with a public checkpoint (Hassaku
|
|
|
30
30
|
(Illustrious) v1.3 Style A, by Ikena, Fair AI Public License 1.0-SD) and no LoRA, on a
|
|
31
31
|
pose that holds the hands apart, then given one hand-detailer pass; its generation
|
|
32
32
|
record, See-through layers and licence are in
|
|
33
|
-
<a href="https://github.com/rig-c/
|
|
33
|
+
<a href="https://github.com/rig-c/examples">rig-c/examples</a>.
|
|
34
34
|
See-through ran twice, once on the whole figure and once on a square head crop.
|
|
35
35
|
Then one command: <code>rig-parts build --loop --lead-in 3</code>. The proposer's bones, meshes,
|
|
36
36
|
regions and motion were used as proposed but for one correction, written in the
|
|
@@ -209,7 +209,7 @@ head run on `st_input_head.png` is what gives the eyes more pixels
|
|
|
209
209
|
four, and `rig-parts comfy` is only a convenience for that route: every
|
|
210
210
|
stage after See-through reads files, whichever route wrote them. On the two
|
|
211
211
|
reference examples each run took 171–199 s through the wrapper, and on the demo 179 s (each run's
|
|
212
|
-
`meta.json`, in [
|
|
212
|
+
`meta.json`, in [rig-c/examples](https://github.com/rig-c/examples)).
|
|
213
213
|
|
|
214
214
|
### The two images See-through is fed: `inputs`
|
|
215
215
|
|
package/cli.ts
CHANGED
|
@@ -660,7 +660,15 @@ usage:
|
|
|
660
660
|
the default (AUTHORING §6, The world the readings are taken in). One
|
|
661
661
|
environment: line names the path, the step count and the sha256's first
|
|
662
662
|
16 hex; check.json's environment holds path (as given), sha256 and
|
|
663
|
-
steps. Without the flag no
|
|
663
|
+
steps. Without the flag no environment provenance key is written.
|
|
664
|
+
Physics rigs with readable declared seam pairs also report independent
|
|
665
|
+
default wind [1, 0] and still wind [0, 0], gravity [0, 1], under
|
|
666
|
+
SEAM_OVER_MOTION.worlds: gap/ghost maximum and worst frame only;
|
|
667
|
+
dir points to retained raw seam renders relative to check.json. No
|
|
668
|
+
new bar or warning, and the custom reading is unchanged. No wind
|
|
669
|
+
response or no band is SKIP; no pair or animation gives no worlds.
|
|
670
|
+
Nonphysics output is unchanged. In rig-play environment(null) returns
|
|
671
|
+
to the default world, not still air (AUTHORING, Physics seam environments).
|
|
664
672
|
|
|
665
673
|
rig-parts loop --frames <dir> --out <file.png | file.gif> [--palette]
|
|
666
674
|
Encode a frame set rigc render wrote (its --out directory, or the set
|
package/docs/AUTHORING.md
CHANGED
|
@@ -3896,7 +3896,7 @@ measured). `build --loop` encodes a product and takes none. One line,
|
|
|
3896
3896
|
`environment: <path>, rigc-environment/1, <n> step(s), sha256 <16 hex>`, says which
|
|
3897
3897
|
schedule, and `check.json` carries `environment` — `path` as the command line gave it,
|
|
3898
3898
|
the `sha256` of the bytes read, `steps` — after `pack_mode`; without the flag neither
|
|
3899
|
-
is written
|
|
3899
|
+
is written; the physics seam reference worlds below are separate probes.
|
|
3900
3900
|
|
|
3901
3901
|
The world before the first step is the runtime's, so a still schedule is the one that
|
|
3902
3902
|
states it: `{ "t": 0, "wind": [1, 0], "gravity": [0, 1] }` reads what a run without the
|
|
@@ -3908,6 +3908,8 @@ wind (1, 0), then (40, 0) from 0.5 s, reads `TEXTURE_STRETCH` 32.181 (FAIL, `hai
|
|
|
3908
3908
|
triangle 258), two meshes folding under `ANIMATIONS_DO_NOT_FOLD` (FAIL; none without the
|
|
3909
3909
|
flag) and 8 `CLOTH_SETTLES` warnings for 9 (selftest `CK515`).
|
|
3910
3910
|
|
|
3911
|
+
**Physics seam environments** (issue #591). Default: wind [1, 0], gravity [0, 1]. Still: wind [0, 0], gravity [0, 1]. In rig-play, environment(null) returns to Default; still air requires an explicit wind [0, 0]. For a physics rig with readable declared pairs and animations, SEAM_OVER_MOTION adds optional `worlds.default` and `worlds.still` readings. They are informational raw gap/ghost cell counts (including cells below the warning floor), never new bars or warnings; neither changes PASS, exit status or the bar count. Each world records its vectors, status and rows; each row names its pair, animation, drawn/banded frames and gap/ghost maximum and worst frame, cell size and world box. Per-frame hits are not embedded in check.json. A rendered world's `dir` is relative to check.json (`seam_motion/default` or `seam_motion/still`): it retains the complete rigc render, including frames.json, frame images and each animation's seam.json with all raw hits. The directories are published with check.json only after the readings are validated; scratch is removed, and an unrendered SKIP has no directory reference. The original reading and any custom `--environment` schedule keep their existing meaning; the two reference worlds are independent of that schedule. A rig with no wind response reports Still as SKIP with its reason; no band is SKIP, never a measured zero. With no readable declared pair or animation no worlds block is written. Only existing declared pairs are read: neighbouring limb segments and explicit seam requirements; thigh/hem pairs are not inferred. Nonphysics output stays byte-identical, with no extra key, line or render. Reference renders start physics at frame 0 at 12 fps; they do not certify a composed player scene or another sampling rate. Malformed sidecars or renderer failures remain named refusals.
|
|
3912
|
+
|
|
3911
3913
|
### compose
|
|
3912
3914
|
|
|
3913
3915
|
Every refusal below is collected with the others and printed at once, under
|
|
@@ -4756,7 +4758,7 @@ is built (`CK83`–`CK85`, `RQ30`–`RQ35`).
|
|
|
4756
4758
|
|
|
4757
4759
|
| step | reference-sample | reference-demo | source |
|
|
4758
4760
|
| --- | --- | --- | --- |
|
|
4759
|
-
| painting (GPU) | 20.2 s | 18.3 s | `generation.json` `elapsed_s`, in [
|
|
4761
|
+
| painting (GPU) | 20.2 s | 18.3 s | `generation.json` `elapsed_s`, in [rig-c/examples](https://github.com/rig-c/examples) |
|
|
4760
4762
|
| See-through full run (GPU) | 194.9 s | 198.5 s | `inputs/layers/full/meta.json` `elapsed_s` |
|
|
4761
4763
|
| See-through head run (GPU) | 170.9 s | 171.4 s | `inputs/layers/head/meta.json` `elapsed_s` |
|
|
4762
4764
|
| `build` (CPU, user + sys) | 14.6 s | 30.7 s | measured with `/usr/bin/time -p` on an Apple M4 machine under other load, rigc's child processes included; wall clock 12.7 s and 27.5 s |
|
package/docs/PAINTING.md
CHANGED
|
@@ -140,7 +140,7 @@ one way that passes `painting`: the `generation` block and the pose file of seed
|
|
|
140
140
|
and chosen to be the next public example — a character for a wind demo, where hair
|
|
141
141
|
and cloth move by physics under a side gust. It is the public example `wind`: its
|
|
142
142
|
painting, See-through layers and generation record are in
|
|
143
|
-
[
|
|
143
|
+
[rig-c/examples](https://github.com/rig-c/examples) (`wind/`), and its config,
|
|
144
144
|
proposal and build are [`examples/wind`](../examples/wind/README.md). The block:
|
|
145
145
|
|
|
146
146
|
```json
|
|
@@ -350,4 +350,4 @@ To paint one without the character drifting, repaint only the limb:
|
|
|
350
350
|
|
|
351
351
|
`comfy paint` does not run an inpainting graph; the demo's hand-detailer pass is
|
|
352
352
|
the same idea at hand scale (a masked repaint on the same checkpoint, recorded
|
|
353
|
-
beside its painting in
|
|
353
|
+
beside its painting in rig-c/examples).
|
package/docs/STABILITY.md
CHANGED
|
@@ -200,7 +200,7 @@ every number) are AUTHORING §3's tables, and the loader refuses any other by na
|
|
|
200
200
|
| `parts.json`, `parts/<name>.png` | `assemble`, `build` | one record per part: `from` (`<run>:<tag>` with a See-through v3 tag, `<run>:author:<label>` for a layer the author made — issue #256, read by no role rule — or `painting:<name>` for a patch), `cut_from` on a cut's piece only (the plan part it was cut from, issue #230), `limb` on a limb segment's part only (issue #349: `name`, `segment`, `of`, `bone`, and below a joint `joint`, `range` when the config segment declares one (issue #360), and `cap` — `at`, `width`, `radius`, `box`, `layer_px`, `image_px` with a cap image, `image_layer` after it when that image was a hidden-art layer — issue #359, `<run>:<layer>:hidden` — `missing_px`, `short_px` (`left`, `right`) after it only when `missing_px` is above 0 (issue #557, additive), `excluded_px` after them only on a subset set's limb part whose cap the set exclusion took a pixel of (issue #603, additive: each pixel taken out of the count it was in, so the cap's counts are the written PNG's), then `shape` and `upper_px` under the segment's `cap_shape` — `upper_px` without the shape only when the upper segment gave a pixel (issue #557, additive) — and after them `lower_px`, `lower_kept_px` and `lower_missing_px` under `cap_shape: "ball"` (issue #579, additive), and `edge`, `feather_radius` and `feathered_px` under its `edge`, and `cut_band`, `cut_offset`, `cut_at`, `cut_crossed`, `joint_crossed`, `band_px` and `stroke_luma_max` (when the band has a split) under its `cut_band`, issue #360), 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` — on a subset set's limb part counted after the set exclusion, issue #603 — `seam_override_px`, and `fringe_pushed_back` when not empty); `rig_size`, `scale_rig_per_source`, `ghost_px`, `recomposite`, and `scene` (`figure_alpha_above`, `figure_px`, `figure_mask`) under `config.scene` only, with the figure's mask `figure_rig.png` beside the recomposite (issue #242); `single_run_pad` (`file_size`, `side`, `left`, `top`) last, only for one run whose file is not the `seethrough.resolution` square (issue #366) |
|
|
201
201
|
| `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; a bone carries `inherit` only where config.bones declares one, rig-c's mode name as written (issue #348) |
|
|
202
202
|
| `rig/mesh_report.json` | `rig`, `build` | one row per mesh: `part`, `vertices`, `triangles`, `hull`, `bones` (the sorted names the attachment's weights bind in the written `rig.json`, in every mode — since 1.49.1 (issue #536) a rig with `turn` or `stops` names the twins and `<link>_d` bones there, where the row named the segment bones they replace: a fix of a wrong name, not a format change; a rig with neither writes the bytes it wrote), `max_influences`, `mean_influences`, `art_coverage`, then `grid` (a lattice); `mode` "contour" with `params`, `contour`, `regions` (`params.maxEdge` and `contour.outlineCut` only when the mesh declares `contour.maxEdge`; `params.bridges` and `contour.bridges` — `islands`, `units`, `joins`, and `faint` only when an island was left out as faint — only when it declares `contour.bridges`); or `mode` "influence" with `influence` (as declared), `reach` (`farthest`, `at`, `nearest`, `capped`, `widest`) and `lattice` (rig-c's `SegmentsLatticeReport`: `cols`, `rows`, `artCells`, `keptCells`, `passes`, `islands`) (issue #378); 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`); `influences`, as declared, when a lattice or contour mesh declares it (after `exponent`, or where `exponent` would be); `stray` and `stray_cleared` when a lattice mesh declares `stray` (after `influences`, or where `influences` would be); `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) |
|
|
203
|
-
| `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`, `environment` after it under `--environment` only (issue #538, additive: `path` as given, `sha256`, `steps`), `loop_max_diff`, the seam figures — `seam_mean`, `seam_px_over_40`, `seam_px_over_80` and, from issue #216, `seam_frame_scale`, the frame scale the bar divides the count by), `loop_physics` after `loop_max_diff` when the rig declares a physics constraint, `seam_border` after the seam figures when `parts.json` declares a scene (issue #242), `seam_pairs` after the seam figures when the seam bar fails or `--seam-pairs` asks (each row's picture under `check/seam/`; `--keep-stills` leaves the setup-pose still and its `frames.json` under `check/setup_still/`), then the judgement lines (`BREATH_VISIBLE`, `BLINK_NO_HOLE`, `CHAIN_LAG`, `TIP_OVER_ROOT`, `STILL_REGIONS_DARK`, `TEXTURE_STRETCH`), `ANIMATIONS_DO_NOT_FOLD` after them when `motion.json` holds an animation, the idle included — on every rig with an idle since issue #333 (issues #320, #363: `status`, `animations`, `fps`, `physics_left_out`, `folding` — each `animation`, `slot`, `attachment`, `frames`, `worst_frame`, `worst_triangles`, `triangles`, `physics_on`, `physics_off`, `physics_constraints`, `declared`, `line` — `folding_undeclared`, `declared_not_folding`, `bar`), `FEET_STAY_LEVEL` after it when a `footwear` part rides a bone that hangs from another and `motion.json` holds an animation (issue #348: `status`, `reason` on a SKIP only, `animations`, `fps`, `physics_left_out`, `bar_degrees`, `bar`, `feet` — each `part`, `bone`, `hangs_from`, `status`, `frames_posed`, `worst`), `LIMB_ONE_BONE` after it (and after `STRESS_OFF_CHAIN` where that is written) when `parts.json` holds a limb segment's part (issue #349: `status`, `parts` — each `part`, `limb`, `bone`, `status`, `others`), `SEAM_OVER_RANGE` after it when a limb record carries a `range` (issue #360: `status`, `frame_scale`, `physics_left_out`, `bar` — `mean`, `rig_px_over_40`, `level` — and `joints`, each `part`, `limb`, `segment`, `joint`, `bone`, `range`, `at`, `radius`, `under_px`, `status`, `poses` — each `degrees`, `status`, `mean`, `px_over_40`, `px_over_80`, `rig_px_over_40`, and `step_px` (`left`, `right`) only on a joint whose cap record carries `short_px` (issue #557, additive) — and `unread` on a joint not read), `SEAM_OVER_MOTION` after it when `parts.json` holds a limb segment or `--requirements` a `seam` (issue #493, a warning and not a bar: `status` — `WARN`, `CLEAR` or `SKIP` — `reason` on a SKIP only, `fps`, `floor`, `pairs` — each `pair`, `a`, `b`, `declared`, and `unread` on a pair not read — `animations`, `warnings`, `rows` — each `pair`, `animation`, `status`, `drawn`, `banded`, `opens`, `px`, `max_px`, `under_floor`), `CLOTH_SETTLES` after it when the rig declares a physics constraint and has an idle (issue #501, a warning and not a bar: `status` — `WARN` or `CLEAR` — `fps`, `frames`, `duration`, `floor_rig_px_per_s`, `floor`, `levers`, `warnings`, `rows` — each `tip`, `root`, `links`, `point`, `settles_at`, `max_rig_px_per_s`, `min_rig_px_per_s`), `RECOMPOSITE_HOLES` (its first figure `declared_scene` when `parts.json` declares a scene), `SETUP_POSE_VS_SOURCE`, `skipped`, `config` under `build` only (issue #607, additive: `path` as `--config` was typed, `sha256` of the bytes the build read; `check` and `compose` read no config and write none), a `requirements` block under `--requirements` (or `build`'s `config.requirements`) — `file` first from issue #556, additive: `path` as typed or declared, `sha256`, `keys` — and `PASS`; `check/build/skeleton.json`, `.atlas` and `.png` are the packed artifact rig-c writes |
|
|
203
|
+
| `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`, `environment` after it under `--environment` only (issue #538, additive: `path` as given, `sha256`, `steps`), `loop_max_diff`, the seam figures — `seam_mean`, `seam_px_over_40`, `seam_px_over_80` and, from issue #216, `seam_frame_scale`, the frame scale the bar divides the count by), `loop_physics` after `loop_max_diff` when the rig declares a physics constraint, `seam_border` after the seam figures when `parts.json` declares a scene (issue #242), `seam_pairs` after the seam figures when the seam bar fails or `--seam-pairs` asks (each row's picture under `check/seam/`; `--keep-stills` leaves the setup-pose still and its `frames.json` under `check/setup_still/`), then the judgement lines (`BREATH_VISIBLE`, `BLINK_NO_HOLE`, `CHAIN_LAG`, `TIP_OVER_ROOT`, `STILL_REGIONS_DARK`, `TEXTURE_STRETCH`), `ANIMATIONS_DO_NOT_FOLD` after them when `motion.json` holds an animation, the idle included — on every rig with an idle since issue #333 (issues #320, #363: `status`, `animations`, `fps`, `physics_left_out`, `folding` — each `animation`, `slot`, `attachment`, `frames`, `worst_frame`, `worst_triangles`, `triangles`, `physics_on`, `physics_off`, `physics_constraints`, `declared`, `line` — `folding_undeclared`, `declared_not_folding`, `bar`), `FEET_STAY_LEVEL` after it when a `footwear` part rides a bone that hangs from another and `motion.json` holds an animation (issue #348: `status`, `reason` on a SKIP only, `animations`, `fps`, `physics_left_out`, `bar_degrees`, `bar`, `feet` — each `part`, `bone`, `hangs_from`, `status`, `frames_posed`, `worst`), `LIMB_ONE_BONE` after it (and after `STRESS_OFF_CHAIN` where that is written) when `parts.json` holds a limb segment's part (issue #349: `status`, `parts` — each `part`, `limb`, `bone`, `status`, `others`), `SEAM_OVER_RANGE` after it when a limb record carries a `range` (issue #360: `status`, `frame_scale`, `physics_left_out`, `bar` — `mean`, `rig_px_over_40`, `level` — and `joints`, each `part`, `limb`, `segment`, `joint`, `bone`, `range`, `at`, `radius`, `under_px`, `status`, `poses` — each `degrees`, `status`, `mean`, `px_over_40`, `px_over_80`, `rig_px_over_40`, and `step_px` (`left`, `right`) only on a joint whose cap record carries `short_px` (issue #557, additive) — and `unread` on a joint not read), `SEAM_OVER_MOTION` after it when `parts.json` holds a limb segment or `--requirements` a `seam` (issue #493, a warning and not a bar: `status` — `WARN`, `CLEAR` or `SKIP` — `reason` on a SKIP only, `fps`, `floor`, `pairs` — each `pair`, `a`, `b`, `declared`, and `unread` on a pair not read — `animations`, `warnings`, `rows` — each `pair`, `animation`, `status`, `drawn`, `banded`, `opens`, `px`, `max_px`, `under_floor`; issue #591 adds optional `worlds` on physics rigs with readable declared pairs and animations: `default` and `still`, each `wind`, `gravity`, `status` (`REPORTED` or `SKIP`), `reason` on SKIP, `dir` only when rendered (relative to check.json: `seam_motion/default` or `seam_motion/still`, the complete retained render with frames.json and each animation's raw seam.json), and `rows` — pair, animation, status, drawn, banded, `gap` and `ghost`, each `max_px` and `worst` only; worst carries `index`, `px`, `cell`, `box`. Per-frame hits stay in the raw sidecars. These raw reference readings add no bar or warning and leave the custom environment reading unchanged), `CLOTH_SETTLES` after it when the rig declares a physics constraint and has an idle (issue #501, a warning and not a bar: `status` — `WARN` or `CLEAR` — `fps`, `frames`, `duration`, `floor_rig_px_per_s`, `floor`, `levers`, `warnings`, `rows` — each `tip`, `root`, `links`, `point`, `settles_at`, `max_rig_px_per_s`, `min_rig_px_per_s`), `RECOMPOSITE_HOLES` (its first figure `declared_scene` when `parts.json` declares a scene), `SETUP_POSE_VS_SOURCE`, `skipped`, `config` under `build` only (issue #607, additive: `path` as `--config` was typed, `sha256` of the bytes the build read; `check` and `compose` read no config and write none), a `requirements` block under `--requirements` (or `build`'s `config.requirements`) — `file` first from issue #556, additive: `path` as typed or declared, `sha256`, `keys` — and `PASS`; `check/build/skeleton.json`, `.atlas` and `.png` are the packed artifact rig-c writes |
|
|
204
204
|
| `layers.json`, `<index>_<name>.png` | `layers --export` | the wrapper form `layers` reads (issue #359): `width`, `height`, then `layers` — each `name`, `filename`, `left`, `top`, `right`, `bottom`, `depth_median` (the input's own; `null` for a PSD, the manifest's order then being the draw order) — drawn layers in draw order, then the hidden-art layers; a PNG's name is its index and its layer's name, and the manifest, not the file name, says which layer it is |
|
|
205
205
|
| the template `.psd` | `layers --template` | one empty layer per name `templateLayerNames` (`src/layers.ts`) holds, bottom first; the same canvas writes the same bytes |
|
|
206
206
|
| `proposal.json`, `render/landmarks*.png` | `propose` | the config's four rig sections, `constraints` with `--cloth` (issue #364: a physics constraint per tree link named `<bone>_physics`, a dangling end's `<bone>_translate`, and a skirt swing's transforms `<swing>_<tree>`), `turn` with `--full`/`--head`, `proposed_rig_scale` (issue #354), and `notes` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-parts",
|
|
3
|
-
"version": "1.60.
|
|
3
|
+
"version": "1.60.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/build.ts
CHANGED
|
@@ -1719,7 +1719,7 @@ export function sweepCheckLines(line: SweepLine, steps: ReadonlyMap<string, { le
|
|
|
1719
1719
|
* read nothing of (its SKIP) or the line could not read. A SKIP with nothing declared prints its reason alone.
|
|
1720
1720
|
*/
|
|
1721
1721
|
export function motionSeamLines(line: MotionSeamLine, plant: MotionSeamPlant = {}): string[] {
|
|
1722
|
-
if (line.status === 'SKIP' && line.rows.length === 0 && line.pairs.every((p) => p.unread === undefined)) return [`${MOTION_SEAM_LINE}: SKIP — ${String(line.reason)}`];
|
|
1722
|
+
if (line.worlds === undefined && line.status === 'SKIP' && line.rows.length === 0 && line.pairs.every((p) => p.unread === undefined)) return [`${MOTION_SEAM_LINE}: SKIP — ${String(line.reason)}`];
|
|
1723
1723
|
const read = `${line.pairs.length} declared pair(s) in ${line.animations.length} animation(s) (${line.animations.join(', ')}), read by rigc render --seam at ${line.fps} fps on the build as played`;
|
|
1724
1724
|
const head =
|
|
1725
1725
|
line.status === 'SKIP'
|
|
@@ -1731,6 +1731,20 @@ export function motionSeamLines(line: MotionSeamLine, plant: MotionSeamPlant = {
|
|
|
1731
1731
|
...line.rows.filter((r) => r.status === 'skip').map((r) => `seam "${r.pair}" in "${r.animation}": SKIP — rigc read no band on any of the ${r.drawn} frame(s) both slots draw, so there is no seam to read`),
|
|
1732
1732
|
...(plant.silent === true ? [] : line.rows.filter((r) => r.opens.length > 0).map(motionSeamWarnText)),
|
|
1733
1733
|
...line.rows.filter((r) => r.opens.length === 0 && r.under_floor.length > 0).map((r) => `seam "${r.pair}" in "${r.animation}": under the floor — rigc names ${r.under_floor.length} frame(s) open ${frameRuns(r.under_floor)}, each opening one cell thick`),
|
|
1734
|
+
...(line.worlds === undefined ? [] : (['default', 'still'] as const).flatMap(name => {
|
|
1735
|
+
const w = line.worlds?.[name];
|
|
1736
|
+
if (w === undefined) return [];
|
|
1737
|
+
const label = 'seam world "' + name + '"';
|
|
1738
|
+
const header = label + ' wind [' + w.wind.join(', ') + '], gravity [' + w.gravity.join(', ') + ']: ' + w.status +
|
|
1739
|
+
(w.status === 'SKIP' ? ' — ' + String(w.reason) : ' — raw gap/ghost cell counts, informational, not judged') +
|
|
1740
|
+
(w.dir === undefined ? '' : '; raw render: ' + w.dir);
|
|
1741
|
+
return [header, ...w.rows.map(r => {
|
|
1742
|
+
const head = label + ' "' + r.pair + '" in "' + r.animation + '": ';
|
|
1743
|
+
if (r.status === 'skip') return head + 'SKIP — rigc read no band';
|
|
1744
|
+
const at = (kind: typeof r.gap) => kind.worst === null ? 'none' : 'frame ' + kind.worst.index + ' (t = ' + kind.worst.index / line.fps + 's)';
|
|
1745
|
+
return head + 'gap ' + r.gap.max_px + ' cell(s), worst ' + at(r.gap) + '; ghost ' + r.ghost.max_px + ' cell(s), worst ' + at(r.ghost);
|
|
1746
|
+
})];
|
|
1747
|
+
})),
|
|
1734
1748
|
];
|
|
1735
1749
|
}
|
|
1736
1750
|
|
package/src/check.ts
CHANGED
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
* `TIME_SLACK`, issue #244).
|
|
79
79
|
*/
|
|
80
80
|
import { createHash } from 'node:crypto';
|
|
81
|
-
import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
82
|
-
import { basename, delimiter, dirname, join, resolve } from 'node:path';
|
|
81
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
82
|
+
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
83
83
|
import { measureRecomposite, type RecompositeFigures, recompositeRecord, scenePhrase, sourceInRig } from './assemble.ts';
|
|
84
84
|
import { type BoneTransform, computeExactFrameTransforms, cropToSpineY, normaliseDegrees, toBoneLocal } from './coords.ts';
|
|
85
85
|
import { capBox, inDisc } from './limbs.ts';
|
|
@@ -160,7 +160,7 @@ import {
|
|
|
160
160
|
} from './requirements.ts';
|
|
161
161
|
import { RIG_BONE_INHERIT, type RigSpec as RigcRigSpec } from 'rig-c/src/rig.ts';
|
|
162
162
|
import { expandStops } from 'rig-c/src/stop.ts';
|
|
163
|
-
import { ENVIRONMENT_SPEC, EnvironmentError, readEnvironmentSchedule } from 'rig-c/src/environment.ts';
|
|
163
|
+
import { ENVIRONMENT_SPEC, EnvironmentError, environmentResponse, readEnvironmentSchedule } from 'rig-c/src/environment.ts';
|
|
164
164
|
import { alphaComposite } from './raster/composite.ts';
|
|
165
165
|
import { readPng, writePng } from './raster/png.ts';
|
|
166
166
|
import { newFloatImage, newRaster, type Raster } from './raster/types.ts';
|
|
@@ -3535,6 +3535,7 @@ export const MOTION_SEAM_LINE = 'SEAM_OVER_MOTION';
|
|
|
3535
3535
|
|
|
3536
3536
|
/** The seam reading's scratch build and renders, under `--out`; removed before the stage returns. */
|
|
3537
3537
|
const MOTION_SEAM_SCRATCH = '_seam_motion';
|
|
3538
|
+
const MOTION_SEAM_DIR = 'seam_motion';
|
|
3538
3539
|
|
|
3539
3540
|
/** rig-c's seam reading: the file it writes in each set's directory, and its format tag. */
|
|
3540
3541
|
export const SEAM_READING_FILE = 'seam.json';
|
|
@@ -3584,6 +3585,8 @@ export interface MotionSeamLine {
|
|
|
3584
3585
|
/** Rows with a frame over the floor: the WARN lines. */
|
|
3585
3586
|
warnings: number;
|
|
3586
3587
|
rows: MotionSeamRow[];
|
|
3588
|
+
/** Physics-only reference worlds (issue #591); do not participate in warnings or bars. */
|
|
3589
|
+
worlds?: MotionSeamWorlds;
|
|
3587
3590
|
}
|
|
3588
3591
|
|
|
3589
3592
|
/**
|
|
@@ -3640,7 +3643,109 @@ export interface MotionSeamPlant {
|
|
|
3640
3643
|
silent?: true;
|
|
3641
3644
|
}
|
|
3642
3645
|
|
|
3643
|
-
|
|
3646
|
+
|
|
3647
|
+
/** One raw opening, in rigc cells; its frame time is index / the line's fps. */
|
|
3648
|
+
export interface MotionSeamHit {
|
|
3649
|
+
index: number;
|
|
3650
|
+
px: number;
|
|
3651
|
+
cell: number;
|
|
3652
|
+
box: [number, number, number, number];
|
|
3653
|
+
}
|
|
3654
|
+
export interface MotionSeamWorldKind {
|
|
3655
|
+
max_px: number;
|
|
3656
|
+
worst: MotionSeamHit | null;
|
|
3657
|
+
}
|
|
3658
|
+
/** Raw gap and ghost readings, including cells under the warning floor. */
|
|
3659
|
+
export interface MotionSeamWorldRow {
|
|
3660
|
+
pair: string;
|
|
3661
|
+
animation: string;
|
|
3662
|
+
status: 'read' | 'skip';
|
|
3663
|
+
drawn: number;
|
|
3664
|
+
banded: number;
|
|
3665
|
+
gap: MotionSeamWorldKind;
|
|
3666
|
+
ghost: MotionSeamWorldKind;
|
|
3667
|
+
}
|
|
3668
|
+
export interface MotionSeamWorld {
|
|
3669
|
+
wind: [number, number];
|
|
3670
|
+
gravity: [number, number];
|
|
3671
|
+
status: 'REPORTED' | 'SKIP';
|
|
3672
|
+
reason?: string;
|
|
3673
|
+
/** Complete rigc render directory, relative to check.json; absent when not rendered. */
|
|
3674
|
+
dir?: string;
|
|
3675
|
+
rows: MotionSeamWorldRow[];
|
|
3676
|
+
}
|
|
3677
|
+
export interface MotionSeamWorlds {
|
|
3678
|
+
default: MotionSeamWorld;
|
|
3679
|
+
still: MotionSeamWorld;
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
/** No callback and no output for nonphysics; a wind no constraint answers is not measured. */
|
|
3683
|
+
export function motionSeamWorlds(physics: boolean, answersWind: boolean, read: (world: 'default' | 'still') => MotionSeamWorldRow[]): MotionSeamWorlds | undefined {
|
|
3684
|
+
if (!physics) return undefined;
|
|
3685
|
+
const world = (wind: [number, number], rows: MotionSeamWorldRow[], reason?: string): MotionSeamWorld => {
|
|
3686
|
+
const measured = rows.some(r => r.status === 'read');
|
|
3687
|
+
return { wind, gravity: [0, 1], status: measured ? 'REPORTED' : 'SKIP',
|
|
3688
|
+
...(measured ? {} : { reason: reason ?? 'rigc read no band on any frame of any declared pair in any animation' }), rows };
|
|
3689
|
+
};
|
|
3690
|
+
return {
|
|
3691
|
+
default: world([1, 0], read('default')),
|
|
3692
|
+
still: answersWind ? world([0, 0], read('still')) : world([0, 0], [], 'no physics constraint answers wind; still air is not separately measurable'),
|
|
3693
|
+
};
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
/** The same validated sidecars as the warning, retaining the raw gap and ghost cells. */
|
|
3697
|
+
export function readMotionSeamWorldRows(dir: string, animations: readonly string[], fps: number, pairs: readonly MotionSeamPair[]): MotionSeamWorldRow[] {
|
|
3698
|
+
const rows: MotionSeamWorldRow[] = [];
|
|
3699
|
+
readMotionSeams(dir, animations, fps, pairs, {}, rows);
|
|
3700
|
+
return rows;
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
|
|
3704
|
+
/** Publish complete validated render directories with check.json; roll back if its writer fails. */
|
|
3705
|
+
export function publishMotionSeamWorlds(out: string, scratch: string, worlds: MotionSeamWorlds, publishCheck: () => void): void {
|
|
3706
|
+
const problems: Problem[] = [];
|
|
3707
|
+
const names = (['default', 'still'] as const).filter(name => worlds[name].dir !== undefined);
|
|
3708
|
+
for (const name of names) {
|
|
3709
|
+
const world = worlds[name];
|
|
3710
|
+
const home = join(scratch, name);
|
|
3711
|
+
if (world.dir !== MOTION_SEAM_DIR + '/' + name) problems.push({ code: 'FRAMES_SIDECAR', object: 'seam world ' + name, detail: 'raw directory reference must be ' + MOTION_SEAM_DIR + '/' + name });
|
|
3712
|
+
const manifest = readJson(join(home, 'frames.json'), problems);
|
|
3713
|
+
const sets = manifest !== null && Array.isArray(manifest.sets) ? manifest.sets.filter(isRecord) : [];
|
|
3714
|
+
for (const animation of new Set(world.rows.map(row => row.animation))) {
|
|
3715
|
+
const set = sets.find(item => item.animation === animation);
|
|
3716
|
+
const rawDir = set !== undefined && typeof set.dir === 'string' ? set.dir : null;
|
|
3717
|
+
const at = rawDir === null ? null : resolve(home, rawDir);
|
|
3718
|
+
const rel = at === null ? '..' : relative(home, at);
|
|
3719
|
+
if (rawDir === null || isAbsolute(rawDir) || at === null || isAbsolute(rel) || /^\.\.(?:[\\/]|$)/.test(rel) || !existsSync(join(at, SEAM_READING_FILE))) {
|
|
3720
|
+
problems.push({ code: 'FRAMES_SIDECAR', object: home, detail: 'animation ' + JSON.stringify(animation) + ' raw dir ' + JSON.stringify(rawDir) + ' must be relative, stay inside its render directory and hold seam.json' });
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
refuseIfAny(problems);
|
|
3725
|
+
const stage = join(scratch, '_publish');
|
|
3726
|
+
const previous = join(scratch, '_previous');
|
|
3727
|
+
const kept = join(out, MOTION_SEAM_DIR);
|
|
3728
|
+
const check = join(out, 'check.json');
|
|
3729
|
+
const previousCheck = existsSync(check) ? readFileSync(check) : null;
|
|
3730
|
+
mkdirSync(out, { recursive: true });
|
|
3731
|
+
mkdirSync(stage, { recursive: true });
|
|
3732
|
+
for (const name of names) renameSync(join(scratch, name), join(stage, name));
|
|
3733
|
+
const hadPrevious = existsSync(kept);
|
|
3734
|
+
if (hadPrevious) renameSync(kept, previous);
|
|
3735
|
+
try {
|
|
3736
|
+
renameSync(stage, kept);
|
|
3737
|
+
publishCheck();
|
|
3738
|
+
} catch (err) {
|
|
3739
|
+
rmSync(kept, { recursive: true, force: true });
|
|
3740
|
+
if (hadPrevious) renameSync(previous, kept);
|
|
3741
|
+
if (previousCheck === null) rmSync(check, { force: true });
|
|
3742
|
+
else writeFileSync(check, previousCheck);
|
|
3743
|
+
throw err;
|
|
3744
|
+
}
|
|
3745
|
+
rmSync(previous, { recursive: true, force: true });
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
type SeamHitRead = MotionSeamHit;
|
|
3644
3749
|
|
|
3645
3750
|
/**
|
|
3646
3751
|
* The rows of one `rigc render --seam` run over every animation: `frames.json`
|
|
@@ -3649,7 +3754,7 @@ type SeamHitRead = { index: number; px: number; cell: number; box: [number, numb
|
|
|
3649
3754
|
* file that is missing, a file that is not the animation's or not at the rate
|
|
3650
3755
|
* asked, a pair out of the flag's order, and a field of the wrong shape.
|
|
3651
3756
|
*/
|
|
3652
|
-
export function readMotionSeams(dir: string, animations: readonly string[], fps: number, pairs: readonly MotionSeamPair[], plant: MotionSeamPlant = {}): MotionSeamRow[] {
|
|
3757
|
+
export function readMotionSeams(dir: string, animations: readonly string[], fps: number, pairs: readonly MotionSeamPair[], plant: MotionSeamPlant = {}, worldRows?: MotionSeamWorldRow[]): MotionSeamRow[] {
|
|
3653
3758
|
const problems: Problem[] = [];
|
|
3654
3759
|
const bad = (object: string, detail: string): void => {
|
|
3655
3760
|
problems.push({ code: 'FRAMES_SIDECAR', object, detail });
|
|
@@ -3686,6 +3791,7 @@ export function readMotionSeams(dir: string, animations: readonly string[], fps:
|
|
|
3686
3791
|
return;
|
|
3687
3792
|
}
|
|
3688
3793
|
const over = new Map<number, number>();
|
|
3794
|
+
const kinds: Record<'gap' | 'ghost', MotionSeamWorldKind> = { gap: { max_px: 0, worst: null }, ghost: { max_px: 0, worst: null } };
|
|
3689
3795
|
for (const kind of ['gap', 'ghost'] as const) {
|
|
3690
3796
|
const k = p[kind];
|
|
3691
3797
|
const frames = isRecord(k) && Array.isArray(k.frames) ? k.frames.map(hitOf) : null;
|
|
@@ -3693,8 +3799,12 @@ export function readMotionSeams(dir: string, animations: readonly string[], fps:
|
|
|
3693
3799
|
bad(`${at} ${kind}`, 'is not { frames: [{ index, px, cell, box }…], spans, worst } as rigc-seam/1 writes it');
|
|
3694
3800
|
return;
|
|
3695
3801
|
}
|
|
3696
|
-
|
|
3802
|
+
const hits = frames as SeamHitRead[];
|
|
3803
|
+
const worst = hits.reduce<MotionSeamHit | null>((best, h) => best === null || h.px > best.px || (h.px === best.px && h.index < best.index) ? h : best, null);
|
|
3804
|
+
kinds[kind] = { max_px: worst?.px ?? 0, worst };
|
|
3805
|
+
for (const h of hits) if (plant.noFloor === true || openingOverFloor(h)) over.set(h.index, Math.max(over.get(h.index) ?? 0, h.px));
|
|
3697
3806
|
}
|
|
3807
|
+
worldRows?.push({ pair: want.pair, animation, status: p.status, drawn: p.drawn, banded: p.banded, gap: kinds.gap, ghost: kinds.ghost });
|
|
3698
3808
|
const opens = [...over.keys()].sort((x, y) => x - y);
|
|
3699
3809
|
rows.push({
|
|
3700
3810
|
pair: want.pair,
|
|
@@ -4070,13 +4180,15 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
4070
4180
|
const seamDir = join(out, SEAM_DIR);
|
|
4071
4181
|
const keptDir = join(out, STILL_DIR);
|
|
4072
4182
|
const foldDir = join(out, FOLD_SCRATCH);
|
|
4073
|
-
|
|
4183
|
+
const seamMotionDir = join(out, MOTION_SEAM_SCRATCH);
|
|
4184
|
+
for (const d of [buildDir, idleDir, stillDir, isoDir, loopDir, seamDir, keptDir, foldDir, seamMotionDir]) rmSync(d, { recursive: true, force: true });
|
|
4074
4185
|
const written: string[] = [];
|
|
4075
4186
|
const write = (name: string, data: string): void => {
|
|
4076
4187
|
writeFileSync(join(out, name), data);
|
|
4077
4188
|
written.push(name);
|
|
4078
4189
|
};
|
|
4079
4190
|
|
|
4191
|
+
try {
|
|
4080
4192
|
// 1. build, packed, under the policy profile
|
|
4081
4193
|
const build = rigc(['build', '--rig', inp.rigPath, '--motion', inp.motionPath, '--out', buildDir, ...packedBuildArgs(mode)]);
|
|
4082
4194
|
const gateHtml = buildGateLines(build.out);
|
|
@@ -4429,15 +4541,38 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
4429
4541
|
else if (animationNames.length === 0) motionSeam = motionSeamLine(pairs, [], fps, [], 'motion.json holds no animation to read the declared pairs in');
|
|
4430
4542
|
else if (read.length === 0) motionSeam = motionSeamLine(pairs, animationNames, fps, []);
|
|
4431
4543
|
else {
|
|
4432
|
-
const seamMotionDir = join(out, MOTION_SEAM_SCRATCH);
|
|
4433
4544
|
rmSync(seamMotionDir, { recursive: true, force: true });
|
|
4434
|
-
|
|
4545
|
+
{
|
|
4435
4546
|
const list = read.map((p) => `${p.a}:${p.b}`).join(',');
|
|
4436
|
-
const
|
|
4547
|
+
const activeDest = stripped === null ? seamMotionDir : join(seamMotionDir, env === null ? 'default' : 'active');
|
|
4548
|
+
const renderedWorlds = new Set<'default' | 'still'>();
|
|
4549
|
+
const r = measured(['render', '--candidate', buildDir, '--seam', list, '--fps', String(fps), '--max', String(FOLD_RENDER_MAX), '--out', activeDest]);
|
|
4437
4550
|
if (r.status !== 0) refuseIfAny(rigcFailed(`render --seam ${list} --fps ${fps}`, r, r.out.split('\n').filter((l) => l.includes('FAIL') || l.includes('rigc:'))));
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4551
|
+
const activeWorldRows: MotionSeamWorldRow[] = [];
|
|
4552
|
+
motionSeam = motionSeamLine(pairs, animationNames, fps, readMotionSeams(activeDest, animationNames, fps, read, {}, stripped !== null && env === null ? activeWorldRows : undefined));
|
|
4553
|
+
if (stripped !== null) {
|
|
4554
|
+
const response = environmentResponse(readFileSync(join(buildDir, 'skeleton.json'), 'utf8'));
|
|
4555
|
+
motionSeam.worlds = motionSeamWorlds(true, response.wind.length > 0, world => {
|
|
4556
|
+
if (world === 'default' && env === null) { renderedWorlds.add(world); return activeWorldRows; }
|
|
4557
|
+
const dest = join(seamMotionDir, world);
|
|
4558
|
+
const args = ['render', '--candidate', buildDir, '--seam', list, '--fps', String(fps), '--max', String(FOLD_RENDER_MAX), '--out', dest];
|
|
4559
|
+
if (world === 'still') {
|
|
4560
|
+
const schedule = join(seamMotionDir, 'still.json');
|
|
4561
|
+
writeFileSync(schedule, JSON.stringify({ spec: ENVIRONMENT_SPEC, steps: [{ t: 0, wind: [0, 0] }] }));
|
|
4562
|
+
args.push('--environment', schedule);
|
|
4563
|
+
}
|
|
4564
|
+
// These reference worlds are independent of the caller's --environment.
|
|
4565
|
+
const rendered = rigc(args);
|
|
4566
|
+
if (rendered.status !== 0) refuseIfAny(rigcFailed('render --seam (' + world + ' reference world)', rendered, rendered.out.split('\n').filter(l => l.includes('FAIL') || l.includes('rigc:'))));
|
|
4567
|
+
const rows = readMotionSeamWorldRows(dest, animationNames, fps, read);
|
|
4568
|
+
renderedWorlds.add(world);
|
|
4569
|
+
return rows;
|
|
4570
|
+
});
|
|
4571
|
+
for (const world of renderedWorlds) {
|
|
4572
|
+
const reading = motionSeam.worlds?.[world];
|
|
4573
|
+
if (reading !== undefined) reading.dir = MOTION_SEAM_DIR + '/' + world;
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4441
4576
|
}
|
|
4442
4577
|
}
|
|
4443
4578
|
}
|
|
@@ -4692,7 +4827,9 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
4692
4827
|
problems.unshift(...barProblems);
|
|
4693
4828
|
problems.push(...foldProblems, ...levelProblems, ...stressProblems, ...limbProblems, ...sweepProblems, ...reqProblems);
|
|
4694
4829
|
figures.PASS = problems.length === 0;
|
|
4695
|
-
write('check.json', `${JSON.stringify(figures, null, 1)}\n`);
|
|
4830
|
+
const publishCheck = (): void => write('check.json', `${JSON.stringify(figures, null, 1)}\n`);
|
|
4831
|
+
if (motionSeam.worlds === undefined) publishCheck();
|
|
4832
|
+
else publishMotionSeamWorlds(out, seamMotionDir, motionSeam.worlds, publishCheck);
|
|
4696
4833
|
const bars: CheckReport['bars'] = { measured: ['gate'], skipped: [] };
|
|
4697
4834
|
const tally = (bar: string, reason: string | null): void => {
|
|
4698
4835
|
if (reason === null) bars.measured.push(bar);
|
|
@@ -4731,6 +4868,9 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
4731
4868
|
cloth,
|
|
4732
4869
|
...(sweepSteps.size === 0 ? {} : { sweepSteps }),
|
|
4733
4870
|
};
|
|
4871
|
+
} finally {
|
|
4872
|
+
rmSync(seamMotionDir, { recursive: true, force: true });
|
|
4873
|
+
}
|
|
4734
4874
|
}
|
|
4735
4875
|
|
|
4736
4876
|
// ---------------------------------------------------------------------------
|