spine-rigc 1.9.2 → 1.9.3

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.
Files changed (47) hide show
  1. package/cli.ts +28 -4397
  2. package/docs/AUTHORING.md +1 -1
  3. package/package.json +1 -1
  4. package/src/assertions/bodies/a13.ts +3 -3
  5. package/src/assertions/bodies/a14.ts +3 -3
  6. package/src/assertions/bodies/a15.ts +5 -5
  7. package/src/assertions/bodies/a22.ts +3 -3
  8. package/src/assertions/facts/animated_bones.ts +4 -2
  9. package/src/assertions/facts/mesh_attachments.ts +33 -4
  10. package/src/assertions/model/animated_bones.ts +13 -5
  11. package/src/assertions/model/bone_timelines.ts +5 -3
  12. package/src/assertions/model/declared.ts +127 -0
  13. package/src/assertions/model/index.ts +63 -40
  14. package/src/assertions/model/mesh_attachments.ts +31 -2
  15. package/src/assertions/model/parse.ts +9 -1
  16. package/src/assertions/model/runtime_timelines.ts +78 -0
  17. package/src/assertions/model/slider_composition.ts +10 -8
  18. package/src/assertions/model/stepped_poses.ts +3 -2
  19. package/src/bonedist.ts +4 -5
  20. package/src/check.ts +3 -2
  21. package/src/checkpics.ts +1 -1
  22. package/src/cli/core_commands.ts +1532 -0
  23. package/src/cli/shared.ts +1935 -0
  24. package/src/cli/spine_commands.ts +1141 -0
  25. package/src/core/additive.ts +52 -62
  26. package/src/core/constraints.ts +26 -3
  27. package/src/core/constraints_physics.ts +11 -1
  28. package/src/core/constraints_slider.ts +136 -6
  29. package/src/core/index.ts +27 -3
  30. package/src/core/skins.ts +28 -3
  31. package/src/core/walk.ts +9 -36
  32. package/src/correspondence.ts +15 -0
  33. package/src/deformbuild.ts +60 -0
  34. package/src/deformmeasure.ts +20 -48
  35. package/src/deformreport.ts +2 -2
  36. package/src/framing.ts +1 -1
  37. package/src/pose.ts +1 -1
  38. package/src/preview.ts +1 -1
  39. package/src/region_joins.ts +54 -0
  40. package/src/render.ts +206 -2836
  41. package/src/render_core.ts +19 -36
  42. package/src/render_shared.ts +2887 -0
  43. package/src/slots.ts +1 -1
  44. package/src/spine_side.ts +123 -0
  45. package/src/validate.ts +11 -91
  46. package/src/assertions/facts/skin_meshes.ts +0 -49
  47. package/src/assertions/model/skin_meshes.ts +0 -90
