rig-c 0.0.0-stage → 2.20.4
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/.claude-plugin/marketplace.json +19 -0
- package/.claude-plugin/plugin.json +13 -0
- package/LICENSE +30 -0
- package/NOTICE.md +145 -0
- package/README.md +817 -3
- package/bin/rigc.cjs +83 -0
- package/cli.ts +61 -0
- package/cli_core.ts +46 -0
- package/docs/AUTHORING.md +9923 -0
- package/docs/FACE.md +1948 -0
- package/docs/INGEST.md +1488 -0
- package/docs/MOTION.md +1241 -0
- package/docs/PROMPTING.md +109 -0
- package/docs/RIGGING.md +1441 -0
- package/docs/SPEC_COVERAGE.md +357 -0
- package/package.json +108 -4
- package/skills/rigc/SKILL.md +133 -0
- package/skills/rigc-face/SKILL.md +60 -0
- package/skills/rigc-ingest/SKILL.md +78 -0
- package/skills/rigc-motion/SKILL.md +51 -0
- package/skills/rigc-rigging/SKILL.md +49 -0
- package/src/areaband.ts +159 -0
- package/src/assertions/bodies/a01.ts +23 -0
- package/src/assertions/bodies/a02.ts +21 -0
- package/src/assertions/bodies/a03.ts +27 -0
- package/src/assertions/bodies/a04.ts +40 -0
- package/src/assertions/bodies/a05.ts +56 -0
- package/src/assertions/bodies/a06.ts +245 -0
- package/src/assertions/bodies/a07.ts +68 -0
- package/src/assertions/bodies/a08.ts +76 -0
- package/src/assertions/bodies/a09.ts +82 -0
- package/src/assertions/bodies/a10.ts +116 -0
- package/src/assertions/bodies/a11.ts +15 -0
- package/src/assertions/bodies/a12.ts +30 -0
- package/src/assertions/bodies/a13.ts +51 -0
- package/src/assertions/bodies/a14.ts +35 -0
- package/src/assertions/bodies/a15.ts +97 -0
- package/src/assertions/bodies/a16.ts +24 -0
- package/src/assertions/bodies/a17.ts +26 -0
- package/src/assertions/bodies/a18.ts +62 -0
- package/src/assertions/bodies/a19.ts +404 -0
- package/src/assertions/bodies/a20.ts +122 -0
- package/src/assertions/bodies/a21.ts +190 -0
- package/src/assertions/bodies/a22.ts +39 -0
- package/src/assertions/bodies/a23.ts +305 -0
- package/src/assertions/bodies/a24.ts +68 -0
- package/src/assertions/bodies/a25.ts +39 -0
- package/src/assertions/bodies/a26.ts +61 -0
- package/src/assertions/bodies/a27.ts +33 -0
- package/src/assertions/bodies/a28.ts +70 -0
- package/src/assertions/bodies/a29.ts +34 -0
- package/src/assertions/bodies/a30.ts +50 -0
- package/src/assertions/bodies/a31.ts +61 -0
- package/src/assertions/bodies/a32.ts +44 -0
- package/src/assertions/bodies/a33.ts +110 -0
- package/src/assertions/bodies/a34.ts +133 -0
- package/src/assertions/bodies/a35.ts +160 -0
- package/src/assertions/bodies/a36.ts +81 -0
- package/src/assertions/bodies/a37.ts +77 -0
- package/src/assertions/bodies/a38.ts +73 -0
- package/src/assertions/bodies/a39.ts +303 -0
- package/src/assertions/bodies/a40.ts +128 -0
- package/src/assertions/bodies/a42.ts +97 -0
- package/src/assertions/bodies/a43.ts +181 -0
- package/src/assertions/bodies/a44.ts +23 -0
- package/src/assertions/bodies/a45.ts +172 -0
- package/src/assertions/bodies/a46.ts +224 -0
- package/src/assertions/bodies/a47.ts +126 -0
- package/src/assertions/bodies/a48.ts +83 -0
- package/src/assertions/bodies/a49.ts +81 -0
- package/src/assertions/bodies/a50.ts +97 -0
- package/src/assertions/constraint_words.ts +169 -0
- package/src/assertions/emitted/index.ts +148 -0
- package/src/assertions/facts/animated_bones.ts +30 -0
- package/src/assertions/facts/animation_durations.ts +37 -0
- package/src/assertions/facts/atlas_pages.ts +19 -0
- package/src/assertions/facts/atlas_regions.ts +52 -0
- package/src/assertions/facts/bone_timelines.ts +37 -0
- package/src/assertions/facts/constraint_targets.ts +56 -0
- package/src/assertions/facts/constraints.ts +155 -0
- package/src/assertions/facts/deform_survey.ts +27 -0
- package/src/assertions/facts/event_keys.ts +55 -0
- package/src/assertions/facts/linked_meshes.ts +38 -0
- package/src/assertions/facts/mesh_attachments.ts +100 -0
- package/src/assertions/facts/region_joins.ts +34 -0
- package/src/assertions/facts/sequences.ts +85 -0
- package/src/assertions/facts/skeleton_roster.ts +45 -0
- package/src/assertions/facts/skin_entries.ts +37 -0
- package/src/assertions/facts/skin_members.ts +53 -0
- package/src/assertions/facts/slider_composition.ts +78 -0
- package/src/assertions/facts/slot_colour.ts +43 -0
- package/src/assertions/facts/stage.ts +27 -0
- package/src/assertions/facts/stage_box.ts +65 -0
- package/src/assertions/facts/stepped_poses.ts +74 -0
- package/src/assertions/facts/two_colour.ts +52 -0
- package/src/assertions/facts/vertex_polygons.ts +53 -0
- package/src/assertions/footprints.ts +367 -0
- package/src/assertions/harness.ts +109 -0
- package/src/assertions/inward_advance.ts +58 -0
- package/src/assertions/kinds.ts +105 -0
- package/src/assertions/mesh_kinds.ts +56 -0
- package/src/assertions/model/animated_bones.ts +38 -0
- package/src/assertions/model/animation_durations.ts +57 -0
- package/src/assertions/model/atlas_pages.ts +15 -0
- package/src/assertions/model/atlas_regions.ts +76 -0
- package/src/assertions/model/bone_timelines.ts +58 -0
- package/src/assertions/model/constraint_targets.ts +82 -0
- package/src/assertions/model/constraints.ts +233 -0
- package/src/assertions/model/declared.ts +125 -0
- package/src/assertions/model/deform_survey.ts +24 -0
- package/src/assertions/model/event_keys.ts +45 -0
- package/src/assertions/model/given.ts +45 -0
- package/src/assertions/model/index.ts +398 -0
- package/src/assertions/model/linked_meshes.ts +24 -0
- package/src/assertions/model/mesh_attachments.ts +119 -0
- package/src/assertions/model/parse.ts +146 -0
- package/src/assertions/model/region_joins.ts +67 -0
- package/src/assertions/model/runtime_timelines.ts +78 -0
- package/src/assertions/model/sequences.ts +157 -0
- package/src/assertions/model/skeleton_roster.ts +23 -0
- package/src/assertions/model/skin_entries.ts +69 -0
- package/src/assertions/model/skin_members.ts +64 -0
- package/src/assertions/model/slider_composition.ts +193 -0
- package/src/assertions/model/slot_colour.ts +81 -0
- package/src/assertions/model/stage.ts +28 -0
- package/src/assertions/model/stage_box.ts +51 -0
- package/src/assertions/model/stepped_poses.ts +105 -0
- package/src/assertions/model/two_colour.ts +61 -0
- package/src/assertions/model/vertex_polygons.ts +72 -0
- package/src/assertions/reasons.ts +129 -0
- package/src/assertions/region_lookups.ts +61 -0
- package/src/assertions/report.ts +189 -0
- package/src/assertions/values.ts +39 -0
- package/src/atlas.ts +2870 -0
- package/src/ballot.ts +866 -0
- package/src/bonedist.ts +643 -0
- package/src/chainfit.ts +2752 -0
- package/src/chains.ts +170 -0
- package/src/check.ts +4303 -0
- package/src/checkpics.ts +295 -0
- package/src/cli/core_commands.ts +1627 -0
- package/src/cli/repack.ts +414 -0
- package/src/cli/shared.ts +2776 -0
- package/src/cli/spine_commands.ts +820 -0
- package/src/compile.ts +9414 -0
- package/src/core/additive.ts +458 -0
- package/src/core/animation.ts +1050 -0
- package/src/core/clipping.ts +696 -0
- package/src/core/constraints.ts +1876 -0
- package/src/core/constraints_path.ts +964 -0
- package/src/core/constraints_physics.ts +881 -0
- package/src/core/constraints_slider.ts +635 -0
- package/src/core/deform.ts +613 -0
- package/src/core/draw_order.ts +125 -0
- package/src/core/events.ts +135 -0
- package/src/core/hooks.ts +249 -0
- package/src/core/index.ts +1400 -0
- package/src/core/raw.ts +739 -0
- package/src/core/skins.ts +129 -0
- package/src/core/uvs.ts +469 -0
- package/src/core/vertices.ts +490 -0
- package/src/core/walk.ts +197 -0
- package/src/core/world.ts +289 -0
- package/src/correspondence.ts +15 -0
- package/src/deformbuild.ts +60 -0
- package/src/deformgen.ts +630 -0
- package/src/deformmeasure.ts +732 -0
- package/src/deformreport.ts +373 -0
- package/src/deformstructure.ts +386 -0
- package/src/deformsurvey.ts +2162 -0
- package/src/depth.ts +784 -0
- package/src/diff.ts +2252 -0
- package/src/emit.ts +134 -0
- package/src/emit_spine.ts +854 -0
- package/src/errors.ts +53 -0
- package/src/framing.ts +819 -0
- package/src/generation.ts +139 -0
- package/src/ingest.ts +2293 -0
- package/src/json-position.ts +253 -0
- package/src/keyorder.ts +587 -0
- package/src/keys.ts +486 -0
- package/src/ladder.ts +121 -0
- package/src/mesh.ts +2382 -0
- package/src/meshcompare.ts +1188 -0
- package/src/meshquality.ts +2042 -0
- package/src/meshrasters.ts +944 -0
- package/src/meshreduce.ts +1425 -0
- package/src/model.ts +1245 -0
- package/src/motion.ts +809 -0
- package/src/nonfinite.ts +54 -0
- package/src/package_meta.ts +48 -0
- package/src/png.ts +297 -0
- package/src/pose.ts +2324 -0
- package/src/preview.ts +434 -0
- package/src/region_joins.ts +54 -0
- package/src/render.ts +1013 -0
- package/src/render_core.ts +871 -0
- package/src/render_shared.ts +2958 -0
- package/src/repack.ts +495 -0
- package/src/rig.ts +2941 -0
- package/src/slots.ts +892 -0
- package/src/spine_side.ts +138 -0
- package/src/timelines.ts +837 -0
- package/src/trackgen.ts +364 -0
- package/src/transform.ts +310 -0
- package/src/types.ts +1797 -0
- package/src/validate.ts +3875 -0
- package/tools/contact.ts +126 -0
- package/tools/editor_roundtrip.ts +1641 -0
- package/tools/font5x7.ts +101 -0
- package/tools/measure_contact_depth.ts +105 -0
- package/tools/plate.ts +508 -0
- package/tools/png_probe.mjs +72 -0
package/src/checkpics.ts
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rigc check --out — the picture each of `check`'s numbers came from.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* `check` renders the candidate onto the reference frames' own grid, subtracts,
|
|
7
|
+
* and prints the result as numbers. A number says *how much*; only a picture says
|
|
8
|
+
* *where*. An agent porting a sample drew the same face composite twice at a
|
|
9
|
+
* 20 px offset — a ghost hairline — with a green gate and a contact sheet of
|
|
10
|
+
* ~100 px tiles in hand, and did not see it (issue #834). The comparison that
|
|
11
|
+
* would have shown it was already computed and thrown away.
|
|
12
|
+
*
|
|
13
|
+
* So for every frame the report lists under *the frames worth reading* (every
|
|
14
|
+
* compared frame under `--all-frames`), this composes one image, left to right:
|
|
15
|
+
*
|
|
16
|
+
* 1. **reference** — the frame as read from `--frames`;
|
|
17
|
+
* 2. **candidate** — the candidate exactly as `check` rendered it onto that grid;
|
|
18
|
+
* 3. **difference** — per pixel, the largest of the three channel differences
|
|
19
|
+
* `|candidate − reference|`, 0..255 on a fixed grey ramp, over the pixels the
|
|
20
|
+
* MAE averages over (the union alpha) and transparent everywhere else;
|
|
21
|
+
* 4. **overlay** — the two at 50 % each, so a displaced edge reads as a double line.
|
|
22
|
+
*
|
|
23
|
+
* Each pane is at the grid's **native size**. An upscaled difference is a
|
|
24
|
+
* difference the instrument invented: a resampling filter moves ink into pixels
|
|
25
|
+
* the comparison never measured. The picture is only ever the comparison.
|
|
26
|
+
*
|
|
27
|
+
* ## 🔒 Nothing here is a new number
|
|
28
|
+
*
|
|
29
|
+
* Every figure burned into a picture is read off the `CheckReport` the table was
|
|
30
|
+
* printed from, at the precision the table prints it — the difference pane's MAE,
|
|
31
|
+
* the per-side change counts, the union — and the per-slot rows are the frame's
|
|
32
|
+
* own `slots`. The rasters are the ones the figures were computed on, kept by
|
|
33
|
+
* `CheckPlates` at the moment they were compared, not re-rendered afterwards: a
|
|
34
|
+
* second render that happened to agree would be a claim, and this is a record.
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ Two things a picture cannot say, and it says neither: which side is right,
|
|
37
|
+
* and anything about a frame it does not show.
|
|
38
|
+
*/
|
|
39
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
40
|
+
import { join } from 'node:path';
|
|
41
|
+
import {
|
|
42
|
+
COMPARISON_FIELD,
|
|
43
|
+
framesToList,
|
|
44
|
+
type AnimationCheck,
|
|
45
|
+
type CheckPlates,
|
|
46
|
+
type CheckReport,
|
|
47
|
+
type ComparedPlates,
|
|
48
|
+
type Framing,
|
|
49
|
+
type FrameCheck,
|
|
50
|
+
} from './check.ts';
|
|
51
|
+
import { FRAMES_SIDECAR, FRAMES_SPEC } from './render_shared.ts';
|
|
52
|
+
import { isContent } from './framing.ts';
|
|
53
|
+
import { isAttributable } from './slots.ts';
|
|
54
|
+
import { encodePng, Plate, type RGBA } from '../tools/plate.ts';
|
|
55
|
+
import { GLYPH_H, textWidth } from '../tools/font5x7.ts';
|
|
56
|
+
|
|
57
|
+
/** The four panes, left to right, by the names their labels carry. */
|
|
58
|
+
export const PANES = ['reference', 'candidate', 'difference', 'overlay'] as const;
|
|
59
|
+
export type Pane = (typeof PANES)[number];
|
|
60
|
+
|
|
61
|
+
/** Transparent pixels between two panes, and between the panes and the slot rows. */
|
|
62
|
+
export const PANE_GAP = 4;
|
|
63
|
+
/** Pixels of strip around a line of text, above and below. */
|
|
64
|
+
const TEXT_PAD = 2;
|
|
65
|
+
/** One line of 5x7 text with its padding: the label strip, and each slot row. */
|
|
66
|
+
export const TEXT_ROW = GLYPH_H + 2 * TEXT_PAD;
|
|
67
|
+
/** The strips' own colour, and the text's — opaque, so the labels read on any viewer background. */
|
|
68
|
+
const STRIP: RGBA = [0, 0, 0, 255];
|
|
69
|
+
const INK: RGBA = [255, 255, 255, 255];
|
|
70
|
+
|
|
71
|
+
const f2 = (n: number): string => n.toFixed(2);
|
|
72
|
+
const frameName = (index: number): string => `f${String(index).padStart(4, '0')}`;
|
|
73
|
+
|
|
74
|
+
/** Where one pane sits in a picture, in picture pixels. */
|
|
75
|
+
export interface PaneRect {
|
|
76
|
+
pane: Pane;
|
|
77
|
+
x: number;
|
|
78
|
+
y: number;
|
|
79
|
+
width: number;
|
|
80
|
+
height: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The geometry of one picture — exported so a reader can find a pane without
|
|
85
|
+
* knowing how the picture was drawn, which is what the selftest does.
|
|
86
|
+
*
|
|
87
|
+
* A column is as wide as its pane, or as its label when the label is wider: the
|
|
88
|
+
* PANE stays at native size either way, and a label clipped to fit a small grid
|
|
89
|
+
* would drop the one figure it exists to carry.
|
|
90
|
+
*/
|
|
91
|
+
export function pictureLayout(
|
|
92
|
+
gridWidth: number,
|
|
93
|
+
gridHeight: number,
|
|
94
|
+
labels: readonly string[],
|
|
95
|
+
rows: readonly string[],
|
|
96
|
+
): { width: number; height: number; panes: PaneRect[]; rowsTop: number } {
|
|
97
|
+
const columns = labels.map((label) => Math.max(gridWidth, textWidth(label, 1) + 2 * TEXT_PAD));
|
|
98
|
+
const panes: PaneRect[] = [];
|
|
99
|
+
let x = 0;
|
|
100
|
+
PANES.forEach((pane, i) => {
|
|
101
|
+
panes.push({ pane, x, y: TEXT_ROW, width: gridWidth, height: gridHeight });
|
|
102
|
+
x += columns[i] + (i < PANES.length - 1 ? PANE_GAP : 0);
|
|
103
|
+
});
|
|
104
|
+
const widestRow = rows.reduce((w, row) => Math.max(w, textWidth(row, 1) + 2 * TEXT_PAD), 0);
|
|
105
|
+
const rowsTop = TEXT_ROW + gridHeight + PANE_GAP;
|
|
106
|
+
return { width: Math.max(x, widestRow), height: rowsTop + rows.length * TEXT_ROW, panes, rowsTop };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The label above each pane: the frame, the pane, and the pane's own figure from the table. */
|
|
110
|
+
export function paneLabels(frame: FrameCheck): string[] {
|
|
111
|
+
const at = frameName(frame.index);
|
|
112
|
+
const moved = (n: number | undefined): string => (n === undefined ? '-' : String(n));
|
|
113
|
+
return [
|
|
114
|
+
// The table's `ref Δ` and `Δpx` columns: how far each side moved since its own previous frame.
|
|
115
|
+
`${at} reference ref d ${moved(frame.change?.reference)}`,
|
|
116
|
+
`${at} candidate dpx ${moved(frame.change?.candidate)}`,
|
|
117
|
+
`${at} difference mae ${f2(frame.mae)}`,
|
|
118
|
+
`${at} overlay union ${frame.unionPixels}`,
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* One row per slot in the frame's own `slots`, in the order the report holds them.
|
|
124
|
+
*
|
|
125
|
+
* The drift is printed at the table's precision, and a slot with no attributable
|
|
126
|
+
* drift says which of the two reasons it is rather than printing a number — the
|
|
127
|
+
* rule `src/slots.ts` owns for every figure beside a part's name. The frame's
|
|
128
|
+
* worst slot, the one the table's row names, is marked `>`.
|
|
129
|
+
*/
|
|
130
|
+
export function slotRows(frame: FrameCheck): string[] {
|
|
131
|
+
return frame.slots.map((track) => {
|
|
132
|
+
const mark = track.slot === frame.worstSlot ? '>' : ' ';
|
|
133
|
+
if (!isAttributable(track)) {
|
|
134
|
+
return `${mark} ${track.slot} - ${track.candidate === null ? 'not drawn' : 'no attributable drift'}`;
|
|
135
|
+
}
|
|
136
|
+
const how = track.method === 'template' ? `tmpl ${(track.confidence ?? 0).toFixed(2)}` : 'component';
|
|
137
|
+
return `${mark} ${track.slot} ${(track.drift as number).toFixed(1)} px ${how}`;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Compose one frame's picture from the rasters its figures were computed on. */
|
|
142
|
+
export function composePicture(frame: FrameCheck, plates: ComparedPlates, background: RGBA): Plate {
|
|
143
|
+
const { reference, candidate, coverage } = plates;
|
|
144
|
+
const w = reference.width;
|
|
145
|
+
const h = reference.height;
|
|
146
|
+
const labels = paneLabels(frame);
|
|
147
|
+
const rows = slotRows(frame);
|
|
148
|
+
const layout = pictureLayout(w, h, labels, rows);
|
|
149
|
+
const out = new Plate(layout.width, layout.height);
|
|
150
|
+
|
|
151
|
+
layout.panes.forEach((rect, i) => {
|
|
152
|
+
out.rect(rect.x, 0, Math.max(rect.width, textWidth(labels[i], 1) + 2 * TEXT_PAD), TEXT_ROW, STRIP);
|
|
153
|
+
out.text(labels[i], rect.x + TEXT_PAD, TEXT_PAD, 1, INK);
|
|
154
|
+
});
|
|
155
|
+
const [ref, cand, diff, over] = layout.panes;
|
|
156
|
+
for (let y = 0; y < h; y++) {
|
|
157
|
+
for (let x = 0; x < w; x++) {
|
|
158
|
+
const a = candidate.get(x, y);
|
|
159
|
+
const b = reference.get(x, y);
|
|
160
|
+
out.set(ref.x + x, ref.y + y, b);
|
|
161
|
+
out.set(cand.x + x, cand.y + y, a);
|
|
162
|
+
// The union `checkOneFrame` averages over: what the candidate's geometry
|
|
163
|
+
// covers, or what the reference drew — the same predicate, not a lookalike.
|
|
164
|
+
if (coverage[y * w + x] === 1 || isContent(reference, x, y, background)) {
|
|
165
|
+
const v = Math.max(Math.abs(a[0] - b[0]), Math.abs(a[1] - b[1]), Math.abs(a[2] - b[2]));
|
|
166
|
+
out.set(diff.x + x, diff.y + y, [v, v, v, 255]);
|
|
167
|
+
}
|
|
168
|
+
out.set(over.x + x, over.y + y, [
|
|
169
|
+
Math.round((a[0] + b[0]) / 2),
|
|
170
|
+
Math.round((a[1] + b[1]) / 2),
|
|
171
|
+
Math.round((a[2] + b[2]) / 2),
|
|
172
|
+
Math.round((a[3] + b[3]) / 2),
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
rows.forEach((row, i) => {
|
|
177
|
+
const top = layout.rowsTop + i * TEXT_ROW;
|
|
178
|
+
out.rect(0, top, layout.width, TEXT_ROW, STRIP);
|
|
179
|
+
out.text(row, TEXT_PAD, top + TEXT_PAD, 1, INK);
|
|
180
|
+
});
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** What `--out` wrote for one frame set. */
|
|
185
|
+
export interface PicturesWritten {
|
|
186
|
+
/** The set's directory, as the report names it. */
|
|
187
|
+
dir: string;
|
|
188
|
+
/** Absolute path of the directory the pictures went into. */
|
|
189
|
+
path: string;
|
|
190
|
+
/** The frame indices written, in index order. */
|
|
191
|
+
frames: number[];
|
|
192
|
+
/** Whether that is every compared frame rather than the frames worth reading. */
|
|
193
|
+
every: boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Write the pictures for a report into `outDir`, and `frames.json` beside them.
|
|
198
|
+
*
|
|
199
|
+
* `<outDir>/<set>/` is cleared and rewritten for every set the report holds, as
|
|
200
|
+
* `render` clears its own: a set that lists fewer frames this run would otherwise
|
|
201
|
+
* keep last run's pictures beside this run's, and a stale picture of a comparison
|
|
202
|
+
* is indistinguishable from a current one. Nothing else under `outDir` is touched.
|
|
203
|
+
*
|
|
204
|
+
* ⚠️ Throws, naming the frame, when `plates` does not hold a frame the report
|
|
205
|
+
* lists. That is not a reachable input: it is `CheckPlates`'s retention and
|
|
206
|
+
* `framesToList` disagreeing about which frames are worth reading, and a picture
|
|
207
|
+
* set with a hole in it would be the silent version of that disagreement.
|
|
208
|
+
*/
|
|
209
|
+
export function writeCheckPictures(
|
|
210
|
+
outDir: string,
|
|
211
|
+
report: CheckReport,
|
|
212
|
+
plates: CheckPlates,
|
|
213
|
+
opts: { allFrames: boolean },
|
|
214
|
+
): PicturesWritten[] {
|
|
215
|
+
mkdirSync(outDir, { recursive: true });
|
|
216
|
+
const written: PicturesWritten[] = [];
|
|
217
|
+
for (const anim of report.animations) {
|
|
218
|
+
const dir = join(outDir, anim.dir);
|
|
219
|
+
if (existsSync(dir)) rmSync(dir, { recursive: true });
|
|
220
|
+
const listed = anim.compared === 0 ? [] : framesToList(anim, opts.allFrames);
|
|
221
|
+
if (listed.length > 0) mkdirSync(dir, { recursive: true });
|
|
222
|
+
for (const frame of listed) {
|
|
223
|
+
const held = plates.of(anim.dir, frame.index);
|
|
224
|
+
if (held === undefined) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`check --out: set ${JSON.stringify(anim.dir)} lists ${frameName(frame.index)} as worth reading and no ` +
|
|
227
|
+
'raster was kept for it — CheckPlates and framesToList disagree about the listing (a defect in rigc)',
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const picture = composePicture(frame, held, report.background);
|
|
231
|
+
writeFileSync(join(dir, `${frameName(frame.index)}.png`), encodePng(picture.width, picture.height, picture.data));
|
|
232
|
+
}
|
|
233
|
+
written.push({
|
|
234
|
+
dir: anim.dir,
|
|
235
|
+
path: dir,
|
|
236
|
+
frames: listed.map((f) => f.index),
|
|
237
|
+
every: listed.length === anim.frames.length,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
writeFileSync(join(outDir, FRAMES_SIDECAR), `${JSON.stringify(picturesSidecar(report, written, opts.allFrames), null, 2)}\n`);
|
|
241
|
+
return written;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* What the pictures are OF — the file a reader opens first, under the name a
|
|
246
|
+
* frame set's sidecar has, so "what is this directory" is answered in the place
|
|
247
|
+
* it is always answered.
|
|
248
|
+
*
|
|
249
|
+
* 🔒 The field named by `COMPARISON_FIELD` is what makes it not a frame set, and
|
|
250
|
+
* `check --frames` refuses a sidecar that carries it, by that name: the reference
|
|
251
|
+
* pane inside each picture is a frame, but the file is a comparison, and scoring
|
|
252
|
+
* a candidate against a picture of a comparison is a number about the wrong
|
|
253
|
+
* thing. There is deliberately no top-level `viewport`: under the per-shot scope
|
|
254
|
+
* every set was measured in its own box, so the box is recorded per set, and one
|
|
255
|
+
* box at the top would be a claim the run did not make.
|
|
256
|
+
*/
|
|
257
|
+
function picturesSidecar(report: CheckReport, written: PicturesWritten[], allFrames: boolean): Record<string, unknown> {
|
|
258
|
+
return {
|
|
259
|
+
spec: FRAMES_SPEC,
|
|
260
|
+
[COMPARISON_FIELD]: {
|
|
261
|
+
candidate: report.candidate,
|
|
262
|
+
// Which poser drew the candidate pane (issue #968) — the report's own field.
|
|
263
|
+
poser: report.poser,
|
|
264
|
+
frames: report.framesDir,
|
|
265
|
+
skin: report.skin,
|
|
266
|
+
referenceSkin: report.referenceSkin,
|
|
267
|
+
framingScope: report.framingScope,
|
|
268
|
+
grid: gridOf(report),
|
|
269
|
+
panes: [...PANES],
|
|
270
|
+
listing: allFrames ? 'every compared frame (--all-frames)' : 'the frames worth reading',
|
|
271
|
+
textureFrom: report.textureFrom === null ? null : report.textureFrom.atlas,
|
|
272
|
+
},
|
|
273
|
+
background: report.background,
|
|
274
|
+
sets: report.animations.map((anim: AnimationCheck, i) => {
|
|
275
|
+
const viewport: Framing = anim.viewport;
|
|
276
|
+
return {
|
|
277
|
+
dir: anim.dir,
|
|
278
|
+
animation: anim.animation,
|
|
279
|
+
candidateAnimation: anim.candidateAnimation,
|
|
280
|
+
fps: anim.fps,
|
|
281
|
+
compared: anim.compared,
|
|
282
|
+
written: written[i].frames.length,
|
|
283
|
+
frames: written[i].frames,
|
|
284
|
+
framing: anim.framing,
|
|
285
|
+
viewport,
|
|
286
|
+
};
|
|
287
|
+
}),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** The comparison grid — the reference frames' own pixel size, which every set shares. */
|
|
292
|
+
function gridOf(report: CheckReport): { pixelWidth: number; pixelHeight: number } | null {
|
|
293
|
+
const v = report.referenceViewport ?? report.animations[0]?.viewport ?? null;
|
|
294
|
+
return v === null ? null : { pixelWidth: v.pixelWidth, pixelHeight: v.pixelHeight };
|
|
295
|
+
}
|