spine-rigc 1.6.2 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.ts +82 -43
- package/docs/AUTHORING.md +9 -6
- package/package.json +1 -1
- package/src/atlas.ts +26 -16
- package/src/check.ts +37 -39
- package/src/compile.ts +59 -74
- package/src/core/constraints_path.ts +60 -38
- package/src/core/constraints_physics.ts +22 -4
- package/src/core/index.ts +95 -10
- package/src/core/uvs.ts +25 -5
- package/src/core/world.ts +79 -24
- package/src/deformmeasure.ts +6 -3
- package/src/emit.ts +17 -6
- package/src/ingest.ts +18 -13
- package/src/keyorder.ts +2 -2
- package/src/model.ts +109 -11
- package/src/render.ts +347 -34
- package/src/render_core.ts +115 -18
- package/src/transform.ts +62 -126
- package/src/types.ts +6 -5
- package/src/validate.ts +51 -29
package/cli.ts
CHANGED
|
@@ -93,7 +93,7 @@ import {
|
|
|
93
93
|
} from './src/diff.ts';
|
|
94
94
|
import { ingest, IngestError, IngestSpecRefused, INGEST_GUTTERS, type IngestFinding, type IngestStage } from './src/ingest.ts';
|
|
95
95
|
import { NotAPngError } from './src/png.ts';
|
|
96
|
-
import { copyAtlasPages } from './src/emit.ts';
|
|
96
|
+
import { copyAtlasPages, plannedPageCopies } from './src/emit.ts';
|
|
97
97
|
import {
|
|
98
98
|
DEFAULT_PADDING,
|
|
99
99
|
DEFAULT_PAGE_EDGES,
|
|
@@ -141,10 +141,9 @@ import {
|
|
|
141
141
|
GEOMETRY_FILE,
|
|
142
142
|
GeometryError,
|
|
143
143
|
UnframeablePoseError,
|
|
144
|
-
skinRosterOf,
|
|
145
144
|
geometryFileOf,
|
|
146
145
|
geometryText,
|
|
147
|
-
|
|
146
|
+
loadCandidate,
|
|
148
147
|
PROTOCOL_FPS,
|
|
149
148
|
renderFrame,
|
|
150
149
|
sampleAll,
|
|
@@ -154,17 +153,16 @@ import {
|
|
|
154
153
|
SHEET_TILE,
|
|
155
154
|
sidecarViewport,
|
|
156
155
|
SlotSubsetError,
|
|
157
|
-
slotSubsetOf,
|
|
158
|
-
candidatePosers,
|
|
159
156
|
POSER_NAMES,
|
|
160
157
|
PoserChoiceError,
|
|
158
|
+
refuseUnchosen,
|
|
159
|
+
SpineRuntimeError,
|
|
161
160
|
throughPoser,
|
|
162
161
|
type Frame,
|
|
163
162
|
type FramesSidecar,
|
|
164
163
|
type FrameSet,
|
|
165
|
-
type Posable,
|
|
166
|
-
type PoserChoice,
|
|
167
164
|
type PoserName,
|
|
165
|
+
type SkeletonFacts,
|
|
168
166
|
type SlotSubset,
|
|
169
167
|
} from './src/render.ts';
|
|
170
168
|
import {
|
|
@@ -456,10 +454,15 @@ function runGate(
|
|
|
456
454
|
modelText: string,
|
|
457
455
|
opts: CompileOptions,
|
|
458
456
|
profile: ValidateProfile,
|
|
457
|
+
/** The atlas text `build` writes for a compile's own — `--copy-images` renames the pages — which the second compile's document is spelled from, as `modelText` was (issue #1016). */
|
|
458
|
+
written: (atlasText: string) => string,
|
|
459
459
|
atlas?: AtlasOverride,
|
|
460
460
|
): number {
|
|
461
461
|
// The determinism check compares a second, independent compile — its model
|
|
462
|
-
// document included, which is the text `build` writes beside the pair
|
|
462
|
+
// document included, which is the text `build` writes beside the pair, and
|
|
463
|
+
// which states where each region sits in the atlas written with it (`pages`,
|
|
464
|
+
// issue #1016): so the second document is spelled from the second compile's
|
|
465
|
+
// atlas as it would be written, or from the second, independent pack.
|
|
463
466
|
const again = compile(opts);
|
|
464
467
|
const report = validate({
|
|
465
468
|
skeletonText: result.skeletonText,
|
|
@@ -467,7 +470,7 @@ function runGate(
|
|
|
467
470
|
atlasDir: opts.outDir,
|
|
468
471
|
declaredDurations: result.declaredDurations,
|
|
469
472
|
modelText,
|
|
470
|
-
reEmit: { skeletonText: again.skeletonText, atlasText: atlas ? atlas.again : again.atlasText, modelText: modelDocument(again.model, again.skeletonText) },
|
|
473
|
+
reEmit: { skeletonText: again.skeletonText, atlasText: atlas ? atlas.again : again.atlasText, modelText: modelDocument(again.model, again.skeletonText, atlas ? atlas.again : written(again.atlasText)) },
|
|
471
474
|
rig: result.rig,
|
|
472
475
|
profile,
|
|
473
476
|
});
|
|
@@ -864,7 +867,7 @@ function deformReportLines(result: CompileResult, exempt: ReadonlySet<string>):
|
|
|
864
867
|
// Through rigc's own core over the model document the build carries (issue #969), spine-core when the core
|
|
865
868
|
// refuses it; the survey's record says which (`source`), and `tools/survey_hashes.ts` holds the two to one block.
|
|
866
869
|
// The block itself is `deformReportBlock` (`src/deformreport.ts`), so a control renders it off either poser's survey.
|
|
867
|
-
const survey = surveyOfBuild({ skeletonText: result.skeletonText, atlasText: result.atlasText, modelText: modelDocument(result.model, result.skeletonText) }, new Set(), 'auto');
|
|
870
|
+
const survey = surveyOfBuild({ skeletonText: result.skeletonText, atlasText: result.atlasText, modelText: modelDocument(result.model, result.skeletonText, result.atlasText) }, new Set(), 'auto');
|
|
868
871
|
return deformReportBlock(survey, result.deformTransforms, exempt);
|
|
869
872
|
}
|
|
870
873
|
|
|
@@ -1113,10 +1116,17 @@ function cmdBuild(flags: Record<string, string>): void {
|
|
|
1113
1116
|
|
|
1114
1117
|
// The model's document is spelled before the gate, so the text A18 compares
|
|
1115
1118
|
// is the text written, and a model the document cannot carry is refused
|
|
1116
|
-
// before anything is (issue #922).
|
|
1117
|
-
|
|
1119
|
+
// before anything is (issue #922). It states where each region sits in the
|
|
1120
|
+
// atlas written beside it (`pages`, issue #1016), so it is spelled from that
|
|
1121
|
+
// atlas: under `--copy-images` the text with the copies' page names, planned
|
|
1122
|
+
// here from the text alone (`plannedPageCopies`) and copied after the gate.
|
|
1123
|
+
// Under `--pack` the pages move again after this gate, and the document
|
|
1124
|
+
// written is the one the packed gate below spells and compares.
|
|
1125
|
+
const copying = flags['copy-images'] !== undefined;
|
|
1126
|
+
const writtenAtlas = (atlasText: string): string => (copying ? plannedPageCopies(atlasText, opts.outDir).atlasText : atlasText);
|
|
1127
|
+
let modelText = modelDocument(result.model, result.skeletonText, writtenAtlas(result.atlasText));
|
|
1118
1128
|
console.log(` .. validate (spine-core round trip + machine assertions, profile ${profile})`);
|
|
1119
|
-
const failures = runGate(result, modelText, opts, profile);
|
|
1129
|
+
const failures = runGate(result, modelText, opts, profile, writtenAtlas);
|
|
1120
1130
|
if (failures > 0) {
|
|
1121
1131
|
console.error(`rigc: ${failures} assertion(s) failed — nothing written`);
|
|
1122
1132
|
process.exit(1);
|
|
@@ -1139,6 +1149,10 @@ function cmdBuild(flags: Record<string, string>): void {
|
|
|
1139
1149
|
let atlasText = result.atlasText;
|
|
1140
1150
|
if (flags['copy-images'] !== undefined) {
|
|
1141
1151
|
const copied = copyAtlasPages(atlasText, opts.outDir);
|
|
1152
|
+
// The document's pages were spelled from the plan; the copy is held to it before anything of the pair is written.
|
|
1153
|
+
if (copied.atlasText !== writtenAtlas(result.atlasText)) {
|
|
1154
|
+
throw new Error(`internal: --copy-images wrote an atlas whose page names are not the ones ${MODEL_DOCUMENT_FILE} was spelled with — nothing of the pair was written`);
|
|
1155
|
+
}
|
|
1142
1156
|
atlasText = copied.atlasText;
|
|
1143
1157
|
console.log(` .. copy-images: ${copied.pages.length} page(s) copied into ${opts.outDir}`);
|
|
1144
1158
|
for (const p of copied.pages) {
|
|
@@ -1197,7 +1211,10 @@ function cmdBuild(flags: Record<string, string>): void {
|
|
|
1197
1211
|
})),
|
|
1198
1212
|
packOpts,
|
|
1199
1213
|
);
|
|
1200
|
-
|
|
1214
|
+
// The document written is spelled from the packed atlas, and this gate's A18 compares it with a
|
|
1215
|
+
// second compile's spelled from the second, independent pack (issue #1016).
|
|
1216
|
+
modelText = modelDocument(result.model, result.skeletonText, atlasText);
|
|
1217
|
+
const packFailures = runGate(result, modelText, opts, profile, (text) => text, { text: atlasText, again: packAgain.atlasText });
|
|
1201
1218
|
if (packFailures > 0) {
|
|
1202
1219
|
console.error(
|
|
1203
1220
|
`rigc: ${packFailures} assertion(s) failed on the PACKED atlas — the pages were written to ` +
|
|
@@ -1209,8 +1226,9 @@ function cmdBuild(flags: Record<string, string>): void {
|
|
|
1209
1226
|
|
|
1210
1227
|
writeFileSync(join(opts.outDir, 'skeleton.json'), result.skeletonText);
|
|
1211
1228
|
writeFileSync(join(opts.outDir, 'skeleton.atlas'), atlasText);
|
|
1212
|
-
// rigc's own record of the compiled rig (`rigc-compiled/
|
|
1213
|
-
//
|
|
1229
|
+
// rigc's own record of the compiled rig (`rigc-compiled/2`, issue #922;
|
|
1230
|
+
// its `pages` the atlas just written, issue #1016), written with the pair
|
|
1231
|
+
// and only after the same gate — under `--pack`, the packed one. rigc's own posing core
|
|
1214
1232
|
// reads it (`readModel`, `src/core/index.ts`, issue #380's step 2).
|
|
1215
1233
|
writeFileSync(join(opts.outDir, MODEL_DOCUMENT_FILE), modelText);
|
|
1216
1234
|
console.log(`rigc: wrote ${join(opts.outDir, 'skeleton.json')}`);
|
|
@@ -1603,13 +1621,13 @@ function readSkinFlag(flags: Record<string, string>, declared: string[]): string
|
|
|
1603
1621
|
*/
|
|
1604
1622
|
function readSlotSubsetFlags(
|
|
1605
1623
|
flags: Record<string, string>,
|
|
1606
|
-
|
|
1624
|
+
facts: SkeletonFacts,
|
|
1607
1625
|
skin: string | undefined,
|
|
1608
1626
|
): SlotSubset | undefined {
|
|
1609
1627
|
const list = (raw: string | undefined): string[] | undefined =>
|
|
1610
1628
|
raw === undefined ? undefined : raw.split(',').map((name) => name.trim()).filter((name) => name !== '');
|
|
1611
1629
|
try {
|
|
1612
|
-
return
|
|
1630
|
+
return facts.subset({ slots: list(flags.slot), hidden: list(flags.hide) }, skin);
|
|
1613
1631
|
} catch (err) {
|
|
1614
1632
|
if (err instanceof SlotSubsetError) throw new UsageError(err.message);
|
|
1615
1633
|
throw err;
|
|
@@ -1617,16 +1635,12 @@ function readSlotSubsetFlags(
|
|
|
1617
1635
|
}
|
|
1618
1636
|
|
|
1619
1637
|
/**
|
|
1620
|
-
* `--poser
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
1638
|
+
* `--poser` as spelled, checked against the posers there are — shared by
|
|
1639
|
+
* `render` and `check`. On `render` it is read where its refusal has always
|
|
1640
|
+
* stood, after the candidate's own flags; the candidate is loaded before that
|
|
1641
|
+
* with the spelling as given (`posersAsked`), so a rigc build the core poses
|
|
1642
|
+
* is chosen without loading spine-core (issue #1014).
|
|
1624
1643
|
*/
|
|
1625
|
-
function readPoserChoice(flags: Record<string, string>, data: Posable['data'], skeletonPath: string, atlasPath: string): PoserChoice {
|
|
1626
|
-
return candidatePosers(data, skeletonPath, atlasPath, readPoserFlag(flags));
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
/** `--poser` as spelled, checked against the posers there are — shared by `render` and `check`. */
|
|
1630
1644
|
function readPoserFlag(flags: Record<string, string>): PoserName | undefined {
|
|
1631
1645
|
const raw = flags.poser;
|
|
1632
1646
|
const forced = POSER_NAMES.find((name) => name === raw);
|
|
@@ -1636,6 +1650,11 @@ function readPoserFlag(flags: Record<string, string>): PoserName | undefined {
|
|
|
1636
1650
|
return forced;
|
|
1637
1651
|
}
|
|
1638
1652
|
|
|
1653
|
+
/** `--poser` as the candidate is loaded with: a known spelling, or the input's own choice — the spelling is refused later, by `readPoserFlag`. */
|
|
1654
|
+
function posersAsked(flags: Record<string, string>): PoserName | undefined {
|
|
1655
|
+
return POSER_NAMES.find((name) => name === flags.poser);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1639
1658
|
/** A resolved subset as the one field it is spelled as, in `PoseOptions` and in `frames.json` alike. */
|
|
1640
1659
|
function subsetFields(subset: SlotSubset | undefined): { slots?: string[]; hidden?: string[] } {
|
|
1641
1660
|
if (subset === undefined) return {};
|
|
@@ -1653,8 +1672,10 @@ function readPositiveNumber(flags: Record<string, string>, key: string, fallback
|
|
|
1653
1672
|
/**
|
|
1654
1673
|
* Does this skeleton declare a setup stage — a numeric width AND height?
|
|
1655
1674
|
*
|
|
1656
|
-
* Read off the
|
|
1657
|
-
*
|
|
1675
|
+
* Read off the file's header for `preview`, which never loads one; `render`
|
|
1676
|
+
* reads the same statement off its candidate (`SkeletonFacts.declaresStage`:
|
|
1677
|
+
* the header again on a rigc build the core poses, the loaded `SkeletonData`
|
|
1678
|
+
* on an export). Both are the same two fields, because
|
|
1658
1679
|
* `SkeletonJson` copies them across unconditionally (`SkeletonJson.js:70-73`),
|
|
1659
1680
|
* so a header that omits them leaves `undefined` on a field typed `number`.
|
|
1660
1681
|
*/
|
|
@@ -1725,10 +1746,18 @@ function cmdRender(flags: Record<string, string>): void {
|
|
|
1725
1746
|
console.log('rigc render');
|
|
1726
1747
|
console.log(` .. skeleton ${skeletonPath}`);
|
|
1727
1748
|
console.log(` .. atlas ${atlasPath}`);
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1749
|
+
// Which implementation of the posing seam draws this (issue #968), chosen
|
|
1750
|
+
// before anything is read off the candidate (issue #1014): a rigc build the
|
|
1751
|
+
// core poses — `skeleton.model.json` beside the pair — reads its names, its
|
|
1752
|
+
// subset roster and its pages without loading spine-core at all.
|
|
1753
|
+
const { choice, facts, pages } = loadCandidate(
|
|
1754
|
+
{ skeletonText: readFileSync(skeletonPath, 'utf8'), atlasText: readFileSync(atlasPath, 'utf8'), atlasDir, label: skeletonPath },
|
|
1755
|
+
{ skeleton: skeletonPath, atlas: atlasPath },
|
|
1756
|
+
posersAsked(flags),
|
|
1757
|
+
);
|
|
1758
|
+
const only = readAnimationFlag(flags, [...facts.animations]);
|
|
1759
|
+
const skin = readSkinFlag(flags, [...facts.skins]);
|
|
1760
|
+
const subset = readSlotSubsetFlags(flags, facts, skin);
|
|
1732
1761
|
// One object, so the framing and the frames cannot be posed under two
|
|
1733
1762
|
// different skins — which would frame one shot with another shot's box.
|
|
1734
1763
|
// Not annotated `PoseOptions`: that name is `src/pose.ts`'s in this file, and
|
|
@@ -1742,23 +1771,25 @@ function cmdRender(flags: Record<string, string>): void {
|
|
|
1742
1771
|
if (skin !== undefined) console.log(` .. skin ${skin}`);
|
|
1743
1772
|
if (subset !== undefined) console.log(` .. ${subset.mode.padEnd(8)} ${subset.names.join(', ')}`);
|
|
1744
1773
|
|
|
1745
|
-
//
|
|
1746
|
-
//
|
|
1747
|
-
//
|
|
1748
|
-
//
|
|
1749
|
-
|
|
1774
|
+
// Rigc's own core when the candidate is a rigc build, and spine-core
|
|
1775
|
+
// otherwise, or where the core refuses the input by name. Never silently:
|
|
1776
|
+
// the `poser` line below says which, and why. `--poser` is refused here, where
|
|
1777
|
+
// it always was: a spelling there is no poser for, then `core` on an input
|
|
1778
|
+
// that cannot carry it.
|
|
1779
|
+
readPoserFlag(flags);
|
|
1780
|
+
refuseUnchosen(choice);
|
|
1750
1781
|
|
|
1751
1782
|
// Everything is posed before anything is written, so a core refusal partway
|
|
1752
1783
|
// re-poses the whole input on spine-core rather than leaving half of a frame
|
|
1753
1784
|
// set drawn by each.
|
|
1754
|
-
const posed = throughPoser(choice, (poser) => {
|
|
1785
|
+
const posed = throughPoser(choice, (poser, roster) => {
|
|
1755
1786
|
// `null` is a skeleton that posed no vertex at all. One that posed a vertex
|
|
1756
1787
|
// it cannot frame — Infinity or NaN — is thrown from the framing as a
|
|
1757
1788
|
// `GeometryError` naming the number (issue #873), and one whose every vertex
|
|
1758
1789
|
// sits at one point — every drawn bone unposed by the skin, or collapsed — as
|
|
1759
1790
|
// an `UnframeablePoseError` (issue #997); neither reaches this. The roster is what
|
|
1760
1791
|
// lets the second name the skins that pose a bone, whichever poser draws.
|
|
1761
|
-
const viewport = framingViewport(poser, maxSide, pose,
|
|
1792
|
+
const viewport = framingViewport(poser, maxSide, pose, roster);
|
|
1762
1793
|
if (!viewport) {
|
|
1763
1794
|
throw new UsageError(
|
|
1764
1795
|
`${skeletonPath} posed no drawable attachment in any animation or in its setup pose${
|
|
@@ -1784,7 +1815,7 @@ function cmdRender(flags: Record<string, string>): void {
|
|
|
1784
1815
|
const geometryTexts = new Map<string, string>();
|
|
1785
1816
|
if (geometry) {
|
|
1786
1817
|
for (const [name, frames] of sampled) {
|
|
1787
|
-
const animation = name === SETUP_POSE_DIR &&
|
|
1818
|
+
const animation = name === SETUP_POSE_DIR && facts.animations.length === 0 ? null : name;
|
|
1788
1819
|
geometryTexts.set(name, geometryText(geometryFileOf(poser, animation, fps, frames, viewport, skin)));
|
|
1789
1820
|
}
|
|
1790
1821
|
}
|
|
@@ -1793,7 +1824,7 @@ function cmdRender(flags: Record<string, string>): void {
|
|
|
1793
1824
|
const { viewport, sampled, geometryTexts } = posed.value;
|
|
1794
1825
|
console.log(` .. poser ${posed.note}`);
|
|
1795
1826
|
console.log(` .. ${viewport.width}x${viewport.height}px at ${fps} fps, ${sampled.size} set(s) -> ${outRoot}`);
|
|
1796
|
-
if (!
|
|
1827
|
+
if (!facts.declaresStage) console.log(` .. ${STAGELESS_FRAMING.render}`);
|
|
1797
1828
|
|
|
1798
1829
|
mkdirSync(outRoot, { recursive: true });
|
|
1799
1830
|
const sets: FrameSet[] = [];
|
|
@@ -1819,7 +1850,7 @@ function cmdRender(flags: Record<string, string>): void {
|
|
|
1819
1850
|
const duration = frames[frames.length - 1].time;
|
|
1820
1851
|
sets.push({
|
|
1821
1852
|
dir: dirName,
|
|
1822
|
-
animation: name === SETUP_POSE_DIR &&
|
|
1853
|
+
animation: name === SETUP_POSE_DIR && facts.animations.length === 0 ? null : name,
|
|
1823
1854
|
fps,
|
|
1824
1855
|
sampled: frames.length,
|
|
1825
1856
|
written: frames.length,
|
|
@@ -4281,6 +4312,14 @@ try {
|
|
|
4281
4312
|
console.error(`rigc: ${err.message}`);
|
|
4282
4313
|
process.exit(1);
|
|
4283
4314
|
}
|
|
4315
|
+
// An input posed through spine-core — a Spine export, `--poser spine`, a
|
|
4316
|
+
// fallback — on a run where the runtime cannot be used (issue #1014). The
|
|
4317
|
+
// sentence names the input and why it needs the runtime; exit 1, like a file
|
|
4318
|
+
// that is not a PNG: the invocation was fine and the run could not pose it.
|
|
4319
|
+
if (err instanceof SpineRuntimeError) {
|
|
4320
|
+
console.error(`rigc ${command}: ${err.message}`);
|
|
4321
|
+
process.exit(1);
|
|
4322
|
+
}
|
|
4284
4323
|
// An install refused before its first write (issue #831): the invocation was
|
|
4285
4324
|
// fine and an entry on disk was not what this command would write, so exit 1
|
|
4286
4325
|
// like a file that is not a PNG. The message names every such entry, what is
|
package/docs/AUTHORING.md
CHANGED
|
@@ -195,7 +195,7 @@ What the flags mean:
|
|
|
195
195
|
| --- | --- |
|
|
196
196
|
| `--rig` | the rig spec — skeleton structure |
|
|
197
197
|
| `--motion` | the motion spec — time |
|
|
198
|
-
| `--out` | directory for `skeleton.json` + `skeleton.atlas`, and on `build` `skeleton.model.json` beside them; atlas page paths and `skeleton.images` are written relative to it. `skeleton.model.json` is rigc's own record of the compiled rig (`rigc-compiled/
|
|
198
|
+
| `--out` | directory for `skeleton.json` + `skeleton.atlas`, and on `build` `skeleton.model.json` beside them; atlas page paths and `skeleton.images` are written relative to it. `skeleton.model.json` is rigc's own record of the compiled rig (`rigc-compiled/2`: the bones, slots, skins, constraints, events and animations the Spine files were emitted from, in the spec's order, and the rest of what posing them takes; then `pages`, every page of the `skeleton.atlas` written beside it and where each region sits on it — after `--pack` the packed pages, after `--copy-images` the copies' names), written after the same gate as the pair and ending in `spine.sha256`, the digest of the `skeleton.json` written beside it. A `rigc-compiled/1` document (rigc 1.6) has no `pages` and is still read: the core then takes the placement from the atlas beside it. It is what rigc's own posing core (`src/core/`) poses: `render` and `check` — and `bench --frames`, which folds `check` in — pose a build through it, and `--poser` below says when they do not. On `check` it is a directory of **pictures** instead: one per frame the table lists (every compared frame under `--all-frames`), reference · candidate · difference · overlay at the comparison grid's native size, as `<dir>/<set>/f####.png` beside a `frames.json` that says what they are pictures of. Each `<dir>/<set>/` is cleared first; a file at `<dir>`, or a directory that is or holds `--frames`, is refused by name — **§9.2.1** |
|
|
199
199
|
| `--copy-images` | `build` only: also copies every page **the emitted atlas names** into `--out` and rewrites the atlas to the copies, so the directory is self-contained enough to zip or commit on its own, and points `skeleton.images` at `--out` itself so the editor's import finds the parts beside the skeleton (§3.1 says why it is spelled `../<out>/` and not `./`). Under `--atlas-in` those pages are the pack's, not one per part (**§0.2**). Without it, page paths point at the source art |
|
|
200
200
|
| `--pack` | `build` only: arrange every part onto **shared** atlas page(s), written into `--out` as real PNGs, instead of one page per part. Lossless — nothing is resampled, trimmed or rotated. The default is one page per part — **§0.1** |
|
|
201
201
|
| `--page-size` | `build --pack` only: the largest page edge (default `2048`). A ceiling, not the size: page edges are powers of two and the one written is the smallest that holds the pack — **§0.1** |
|
|
@@ -210,7 +210,7 @@ What the flags mean:
|
|
|
210
210
|
| `--candidate` | `check`, `bench`, `render`, `preview`, `chainfit` and `vote` only: a **compiled** artifact — the directory `build --out` wrote, or a `skeleton.json` path. `--atlas <path>` names the atlas when it does not sit beside the skeleton. **`vote` and `preview` take it more than once**: `vote` 2–4 times, one per pane, labelled A, B, C, D in the order given; `preview` any number of times, one pane per candidate in the order given, each headed by its path and its gate line, and the same skeleton twice (a directory and its `skeleton.json` are one) is refused by name. `--atlas` goes with one candidate only. Everywhere else a repeat is a typo and is refused |
|
|
211
211
|
| `--animation` | `render`, `preview` and `vote` only: which animation to show. The default is **every** one for `render`, the **first** for `preview` (each candidate's own first, with several), and for `vote` the first of candidate A. A name the skeleton does not have is refused, with the ones it does have listed — and for `vote` and a several-candidate `preview`, so is a name that only *some* candidates have, naming the one that lacks it |
|
|
212
212
|
| `--geometry` | `render` only: also write a `geometry.json` into each frame directory — per frame, every bone's world transform and every slot's region or mesh vertices in world units after skinning, plus each attachment's rest geometry and topology, on the frames' own grid and viewport. Every other file is byte for byte what the render writes without it. Refused with `--slot`/`--hide`, because the geometry is the whole pose whatever is drawn — **§8.2** |
|
|
213
|
-
| `--poser` | `render` and `check`: `core` or `spine` — which implementation poses the frames (on `check`, the candidate's; the reference side is pixels either way). By default a **rigc build** (the `skeleton.model.json` `build` writes, with the atlas beside it) is posed by rigc's own core and a **Spine export** by spine-core; the render prints a `poser` line saying which and why, and so does the check report (`check.json`'s `poser`, and the pictures' `frames.json` under `--out`; `bench --frames` prints the same line). A concave or an inverse clip is posed by the core. Where the input cannot carry the core, or the core refuses it, the render poses it through spine-core and the `poser` line says why, in one of these words: `no skeleton.model.json beside` (a Spine export) · `is not beside` (`--atlas` names an atlas outside the build's directory) · `does not describe` (a model document whose bones, slots or animations are not the skeleton's) · `is not the skeleton.json` (a `skeleton.json` whose SHA-256 is not the `spine.sha256` its model document records — edited or replaced after the build — both digests named) · `the polygon is not simple` (a clip polygon two of whose edges cross or touch, the slot named) · `the "default" skin names` (no `--skin`, and the default skin names a skin-required bone or constraint, each named) · `and no "default" one` (no `--skin`, and skins with none named `default` — spine-core draws nothing there either, so the render stops at `there is nothing to draw`, exit 2, and only `--poser core` prints this refusal) · `depends on the runtime's previous pass` (a constraint moves a posed bone from a bone that a constraint writes into while that bone is inactive, all four named) · `--poser spine` (asked for). Any other refusal of the core's — an unreadable model document among them — reads `the core refused` and then the core's own words. `--poser core` on an input that cannot carry it is refused in the same words (exit 2, nothing written); `--poser spine` forces the runtime. The repository's selftest renders one probe per reason above and holds this list to the lines they print (`RC09`). Both posers draw the same frames, geometry and framing box to the bit on every corpus (issue #968), and `check` through either writes the same report, `check.json` and pictures but for the poser it names on every tree row |
|
|
213
|
+
| `--poser` | `render` and `check`: `core` or `spine` — which implementation poses the frames (on `check`, the candidate's; the reference side is pixels either way). By default a **rigc build** (the `skeleton.model.json` `build` writes, with the atlas beside it) is posed by rigc's own core and a **Spine export** by spine-core; the render prints a `poser` line saying which and why, and so does the check report (`check.json`'s `poser`, and the pictures' `frames.json` under `--out`; `bench --frames` prints the same line). A concave or an inverse clip is posed by the core. Where the input cannot carry the core, or the core refuses it, the render poses it through spine-core and the `poser` line says why, in one of these words: `no skeleton.model.json beside` (a Spine export) · `is not beside` (`--atlas` names an atlas outside the build's directory) · `does not describe` (a model document whose bones, slots or animations are not the skeleton's) · `is not the skeleton.json` (a `skeleton.json` whose SHA-256 is not the `spine.sha256` its model document records — edited or replaced after the build — both digests named) · `the polygon is not simple` (a clip polygon two of whose edges cross or touch, the slot named) · `the "default" skin names` (no `--skin`, and the default skin names a skin-required bone or constraint, each named) · `and no "default" one` (no `--skin`, and skins with none named `default` — spine-core draws nothing there either, so the render stops at `there is nothing to draw`, exit 2, and only `--poser core` prints this refusal) · `depends on the runtime's previous pass` (a constraint moves a posed bone from a bone that a constraint writes into while that bone is inactive, all four named) · `--poser spine` (asked for). Any other refusal of the core's — an unreadable model document among them — reads `the core refused` and then the core's own words. `--poser core` on an input that cannot carry it is refused in the same words (exit 2, nothing written); `--poser spine` forces the runtime. The repository's selftest renders one probe per reason above and holds this list to the lines they print (`RC09`). Both posers draw the same frames, geometry and framing box to the bit on every corpus (issue #968), and `check` through either writes the same report, `check.json` and pictures but for the poser it names on every tree row. A rigc build the core poses reads nothing through spine-core on `render`, `render --geometry` and `check` (and the check `bench --frames` folds in; `bench`'s own `validate`, like `build`'s, is the runtime's round trip by design): its animation and skin names, slot and skin rosters, stage and bone tree come off its own `skeleton.json` and model document, its pages off rigc's atlas reader. The runtime is loaded for a Spine export, `--poser spine`, a fallback the `poser` line names and `--texture-from`; a run that cannot load it there is refused naming the input and why it needs the runtime (exit 1; `RC11`) |
|
|
214
214
|
| `--record` | `vote` only: a saved vote to check against its ballot and append to the ledger, instead of writing a ballot. This is the command's second mode; it takes no `--candidate` |
|
|
215
215
|
| `--ballot` | `vote --record` only: the ballot the vote answers (default `ballot.html`). Its embedded manifest is what the vote is checked against, so the ballot file is the record of the question |
|
|
216
216
|
| `--ledger` | `vote --record` only: the append-only JSONL the vote lands in (default `votes.jsonl`), one vote per line |
|
|
@@ -231,7 +231,9 @@ What the flags mean:
|
|
|
231
231
|
`render` also takes `--fps <n>` (the rate it samples at, default 12 — the same
|
|
232
232
|
protocol rate the reference frames use) and `--max <px>` (the long side of a
|
|
233
233
|
frame, default 256). A clipping attachment is applied as the runtime applies it —
|
|
234
|
-
spine-core's own `SkeletonClipping`, in spine-webgl's call order
|
|
234
|
+
spine-core's own `SkeletonClipping`, in spine-webgl's call order, on a Spine export,
|
|
235
|
+
and the core's clipper, drawing the same pixels, on a rigc build (the `--poser`
|
|
236
|
+
row) — so a slot from
|
|
235
237
|
the clip's through its `end` slot draws only what falls inside the polygon, in
|
|
236
238
|
`render`, `check` and every other picture rigc's own rasteriser draws (`preview`
|
|
237
239
|
plays in Spine's player, which always clipped); the framing box still counts
|
|
@@ -6481,8 +6483,9 @@ Inside `setup` and each frame:
|
|
|
6481
6483
|
scale and shear all live in `a b c d`.
|
|
6482
6484
|
- `attachments` — every slot showing a region or a mesh, in the frame's **draw
|
|
6483
6485
|
order**: `{ slot, attachment, vertices, color }`. `vertices` are `x, y` per vertex
|
|
6484
|
-
in world units after skinning and deform
|
|
6485
|
-
|
|
6486
|
+
in world units after skinning and deform, over the **whole** attachment — on a
|
|
6487
|
+
Spine export spine-core's own `computeWorldVertices`, and on a rigc build the
|
|
6488
|
+
core's, the same numbers to the bit (the `--poser` row). A region's four are its
|
|
6486
6489
|
corners in the order bottom-left, top-left, top-right, bottom-right; a mesh's are
|
|
6487
6490
|
in the attachment's own order, so vertex `i` is the same vertex in every frame and
|
|
6488
6491
|
in `rest`. `color` is the slot colour times the attachment colour, straight RGBA
|
|
@@ -6501,7 +6504,7 @@ are not numbered like them — so `vertices` are the attachment's own, and the f
|
|
|
6501
6504
|
show what the clip left of them. Bounding boxes, points, paths and clipping polygons
|
|
6502
6505
|
draw nothing and are not listed. Numbers are printed the way every JSON file this
|
|
6503
6506
|
tool writes prints them — the shortest decimal that reads back as the same double —
|
|
6504
|
-
so a vertex in the file *is* the
|
|
6507
|
+
so a vertex in the file *is* the poser's vertex, not a rounding of it; a pose that
|
|
6505
6508
|
is not finite is refused rather than written as `null`, naming where — the setup pose,
|
|
6506
6509
|
`animation "a" frame i at F fps (t=…s)`, or the rest table — and then the bone and
|
|
6507
6510
|
its world-transform field, or the slot, the attachment and the vertex, with the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "AI-authored Spine 2D rigging and animation, verified before it is written. rigc compiles a rig spec into Spine 4.3 skeleton data, round-trips it through spine-core and named assertions, and emits nothing that fails. Output imports into the Spine editor. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/atlas.ts
CHANGED
|
@@ -80,18 +80,20 @@
|
|
|
80
80
|
*
|
|
81
81
|
* `src/compile.ts` must stay independent of the runtime — that is what keeps the
|
|
82
82
|
* compiler and the gate from checking each other's assumptions — so the importer
|
|
83
|
-
* cannot reach for `TextureAtlas`. The reader below
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
83
|
+
* cannot reach for `TextureAtlas`. The reader below therefore states, for every
|
|
84
|
+
* field the format has, what the runtime reads it as — the page and region
|
|
85
|
+
* fields of `dist/TextureAtlas.js` — including the readings that look like
|
|
86
|
+
* mistakes and are not: the page name is trimmed and a region name is the RAW
|
|
87
|
+
* line, a blank line closes a page block, an entry holds at most four values,
|
|
88
|
+
* and `originalWidth/Height` fall back to `width/height` only when BOTH are
|
|
89
|
+
* zero.
|
|
90
90
|
*
|
|
91
91
|
* A second opinion about a format is a liability, so it is measured rather than
|
|
92
|
-
* asserted:
|
|
93
|
-
*
|
|
94
|
-
*
|
|
92
|
+
* asserted: `PKR01` parses every `.atlas` in the example corpus with both this
|
|
93
|
+
* reader and `spine-core`'s `TextureAtlas` and compares every page's name, size
|
|
94
|
+
* and `pma` and every region's eleven fields — 10 atlas files, 132 regions, 3
|
|
95
|
+
* of them rotated, 0 fields apart when this was written (issue #1015). If they
|
|
96
|
+
* ever disagree, that control goes red and this file is wrong.
|
|
95
97
|
*/
|
|
96
98
|
import { CompileError } from './errors.ts';
|
|
97
99
|
import { Plate, readPlate } from '../tools/plate.ts';
|
|
@@ -272,8 +274,9 @@ function int(text: string | undefined): number {
|
|
|
272
274
|
/**
|
|
273
275
|
* Read an atlas file's text into pages and regions.
|
|
274
276
|
*
|
|
275
|
-
*
|
|
276
|
-
* every branch below
|
|
277
|
+
* The format as the runtime reads it, field for field, and deliberately a dull
|
|
278
|
+
* reader — every branch below states a reading `PKR01` holds against
|
|
279
|
+
* `TextureAtlas`'s parse of the corpus (see the header). Two additions, both of
|
|
277
280
|
* them fields a PLAYER has no use for and an IMPORTER does: `nameLine`, which
|
|
278
281
|
* `rewritePageNames` needs, and the page's `scale:`, which is what turns a
|
|
279
282
|
* region's texels back into the drawing's own size (`AtlasPage.scale`).
|
|
@@ -1337,7 +1340,7 @@ export function packAtlas(inputs: PackInput[], opts: PackOptions = {}): PackResu
|
|
|
1337
1340
|
* a plate's rows run downwards, so the kept rectangle's top row is
|
|
1338
1341
|
* `originalHeight - offsetY - height`.
|
|
1339
1342
|
*
|
|
1340
|
-
* ## A rotated region is
|
|
1343
|
+
* ## A rotated region is MEASURED, not guessed at (issue #570)
|
|
1341
1344
|
*
|
|
1342
1345
|
* This refused a rotated region until 2026-09-17, on the argument that the
|
|
1343
1346
|
* runtime holds "three opinions" about the mapping. Measurement refutes the
|
|
@@ -1348,7 +1351,13 @@ export function packAtlas(inputs: PackInput[], opts: PackOptions = {}): PackResu
|
|
|
1348
1351
|
* `dist/attachments/MeshAttachment.js:126-162`) is the one routine that
|
|
1349
1352
|
* states where a region's texels are for **all four** `degrees`, and it is
|
|
1350
1353
|
* the routine `substituteTexture` in [`src/render.ts`](render.ts) already
|
|
1351
|
-
* goes through. The loop below
|
|
1354
|
+
* goes through. The loop below inverts what it samples: `PKR02` asks it,
|
|
1355
|
+
* for every texel of every region of the corpus atlases, which page texel
|
|
1356
|
+
* the runtime samples and compares the lift — 132 regions, 2,848,402
|
|
1357
|
+
* texels, 0 apart, three of them turned (90 twice, 270 once) and each of
|
|
1358
|
+
* those lifting differently with the turn ignored; `PK29` lifts the
|
|
1359
|
+
* packer's fixture at each of 0, 90, 180 and 270 back to its PNG byte for
|
|
1360
|
+
* byte;
|
|
1352
1361
|
* * `TextureAtlas`'s `u2`/`v2` (`dist/TextureAtlas.js:164-171`) transpose the
|
|
1353
1362
|
* rectangle at 90 and not at 270, so at 270 they describe a rectangle the
|
|
1354
1363
|
* page does not have — but `MeshAttachment.computeUVs` never reads them for
|
|
@@ -1358,8 +1367,9 @@ export function packAtlas(inputs: PackInput[], opts: PackOptions = {}): PackResu
|
|
|
1358
1367
|
* region-attachment rendering defect (issue #199) and not a statement about
|
|
1359
1368
|
* where the drawing sits.
|
|
1360
1369
|
*
|
|
1361
|
-
*
|
|
1362
|
-
*
|
|
1370
|
+
* On texel centres — at 90 and 270 measured against the runtime's sampling
|
|
1371
|
+
* (`PKR02`), at 180 against the packer fixture's turn (`PK29`) — the mapping
|
|
1372
|
+
* puts kept-rectangle pixel `(x, y)` — `x` from the drawing's left, `y` down from `top` — at page
|
|
1363
1373
|
* pixel `(X + x, Y + y)` unturned, `(X + y, Y + width - 1 - x)` at 90,
|
|
1364
1374
|
* `(X + width - 1 - x, Y + height - 1 - y)` at 180 and
|
|
1365
1375
|
* `(X + height - 1 - y, Y + x)` at 270, writing `X`/`Y` for the region's own
|
package/src/check.ts
CHANGED
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
BACKGROUND,
|
|
70
70
|
frameGeometry,
|
|
71
71
|
PROTOCOL_FPS,
|
|
72
|
-
|
|
72
|
+
loadCandidate,
|
|
73
73
|
renderFrame,
|
|
74
74
|
sampleAnimation,
|
|
75
75
|
sampleSetupPose,
|
|
@@ -83,11 +83,8 @@ import {
|
|
|
83
83
|
nonFinitePoseOf,
|
|
84
84
|
unframeableSentence,
|
|
85
85
|
UnframeablePoseError,
|
|
86
|
-
|
|
87
|
-
candidatePosers,
|
|
88
|
-
spinePoser,
|
|
86
|
+
refuseUnchosen,
|
|
89
87
|
throughPoser,
|
|
90
|
-
PoserChoiceError,
|
|
91
88
|
SHEET_COLUMNS,
|
|
92
89
|
SHEET_FILE,
|
|
93
90
|
SHEET_GAP,
|
|
@@ -95,9 +92,10 @@ import {
|
|
|
95
92
|
type Frame,
|
|
96
93
|
type FramesSidecar,
|
|
97
94
|
type FrameSet,
|
|
95
|
+
type MakeCorePoser,
|
|
96
|
+
type Posable,
|
|
98
97
|
type PoseOptions,
|
|
99
98
|
type Poser,
|
|
100
|
-
type PoserChoice,
|
|
101
99
|
type PoserName,
|
|
102
100
|
type TextureSubstitution,
|
|
103
101
|
type Viewport,
|
|
@@ -1062,22 +1060,11 @@ export interface CheckOptions {
|
|
|
1062
1060
|
* What builds the core poser — `candidatePosers`' own `make`. The suite's
|
|
1063
1061
|
* `CH01` passes a planted copy, and nothing else passes any.
|
|
1064
1062
|
*/
|
|
1065
|
-
makeCorePoser?:
|
|
1063
|
+
makeCorePoser?: MakeCorePoser;
|
|
1066
1064
|
}
|
|
1067
1065
|
|
|
1068
|
-
/**
|
|
1069
|
-
|
|
1070
|
-
* `candidatePosers` over the candidate's files when the caller named them, and
|
|
1071
|
-
* spine-core alone when it handed texts only — there is then no directory to
|
|
1072
|
-
* find a model document in, and the reason says so.
|
|
1073
|
-
*/
|
|
1074
|
-
function checkPoserChoice(data: Parameters<typeof spinePoser>[0], options: CheckOptions): PoserChoice {
|
|
1075
|
-
const paths = options.candidatePaths;
|
|
1076
|
-
if (paths !== undefined) return candidatePosers(data, paths.skeleton, paths.atlas, options.poser, options.makeCorePoser);
|
|
1077
|
-
const why = 'the candidate was handed to check as text, with no path to find a skeleton.model.json beside';
|
|
1078
|
-
if (options.poser === 'core') throw new PoserChoiceError(`--poser core: ${why}`);
|
|
1079
|
-
return { forced: options.poser, core: null, why: options.poser === 'spine' ? '--poser spine' : why, spine: spinePoser(data) };
|
|
1080
|
-
}
|
|
1066
|
+
/** Why a candidate handed to `check` as text alone is posed through spine-core — there is no directory to find a model document in. */
|
|
1067
|
+
const UNPLACED_CANDIDATE = 'the candidate was handed to check as text, with no path to find a skeleton.model.json beside';
|
|
1081
1068
|
|
|
1082
1069
|
// ---------------------------------------------------------------------------
|
|
1083
1070
|
|
|
@@ -1145,14 +1132,27 @@ export function checkAgainstFrames(options: CheckOptions): CheckReport {
|
|
|
1145
1132
|
}
|
|
1146
1133
|
const notes: string[] = [];
|
|
1147
1134
|
|
|
1148
|
-
|
|
1135
|
+
// Which implementation poses the candidate (issue #968), chosen once for the
|
|
1136
|
+
// whole run — every set, the setup pose and the non-finite sentence — so no
|
|
1137
|
+
// two figures in one report can come from two posers: `candidatePaths` over
|
|
1138
|
+
// the candidate's files, as `render` chooses, and spine-core alone when it
|
|
1139
|
+
// was handed texts only. Chosen BEFORE anything is read off the candidate
|
|
1140
|
+
// (issue #1014), so a rigc build the core poses reads its names, its stage,
|
|
1141
|
+
// its bone tree and its pages without loading spine-core.
|
|
1142
|
+
const { choice, facts, pages } = loadCandidate(
|
|
1143
|
+
{ skeletonText: options.skeletonText, atlasText: options.atlasText, atlasDir: options.atlasDir, label: options.labels?.skeleton ?? 'the candidate' },
|
|
1144
|
+
options.candidatePaths ?? null,
|
|
1145
|
+
options.poser,
|
|
1146
|
+
{ ...(options.makeCorePoser === undefined ? {} : { make: options.makeCorePoser }), unplaced: UNPLACED_CANDIDATE },
|
|
1147
|
+
);
|
|
1148
|
+
const posable: Pick<Posable, 'pages'> = { pages };
|
|
1149
1149
|
// A candidate that declares no stage is framed exactly as one that does,
|
|
1150
1150
|
// because no framing here reads a stage: the world box is fitted from what the
|
|
1151
1151
|
// candidate draws. Said on the one candidate where a reader can ask what box
|
|
1152
1152
|
// stood in for the absent one (issue #714) — `SkeletonJson` copies the header
|
|
1153
1153
|
// fields across unconditionally (`SkeletonJson.js:70-73`), so an omitted
|
|
1154
|
-
// extent is `undefined`
|
|
1155
|
-
if (
|
|
1154
|
+
// extent is `undefined` there, not 0, and the header says the same.
|
|
1155
|
+
if (!facts.declaresStage) {
|
|
1156
1156
|
notes.push(
|
|
1157
1157
|
'the candidate declares no stage (no `skeleton.width`/`height`), and nothing stands in for one: its world ' +
|
|
1158
1158
|
'box is fitted from the pixels it draws, as it is for every candidate, so the absence moves no figure below.',
|
|
@@ -1167,18 +1167,16 @@ export function checkAgainstFrames(options: CheckOptions): CheckReport {
|
|
|
1167
1167
|
// The skin is refused here rather than deeper in the sampler, for the reason
|
|
1168
1168
|
// every miss in this project is refused where the names are: the skeleton is
|
|
1169
1169
|
// open on this line and the alternatives can be listed.
|
|
1170
|
-
if (options.skin !== undefined && !
|
|
1170
|
+
if (options.skin !== undefined && !facts.skins.includes(options.skin)) {
|
|
1171
1171
|
throw new CheckError(
|
|
1172
1172
|
`the candidate declares no skin ${JSON.stringify(options.skin)}; it declares [${
|
|
1173
|
-
|
|
1173
|
+
facts.skins.join(', ') || 'none'
|
|
1174
1174
|
}]`,
|
|
1175
1175
|
);
|
|
1176
1176
|
}
|
|
1177
|
-
//
|
|
1178
|
-
//
|
|
1179
|
-
|
|
1180
|
-
// input that cannot carry it is refused here, before anything is posed.
|
|
1181
|
-
const choice = checkPoserChoice(posable.data, options);
|
|
1177
|
+
// `--poser core` on an input that cannot carry it is refused here, before
|
|
1178
|
+
// anything is posed.
|
|
1179
|
+
refuseUnchosen(choice);
|
|
1182
1180
|
const poseOptions: PoseOptions | undefined =
|
|
1183
1181
|
substitution || options.skin !== undefined
|
|
1184
1182
|
? { ...(substitution ? { texture: true } : {}), ...(options.skin === undefined ? {} : { skin: options.skin }) }
|
|
@@ -1220,7 +1218,7 @@ export function checkAgainstFrames(options: CheckOptions): CheckReport {
|
|
|
1220
1218
|
sets = [
|
|
1221
1219
|
{
|
|
1222
1220
|
dir,
|
|
1223
|
-
animation:
|
|
1221
|
+
animation: facts.animations.length === 0 ? null : animation,
|
|
1224
1222
|
fps,
|
|
1225
1223
|
sampled: disk[disk.length - 1].index + 1,
|
|
1226
1224
|
written: disk.length,
|
|
@@ -1292,8 +1290,8 @@ export function checkAgainstFrames(options: CheckOptions): CheckReport {
|
|
|
1292
1290
|
// poser runs — the model document lists the spec's order, and a refusal that
|
|
1293
1291
|
// listed the names in another order would be a byte of the report that
|
|
1294
1292
|
// depends on the poser.
|
|
1295
|
-
const have =
|
|
1296
|
-
const posing = throughPoser(choice, (poser) => {
|
|
1293
|
+
const have = [...facts.animations];
|
|
1294
|
+
const posing = throughPoser(choice, (poser, roster) => {
|
|
1297
1295
|
const sampled = sets.map((set) => prepareSet(located.root, set, poser, have, options.as, poseOptions));
|
|
1298
1296
|
// 🔒 A pose that is not finite is refused before anything measures it (issue
|
|
1299
1297
|
// #873), in the words `render` and the geometry export use for it. Measured
|
|
@@ -1325,7 +1323,7 @@ export function checkAgainstFrames(options: CheckOptions): CheckReport {
|
|
|
1325
1323
|
posedSets.map((p) => p.frames),
|
|
1326
1324
|
poser.slots,
|
|
1327
1325
|
options.skin,
|
|
1328
|
-
|
|
1326
|
+
roster,
|
|
1329
1327
|
);
|
|
1330
1328
|
if (unframeable !== null) throw new UnframeablePoseError(unframeable);
|
|
1331
1329
|
return sampled;
|
|
@@ -1433,7 +1431,7 @@ export function checkAgainstFrames(options: CheckOptions): CheckReport {
|
|
|
1433
1431
|
throw new CheckError(
|
|
1434
1432
|
`no reference frame could be compared, so there is nothing to frame against${nothingToFrameWhy(
|
|
1435
1433
|
prepared,
|
|
1436
|
-
|
|
1434
|
+
[...facts.animations],
|
|
1437
1435
|
options.as,
|
|
1438
1436
|
located.sidecar !== null,
|
|
1439
1437
|
)}`,
|
|
@@ -1574,8 +1572,8 @@ export function checkAgainstFrames(options: CheckOptions): CheckReport {
|
|
|
1574
1572
|
// `src/chains.ts`. Reading the CANDIDATE's tree is what keeps this on the right
|
|
1575
1573
|
// side of the honesty rule: the reference is still nothing but pixels.
|
|
1576
1574
|
const chains = chainsOf(
|
|
1577
|
-
|
|
1578
|
-
|
|
1575
|
+
facts.bones.map(({ name, parent }) => ({ name, parent })),
|
|
1576
|
+
facts.slots.map(({ name, bone }) => ({ name, bone })),
|
|
1579
1577
|
);
|
|
1580
1578
|
const chainOfSlot = new Map<string, number>();
|
|
1581
1579
|
chains.forEach((chain, index) => {
|
|
@@ -2629,7 +2627,7 @@ function nothingToFrameWhy(
|
|
|
2629
2627
|
function checkOneSet(
|
|
2630
2628
|
root: string,
|
|
2631
2629
|
prepared: PreparedSet,
|
|
2632
|
-
posable:
|
|
2630
|
+
posable: Pick<Posable, 'pages'>,
|
|
2633
2631
|
framing: SetFraming,
|
|
2634
2632
|
background: RGBA,
|
|
2635
2633
|
chains: BoneChain[],
|
|
@@ -3310,7 +3308,7 @@ function labelBox(index: number): { width: number; height: number } {
|
|
|
3310
3308
|
function checkAgainstSheet(
|
|
3311
3309
|
root: string,
|
|
3312
3310
|
prepared: PreparedSet,
|
|
3313
|
-
posable:
|
|
3311
|
+
posable: Pick<Posable, 'pages'>,
|
|
3314
3312
|
viewport: Viewport,
|
|
3315
3313
|
background: RGBA,
|
|
3316
3314
|
): { sheet: SheetCheck | null; notes: string[] } {
|