package/docs/AUTHORING.md CHANGED
@@ -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. On `render` and `check` a rigc build whose `skeleton.model.json` is `rigc-compiled/3` or `/2` needs no atlas at all: with no `--atlas` and no atlas beside it, it is drawn from the document's `pages`, the same frames, geometry and figures as with it, and the `atlas` line says `— not there`. An atlas that is there is still read, and held to `pages`: one that differs (edited after the build) is drawn through spine-core, and the `poser` line names the first difference. A build drawn through an atlas that is not there — a `rigc-compiled/1` document, or a build the core refuses — is refused `nothing at <the atlas>: … is drawn through its atlas (<why>)`, exit 2, nothing written; a Spine export without its atlas is refused `nothing at <the atlas>` as it always was. A directory whose files are not one build — a `skeleton.json` or a `skeleton.atlas` from another build beside this one's `skeleton.model.json` — is drawn through spine-core where the runtime can load the pair, the `poser` line naming the two digests or the atlas's first difference; where it cannot, it is refused `<skeleton> does not load against <the atlas>: spine-core draws this pair (<the poser line's why>) and could not resolve it — "<the runtime's own words>". <dir> is not one build: …`, exit 2, nothing written (`RC18`) — another build's skeleton beside this build's atlas typically ends `"Region not found in atlas: <region> (attachment: <name>)"`. Rebuild, or put the build's own three files back beside each other. A Spine export beside an atlas it was not exported with is refused in the same words, ending `The skeleton and the atlas are not one pair`. An atlas copied from another build's directory usually stops earlier, at its pages — their paths are relative to the directory it was written in — and is refused naming the first one: `nothing at <the page>: <the atlas> names it as a page, and spine-core draws this pair through that atlas (<why>). … <dir> is not one build: …`, exit 2, nothing written (`RC18`); an export whose atlas names a page that is not there is refused the same way, ending `and the page has to be there`. A build moved or copied whole away from the directory it was built in stops at the same place, on either poser, and is refused naming the page and the file that names it: `nothing at <the page>: <the skeleton.model.json> names it as a page, and the core draws this build from it. A page path is relative to the build's directory, so a build moved or copied away from the directory it was built in names pages that are not here — build it again where it is, or build it with --copy-images, which writes its pages beside it` (under `--poser spine`, `<the atlas> names it as a page, and spine-core draws this pair through that atlas (--poser spine)` with the same ending), exit 2, nothing written (`RC21`); only where the core refused the files does a page refusal say the directory is not one build. `bench --bones` and `rigc bonedist` — on its `--candidate` and on its `--reference` — refuse a pair the runtime cannot load in the words above with `spine-core poses this pair (bonedist's candidate: …)` or `(bonedist's reference: …)`, exit 2, nothing written (`RC20`); they read no page image, so a build whose pages are elsewhere is measured as it is in place. A `skeleton.json` that is not JSON is refused `rigc: cannot read <the file>: <the parser's words> (line N, column N)`, exit 2, by `render`, `check`, `bench` and `bonedist` as by every other command that reads one (`RC22`). With the atlas gone, `check` on a `rigc-compiled/3` build reads its pages' `scale:` lines off the document, so its texture note and `check.json`'s `textureFrom.candidateScales` are what they are with the atlas; on a `/2` build its texture note says whether the atlas declares a `scale:` line is not read (that document does not state one), and `candidateScales` is `null` (`RC13`). **`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`, `check` and `explain`: `core` or `spine` — on `render` and `check`, 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; its atlas, when one is named or present, the one 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. A fallback whose pair the runtime cannot load — another build's `skeleton.json` or atlas beside this build's document, or an export beside an atlas it was not exported with — is not drawn: it is refused `… does not load against <the atlas>: spine-core draws this pair (<why>) and could not resolve it — "<the runtime's own words>"` — or, where the atlas names a page that is not there, `nothing at <the page>: …` — exit 2, nothing written, on `render`, `render --geometry`, `check` and `bench --frames` alike; under `--poser core` the same pair is that flag's refusal, in the words above (`RC18`; the `--candidate` row says what to change). 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`, `--texture-from` included (and the check `bench --frames` folds in; `bench`'s own `validate`, like `build`'s, is the runtime's round trip by design): its bone tree, slot list and slot roster come off its model document; so do the order its animations and skins are listed in, its stage and its pages' `scale:` lines, on a `rigc-compiled/3` document (`editorOrder`, `stage`, `pages`) — a `/2` or `/1` document states none of those, so they come off its own `skeleton.json` and atlas, and the `poser` line then says `a rigc-compiled/2 document, which does not state the order its skins and animations are listed in or its stage, read from` and names both files; where each region sits and the page images it draws off the document's `pages` (a `rigc-compiled/1` document's off the atlas beside it, and the `poser` line then says `a rigc-compiled/1 document, which does not state where each region sits on its page: that is read from` and names the atlas); and a `--texture-from` atlas off rigc's own atlas reader. The runtime is loaded for a Spine export, `--poser spine` and a fallback the `poser` line names; a run that cannot load it there is refused naming the input and why it needs the runtime (exit 1; `RC11`, `RC13`). On `explain` the flag chooses the `DEFORM` block's survey (§4.11.2): `core`, the default, reads the skeleton's structure — its deform timelines and their keys, curves and slots, the skin each is keyed under, the sliders — off the model document the compile writes and poses it through the core, and spine-core is not touched (`RC15`); `spine` parses and poses the Spine skeleton through spine-core. The two are the same survey to the byte on every corpus row (`DM12`). Where the core refuses the document, the survey falls back to spine-core and the block says so on a line of its own (`read and posed through spine-core: …`); `--poser core` there is refused instead (exit 2), and a run that cannot load the runtime where it needs it is refused naming the survey and why (exit 1) |
213
+ | `--poser` | `render`, `check` and `explain`: `core` or `spine` — on `render` and `check`, 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; its atlas, when one is named or present, the one 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, and so is a build rendered with no `--skin` whose default skin names a skin-required bone or constraint, or whose skins name no `default` one — posed as a fresh skeleton is, no skin's bones or constraints applied (the default skin's included) and every slot resolved through the default skin alone, so the second draws nothing and stops at `there is nothing to draw` (exit 2) under either poser (issue #1051). 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) · `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. A fallback whose pair the runtime cannot load — another build's `skeleton.json` or atlas beside this build's document, or an export beside an atlas it was not exported with — is not drawn: it is refused `… does not load against <the atlas>: spine-core draws this pair (<why>) and could not resolve it — "<the runtime's own words>"` — or, where the atlas names a page that is not there, `nothing at <the page>: …` — exit 2, nothing written, on `render`, `render --geometry`, `check` and `bench --frames` alike; under `--poser core` the same pair is that flag's refusal, in the words above (`RC18`; the `--candidate` row says what to change). 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`, `--texture-from` included (and the check `bench --frames` folds in; `bench`'s own `validate`, like `build`'s, is the runtime's round trip by design): its bone tree, slot list and slot roster come off its model document; so do the order its animations and skins are listed in, its stage and its pages' `scale:` lines, on a `rigc-compiled/3` document (`editorOrder`, `stage`, `pages`) — a `/2` or `/1` document states none of those, so they come off its own `skeleton.json` and atlas, and the `poser` line then says `a rigc-compiled/2 document, which does not state the order its skins and animations are listed in or its stage, read from` and names both files; where each region sits and the page images it draws off the document's `pages` (a `rigc-compiled/1` document's off the atlas beside it, and the `poser` line then says `a rigc-compiled/1 document, which does not state where each region sits on its page: that is read from` and names the atlas); and a `--texture-from` atlas off rigc's own atlas reader. The runtime is loaded for a Spine export, `--poser spine` and a fallback the `poser` line names; a run that cannot load it there is refused naming the input and why it needs the runtime (exit 1; `RC11`, `RC13`). On `explain` the flag chooses the `DEFORM` block's survey (§4.11.2): `core`, the default, reads the skeleton's structure — its deform timelines and their keys, curves and slots, the skin each is keyed under, the sliders — off the model document the compile writes and poses it through the core, and spine-core is not touched (`RC15`); `spine` parses and poses the Spine skeleton through spine-core. The two are the same survey to the byte on every corpus row (`DM12`). Where the core refuses the document, the survey falls back to spine-core and the block says so on a line of its own (`read and posed through spine-core: …`); `--poser core` there is refused instead (exit 2), and a run that cannot load the runtime where it needs it is refused naming the survey and why (exit 1) |
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 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-rigc",
3
- "version": "1.9.2",
3
+ "version": "1.9.3",
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": {
@@ -3,17 +3,17 @@
3
3
  * triangles stay within the budget the rig declares.
4
4
  *
5
5
  * Moved out of `src/validate.ts` unchanged but for what it reads: the meshes
6
- * are a fact (`../facts/skin_meshes.ts`) rather than the lists `validate()`'s
6
+ * are a fact (`../facts/mesh_attachments.ts`) rather than the lists `validate()`'s
7
7
  * prelude filled from spine-core's loaded skins, and the rig info is the
8
8
  * caller's. Why the two budgets come from the rig and never from here stays
9
9
  * above the `check` call in `validate()`.
10
10
  */
11
11
  import type { Verdicts } from '../harness.ts';
12
- import type { SkinMeshFacts } from '../facts/skin_meshes.ts';
12
+ import type { MeshFacts } from '../facts/mesh_attachments.ts';
13
13
  import { SKIP_NO_MESH_ATTACHMENT } from '../reasons.ts';
14
14
  import type { RigInfo } from '../../types.ts';
15
15
 
16
- export function a13MeshBudget({ fail, skip }: Verdicts, { meshes }: SkinMeshFacts, input: { rig?: RigInfo }): void {
16
+ export function a13MeshBudget({ fail, skip }: Verdicts, { meshes }: Pick<MeshFacts, 'meshes'>, input: { rig?: RigInfo }): void {
17
17
  const meshAttachments = meshes;
18
18
  const meshSlots = new Set(meshes.map((mesh) => mesh.slot));
19
19
  const slotBudget = input.rig?.meshSlotBudget ?? null;
@@ -3,15 +3,15 @@
3
3
  * stage.
4
4
  *
5
5
  * Moved out of `src/validate.ts` unchanged but for what it reads: the meshes
6
- * and the stage are facts (`../facts/skin_meshes.ts`, `../facts/stage.ts`)
6
+ * and the stage are facts (`../facts/mesh_attachments.ts`, `../facts/stage.ts`)
7
7
  * rather than spine-core's loaded skins and skeleton header. The `check` call
8
8
  * stays in `validate()`.
9
9
  */
10
10
  import type { Verdicts } from '../harness.ts';
11
- import type { SkinMeshFacts } from '../facts/skin_meshes.ts';
11
+ import type { MeshFacts } from '../facts/mesh_attachments.ts';
12
12
  import type { StageFacts } from '../facts/stage.ts';
13
13
 
14
- export function a14NoFullFrameMesh({ fail, skip }: Verdicts, { meshes }: SkinMeshFacts, stage: StageFacts): void {
14
+ export function a14NoFullFrameMesh({ fail, skip }: Verdicts, { meshes }: Pick<MeshFacts, 'meshes'>, stage: StageFacts): void {
15
15
  const meshAttachments = meshes;
16
16
  const stageW = stage.width || 0;
17
17
  const stageH = stage.height || 0;
@@ -3,7 +3,7 @@
3
3
  * drives a mesh, unless the rig declares that its idle deforms them.
4
4
  *
5
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/skin_meshes.ts`) rather
6
+ * and the bones that drive them are a fact (`../facts/mesh_attachments.ts`, the bones derived from the weights by `weightBonesOf`) rather
7
7
  * than spine-core's loaded skins and the weight run decoded here, the bones
8
8
  * `idle` keys are a fact (`../facts/animated_bones.ts`) rather than the raw
9
9
  * JSON, and the rig info is the caller's. Why the rule assumes meshes are
@@ -11,11 +11,11 @@
11
11
  * `check` call in `validate()`.
12
12
  */
13
13
  import type { Verdicts } from '../harness.ts';
14
- import type { MeshEntry, SkinMeshFacts } from '../facts/skin_meshes.ts';
14
+ import { weightBonesOf, type MeshEntry, type MeshFacts } from '../facts/mesh_attachments.ts';
15
15
  import type { AnimatedBoneFacts } from '../facts/animated_bones.ts';
16
16
  import type { RigInfo } from '../../types.ts';
17
17
 
18
- export function a15IdleNoMeshBoneKeys({ fail, skip }: Verdicts, { meshes }: SkinMeshFacts, animated: AnimatedBoneFacts, input: { rig?: RigInfo }): void {
18
+ export function a15IdleNoMeshBoneKeys({ fail, skip }: Verdicts, facts: MeshFacts, animated: AnimatedBoneFacts, input: { rig?: RigInfo }): void {
19
19
  const A15 = 'A15_IDLE_NO_MESH_BONE_KEYS';
20
20
  /**
21
21
  * Every mesh attachment, loaded, with the bones that drive it — its slot's
@@ -25,9 +25,9 @@ export function a15IdleNoMeshBoneKeys({ fail, skip }: Verdicts, { meshes }: Skin
25
25
  * actually dirties the canvas every frame.
26
26
  */
27
27
  const meshDrivers: Array<{ mesh: MeshEntry; bones: Set<string> }> = [];
28
- for (const mesh of meshes) {
28
+ for (const mesh of facts.meshes) {
29
29
  const bones = new Set<string>([mesh.slotBone]);
30
- for (const bone of mesh.weightBones) bones.add(bone);
30
+ for (const bone of weightBonesOf(facts, mesh)) bones.add(bone);
31
31
  meshDrivers.push({ mesh, bones });
32
32
  }
33
33
  const meshBoneNames = new Set<string>();
@@ -3,14 +3,14 @@
3
3
  * in the unit square, one pair per vertex.
4
4
  *
5
5
  * Moved out of `src/validate.ts` unchanged but for what it reads: the meshes
6
- * are a fact (`../facts/skin_meshes.ts`). The `check` call stays in
6
+ * are a fact (`../facts/mesh_attachments.ts`). The `check` call stays in
7
7
  * `validate()`.
8
8
  */
9
9
  import type { Verdicts } from '../harness.ts';
10
- import type { SkinMeshFacts } from '../facts/skin_meshes.ts';
10
+ import type { MeshFacts } from '../facts/mesh_attachments.ts';
11
11
  import { SKIP_NO_MESH_ATTACHMENT } from '../reasons.ts';
12
12
 
13
- export function a22MeshUvsInUnitRange({ fail, skip }: Verdicts, { meshes }: SkinMeshFacts): void {
13
+ export function a22MeshUvsInUnitRange({ fail, skip }: Verdicts, { meshes }: Pick<MeshFacts, 'meshes'>): void {
14
14
  const meshAttachments = meshes;
15
15
  if (meshAttachments.length === 0) return skip('A22_MESH_UVS_IN_UNIT_RANGE', SKIP_NO_MESH_ATTACHMENT);
16
16
  for (const mesh of meshAttachments) {
@@ -11,8 +11,10 @@
11
11
  * rather than folding the first two into an empty list.
12
12
  *
13
13
  * The order is the file's: A15 prints one line per keyed bone in it. The file
14
- * keys an animation's bones in the order the emitter writes the model's
15
- * (`emitAnimation`), which is the document's own list.
14
+ * keys an animation's bones in an object filled in the order the emitter
15
+ * writes the model's (`emitAnimation`), which is the document's own list — so
16
+ * an array-index bone name (`5`, `10`) is listed first whatever its place in
17
+ * that list (issue #1039), and the model side reads it through `keyedOrder`.
16
18
  *
17
19
  * Links nothing from the runtime: `validate()` reads it off the skeleton JSON
18
20
  * as A15 always did, and the model side off the document
@@ -1,8 +1,16 @@
1
1
  /**
2
- * The mesh attachments, as the bodies that read their geometry and their
3
- * weights see them (issue #1025, step 4c of #380) — the census's F07 (vertex
4
- * data), F08 (mesh geometry) and F10 (the linked-mesh join), for A04, A20,
5
- * A21, A28 and the mesh half of A23.
2
+ * The mesh attachments, as every body that reads a mesh sees them (issue
3
+ * #1025, step 4c of #380) — the census's F03, F05, F07 (vertex data), F08
4
+ * (mesh geometry) and F10 (the linked-mesh join), for A04, A13, A14, A15, A20,
5
+ * A21, A22, A28 and the mesh half of A23.
6
+ *
7
+ * ⭐ **One family** (issue #1054). Cut 4c-1 stated the meshes for A13, A14,
8
+ * A15 and A22 as a family of their own (`SkinMeshFacts`) and cut 4c-2 stated
9
+ * them again here; the two walked the same entries in the same order and
10
+ * agreed on every field they shared, and two families of one fact are two
11
+ * places for a supplier to disagree with itself. The size the budget and
12
+ * canvas rules read joined this one, and the bones a mesh's weights name are
13
+ * derived from `weights` (`weightBonesOf`) rather than stated beside them.
6
14
  *
7
15
  * ⭐ **The order is the file's**, as `./skin_entries.ts` states it: every
8
16
  * skin in the file's order, and within a skin the entries slot by slot in the
@@ -56,6 +64,9 @@ export interface MeshEntry {
56
64
  readonly regionUVs: readonly number[];
57
65
  /** `hullLength`: two numbers per hull vertex. */
58
66
  readonly hullLength: number;
67
+ /** Its `width` and `height` — a link's are its source's, which the runtime's `setSourceMesh` writes over the link's own. */
68
+ readonly width: number;
69
+ readonly height: number;
59
70
  /** Per vertex its influences, in order — or `null` for an unweighted mesh. */
60
71
  readonly weights: ReadonlyArray<readonly MeshBinding[]> | null;
61
72
  /** The kept clauses' findings about this mesh's vertex run (`A04`'s encoding rules), in order; never on the model side. */
@@ -69,3 +80,21 @@ export interface MeshFacts {
69
80
  /** Every mesh attachment of every skin, in the file's walk order. */
70
81
  readonly meshes: readonly MeshEntry[];
71
82
  }
83
+
84
+ /**
85
+ * Every bone a mesh's weights name, in the weight run's order (repeats kept),
86
+ * by name — empty for an unweighted mesh. A binding whose index the roster
87
+ * does not hold names nothing here: that index is the encoding's fault, which
88
+ * `A20`'s kept clause names (`MeshBinding.encoding`), and no bone drives the
89
+ * mesh through it.
90
+ */
91
+ export function weightBonesOf(facts: Pick<MeshFacts, 'bones'>, mesh: Pick<MeshEntry, 'weights'>): string[] {
92
+ const names: string[] = [];
93
+ for (const vertex of mesh.weights ?? []) {
94
+ for (const binding of vertex) {
95
+ const name = facts.bones[binding.bone];
96
+ if (name !== undefined) names.push(name);
97
+ }
98
+ }
99
+ return names;
100
+ }
@@ -5,13 +5,21 @@
5
5
  * The document writes each animation's `bones` as a list in the model's map
6
6
  * order, and the emitter writes the Spine file's `bones` group from that same
7
7
  * map, entry for entry, leaving the group out when it is empty
8
- * (`emitAnimation`'s `if (animation.bones.size)`) — so the list's order is the
9
- * file's, an empty list is a file with no `bones` group, and an animation the
10
- * document does not hold is one the file does not key. The first animation of
11
- * a name answers, as the file's object would hold only one.
8
+ * (`emitAnimation`'s `if (animation.bones.size)`) — so an empty list is a file
9
+ * with no `bones` group, and an animation the document does not hold is one the
10
+ * file does not key. The first animation of a name answers, as the file's
11
+ * object would hold only one.
12
+ *
13
+ * 🔢 **The order is `keyedOrder` over the list's, not the list's** (issue
14
+ * #1039). The group is an object keyed by bone name, so the file lists an
15
+ * array-index name (`5`, `10`) first whatever order the emitter filled it in —
16
+ * measured on a walk whose tracks key `-a, 5, 2b, 10, …`: the document lists
17
+ * that, the file and spine-core `5, 10, -a, 2b, …`, and A15 prints one line per
18
+ * keyed bone in the file's order.
12
19
  *
13
20
  * Links nothing from the runtime.
14
21
  */
22
+ import { keyedOrder } from '../../compile.ts';
15
23
  import type { AnimatedBoneFacts } from '../facts/animated_bones.ts';
16
24
  import { isObj } from '../values.ts';
17
25
  import type { ReadDocument } from './parse.ts';
@@ -24,7 +32,7 @@ export function modelAnimatedBones(read: ReadDocument): AnimatedBoneFacts {
24
32
  if (animation === undefined) return undefined;
25
33
  const bones = Array.isArray(animation.bones) ? animation.bones.filter(isObj) : [];
26
34
  if (bones.length === 0) return null;
27
- return bones.map((bone) => String(bone.name));
35
+ return keyedOrder(bones.map((bone) => String(bone.name)));
28
36
  },
29
37
  };
30
38
  }
@@ -6,9 +6,11 @@
6
6
  * `./slot_colour.ts`), over the `bones` group: the animations put in the
7
7
  * file's order by `editorAnimationOrder` (`src/compile.ts`, the function the
8
8
  * emitter is handed), each animation's bones and their timelines laid out as
9
- * the file lays them (`animations.<name>.bones.<bone>.<timeline> = keys`, in
10
- * the document's order, which is the order `emitAnimation` writes the model's
11
- * map in), and the whole handed to the emitter's own `withoutParserDefaults`
9
+ * the file lays them (`animations.<name>.bones.<bone>.<timeline> = keys`,
10
+ * filled in the document's order, which is the order `emitAnimation` writes
11
+ * the model's map in — and read back out of that object, so an array-index
12
+ * bone name comes first as it does in the file: issue #1039 measured this
13
+ * reader already walking the file's order), and the whole handed to the emitter's own `withoutParserDefaults`
12
14
  * and `inEditorKeyOrder` (`src/keyorder.ts`) — so a key field the emitter
13
15
  * leaves out at the parser's default (`time: 0`, a translate key's `y: 0`) is
14
16
  * left out here too, and a body printing a key's `time` prints what it prints
@@ -0,0 +1,127 @@
1
+ /**
2
+ * What the build declared, read off the document (issue #1054): the rig info
3
+ * the archetype and declaration rules read, and each animation's declared
4
+ * duration, which A09 holds the keys against.
5
+ *
6
+ * ⭐ **The document is the one source, on every spec it is read at.** A
7
+ * `rigc-compiled/3`, `/2` and `/1` document all carry the `rig` section
8
+ * (`CompileResult.rig` as `modelDocument` writes it) and every animation's
9
+ * `duration` (the motion spec's, which `readModel` reads as
10
+ * `CoreAnimationTimelines.declared` and refuses when it is not a finite
11
+ * number). Until this module the model side took both from its caller, as
12
+ * `validate()` does, and its verdict moved with what the caller handed it:
13
+ * measured over the 19 recipes with the document's own values left out, A09
14
+ * read SKIP on 38 of 38 cells, ten rules read their bare-directory SKIP, and
15
+ * A20 read FAIL on 7 cells where it reads PASS — without the rig every mesh is
16
+ * judged a generated ring (`meshKindOf`), so an authored mesh fails the
17
+ * generator policy. A value the document states is read from the document,
18
+ * and the same value given beside it is refused by name
19
+ * (`refuseDeclaredBeside`), as issue #1026 made the stage and `pma`
20
+ * (`./given.ts`).
21
+ *
22
+ * 🔒 **The rig section is read field by field, never cast.** A body reads a
23
+ * declaration only through `modelRigInfo`, which refuses by its path a field
24
+ * that is missing, of the wrong type or not one the writer writes — the
25
+ * model side's parse runs it (`./parse.ts`, `A00_MODEL_READ`), so a document
26
+ * whose declarations are malformed is refused where the reader refuses, and
27
+ * never judged against a guess.
28
+ *
29
+ * Links nothing from the runtime.
30
+ */
31
+ import type { MeshKind } from '../../mesh.ts';
32
+ import type { RigInfo } from '../../types.ts';
33
+ import { isObj } from '../values.ts';
34
+ import type { ReadDocument } from './parse.ts';
35
+
36
+ /** A rig section the model side cannot read as rig info: every problem, each by its path, in one sentence. */
37
+ export class DeclaredInputError extends Error {}
38
+
39
+ /** The mesh kinds a rig's `meshKinds` may name: the generators' and `authored`. */
40
+ const MESH_KINDS = ['ring', 'ribbon', 'contour', 'grid', 'segments', 'authored'] as const satisfies ReadonlyArray<MeshKind | 'authored'>;
41
+ // Every kind the type names is in the list, or this line does not type-check.
42
+ const MESH_KINDS_COMPLETE: Exclude<MeshKind | 'authored', (typeof MESH_KINDS)[number]> extends never ? true : false = true;
43
+ void MESH_KINDS_COMPLETE;
44
+
45
+ type Check = (value: unknown, at: string, problems: string[]) => void;
46
+ const say = (problems: string[], at: string, value: unknown, what: string): void => void problems.push(`${at} is ${JSON.stringify(value) ?? 'absent'}, not ${what}`);
47
+ const str: Check = (v, at, p) => (typeof v === 'string' ? undefined : say(p, at, v, 'a string'));
48
+ const bool: Check = (v, at, p) => (typeof v === 'boolean' ? undefined : say(p, at, v, 'true or false'));
49
+ const num: Check = (v, at, p) => (typeof v === 'number' && Number.isFinite(v) ? undefined : say(p, at, v, 'a finite number'));
50
+ const nullable = (inner: Check): Check => (v, at, p) => (v === null ? undefined : inner(v, at, p));
51
+ const list = (inner: Check, length?: number): Check => (v, at, p) => {
52
+ if (!Array.isArray(v) || (length !== undefined && v.length !== length)) return say(p, at, v, length === undefined ? 'a list' : `a list of ${length}`);
53
+ v.forEach((x, i) => inner(x, `${at}[${i}]`, p));
54
+ };
55
+ const record = (inner: Check): Check => (v, at, p) => {
56
+ if (!isObj(v)) return say(p, at, v, 'an object');
57
+ for (const [k, x] of Object.entries(v)) inner(x, `${at}.${k}`, p);
58
+ };
59
+ const oneOf = (words: readonly string[]): Check => (v, at, p) => (typeof v === 'string' && words.includes(v) ? undefined : say(p, at, v, `one of ${words.join(', ')}`));
60
+ const shape = (fields: Readonly<Record<string, Check>>): Check => (v, at, p) => {
61
+ if (!isObj(v)) return say(p, at, v, 'an object');
62
+ for (const [k, check] of Object.entries(fields)) check(v[k], `${at}.${k}`, p);
63
+ for (const k of Object.keys(v)) if (!(k in fields)) p.push(`${at}.${k} is not a field the rig section has`);
64
+ };
65
+
66
+ /** The rig section's fields, as `RigInfo` declares them and the writer writes every one — each required, none defaulted. */
67
+ const RIG_FIELDS: Readonly<Record<keyof RigInfo, Check>> = {
68
+ archetype: str,
69
+ axisBone: nullable(str),
70
+ axisSubtree: list(str),
71
+ detached: list(list(str, 2)),
72
+ slotOrder: nullable(list(str)),
73
+ meshKinds: record(oneOf(MESH_KINDS)),
74
+ meshDeclaredBones: record(list(str)),
75
+ meshSoftBones: record(str),
76
+ deformMayFold: list(str),
77
+ editorRoundTrip: bool,
78
+ consumerDrivenMix: list(shape({ type: oneOf(['ik', 'transform']), constraint: str, why: str })),
79
+ idleDrivesMeshes: nullable(str),
80
+ basePlates: list(str),
81
+ meshSlotBudget: nullable(num),
82
+ meshTriangleBudget: nullable(num),
83
+ contactDepth: nullable(num),
84
+ capContainmentCeiling: nullable(num),
85
+ massBone: nullable(str),
86
+ inwardUnit: nullable(list(num, 2)),
87
+ };
88
+
89
+ /** Every problem with the document's `rig` section, each by its path — empty when it reads as rig info. */
90
+ export function rigSectionProblems(json: Readonly<Record<string, unknown>>): string[] {
91
+ const problems: string[] = [];
92
+ shape(RIG_FIELDS)(json.rig, 'rig', problems);
93
+ return problems;
94
+ }
95
+
96
+ /** The rig info the document declares — its `rig` section, read field by field; a section that does not read is refused by its paths. */
97
+ export function modelRigInfo(read: ReadDocument): RigInfo {
98
+ const problems = rigSectionProblems(read.json);
99
+ if (problems.length > 0) throw new DeclaredInputError(`the document's rig section does not read as rig info: ${problems.length} problem(s): ${problems.join('; ')}`);
100
+ return read.json.rig as RigInfo;
101
+ }
102
+
103
+ /**
104
+ * Each animation's declared duration, as the document states it and the core
105
+ * reads it (`CoreAnimationTimelines.declared`), keyed in the document's order —
106
+ * the motion spec's, the order `CompileResult.declaredDurations` is filled in.
107
+ */
108
+ export function modelDeclaredDurations(read: ReadDocument): Record<string, number> {
109
+ const out: Record<string, number> = {};
110
+ for (const anim of read.doc.animations) out[anim.name] = anim.timelines.declared;
111
+ return out;
112
+ }
113
+
114
+ /** What a caller may not give beside a document, because every document states it. */
115
+ export type DeclaredValue = 'rig' | 'declaredDurations';
116
+
117
+ /** The sentence a value given beside the document is refused with. */
118
+ export function declaredBesideRefusal(spec: string, what: DeclaredValue): string {
119
+ const given = what === 'rig' ? 'the rig info' : 'the declared durations';
120
+ const stated = what === 'rig' ? 'states it itself in its rig section' : "states each animation's duration itself";
121
+ return `the caller gave ${given} beside a ${spec} document, which ${stated} (issue #1054); a second source for one fact is refused rather than one of them silently read`;
122
+ }
123
+
124
+ /** A caller's value beside a document that states it, refused by name. */
125
+ export function refuseDeclaredBeside(read: ReadDocument, input: { rig?: unknown; declaredDurations?: unknown }, what: DeclaredValue): void {
126
+ if (input[what] !== undefined) throw new Error(declaredBesideRefusal(read.doc.spec, what));
127
+ }
@@ -21,11 +21,14 @@
21
21
  * 🔸 **What the entry is given.** The document's text, the directory its
22
22
  * pages resolve against, and the profile — and nothing read off a Spine file:
23
23
  * a model-side verdict that read `skeleton.json` would be a second reading of
24
- * the encoding, which is the round trip's subject and not the rig's. Since cut
25
- * 4c-1 it is also given the rig info (as `validate()` is) and, in `given`, the
26
- * two values a `rigc-compiled/2` or `/1` document does not hold — the stage and
27
- * each page's `pma` (`./given.ts`). A `/3` document states both (issue #1026)
28
- * and is read for them; `given` beside one is refused by name.
24
+ * the encoding, which is the round trip's subject and not the rig's. In
25
+ * `given`, the two values a `rigc-compiled/2` or `/1` document does not hold —
26
+ * the stage and each page's `pma` (`./given.ts`); a `/3` document states both
27
+ * (issue #1026) and is read for them, and `given` beside one is refused by
28
+ * name. The rig info and the declared durations are the document's on every
29
+ * spec (issue #1054, `./declared.ts`): cuts 4c-1 to 4c-3 took them from the
30
+ * caller, as `validate()` does, and the verdict moved with what the caller
31
+ * handed over — now either one given beside the document is refused by name.
29
32
  *
30
33
  * ⛔ **Not wired into any command.** `build` keeps the round trip and its
31
34
  * forty-nine lines; this entry is what the selftest and the instrument call
@@ -50,6 +53,7 @@ import type { AtlasPageFacts } from '../facts/atlas_pages.ts';
50
53
  import type { SlotColourFacts } from '../facts/slot_colour.ts';
51
54
  import type { RigInfo } from '../../types.ts';
52
55
  import type { ModelGiven } from './given.ts';
56
+ import { modelDeclaredDurations, modelRigInfo, refuseDeclaredBeside } from './declared.ts';
53
57
  import { a08RegionNamesMatchAttachments } from '../bodies/a08.ts';
54
58
  import { a13MeshBudget } from '../bodies/a13.ts';
55
59
  import { a14NoFullFrameMesh } from '../bodies/a14.ts';
@@ -59,13 +63,11 @@ import { a38SkinMembersAreSkinRequired } from '../bodies/a38.ts';
59
63
  import { a06AtlasPageSizeMatchesPng } from '../bodies/a06.ts';
60
64
  import { a19OverlayPngsHaveAlpha } from '../bodies/a19.ts';
61
65
  import { a27RegionNameMatchesPageFilename } from '../bodies/a27.ts';
62
- import { modelSkinMeshes } from './skin_meshes.ts';
63
66
  import { modelAnimatedBones } from './animated_bones.ts';
64
67
  import { modelSkinMembers } from './skin_members.ts';
65
68
  import { modelRegionJoins } from './region_joins.ts';
66
69
  import { modelAtlasRegions } from './atlas_regions.ts';
67
70
  import { modelStage } from './stage.ts';
68
- import type { SkinMeshFacts } from '../facts/skin_meshes.ts';
69
71
  import type { AnimatedBoneFacts } from '../facts/animated_bones.ts';
70
72
  import type { SkinMemberFacts } from '../facts/skin_members.ts';
71
73
  import type { RegionJoinFacts } from '../facts/region_joins.ts';
@@ -134,7 +136,14 @@ export interface ModelValidateInput {
134
136
  /** The directory the document's page names resolve against — the build's `--out`. */
135
137
  atlasDir: string;
136
138
  profile: AssertionProfile;
137
- /** The rig info the build carries, as `validate()` is handed it — A13, A15, A19 (cut 4c-1) and A20, A21, A28, A41, A47, A48 (cut 4c-2) and A24, A25, A26, A29, A30 (cut 4c-4) read it; optional because `ValidateInput.rig` is: absent for a bare directory, on both sides. */
139
+ /**
140
+ * Refused (issue #1054): every document states the rig info in its `rig`
141
+ * section, and the model side reads it there (`./declared.ts`). A caller
142
+ * that gives it anyway is a second source for one fact, and each rule that
143
+ * reads the declarations refuses the call by name rather than reading
144
+ * either. Kept on the type so a caller still handing it over is named, not
145
+ * silently ignored.
146
+ */
138
147
  rig?: RigInfo;
139
148
  /**
140
149
  * The stage and the pages' `pma`, for a `rigc-compiled/2` or `/1` document,
@@ -144,7 +153,7 @@ export interface ModelValidateInput {
144
153
  * `given` beside it is refused by name.
145
154
  */
146
155
  given?: ModelGiven;
147
- /** The motion spec's declared durations, as `validate()` is handed them — A09 reads them (cut 4c-3); absent where the caller has no motion spec, on both sides. */
156
+ /** Refused, as `rig` is (issue #1054): every document states each animation's declared duration, and A09 reads them there. */
148
157
  declaredDurations?: Record<string, number>;
149
158
  }
150
159
 
@@ -164,7 +173,6 @@ export interface ModelSupply {
164
173
  skinEntries: (read: ReadDocument) => SkinEntryFacts;
165
174
  atlasPages: (read: ReadDocument) => AtlasPageFacts;
166
175
  slotColour: (read: ReadDocument) => SlotColourFacts;
167
- skinMeshes: (read: ReadDocument) => SkinMeshFacts;
168
176
  animatedBones: (read: ReadDocument) => AnimatedBoneFacts;
169
177
  skinMembers: (read: ReadDocument) => SkinMemberFacts;
170
178
  regionJoins: (read: ReadDocument) => RegionJoinFacts;
@@ -184,6 +192,10 @@ export interface ModelSupply {
184
192
  sliderComposition: (read: ReadDocument) => SliderCompositionFacts;
185
193
  constraintTargets: (read: ReadDocument) => ConstraintTargetFacts;
186
194
  steppedPoses: (read: ReadDocument) => SteppedPoseFacts;
195
+ /** The rig info the document declares (issue #1054, `./declared.ts`); a caller's `rig` beside it is refused by name. */
196
+ rigInfo: (read: ReadDocument, input: ModelValidateInput) => RigInfo;
197
+ /** Each animation's declared duration as the document states it (issue #1054); a caller's `declaredDurations` beside it is refused by name. */
198
+ declaredDurations: (read: ReadDocument, input: ModelValidateInput) => Record<string, number>;
187
199
  }
188
200
 
189
201
  /** The suppliers the model side runs on. */
@@ -191,7 +203,6 @@ export const MODEL_SUPPLY: ModelSupply = {
191
203
  skinEntries: modelSkinEntries,
192
204
  atlasPages: modelAtlasPages,
193
205
  slotColour: modelSlotColour,
194
- skinMeshes: modelSkinMeshes,
195
206
  animatedBones: modelAnimatedBones,
196
207
  skinMembers: modelSkinMembers,
197
208
  regionJoins: modelRegionJoins,
@@ -211,12 +222,21 @@ export const MODEL_SUPPLY: ModelSupply = {
211
222
  sliderComposition: modelSliderComposition,
212
223
  constraintTargets: modelConstraintTargets,
213
224
  steppedPoses: modelSteppedPoses,
225
+ rigInfo: (read, input) => {
226
+ refuseDeclaredBeside(read, input, 'rig');
227
+ return modelRigInfo(read);
228
+ },
229
+ declaredDurations: (read, input) => {
230
+ refuseDeclaredBeside(read, input, 'declaredDurations');
231
+ return modelDeclaredDurations(read);
232
+ },
214
233
  };
215
234
 
216
235
  /**
217
236
  * One moved assertion: its code, and how the model side runs its body once the
218
- * document is read. The list is the registry of what has moved — each later
219
- * cut appends its rows — and the selftest reads it for the codes it compares.
237
+ * document is read. The list is the registry of what has moved — a later cut
238
+ * puts its rows where `validate()` runs those rules, not at the end — and the
239
+ * selftest reads it for the codes it compares.
220
240
  */
221
241
  export interface MovedAssertion {
222
242
  code: string;
@@ -241,53 +261,56 @@ export interface MovedAssertion {
241
261
  }
242
262
 
243
263
  /**
244
- * The moved assertions, in `validate()`'s report order (the order its `check`
245
- * calls stand in; the selftest and the instrument compare per code). `SKIP_NO_ATLAS` for
264
+ * The moved assertions, in `validate()`'s report order — the order its `check`
265
+ * calls stand in, so a model-side report lists its PASS, SKIP and FAIL lines in
266
+ * the order the round trip's does. The seven cuts of issue #1025 appended their
267
+ * rows in landing order; issue #1054 put them back, and the selftest's `VF16`
268
+ * names a row that stands out of that order. `SKIP_NO_ATLAS` for
246
269
  * A17 rather than `SKIP_NO_MODEL` because that is what A17 says over a missing
247
270
  * atlas on either side: its body reads only the pages, and on this side no
248
271
  * document means no pages.
249
272
  */
250
273
  export const MOVED_ASSERTIONS: readonly MovedAssertion[] = [
251
274
  { code: 'A08_REGION_NAMES_MATCH_ATTACHMENTS', run: (v, read, _input, supply) => (read.doc.pages === null ? v.skip('A08_REGION_NAMES_MATCH_ATTACHMENTS', SKIP_NO_MODEL_PAGES) : a08RegionNamesMatchAttachments(v, supply.regionJoins(read), new Set<string>())), unread: SKIP_NO_MODEL, beforeTheParse: true },
275
+ { code: 'A32_EVENT_KEYS_RESOLVE', run: (v, read, _input, supply) => a32EventKeysResolve(v, supply.eventKeys(read)), unread: SKIP_NO_MODEL, beforeTheParse: true },
276
+ { code: 'A34_CONSTRAINT_TIMELINE_TARGETS', run: (v, read, _input, supply) => a34ConstraintTimelineTargets(v, supply.constraintTargets(read)), unread: SKIP_NO_MODEL, beforeTheParse: true },
277
+ { code: 'A12_NO_DARK_COLOR', run: (v, read, _input, supply) => a12NoDarkColor(v, supply.skeletonRoster(read), supply.slotColour(read).slotTimelines), unread: SKIP_NO_MODEL, beforeTheParse: true },
252
278
  { code: 'A03_REGION_WIDTH_HEIGHT_FINITE', run: (v, read, _input, supply) => a03RegionWidthHeightFinite(v, supply.skinEntries(read)), unread: SKIP_NO_MODEL },
253
279
  { code: 'A04_MESH_TRIANGLES_AND_ENCODING', run: (v, read, _input, supply) => a04MeshTrianglesAndEncoding(v, supply.meshes(read)), unread: SKIP_NO_MODEL },
254
280
  { code: 'A33_VERTEX_ATTACHMENT_GEOMETRY', run: (v, read, _input, supply) => a33VertexAttachmentGeometry(v, supply.polygons(read)), unread: SKIP_NO_MODEL },
255
281
  { code: 'A11_NO_CLIPPING_ATTACHMENTS', run: (v, read, _input, supply) => a11NoClippingAttachments(v, supply.skinEntries(read)), unread: SKIP_NO_MODEL },
256
- { code: 'A13_MESH_BUDGET', run: (v, read, input, supply) => a13MeshBudget(v, supply.skinMeshes(read), input), unread: SKIP_NO_MODEL },
257
- { code: 'A14_NO_FULL_FRAME_MESH', run: (v, read, input, supply) => a14NoFullFrameMesh(v, supply.skinMeshes(read), supply.stage(read, input)), unread: SKIP_NO_MODEL },
258
- { code: 'A15_IDLE_NO_MESH_BONE_KEYS', run: (v, read, input, supply) => a15IdleNoMeshBoneKeys(v, supply.skinMeshes(read), supply.animatedBones(read), input), unread: SKIP_NO_MODEL },
259
- { code: 'A20_MESH_WEIGHTS_COHERENT', run: (v, read, input, supply) => a20MeshWeightsCoherent(v, supply.meshes(read), input.profile === 'spine-html', input.rig), unread: SKIP_NO_MODEL },
260
- { code: 'A21_MESH_RIM_PINNED', run: (v, read, input, supply) => a21MeshRimPinned(v, supply.meshes(read), input.rig), unread: SKIP_NO_MODEL },
261
- { code: 'A22_MESH_UVS_IN_UNIT_RANGE', run: (v, read, _input, supply) => a22MeshUvsInUnitRange(v, supply.skinMeshes(read)), unread: SKIP_NO_MODEL },
282
+ { code: 'A13_MESH_BUDGET', run: (v, read, input, supply) => a13MeshBudget(v, supply.meshes(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL },
283
+ { code: 'A14_NO_FULL_FRAME_MESH', run: (v, read, input, supply) => a14NoFullFrameMesh(v, supply.meshes(read), supply.stage(read, input)), unread: SKIP_NO_MODEL },
284
+ { code: 'A15_IDLE_NO_MESH_BONE_KEYS', run: (v, read, input, supply) => a15IdleNoMeshBoneKeys(v, supply.meshes(read), supply.animatedBones(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL },
285
+ { code: 'A20_MESH_WEIGHTS_COHERENT', run: (v, read, input, supply) => a20MeshWeightsCoherent(v, supply.meshes(read), input.profile === 'spine-html', supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
286
+ { code: 'A21_MESH_RIM_PINNED', run: (v, read, input, supply) => a21MeshRimPinned(v, supply.meshes(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
287
+ { code: 'A22_MESH_UVS_IN_UNIT_RANGE', run: (v, read, _input, supply) => a22MeshUvsInUnitRange(v, supply.meshes(read)), unread: SKIP_NO_MODEL },
288
+ { code: 'A39_DEFORM_KEEPS_TRIANGLE_WINDING', run: (v, read, input, supply) => a39DeformKeepsTriangleWinding(v, supply.deformSurvey(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
262
289
  { code: 'A23_PHYSICS_CONSTRAINT_EFFECTIVE', run: (v, read, _input, supply) => a23PhysicsConstraintEffective(v, supply.constraints(read), supply.meshes(read)), unread: SKIP_NO_MODEL },
263
- { code: 'A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP', run: (v, read, input, supply) => a41PhysicsSurvivesEditorRoundTrip(v, supply.constraints(read), input.rig), unread: SKIP_NO_MODEL },
290
+ { code: 'A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP', run: (v, read, input, supply) => a41PhysicsSurvivesEditorRoundTrip(v, supply.constraints(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
264
291
  { code: 'A36_PATH_CONSTRAINT_EFFECTIVE', run: (v, read, _input, supply) => a36PathConstraintEffective(v, supply.constraints(read)), unread: SKIP_NO_MODEL },
265
292
  { code: 'A37_SLIDER_CONSTRAINT_EFFECTIVE', run: (v, read, _input, supply) => a37SliderConstraintEffective(v, supply.constraints(read)), unread: SKIP_NO_MODEL },
266
- { code: 'A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT', run: (v, read, input, supply) => a47IkConstraintNotMutedThroughout(v, supply.constraints(read), input.rig), unread: SKIP_NO_MODEL },
267
- { code: 'A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT', run: (v, read, input, supply) => a48TransformConstraintNotMutedThroughout(v, supply.constraints(read), input.rig), unread: SKIP_NO_MODEL },
293
+ { code: 'A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT', run: (v, read, input, supply) => a47IkConstraintNotMutedThroughout(v, supply.constraints(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
294
+ { code: 'A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT', run: (v, read, input, supply) => a48TransformConstraintNotMutedThroughout(v, supply.constraints(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
295
+ { code: 'A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET', run: (v, read, _input, supply) => a40SlidersComposeOnASharedTarget(v, supply.sliderComposition(read), supply.constraints(read)), unread: SKIP_NO_MODEL },
268
296
  { code: 'A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER', run: (v, read, _input, supply) => a42DrivenConstraintsUpdateAfterTheirDriver(v, supply.constraints(read)), unread: SKIP_NO_MODEL },
269
297
  { code: 'A38_SKIN_MEMBERS_ARE_SKIN_REQUIRED', run: (v, read, _input, supply) => a38SkinMembersAreSkinRequired(v, supply.skinMembers(read)), unread: SKIP_NO_MODEL },
298
+ { code: 'A09_ANIMATION_DURATION_MATCHES_SPEC', run: (v, read, input, supply) => a09AnimationDurationMatchesSpec(v, supply.animationDurations(read), supply.declaredDurations(read, input)), unread: SKIP_NO_MODEL },
299
+ { code: 'A10_NO_NAN_AFTER_STEPPING', run: (v, read, _input, supply) => a10NoNanAfterStepping(v, supply.boneTimelines(read), supply.steppedPoses(read)), unread: SKIP_NO_MODEL },
300
+ { code: 'A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN', run: (v, read, _input, supply) => a43TwoColorTintLoadsAndPosesAsWritten(v, supply.twoColour(read)), unread: SKIP_NO_MODEL },
270
301
  { code: 'A45_SEPARABLE_COLOR_TIMELINES_OWN_THEIR_CHANNELS_AND_POSE_AS_WRITTEN', run: (v, read, _input, supply) => a45SeparableColorTimelinesOwnTheirChannelsAndPoseAsWritten(v, supply.slotColour(read)), unread: SKIP_NO_MODEL },
271
302
  { code: 'A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN', run: (v, read, _input, supply) => a44LinkedMeshStatesNoGeometryOfItsOwn(v, supply.links(read)), unread: SKIP_NO_MODEL },
303
+ { code: 'A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES', run: (v, read, _input, supply) => a46SequenceAttachmentsShowTheFrameTheFileStates(v, supply.sequences(read)), unread: SKIP_NO_MODEL },
272
304
  { code: 'A17_ATLAS_PAGE_FILES_EXIST', run: (v, read, input, supply) => a17AtlasPageFilesExist(v, supply.atlasPages(read), input), unread: SKIP_NO_ATLAS },
273
305
  { code: 'A06_ATLAS_PAGE_SIZE_MATCHES_PNG', run: (v, read, input, supply) => a06AtlasPageSizeMatchesPng(v, supply.atlasRegions(read, input), input, input.profile === 'spine-html'), unread: SKIP_NO_ATLAS },
274
- { code: 'A19_OVERLAY_PNGS_HAVE_ALPHA', run: (v, read, input, supply) => a19OverlayPngsHaveAlpha(v, supply.atlasRegions(read, input), supply.stage(read, input), supply.skinEntries(read), input), unread: SKIP_NO_ATLAS },
306
+ { code: 'A19_OVERLAY_PNGS_HAVE_ALPHA', run: (v, read, input, supply) => a19OverlayPngsHaveAlpha(v, supply.atlasRegions(read, input), supply.stage(read, input), supply.skinEntries(read), { atlasDir: input.atlasDir, rig: supply.rigInfo(read, input) }), unread: SKIP_NO_ATLAS },
307
+ { code: 'A24_AXIS_SPACE_STROKE', run: (v, read, input, supply) => a24AxisSpaceStroke(v, supply.boneTimelines(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
308
+ { code: 'A25_DETACHED_BONE_PARENTAGE', run: (v, read, input, supply) => a25DetachedBoneParentage(v, supply.skeletonRoster(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
309
+ { code: 'A26_SLOT_DRAW_ORDER', run: (v, read, input, supply) => a26SlotDrawOrder(v, supply.skeletonRoster(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
275
310
  { code: 'A27_REGION_NAME_MATCHES_PAGE_FILENAME', run: (v, read, input, supply) => a27RegionNameMatchesPageFilename(v, supply.atlasRegions(read, input)), unread: SKIP_NO_ATLAS },
276
- { code: 'A28_RIBBON_ROWS_SHARE_WEIGHTS', run: (v, read, input, supply) => a28RibbonRowsShareWeights(v, supply.meshes(read), input.rig), unread: SKIP_NO_MODEL },
277
- { code: 'A32_EVENT_KEYS_RESOLVE', run: (v, read, _input, supply) => a32EventKeysResolve(v, supply.eventKeys(read)), unread: SKIP_NO_MODEL, beforeTheParse: true },
278
- { code: 'A12_NO_DARK_COLOR', run: (v, read, _input, supply) => a12NoDarkColor(v, supply.skeletonRoster(read), supply.slotColour(read).slotTimelines), unread: SKIP_NO_MODEL, beforeTheParse: true },
279
- { code: 'A24_AXIS_SPACE_STROKE', run: (v, read, input, supply) => a24AxisSpaceStroke(v, supply.boneTimelines(read), input), unread: SKIP_NO_MODEL, beforeTheParse: true },
280
- { code: 'A25_DETACHED_BONE_PARENTAGE', run: (v, read, input, supply) => a25DetachedBoneParentage(v, supply.skeletonRoster(read), input), unread: SKIP_NO_MODEL, beforeTheParse: true },
281
- { code: 'A26_SLOT_DRAW_ORDER', run: (v, read, input, supply) => a26SlotDrawOrder(v, supply.skeletonRoster(read), input), unread: SKIP_NO_MODEL, beforeTheParse: true },
282
- { code: 'A29_STROKE_WITHIN_CONTACT_DEPTH', run: (v, read, input, supply) => a29StrokeWithinContactDepth(v, supply.boneTimelines(read), input), unread: SKIP_NO_MODEL, beforeTheParse: true },
283
- { code: 'A30_STROKE_WITHIN_CAP_CONTAINMENT', run: (v, read, input, supply) => a30StrokeWithinCapContainment(v, supply.boneTimelines(read), input), unread: SKIP_NO_MODEL, beforeTheParse: true },
284
- { code: 'A39_DEFORM_KEEPS_TRIANGLE_WINDING', run: (v, read, input, supply) => a39DeformKeepsTriangleWinding(v, supply.deformSurvey(read), input.rig), unread: SKIP_NO_MODEL },
285
- { code: 'A09_ANIMATION_DURATION_MATCHES_SPEC', run: (v, read, input, supply) => a09AnimationDurationMatchesSpec(v, supply.animationDurations(read), input.declaredDurations), unread: SKIP_NO_MODEL },
286
- { code: 'A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN', run: (v, read, _input, supply) => a43TwoColorTintLoadsAndPosesAsWritten(v, supply.twoColour(read)), unread: SKIP_NO_MODEL },
287
- { code: 'A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES', run: (v, read, _input, supply) => a46SequenceAttachmentsShowTheFrameTheFileStates(v, supply.sequences(read)), unread: SKIP_NO_MODEL },
288
- { code: 'A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET', run: (v, read, _input, supply) => a40SlidersComposeOnASharedTarget(v, supply.sliderComposition(read), supply.constraints(read)), unread: SKIP_NO_MODEL },
289
- { code: 'A34_CONSTRAINT_TIMELINE_TARGETS', run: (v, read, _input, supply) => a34ConstraintTimelineTargets(v, supply.constraintTargets(read)), unread: SKIP_NO_MODEL, beforeTheParse: true },
290
- { code: 'A10_NO_NAN_AFTER_STEPPING', run: (v, read, _input, supply) => a10NoNanAfterStepping(v, supply.boneTimelines(read), supply.steppedPoses(read)), unread: SKIP_NO_MODEL },
311
+ { code: 'A28_RIBBON_ROWS_SHARE_WEIGHTS', run: (v, read, input, supply) => a28RibbonRowsShareWeights(v, supply.meshes(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
312
+ { code: 'A29_STROKE_WITHIN_CONTACT_DEPTH', run: (v, read, input, supply) => a29StrokeWithinContactDepth(v, supply.boneTimelines(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
313
+ { code: 'A30_STROKE_WITHIN_CAP_CONTAINMENT', run: (v, read, input, supply) => a30StrokeWithinCapContainment(v, supply.boneTimelines(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
291
314
  ];
292
315
 
293
316
  /** The codes the model side prints: its two parse rules, then the moved assertions. */