rig-parts 1.5.0 → 1.5.2
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 +3 -3
- package/cli.ts +15 -8
- package/docs/AUTHORING.md +25 -7
- package/docs/STABILITY.md +1 -1
- package/package.json +1 -1
- package/src/build.ts +2 -2
- package/src/check.ts +48 -9
package/README.md
CHANGED
|
@@ -37,8 +37,8 @@ is taken from the full run, because the twin tails leave the head crop sideways
|
|
|
37
37
|
(a plan edit). <code>check</code> printed, verbatim:<br/>
|
|
38
38
|
<code>50 assertions: 24 measured (24 passed, 0 failed), 26 skipped, 0 not in profile "spine-html"</code><br/>
|
|
39
39
|
<code>loop: idle 49 frame(s) at 12 fps, f0000 vs f0048 (t = 4s): max |d| 0 (0 required)</code><br/>
|
|
40
|
-
<code>seam: setup pose at 714x1216, scale 0.9422: mean |d| 0.
|
|
41
|
-
<code>pack: skeleton.png 922x1348, 22 region(s),
|
|
40
|
+
<code>seam: setup pose at 714x1216, scale 0.9422: mean |d| 0.234 (<= 1.0), 2 px over 40, 2.25 rig px (the count / s²; <= 50), 0 px over 80 (reported)</code><br/>
|
|
41
|
+
<code>pack: skeleton.png 922x1348, 22 region(s), 94.9% covered, padding 2, page edges free, shape polygon</code><br/>
|
|
42
42
|
<code>--seam silhouette</code> repaired the navy blobs the default rule leaves on the white
|
|
43
43
|
blouse (recomposite error pixels 11,050 → 9,540); the default stays
|
|
44
44
|
<code>near-white</code>, the reference implementation's rule, so the examples stay
|
|
@@ -60,7 +60,7 @@ made the contact sheet.
|
|
|
60
60
|
<p align="center"><em>
|
|
61
61
|
The plain fixture, <code>examples/sample</code>: same checkpoint, no LoRA, and a rig
|
|
62
62
|
built from its own proposal with no hand edit at all —
|
|
63
|
-
<code>seam:
|
|
63
|
+
<code>seam: setup pose at 379x1216, scale 1.0116: mean |d| 0.204 (<= 1.0), 2 px over 40, 1.95 rig px (the count / s²; <= 50), 0 px over 80 (reported)</code>, loop max |d| 0.
|
|
64
64
|
</em></p>
|
|
65
65
|
|
|
66
66
|
<p align="center"><em>
|
package/cli.ts
CHANGED
|
@@ -225,12 +225,17 @@ usage:
|
|
|
225
225
|
contact.png, motion_heat.png and check.json (with rigc_entry, the rigc
|
|
226
226
|
entry that gated the build, and pack_mode, the two pack flags). PASS needs every gate summary
|
|
227
227
|
"0 failed", the seam (setup pose vs the flat composite of parts/) at mean
|
|
228
|
-
|d| <= ${SEAM_MEAN_BAR.toFixed(1)} with <= ${SEAM_PX_BAR} px over ${SEAM_PX_LEVEL}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
228
|
+
|d| <= ${SEAM_MEAN_BAR.toFixed(1)} with <= ${SEAM_PX_BAR} rig px over ${SEAM_PX_LEVEL} (the frame pixels over ${SEAM_PX_LEVEL}
|
|
229
|
+
divided by s², s the frame scale the seam: line prints and check.json's
|
|
230
|
+
seam_frame_scale holds, so the bar means the same at every scale), and
|
|
231
|
+
the loop (idle frame 0 vs the frame at t = duration) at max |d| 0. A
|
|
232
|
+
failing seam, or any seam under --seam-pairs, is split by the pair of
|
|
233
|
+
parts each counted pixel lies between: one "seam pair" line each, worst
|
|
234
|
+
first, with its count in frame px, frame and rig boxes and a picture
|
|
235
|
+
under seam/, and check.json's seam_pairs (AUTHORING §7). A seam picture
|
|
236
|
+
is the setup-pose frame with that pair's counted pixels painted magenta
|
|
237
|
+
(255, 0, 255) — magenta = a counted pixel — and a cyan (0, 255, 255)
|
|
238
|
+
box one pixel outside them. --keep-stills leaves the setup-pose still the seam reads,
|
|
234
239
|
rigc's render directory with its frames.json (viewport and background),
|
|
235
240
|
under setup_still/. Then six judgement lines, each in
|
|
236
241
|
check.json and on the console as NAME: PASS|FAIL|SKIP with its figures and
|
|
@@ -694,12 +699,13 @@ function rigcRunner(bin: string): RigcRunner {
|
|
|
694
699
|
};
|
|
695
700
|
}
|
|
696
701
|
|
|
697
|
-
|
|
702
|
+
/** `switches` are the command's bare flags, taken out by the caller before this reads the valued ones; they are only named in the unknown-flag refusal, so it lists every flag the command takes. */
|
|
703
|
+
function flags(args: string[], known: readonly string[], command: string, optional: readonly string[] = [], switches: readonly string[] = []): Map<string, string> | string {
|
|
698
704
|
const got = new Map<string, string>();
|
|
699
705
|
const all = [...known, ...optional];
|
|
700
706
|
for (let i = 0; i < args.length; i += 2) {
|
|
701
707
|
const flag = args[i];
|
|
702
|
-
if (!all.includes(flag)) return `${command} does not take "${flag}"; it takes ${all.join(', ')}`;
|
|
708
|
+
if (!all.includes(flag)) return `${command} does not take "${flag}"; it takes ${[...all, ...switches].join(', ')}`;
|
|
703
709
|
if (args[i + 1] === undefined) return `${flag} needs a value`;
|
|
704
710
|
if (got.has(flag)) return `${flag} is given twice`;
|
|
705
711
|
got.set(flag, args[i + 1]);
|
|
@@ -719,6 +725,7 @@ function cmdCheck(args: string[]): number {
|
|
|
719
725
|
['--rig', '--out'],
|
|
720
726
|
'check',
|
|
721
727
|
['--parts', '--source', '--page-edges', '--pack-shape', '--requirements'],
|
|
728
|
+
['--seam-pairs', '--keep-stills'],
|
|
722
729
|
);
|
|
723
730
|
if (typeof f === 'string') return usage(f);
|
|
724
731
|
const rig = f.get('--rig') as string;
|
package/docs/AUTHORING.md
CHANGED
|
@@ -1048,10 +1048,15 @@ constraint. Declare the bend in `meshes.<part>.auto.stimulus` (issue #196, the f
|
|
|
1048
1048
|
Choose `degrees` as the largest bend, per link, that the physics is expected to reach in the
|
|
1049
1049
|
animations that drive it: the mesh is held only to what was declared, so a smaller bend passes a
|
|
1050
1050
|
mesh that tears at the real one, and nothing here checks the declaration against the
|
|
1051
|
-
constraint.
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
the
|
|
1051
|
+
constraint. The recipe for a thin strand only physics moves is therefore two steps: put its
|
|
1052
|
+
chain on the strand's own art, and declare the stimulus. The chain's position decides whether
|
|
1053
|
+
the motion bound can be held at all: on the scarf strap cut out of the sweater, with the chain
|
|
1054
|
+
on the strap (the tracked example's), the automatic mesh passes at the default
|
|
1055
|
+
`maxLocalDeformation` 1 with 68 vertices (the contour mesh's 133); with the chain beside the
|
|
1056
|
+
strap, as the proposal first placed it, the same part is refused `AUTO_MESH_MOTION` at 1 and
|
|
1057
|
+
passes only at 3, with its hull alone (31 vertices) — a hull cannot hold a bend the chain does
|
|
1058
|
+
not run through. Raising the bound is the author's choice, not the package's; moving the chain
|
|
1059
|
+
is the fix.
|
|
1055
1060
|
- **An added animation can move the idle's figures.** The render's viewport is fitted over
|
|
1056
1061
|
every animation, and the idle frames share it, so an animation that travels further than the
|
|
1057
1062
|
idle redraws the idle at another size: adding `hop` to `sample` narrowed the idle render from
|
|
@@ -1680,7 +1685,7 @@ See-through with another seed.
|
|
|
1680
1685
|
| `CHECK_RIGC_GREEN` | a rigc step failed; its line is quoted | as `RIG_RIGC_GREEN` |
|
|
1681
1686
|
| `CHECK_LOOP_LAST_FRAME_AT_DURATION` | the idle's last frame does not sit at `duration` | `motion.duration` — a whole number of 1/12 s |
|
|
1682
1687
|
| `CHECK_LOOP_CLOSES` | frame 0 and the frame at `duration` differ (max and first pixel quoted); with a physics constraint, the frames of the rig with every physics constraint left out, and the object says so (§7, *loop*) | a track whose last key is not its first |
|
|
1683
|
-
| `CHECK_SEAM_WITHIN_BAR` | the setup pose does not reproduce the flat stack of parts | read the `seam pair` lines and `check/seam/` pictures (§7): the pair, its boxes and the pixels counted. Usually a region or mesh placed off its part; compare with `recomposite_rig.png`. A pair of one layer cut in two with `differing_copies_px` is the cut line: move the polygon's edge, not `overlap` |
|
|
1688
|
+
| `CHECK_SEAM_WITHIN_BAR` | the setup pose does not reproduce the flat stack of parts | read the `seam pair` lines and `check/seam/` pictures (§7; magenta = a counted pixel, cyan = the box round them): the pair, its boxes and the pixels counted. Usually a region or mesh placed off its part; compare with `recomposite_rig.png`. A pair of one layer cut in two with `differing_copies_px` is the cut line: move the polygon's edge, not `overlap` |
|
|
1684
1689
|
| `CHECK_BREATH_VISIBLE` | the torso (`topwear`), rendered alone, barely moves over the idle — or the feet (`footwear`), rendered alone, move at all | the chest's breath tracks (`motion.tracks` on `chest`), or the torso mesh's `segments`; for the feet, the bone their region rides (`regions.<part>`, `root` in both examples) |
|
|
1685
1690
|
| `CHECK_BLINK_NO_HOLE` | with the blink held shut, the eyewhite box shows the page where the open eye had art | the layer under the eye: the `face` part has no art there. Take the face from the other run, or add a part under the eye; `motion.blink.squash` only hides the hole less |
|
|
1686
1691
|
| `CHECK_CHAIN_LAG` | a rotate track leads (or does not lag) the keyed bone above it, or a chain link swings less than the link above | that chain track's `phase`/`lag` (a positive `lag`, a child `phase` above its parent's) or its `amps` (non-decreasing toward the tip) |
|
|
@@ -1748,7 +1753,19 @@ assemble*, at the end of this section):
|
|
|
1748
1753
|
`A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP` on the packed pass, 16 not in `spine`; `CH09`
|
|
1749
1754
|
holds it on every fetched example);
|
|
1750
1755
|
- **seam**: the setup-pose render against the flat composite of `parts/`: mean
|
|
1751
|
-
max-channel |d| ≤ 1.0 of 255, and at most 50 pixels over 40
|
|
1756
|
+
max-channel |d| ≤ 1.0 of 255, and at most 50 **rig** pixels over 40 — the
|
|
1757
|
+
frame pixels over 40 divided by s², `s` the frame scale the `seam:` line
|
|
1758
|
+
prints and `check.json`'s `seam_frame_scale` holds (issue #216). The frame
|
|
1759
|
+
holds s² pixels per rig pixel, so a stack that parts from the render over a
|
|
1760
|
+
fixed area of the rig reads s² times as many frame pixels: on the public
|
|
1761
|
+
`sample` a part moved 2 rig px read 400, 891 and 1,560 frame px over 40 at
|
|
1762
|
+
s = 1.0, 1.5 and 2.0, and 400, 396 and 390 rig px; the sample as built read
|
|
1763
|
+
3, 3 and 7 frame px [measured, issue #216]. Divided by s² the bar reads the
|
|
1764
|
+
same rig at every scale, and at s = 1 it is the reference's count. The mean
|
|
1765
|
+
is a share of the frame and needs no such division (0.242, 0.188 and 0.226 on
|
|
1766
|
+
the sample as built; 0.353, 0.305 and 0.340 with the part moved).
|
|
1767
|
+
`seam_px_over_40` and `seam_px_over_80` stay frame pixels, and so does each
|
|
1768
|
+
pair's count below. The bar reads one
|
|
1752
1769
|
frame, the setup-pose still, never the idle. When it fails (or when `check
|
|
1753
1770
|
--seam-pairs` asks, pass or fail), the pixels over 40 are split by the pair of
|
|
1754
1771
|
parts that answers for each (issue #202): the two parts drawn last among those
|
|
@@ -1756,7 +1773,8 @@ assemble*, at the end of this section):
|
|
|
1756
1773
|
sample reads for that frame pixel, lower first, `(no art)` standing in where
|
|
1757
1774
|
fewer than two do. Each pair is a console line `seam pair <i> of <n>: "<a>" /
|
|
1758
1775
|
"<b>" at the setup pose: <count> px over 40 (<count> over 80), frame box x
|
|
1759
|
-
<x0>..<x1>, y <y0>..<y1>, rig box …, picture seam/<i>_<a>_<b>.png
|
|
1776
|
+
<x0>..<x1>, y <y0>..<y1>, rig box …, picture seam/<i>_<a>_<b>.png` (in the
|
|
1777
|
+
picture, **magenta = a counted pixel**, cyan = the box round them), worst first,
|
|
1760
1778
|
and a row of `check.json`'s `seam_pairs` (`parts`, `px_over_40`, `px_over_80`,
|
|
1761
1779
|
`frame_box` in frame pixels, `rig_box` in the rig pixels of `parts.json` and
|
|
1762
1780
|
`cuts[].polygon`, `picture`); `CHECK_SEAM_WITHIN_BAR`'s line quotes the worst.
|
package/docs/STABILITY.md
CHANGED
|
@@ -158,7 +158,7 @@ every number) are AUTHORING §3's tables, and the loader refuses any other by na
|
|
|
158
158
|
| `parts.json`, `parts/<name>.png` | `assemble`, `build` | one record per part: `from`, the box `x`, `y`, `w`, `h` (rig px), the pixel counts `src/parts.ts` documents (`opaque_px`, `visible_px`, `occluded_px`, `projected_core_px`, `source_px_taken`, `visible_not_projected_px`, `refused_drift_px`, `merged_px`, `seam_override_px`, and `fringe_pushed_back` when not empty); `rig_size`, `scale_rig_per_source`, `ghost_px`, `recomposite` |
|
|
159
159
|
| `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 |
|
|
160
160
|
| `rig/mesh_report.json` | `rig`, `build` | one row per mesh: `part`, `vertices`, `triangles`, `hull`, `bones`, `max_influences`, `mean_influences`, `art_coverage`, then `grid` (a lattice); `mode` "contour" with `params`, `contour`, `regions`; or `mode` "auto" with `settings`, `source`, `result`, `residuals`, `worst_residual`, `worst_region`, `motion_amplitude`, `termination`, `weights`, `regions`, `deformation`, `quality_report` (rig-c's `mesh-quality-report/1` document whole), `motion_report`, and, only when they apply, `stray_cleared` (after `source`, when `source.stray` is declared), `skinning_residual` and `replay` (`AutoMeshReport` in `src/rig.ts`); in every mode, `exponent` when the mesh declares one (after `grid`, or after `mode`); `ribs` when a contour or auto mesh declares them (after `exponent`, or after `mode`), and then `contour.ribs` (`source.contour.ribs` in the automatic mode) |
|
|
161
|
-
| `check/check.json` and the rest of `check/` | `check`, `build`, `compose` | the reference's fields in its order (`gate_spine_html_green`, `rigc_entry`, `pack_mode`, `loop_max_diff`, the seam figures), `loop_physics` after `loop_max_diff` when the rig declares a physics constraint, `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`), `RECOMPOSITE_HOLES`, `SETUP_POSE_VS_SOURCE`, `skipped`, a `requirements` block under `--requirements`, and `PASS`; `check/build/skeleton.json`, `.atlas` and `.png` are the packed artifact rig-c writes |
|
|
161
|
+
| `check/check.json` and the rest of `check/` | `check`, `build`, `compose` | the reference's fields in its order (`gate_spine_html_green`, `rigc_entry`, `pack_mode`, `loop_max_diff`, the seam figures — `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_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`), `RECOMPOSITE_HOLES`, `SETUP_POSE_VS_SOURCE`, `skipped`, a `requirements` block under `--requirements`, and `PASS`; `check/build/skeleton.json`, `.atlas` and `.png` are the packed artifact rig-c writes |
|
|
162
162
|
| `proposal.json`, `render/landmarks*.png` | `propose` | the config's four rig sections and `notes` |
|
|
163
163
|
| `basis.json` | `propose` | spec `spine-parts-basis/1` |
|
|
164
164
|
| the scene report | `compose` | spec `spine-parts-scene-report/1` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-parts",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
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
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
splitSchedule,
|
|
71
71
|
untestedIntervals,
|
|
72
72
|
} from './autoreplay.ts';
|
|
73
|
-
import { BARS, besideIdleLine, causeLines, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, JUDGEMENT_LINES, type JudgementLine, packedBuildArgs, packedBuildLabel, type PackMode, type PackShape, type PageEdges, readFrameSet, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_DIR, SEAM_MEAN_BAR, seamPairText, SOURCE_LINE, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK, STILL_DIR } from './check.ts';
|
|
73
|
+
import { BARS, besideIdleLine, causeLines, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, JUDGEMENT_LINES, type JudgementLine, packedBuildArgs, packedBuildLabel, type PackMode, type PackShape, type PageEdges, readFrameSet, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_DIR, SEAM_MEAN_BAR, seamPairText, seamRigPxText, SOURCE_LINE, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK, STILL_DIR } from './check.ts';
|
|
74
74
|
import { type Cut, loadConfig, loadConfigAndAnimations, loadEarlyConfig } from './config.ts';
|
|
75
75
|
import { PartsError, type Problem, problemLine, refuseIfAny } from './errors.ts';
|
|
76
76
|
import type { MeshQualityReport, MotionSchedule } from 'rig-c/mesh';
|
|
@@ -981,7 +981,7 @@ export function checkStage(input: CheckStageInput, rigc: RigcRunner, bin: string
|
|
|
981
981
|
else {
|
|
982
982
|
log(
|
|
983
983
|
` seam: setup pose at ${r.seamViewport.pixelWidth}x${r.seamViewport.pixelHeight}, scale ${r.seamViewport.scale.toFixed(4)}: mean |d| ${fig.seam_mean} (<= ${SEAM_MEAN_BAR.toFixed(1)}), ` +
|
|
984
|
-
`${fig.seam_px_over_40} px over ${SEAM_PX_LEVEL} (<= ${SEAM_PX_BAR}), ${fig.seam_px_over_80} px over ${SEAM_PX_LEVEL_HIGH} (reported)`,
|
|
984
|
+
`${fig.seam_px_over_40} px over ${SEAM_PX_LEVEL}, ${fig.seam_frame_scale === null || fig.seam_px_over_40 === null ? '' : seamRigPxText(fig.seam_px_over_40, fig.seam_frame_scale)} rig px (the count / s²; <= ${SEAM_PX_BAR}), ${fig.seam_px_over_80} px over ${SEAM_PX_LEVEL_HIGH} (reported)`,
|
|
985
985
|
);
|
|
986
986
|
const pairs = fig.seam_pairs ?? [];
|
|
987
987
|
pairs.forEach((row, i) => log(` seam pair ${i + 1} of ${pairs.length}: ${seamPairText(row)}`));
|
package/src/check.ts
CHANGED
|
@@ -153,7 +153,17 @@ import { type ScaleTranslate, warpAffine } from './raster/warp.ts';
|
|
|
153
153
|
|
|
154
154
|
/** Seam: mean max-channel |d| over the frame, in levels of 255, at most this. */
|
|
155
155
|
export const SEAM_MEAN_BAR = 1.0;
|
|
156
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* Seam: pixels whose max-channel |d| exceeds {@link SEAM_PX_LEVEL}, at most
|
|
158
|
+
* this many **rig** pixels — the frame count divided by s², s the frame scale
|
|
159
|
+
* the setup-pose still renders at (issue #216). The frame holds s² pixels per
|
|
160
|
+
* rig pixel, so a stack that parts from the render over a fixed area of the
|
|
161
|
+
* rig reads s² times as many frame pixels: on the public sample a part moved
|
|
162
|
+
* 2 rig px read 400, 891 and 1,560 frame px over 40 at s = 1.0, 1.5 and 2.0,
|
|
163
|
+
* and 400, 396 and 390 divided by s²; the uncut sample 3, 3 and 7 frame px
|
|
164
|
+
* [measured for issue #216, a scratch run, not tracked]. At s = 1 the bar is
|
|
165
|
+
* the reference's frame count.
|
|
166
|
+
*/
|
|
157
167
|
export const SEAM_PX_BAR = 50;
|
|
158
168
|
export const SEAM_PX_LEVEL = 40;
|
|
159
169
|
/** Seam: the second, reported-only level. */
|
|
@@ -1372,9 +1382,29 @@ function round3(x: number): number {
|
|
|
1372
1382
|
return Number(x.toFixed(3));
|
|
1373
1383
|
}
|
|
1374
1384
|
|
|
1375
|
-
/**
|
|
1376
|
-
function
|
|
1377
|
-
return
|
|
1385
|
+
/** Python's `round(x, 6)` for the frame scale written: to six places, on the value's exact decimal expansion. */
|
|
1386
|
+
function round6(x: number): number {
|
|
1387
|
+
return Number(x.toFixed(6));
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/** The seam's count in rig pixels (issue #216): the frame pixels over {@link SEAM_PX_LEVEL} divided by the square of the frame scale. */
|
|
1391
|
+
export function seamRigPx(over40: number, scale: number): number {
|
|
1392
|
+
return over40 / (scale * scale);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/** {@link seamRigPx} as the console and the refusal print it, to two places. */
|
|
1396
|
+
export function seamRigPxText(over40: number, scale: number): string {
|
|
1397
|
+
return seamRigPx(over40, scale).toFixed(2);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* The seam bar, on the figures as check.json writes them: the mean (rounded
|
|
1402
|
+
* to three places) over {@link SEAM_MEAN_BAR}, or more than
|
|
1403
|
+
* {@link SEAM_PX_BAR} rig pixels over {@link SEAM_PX_LEVEL} — the frame count
|
|
1404
|
+
* over the square of the written frame scale (issue #216).
|
|
1405
|
+
*/
|
|
1406
|
+
function seamFails(mean: number, over40: number, scale: number): boolean {
|
|
1407
|
+
return mean > SEAM_MEAN_BAR || seamRigPx(over40, scale) > SEAM_PX_BAR;
|
|
1378
1408
|
}
|
|
1379
1409
|
|
|
1380
1410
|
/** A seam row as the console prints it: the pair, the frame, the counts, both boxes and the picture. */
|
|
@@ -1536,10 +1566,16 @@ export interface CheckFigures {
|
|
|
1536
1566
|
* read from. Reported, not judged; a rig without one writes the keys it always wrote.
|
|
1537
1567
|
*/
|
|
1538
1568
|
loop_physics?: LoopPhysics[];
|
|
1539
|
-
/** The
|
|
1569
|
+
/** The seam figures: null when there is no parts.json (issue #77); `skipped.seam` then says why. The two counts are frame pixels. */
|
|
1540
1570
|
seam_mean: number | null;
|
|
1541
1571
|
seam_px_over_40: number | null;
|
|
1542
1572
|
seam_px_over_80: number | null;
|
|
1573
|
+
/**
|
|
1574
|
+
* The frame scale s the setup-pose still rendered at (rigc's viewport
|
|
1575
|
+
* scale, to six places), issue #216: the seam bar reads `seam_px_over_40`
|
|
1576
|
+
* divided by its square ({@link seamRigPx}). Null with the other three.
|
|
1577
|
+
*/
|
|
1578
|
+
seam_frame_scale: number | null;
|
|
1543
1579
|
/**
|
|
1544
1580
|
* Written only when the seam bar fails, or when it was asked for (`check
|
|
1545
1581
|
* --seam-pairs`), issue #202: the counted pixels split by the pair of parts
|
|
@@ -1600,7 +1636,9 @@ export function barsNotMet(fig: Readonly<Record<string, unknown>>): string[] {
|
|
|
1600
1636
|
const out: string[] = [];
|
|
1601
1637
|
if (fig.gate_spine_html_green !== true) out.push('gate');
|
|
1602
1638
|
if (typeof fig.loop_max_diff === 'number' && fig.loop_max_diff !== LOOP_MAX_BAR) out.push('loop');
|
|
1603
|
-
|
|
1639
|
+
// A check.json written before issue #216 has no seam_frame_scale; it is read by the frame count, the bar it was written under.
|
|
1640
|
+
const scale = typeof fig.seam_frame_scale === 'number' ? fig.seam_frame_scale : 1;
|
|
1641
|
+
if (typeof fig.seam_mean === 'number' && typeof fig.seam_px_over_40 === 'number' && seamFails(fig.seam_mean, fig.seam_px_over_40, scale)) out.push('seam');
|
|
1604
1642
|
for (const n of JUDGEMENT_LINES) {
|
|
1605
1643
|
const l = fig[n];
|
|
1606
1644
|
if (typeof l === 'object' && l !== null && (l as { status?: unknown }).status === 'FAIL') out.push(n);
|
|
@@ -1895,7 +1933,7 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
1895
1933
|
if (inp.parts !== null) {
|
|
1896
1934
|
const composite = flatComposite(inp.parts, inp.partsDir, stillSet.background);
|
|
1897
1935
|
seam = seamFigures(composite, stillSet.frames[0].image, inp.stage, stillSet.viewport, stillSet.background);
|
|
1898
|
-
if (seamPairsAsked || seamFails(round3(seam.mean), seam.over40)) {
|
|
1936
|
+
if (seamPairsAsked || seamFails(round3(seam.mean), seam.over40, round6(stillSet.viewport.scale))) {
|
|
1899
1937
|
const placed = inp.parts.parts.map((p) => ({ name: p.name, from: p.from, x: p.x, y: p.y, image: readPng(join(inp.partsDir, `${p.name}.png`)) }));
|
|
1900
1938
|
const split = seamPairRows(seam, placed, inp.parts.rig_size, stillSet.viewport);
|
|
1901
1939
|
mkdirSync(seamDir, { recursive: true });
|
|
@@ -1955,6 +1993,7 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
1955
1993
|
seam_mean: seam === null ? null : round3(seam.mean),
|
|
1956
1994
|
seam_px_over_40: seam === null ? null : seam.over40,
|
|
1957
1995
|
seam_px_over_80: seam === null ? null : seam.over80,
|
|
1996
|
+
seam_frame_scale: seam === null || seamViewport === null ? null : round6(seamViewport.scale),
|
|
1958
1997
|
...(seamPairs === null ? {} : { seam_pairs: seamPairs }),
|
|
1959
1998
|
BREATH_VISIBLE: breath,
|
|
1960
1999
|
BLINK_NO_HOLE: blink,
|
|
@@ -1978,13 +2017,13 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
1978
2017
|
detail: `max |d| ${loop.max}/255, first at pixel ${loop.x},${loop.y} of ${loopSet.viewport.pixelWidth}x${loopSet.viewport.pixelHeight}; ${LOOP_MAX_BAR} is required — the idle's last key must equal its first`,
|
|
1979
2018
|
});
|
|
1980
2019
|
}
|
|
1981
|
-
if (figures.seam_mean !== null && figures.seam_px_over_40 !== null && seamFails(figures.seam_mean, figures.seam_px_over_40)) {
|
|
2020
|
+
if (figures.seam_mean !== null && figures.seam_px_over_40 !== null && figures.seam_frame_scale !== null && seamFails(figures.seam_mean, figures.seam_px_over_40, figures.seam_frame_scale)) {
|
|
1982
2021
|
const worst = seamPairs === null || seamPairs.length === 0 ? null : seamPairs[0];
|
|
1983
2022
|
barProblems.push({
|
|
1984
2023
|
code: 'CHECK_SEAM_WITHIN_BAR',
|
|
1985
2024
|
object: 'the setup-pose render vs the flat composite of parts/',
|
|
1986
2025
|
detail:
|
|
1987
|
-
`mean |d| ${figures.seam_mean}/255 and ${figures.seam_px_over_40} px over ${SEAM_PX_LEVEL} (${figures.seam_px_over_80} over ${SEAM_PX_LEVEL_HIGH}); mean <= ${SEAM_MEAN_BAR.toFixed(1)} and <= ${SEAM_PX_BAR} px over ${SEAM_PX_LEVEL} are required` +
|
|
2026
|
+
`mean |d| ${figures.seam_mean}/255 and ${figures.seam_px_over_40} frame px over ${SEAM_PX_LEVEL} (${figures.seam_px_over_80} over ${SEAM_PX_LEVEL_HIGH}) at frame scale ${figures.seam_frame_scale}, ${seamRigPxText(figures.seam_px_over_40, figures.seam_frame_scale)} rig px (the count / s²); mean <= ${SEAM_MEAN_BAR.toFixed(1)} and <= ${SEAM_PX_BAR} rig px over ${SEAM_PX_LEVEL} are required` +
|
|
1988
2027
|
(worst === null
|
|
1989
2028
|
? `; no pixel is over ${SEAM_PX_LEVEL}, so the mean alone fails and there is no pair to name`
|
|
1990
2029
|
: `; worst of ${(seamPairs as SeamPairRow[]).length} pair(s): ${seamPairText(worst)} — every pair is a \`seam pair\` line above and a row of check.json's seam_pairs`),
|