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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A13, the body (issue #1025, step 4c of #380): the mesh slots and each mesh's
|
|
3
|
+
* triangles stay within the budget the rig declares.
|
|
4
|
+
*
|
|
5
|
+
* Moved out of `src/validate.ts` unchanged but for what it reads: the meshes
|
|
6
|
+
* are a fact (`../facts/mesh_attachments.ts`) rather than the lists `validate()`'s
|
|
7
|
+
* prelude filled from spine-core's loaded skins, and the rig info is the
|
|
8
|
+
* caller's. Why the two budgets come from the rig and never from here stays
|
|
9
|
+
* above the `check` call in `validate()`.
|
|
10
|
+
*/
|
|
11
|
+
import type { Verdicts } from '../harness.ts';
|
|
12
|
+
import type { MeshFacts } from '../facts/mesh_attachments.ts';
|
|
13
|
+
import { SKIP_NO_MESH_ATTACHMENT } from '../reasons.ts';
|
|
14
|
+
import type { RigInfo } from '../../types.ts';
|
|
15
|
+
|
|
16
|
+
export function a13MeshBudget({ fail, skip }: Verdicts, { meshes }: Pick<MeshFacts, 'meshes'>, input: { rig?: RigInfo }): void {
|
|
17
|
+
const meshAttachments = meshes;
|
|
18
|
+
const meshSlots = new Set(meshes.map((mesh) => mesh.slot));
|
|
19
|
+
const slotBudget = input.rig?.meshSlotBudget ?? null;
|
|
20
|
+
const triangleBudget = input.rig?.meshTriangleBudget ?? null;
|
|
21
|
+
if (slotBudget === null && triangleBudget === null) {
|
|
22
|
+
return skip(
|
|
23
|
+
'A13_MESH_BUDGET',
|
|
24
|
+
input.rig
|
|
25
|
+
? `the rig "${input.rig.archetype}" declares no \`invariants.meshSlots\` or \`invariants.meshTriangles\` budget`
|
|
26
|
+
: 'no rig info (validating a bare directory), so no budget is declared',
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
// Two clauses and only one of them has a subject that can vanish (#580).
|
|
30
|
+
// A slot budget is a ceiling on a COUNT, and zero is a count — "this rig
|
|
31
|
+
// uses 0 of its 3 mesh slots" is a measurement — so that half holds on a
|
|
32
|
+
// skeleton with no mesh. A triangle budget is a ceiling on each mesh, so a
|
|
33
|
+
// rig that declares only that one and carries no mesh has measured
|
|
34
|
+
// nothing at all.
|
|
35
|
+
if (slotBudget === null && meshAttachments.length === 0) {
|
|
36
|
+
return skip(
|
|
37
|
+
'A13_MESH_BUDGET',
|
|
38
|
+
`the rig "${input.rig?.archetype}" budgets mesh triangles and nothing else, and ${SKIP_NO_MESH_ATTACHMENT}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (slotBudget !== null && meshSlots.size > slotBudget) {
|
|
42
|
+
fail('A13_MESH_BUDGET', `${meshSlots.size} mesh slots, the rig budgets ${slotBudget}`);
|
|
43
|
+
}
|
|
44
|
+
if (triangleBudget === null) return;
|
|
45
|
+
for (const mesh of meshAttachments) {
|
|
46
|
+
const tris = (mesh.triangles?.length ?? 0) / 3;
|
|
47
|
+
if (tris > triangleBudget) {
|
|
48
|
+
fail('A13_MESH_BUDGET', `mesh "${mesh.name}" has ${tris} triangles, the rig budgets ${triangleBudget}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A14, the body (issue #1025, step 4c of #380): no mesh spans the whole
|
|
3
|
+
* stage.
|
|
4
|
+
*
|
|
5
|
+
* Moved out of `src/validate.ts` unchanged but for what it reads: the meshes
|
|
6
|
+
* and the stage are facts (`../facts/mesh_attachments.ts`, `../facts/stage.ts`)
|
|
7
|
+
* rather than spine-core's loaded skins and skeleton header. The `check` call
|
|
8
|
+
* stays in `validate()`.
|
|
9
|
+
*/
|
|
10
|
+
import type { Verdicts } from '../harness.ts';
|
|
11
|
+
import type { MeshFacts } from '../facts/mesh_attachments.ts';
|
|
12
|
+
import type { StageFacts } from '../facts/stage.ts';
|
|
13
|
+
|
|
14
|
+
export function a14NoFullFrameMesh({ fail, skip }: Verdicts, { meshes }: Pick<MeshFacts, 'meshes'>, stage: StageFacts): void {
|
|
15
|
+
const meshAttachments = meshes;
|
|
16
|
+
const stageW = stage.width || 0;
|
|
17
|
+
const stageH = stage.height || 0;
|
|
18
|
+
// ⚠️ A stage-less skeleton has nothing for a mesh to span, and this rule
|
|
19
|
+
// used to report that as a PASS — the `stageW && stageH` guard below reads
|
|
20
|
+
// as a measurement of a 0x0 stage that no mesh can reach. It was only ever
|
|
21
|
+
// reachable from a foreign file until a rig spec could *declare* no stage
|
|
22
|
+
// (issue #578), and a pass certifying an unmeasured rig is the exact
|
|
23
|
+
// failure mode `A21_MESH_RIM_PINNED`'s `|| 'ring'` default was (#44).
|
|
24
|
+
if (!stageW || !stageH) {
|
|
25
|
+
return skip(
|
|
26
|
+
'A14_NO_FULL_FRAME_MESH',
|
|
27
|
+
'the skeleton declares no stage size, so there is no full frame for a mesh to span',
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
for (const mesh of meshAttachments) {
|
|
31
|
+
if (mesh.width >= stageW && mesh.height >= stageH) {
|
|
32
|
+
fail('A14_NO_FULL_FRAME_MESH', `mesh "${mesh.name}" spans the whole ${stageW}x${stageH} stage`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A15, the body (issue #1025, step 4c of #380): `idle` keys no bone that
|
|
3
|
+
* drives a mesh, unless the rig declares that its idle deforms them.
|
|
4
|
+
*
|
|
5
|
+
* Moved out of `src/validate.ts` unchanged but for what it reads: the meshes
|
|
6
|
+
* and the bones that drive them are a fact (`../facts/mesh_attachments.ts`, the bones derived from the weights by `weightBonesOf`) rather
|
|
7
|
+
* than spine-core's loaded skins and the weight run decoded here, the bones
|
|
8
|
+
* `idle` keys are a fact (`../facts/animated_bones.ts`) rather than the raw
|
|
9
|
+
* JSON, and the rig info is the caller's. Why the rule assumes meshes are
|
|
10
|
+
* mostly static, and why a painting rig may say otherwise, stays above the
|
|
11
|
+
* `check` call in `validate()`.
|
|
12
|
+
*/
|
|
13
|
+
import type { Verdicts } from '../harness.ts';
|
|
14
|
+
import { weightBonesOf, type MeshEntry, type MeshFacts } from '../facts/mesh_attachments.ts';
|
|
15
|
+
import type { AnimatedBoneFacts } from '../facts/animated_bones.ts';
|
|
16
|
+
import type { RigInfo } from '../../types.ts';
|
|
17
|
+
|
|
18
|
+
export function a15IdleNoMeshBoneKeys({ fail, skip }: Verdicts, facts: MeshFacts, animated: AnimatedBoneFacts, input: { rig?: RigInfo }): void {
|
|
19
|
+
const A15 = 'A15_IDLE_NO_MESH_BONE_KEYS';
|
|
20
|
+
/**
|
|
21
|
+
* Every mesh attachment, loaded, with the bones that drive it — its slot's
|
|
22
|
+
* bone, and, once weights exist, every bone its weights name. A ring mesh
|
|
23
|
+
* is driven by its CONTROL bone, a different bone from the slot's, and
|
|
24
|
+
* checking only the slot bone would let `idle` key the one bone that
|
|
25
|
+
* actually dirties the canvas every frame.
|
|
26
|
+
*/
|
|
27
|
+
const meshDrivers: Array<{ mesh: MeshEntry; bones: Set<string> }> = [];
|
|
28
|
+
for (const mesh of facts.meshes) {
|
|
29
|
+
const bones = new Set<string>([mesh.slotBone]);
|
|
30
|
+
for (const bone of weightBonesOf(facts, mesh)) bones.add(bone);
|
|
31
|
+
meshDrivers.push({ mesh, bones });
|
|
32
|
+
}
|
|
33
|
+
const meshBoneNames = new Set<string>();
|
|
34
|
+
for (const { bones } of meshDrivers) for (const name of bones) meshBoneNames.add(name);
|
|
35
|
+
const declared = input.rig?.idleDrivesMeshes ?? null;
|
|
36
|
+
/**
|
|
37
|
+
* A declaration that switches off nothing is refused rather than skipped,
|
|
38
|
+
* the standard `consumerDrivenMix` is held to: an opt-out that exempts
|
|
39
|
+
* nothing reads exactly like one that worked, and the next reader cannot
|
|
40
|
+
* tell the rig that needs it from the rig it was copied onto.
|
|
41
|
+
*/
|
|
42
|
+
const stale = (why: string): void => {
|
|
43
|
+
fail(
|
|
44
|
+
A15,
|
|
45
|
+
`the rig "${input.rig?.archetype}" declares invariants.idleDrivesMeshes ("${declared}"), but ${why}, so the ` +
|
|
46
|
+
'declaration switches off nothing — remove it',
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
// The same shape as A06's and A17's guards, found by auditing for it
|
|
50
|
+
// (#568): a rig with no `idle` at all has nothing here to be wrong, and a
|
|
51
|
+
// rule that reports "held" over a subject that does not exist is the
|
|
52
|
+
// vacuous pass this file's own doctrine refuses. The two states get their
|
|
53
|
+
// own sentences because they are different absences — no such animation,
|
|
54
|
+
// versus one that keys no bone.
|
|
55
|
+
const idleBones = animated.bonesKeyedBy('idle');
|
|
56
|
+
if (idleBones === undefined) {
|
|
57
|
+
if (declared !== null) return stale('the skeleton declares no "idle" animation');
|
|
58
|
+
return skip(A15, 'the skeleton declares no "idle" animation, so nothing here can key a mesh-driving bone');
|
|
59
|
+
}
|
|
60
|
+
if (idleBones === null) {
|
|
61
|
+
if (declared !== null) return stale('"idle" carries no bone timeline at all');
|
|
62
|
+
return skip(A15, '"idle" carries no bone timeline at all, so there is no key to hold against the mesh-driving bones');
|
|
63
|
+
}
|
|
64
|
+
const keyed = idleBones.filter((name) => meshBoneNames.has(name));
|
|
65
|
+
if (declared !== null) {
|
|
66
|
+
if (keyed.length === 0) return stale('"idle" keys no bone that drives a mesh');
|
|
67
|
+
const keyedSet = new Set(keyed);
|
|
68
|
+
const moved = meshDrivers.filter(({ bones }) => [...bones].some((name) => keyedSet.has(name)));
|
|
69
|
+
// `worldVerticesLength` is two numbers per vertex whatever the encoding,
|
|
70
|
+
// which is the count the runtime recomputes; reading `vertices.length`
|
|
71
|
+
// would count a weighted mesh's bone entries instead.
|
|
72
|
+
const vertices = moved.reduce((sum, { mesh }) => sum + mesh.worldVerticesLength / 2, 0);
|
|
73
|
+
const SHOWN = 8;
|
|
74
|
+
const names =
|
|
75
|
+
keyed.slice(0, SHOWN).map((name) => `"${name}"`).join(', ') + (keyed.length > SHOWN ? ` +${keyed.length - SHOWN}` : '');
|
|
76
|
+
return skip(
|
|
77
|
+
A15,
|
|
78
|
+
`declared by the rig ("${declared}"): idle keys ${keyed.length} bone(s) that drive ${moved.length} mesh ` +
|
|
79
|
+
`attachment(s) totalling ${vertices} vertices — ${names} — and each of those meshes is recomputed on every ` +
|
|
80
|
+
'frame it is shown',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
for (const [i, boneName] of keyed.entries()) {
|
|
84
|
+
// The case is named once, on the first line an agent reads, rather than
|
|
85
|
+
// after every bone: the first painting rig this met printed 42 of these,
|
|
86
|
+
// and 42 identical sentences read as 42 separate mistakes. It rides the
|
|
87
|
+
// first finding rather than being a finding of its own, so the count of
|
|
88
|
+
// FAIL lines is still the count of bones.
|
|
89
|
+
const hint =
|
|
90
|
+
i > 0
|
|
91
|
+
? ''
|
|
92
|
+
: `. ${keyed.length} bone(s) keyed by idle drive meshes; if this idle is meant to deform them (a painting ` +
|
|
93
|
+
'rig), declare invariants.idleDrivesMeshes: { "why": … } in the rig spec — or, where the motion belongs to ' +
|
|
94
|
+
'a pivot above the mesh, key that pivot one link up (FACE.md §3)';
|
|
95
|
+
fail(A15, `idle keys bone "${boneName}", which drives a mesh — meshes never idle-skip${hint}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A16, restated over the emitted text (issue #1060): the clause
|
|
3
|
+
* `validate()` runs in `src/validate.ts`, word for word, over the skeleton JSON — the
|
|
4
|
+
* text rigc wrote, read by rigc's own reader rather than by spine-core's. The
|
|
5
|
+
* round trip's copy is unchanged and runs in `cli.ts build`; this one runs in
|
|
6
|
+
* the gate of the entry that links none of the runtime
|
|
7
|
+
* (`../emitted/index.ts`), and the selftest's `RC28` holds the two to the
|
|
8
|
+
* same lines on every recipe and on this assertion's mutants.
|
|
9
|
+
*/
|
|
10
|
+
import type { Verdicts } from '../harness.ts';
|
|
11
|
+
import { spineGeneration, type SpineGeneration } from '../../generation.ts';
|
|
12
|
+
import { isObj, type Json } from '../values.ts';
|
|
13
|
+
|
|
14
|
+
const SPINE_4_3: SpineGeneration = '4.3';
|
|
15
|
+
|
|
16
|
+
export function a16SkeletonVersion43({ fail }: Verdicts, raw: Json | null): void {
|
|
17
|
+
const declared = isObj(raw?.skeleton) ? (raw.skeleton as Json).spine : undefined;
|
|
18
|
+
if (typeof declared !== 'string' || spineGeneration(declared) !== SPINE_4_3) {
|
|
19
|
+
fail(
|
|
20
|
+
'A16_SKELETON_VERSION_4_3',
|
|
21
|
+
`skeleton.spine is ${JSON.stringify(declared)}, expected 4.3, 4.3.<patch> or 4.3.<patch>-<suffix>`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A17, the body (issue #1025, step 4c of #380): every page the atlas names is
|
|
3
|
+
* a file on disk, resolved against the directory the atlas sits in.
|
|
4
|
+
*
|
|
5
|
+
* Moved out of `src/validate.ts` unchanged but for what it reads: the pages are
|
|
6
|
+
* a fact (`../facts/atlas_pages.ts`), and the directory is the caller's. Why
|
|
7
|
+
* the guard is a SKIP and never a bare `return` (issues #568, #608) is argued
|
|
8
|
+
* above the `check` call in `validate()`, which stays there with A06 and A19.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
import { resolve } from 'node:path';
|
|
12
|
+
import type { Verdicts } from '../harness.ts';
|
|
13
|
+
import type { AtlasPageFacts } from '../facts/atlas_pages.ts';
|
|
14
|
+
import { SKIP_NO_ATLAS, SKIP_NO_ATLAS_PAGE } from '../reasons.ts';
|
|
15
|
+
|
|
16
|
+
export function a17AtlasPageFilesExist({ fail, skip }: Verdicts, { atlas }: AtlasPageFacts, input: { atlasDir: string }): void {
|
|
17
|
+
if (!atlas) return skip('A17_ATLAS_PAGE_FILES_EXIST', SKIP_NO_ATLAS);
|
|
18
|
+
// The other absence, one ring in (#580): the atlas LOADED and declares no
|
|
19
|
+
// page, so there is no file to find missing. `!atlas` and "zero pages" print
|
|
20
|
+
// different reasons because they are different facts about the artifact.
|
|
21
|
+
if (atlas.pages.length === 0) return skip('A17_ATLAS_PAGE_FILES_EXIST', SKIP_NO_ATLAS_PAGE);
|
|
22
|
+
for (const page of atlas.pages) {
|
|
23
|
+
const abs = resolve(input.atlasDir, page.name);
|
|
24
|
+
if (!existsSync(abs)) fail('A17_ATLAS_PAGE_FILES_EXIST', `page "${page.name}" is not on disk at ${abs}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A18, the body (issue #1060, step 4e of #380): a second, independent compile
|
|
3
|
+
* writes the same model document, byte for byte.
|
|
4
|
+
*
|
|
5
|
+
* One body, two suppliers, as every moved assertion is — but split. The
|
|
6
|
+
* runtime's side (`validate()` in `src/validate.ts`) compares the second
|
|
7
|
+
* compile's Spine pair as well, `skeleton.json` and `skeleton.atlas`, and
|
|
8
|
+
* hands those findings over as `encoding`, in the order it always printed
|
|
9
|
+
* them; the model side (`../model/index.ts`) compares the document alone,
|
|
10
|
+
* which is all a build with no Spine pair writes (`cli_core.ts build`). So
|
|
11
|
+
* the model's clause is written once, here, and the two sides cannot spell it
|
|
12
|
+
* apart. When neither side was handed a second compile — a re-gate of files on
|
|
13
|
+
* disk — there is nothing determinate about a comparison that never ran, and
|
|
14
|
+
* it SKIPs with the reason the round trip always gave (the vacuous-pass trap
|
|
15
|
+
* `A09` is guarded against too).
|
|
16
|
+
*
|
|
17
|
+
* Moved out of `src/validate.ts` unchanged but for the seam; `firstDifferingLine`
|
|
18
|
+
* moved with it, the one reader of it.
|
|
19
|
+
*/
|
|
20
|
+
import type { Verdicts } from '../harness.ts';
|
|
21
|
+
|
|
22
|
+
const A18 = 'A18_DETERMINISTIC_EMIT';
|
|
23
|
+
|
|
24
|
+
/** What A18 reads: whether a second compile was handed over, the encoding's findings, and the two documents. */
|
|
25
|
+
export interface DeterminismFacts {
|
|
26
|
+
/** Whether a second, independent compile was handed over — without one the assertion has nothing to compare. */
|
|
27
|
+
again: boolean;
|
|
28
|
+
/** The findings of the clauses only one side compares (the Spine pair, on the runtime's), each a detail, in the order they print. */
|
|
29
|
+
encoding: readonly string[];
|
|
30
|
+
/** The first compile's model document — `undefined` where the caller handed a second compile and no first document. */
|
|
31
|
+
first: string | undefined;
|
|
32
|
+
/** The second compile's model document; read only where `again`. */
|
|
33
|
+
second: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Where two texts first differ, as A18 names it: the 1-based line and each
|
|
38
|
+
* side's line, trimmed and cut to 120 characters.
|
|
39
|
+
*/
|
|
40
|
+
export function firstDifferingLine(first: string, second: string): string {
|
|
41
|
+
const a = first.split('\n');
|
|
42
|
+
const b = second.split('\n');
|
|
43
|
+
let i = 0;
|
|
44
|
+
while (i < a.length && i < b.length && a[i] === b[i]) i += 1;
|
|
45
|
+
const cut = (line: string | undefined): string => (line === undefined ? '(end of text)' : JSON.stringify(line.trim().slice(0, 120)));
|
|
46
|
+
return `${i + 1}: ${cut(a[i])} first, ${cut(b[i])} second`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function a18DeterministicEmit({ fail, skip }: Verdicts, facts: DeterminismFacts): void {
|
|
50
|
+
if (!facts.again) return skip(A18, 'no second compile to compare against (re-gating artifacts on disk)');
|
|
51
|
+
for (const detail of facts.encoding) fail(A18, detail);
|
|
52
|
+
// The model document is compared like the Spine pair (issue #922): it is
|
|
53
|
+
// written beside them, and a map iterated in an order nothing fixed would
|
|
54
|
+
// reach it before it reached either of them — the document writes every
|
|
55
|
+
// `Map` of the model as an array in the map's order. On a build with no
|
|
56
|
+
// Spine pair it is the only artifact there is to compare.
|
|
57
|
+
if (facts.first === undefined) {
|
|
58
|
+
fail(A18, 'a second compile was handed over with no first model document to compare its skeleton.model.json against');
|
|
59
|
+
} else if (facts.second !== facts.first) {
|
|
60
|
+
fail(A18, `recompiling produced a different skeleton.model.json (first differing line ${firstDifferingLine(facts.first, facts.second)})`);
|
|
61
|
+
}
|
|
62
|
+
}
|