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,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model side of the validator (issue #1025, step 4c of #380): the
|
|
3
|
+
* assertions that moved out of `src/validate.ts`, run over the model document
|
|
4
|
+
* and rigc's core instead of over what spine-core loaded.
|
|
5
|
+
*
|
|
6
|
+
* ⭐ **One body, two suppliers.** Every moved assertion is one function under
|
|
7
|
+
* `../bodies/`, written against a fact interface under `../facts/`.
|
|
8
|
+
* `validate()` supplies the facts from spine-core's loaded objects and calls
|
|
9
|
+
* the body inside its own `check`; this entry supplies them from the document
|
|
10
|
+
* (`./parse.ts`, `./skin_entries.ts`, `./atlas_pages.ts`, `./slot_colour.ts`;
|
|
11
|
+
* cut 4c-2's `./mesh_attachments.ts`, `./vertex_polygons.ts`,
|
|
12
|
+
* `./linked_meshes.ts`, `./constraints.ts`; cut 4c-4's
|
|
13
|
+
* `./skeleton_roster.ts`, `./bone_timelines.ts`, `./event_keys.ts`; cut
|
|
14
|
+
* 4c-3's `./deform_survey.ts`, `./animation_durations.ts`, `./two_colour.ts`,
|
|
15
|
+
* `./sequences.ts`; cut 4c-5's `./slider_composition.ts`,
|
|
16
|
+
* `./constraint_targets.ts`; cut 4c-5a's `./stepped_poses.ts`)
|
|
17
|
+
* and calls the same body inside the same harness (`../harness.ts`). The
|
|
18
|
+
* selftest holds the two to the same lines on every call it makes with a
|
|
19
|
+
* model in hand, and `tools/verdict_gate.ts` on every recipe.
|
|
20
|
+
*
|
|
21
|
+
* 🔸 **What the entry is given.** The document's text, the directory its
|
|
22
|
+
* pages resolve against, and the profile — and nothing read off a Spine file:
|
|
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. 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.
|
|
32
|
+
*
|
|
33
|
+
* ⛔ **Not wired into any command.** `build` keeps the round trip and its
|
|
34
|
+
* fifty lines; this entry is what the selftest and the instrument call
|
|
35
|
+
* until every moved assertion is on it (the card's design, point 7).
|
|
36
|
+
*
|
|
37
|
+
* Links nothing from the runtime, directly or through what it imports — held
|
|
38
|
+
* by the selftest's `VF` controls.
|
|
39
|
+
*/
|
|
40
|
+
import { ASSERTION_KIND, type AssertionProfile } from '../kinds.ts';
|
|
41
|
+
import { verdictHarness, type VerdictHarness, type Verdicts, type VerdictLists } from '../harness.ts';
|
|
42
|
+
import { SKIP_NO_ATLAS, SKIP_NO_MODEL } from '../reasons.ts';
|
|
43
|
+
import { a03RegionWidthHeightFinite } from '../bodies/a03.ts';
|
|
44
|
+
import { a11NoClippingAttachments } from '../bodies/a11.ts';
|
|
45
|
+
import { a17AtlasPageFilesExist } from '../bodies/a17.ts';
|
|
46
|
+
import { a45SeparableColorTimelinesOwnTheirChannelsAndPoseAsWritten } from '../bodies/a45.ts';
|
|
47
|
+
import { A00_MODEL_READ, A00_MODEL_REGIONS_ON_PAGES, MODEL_PARSE_KIND, modelRead, modelRegionsOnPages, SKIP_NO_MODEL_DOCUMENT, SKIP_NO_MODEL_PAGES, type ReadDocument } from './parse.ts';
|
|
48
|
+
import { fileOrderedEntries, modelSkinEntries } from './skin_entries.ts';
|
|
49
|
+
import { modelAtlasPages } from './atlas_pages.ts';
|
|
50
|
+
import { modelSlotColour } from './slot_colour.ts';
|
|
51
|
+
import type { SkinEntryFacts } from '../facts/skin_entries.ts';
|
|
52
|
+
import type { AtlasPageFacts } from '../facts/atlas_pages.ts';
|
|
53
|
+
import type { SlotColourFacts } from '../facts/slot_colour.ts';
|
|
54
|
+
import type { RigInfo } from '../../types.ts';
|
|
55
|
+
import type { ModelGiven } from './given.ts';
|
|
56
|
+
import { modelDeclaredDurations, modelRigInfo, refuseDeclaredBeside } from './declared.ts';
|
|
57
|
+
import { a08RegionNamesMatchAttachments } from '../bodies/a08.ts';
|
|
58
|
+
import { a13MeshBudget } from '../bodies/a13.ts';
|
|
59
|
+
import { a14NoFullFrameMesh } from '../bodies/a14.ts';
|
|
60
|
+
import { a15IdleNoMeshBoneKeys } from '../bodies/a15.ts';
|
|
61
|
+
import { a22MeshUvsInUnitRange } from '../bodies/a22.ts';
|
|
62
|
+
import { a38SkinMembersAreSkinRequired } from '../bodies/a38.ts';
|
|
63
|
+
import { a06AtlasPageSizeMatchesPng } from '../bodies/a06.ts';
|
|
64
|
+
import { a19OverlayPngsHaveAlpha } from '../bodies/a19.ts';
|
|
65
|
+
import { a49PackedFootprintsDoNotOverlap } from '../bodies/a49.ts';
|
|
66
|
+
import { a50StageBoxIsTheStage } from '../bodies/a50.ts';
|
|
67
|
+
import { modelStageBox } from './stage_box.ts';
|
|
68
|
+
import type { StageBoxFacts } from '../facts/stage_box.ts';
|
|
69
|
+
import { a27RegionNameMatchesPageFilename } from '../bodies/a27.ts';
|
|
70
|
+
import { modelAnimatedBones } from './animated_bones.ts';
|
|
71
|
+
import { modelSkinMembers } from './skin_members.ts';
|
|
72
|
+
import { modelRegionJoins } from './region_joins.ts';
|
|
73
|
+
import { modelAtlasRegions } from './atlas_regions.ts';
|
|
74
|
+
import { modelStage } from './stage.ts';
|
|
75
|
+
import type { AnimatedBoneFacts } from '../facts/animated_bones.ts';
|
|
76
|
+
import type { SkinMemberFacts } from '../facts/skin_members.ts';
|
|
77
|
+
import type { RegionJoinFacts } from '../facts/region_joins.ts';
|
|
78
|
+
import type { AtlasRegionFacts } from '../facts/atlas_regions.ts';
|
|
79
|
+
import type { StageFacts } from '../facts/stage.ts';
|
|
80
|
+
import { a04MeshTrianglesAndEncoding } from '../bodies/a04.ts';
|
|
81
|
+
import { a20MeshWeightsCoherent } from '../bodies/a20.ts';
|
|
82
|
+
import { a21MeshRimPinned } from '../bodies/a21.ts';
|
|
83
|
+
import { a23PhysicsConstraintEffective } from '../bodies/a23.ts';
|
|
84
|
+
import { a28RibbonRowsShareWeights } from '../bodies/a28.ts';
|
|
85
|
+
import { a33VertexAttachmentGeometry } from '../bodies/a33.ts';
|
|
86
|
+
import { a36PathConstraintEffective } from '../bodies/a36.ts';
|
|
87
|
+
import { a37SliderConstraintEffective } from '../bodies/a37.ts';
|
|
88
|
+
import { a42DrivenConstraintsUpdateAfterTheirDriver } from '../bodies/a42.ts';
|
|
89
|
+
import { a44LinkedMeshStatesNoGeometryOfItsOwn } from '../bodies/a44.ts';
|
|
90
|
+
import { a47IkConstraintNotMutedThroughout } from '../bodies/a47.ts';
|
|
91
|
+
import { a48TransformConstraintNotMutedThroughout } from '../bodies/a48.ts';
|
|
92
|
+
import { modelMeshFacts } from './mesh_attachments.ts';
|
|
93
|
+
import { modelPolygonFacts } from './vertex_polygons.ts';
|
|
94
|
+
import { modelLinkFacts } from './linked_meshes.ts';
|
|
95
|
+
import { modelConstraintFacts } from './constraints.ts';
|
|
96
|
+
import type { MeshFacts } from '../facts/mesh_attachments.ts';
|
|
97
|
+
import type { PolygonFacts } from '../facts/vertex_polygons.ts';
|
|
98
|
+
import type { LinkFacts } from '../facts/linked_meshes.ts';
|
|
99
|
+
import type { ConstraintFacts } from '../facts/constraints.ts';
|
|
100
|
+
import { a12NoDarkColor } from '../bodies/a12.ts';
|
|
101
|
+
import { a24AxisSpaceStroke } from '../bodies/a24.ts';
|
|
102
|
+
import { a25DetachedBoneParentage } from '../bodies/a25.ts';
|
|
103
|
+
import { a26SlotDrawOrder } from '../bodies/a26.ts';
|
|
104
|
+
import { a29StrokeWithinContactDepth } from '../bodies/a29.ts';
|
|
105
|
+
import { a30StrokeWithinCapContainment } from '../bodies/a30.ts';
|
|
106
|
+
import { a32EventKeysResolve } from '../bodies/a32.ts';
|
|
107
|
+
import { modelSkeletonRoster } from './skeleton_roster.ts';
|
|
108
|
+
import { modelBoneTimelines } from './bone_timelines.ts';
|
|
109
|
+
import { modelEventKeys } from './event_keys.ts';
|
|
110
|
+
import type { SkeletonRosterFacts } from '../facts/skeleton_roster.ts';
|
|
111
|
+
import type { BoneTimelineFacts } from '../facts/bone_timelines.ts';
|
|
112
|
+
import type { EventKeyFacts } from '../facts/event_keys.ts';
|
|
113
|
+
import { a39DeformKeepsTriangleWinding } from '../bodies/a39.ts';
|
|
114
|
+
import { a09AnimationDurationMatchesSpec } from '../bodies/a09.ts';
|
|
115
|
+
import { a43TwoColorTintLoadsAndPosesAsWritten } from '../bodies/a43.ts';
|
|
116
|
+
import { a46SequenceAttachmentsShowTheFrameTheFileStates } from '../bodies/a46.ts';
|
|
117
|
+
import { modelDeformSurvey } from './deform_survey.ts';
|
|
118
|
+
import { modelAnimationDurations } from './animation_durations.ts';
|
|
119
|
+
import { modelTwoColour } from './two_colour.ts';
|
|
120
|
+
import { modelSequences } from './sequences.ts';
|
|
121
|
+
import type { DeformSurveyFacts } from '../facts/deform_survey.ts';
|
|
122
|
+
import type { AnimationDurationFacts } from '../facts/animation_durations.ts';
|
|
123
|
+
import type { TwoColourFacts } from '../facts/two_colour.ts';
|
|
124
|
+
import type { SequenceFacts } from '../facts/sequences.ts';
|
|
125
|
+
import { a40SlidersComposeOnASharedTarget } from '../bodies/a40.ts';
|
|
126
|
+
import { modelSliderComposition } from './slider_composition.ts';
|
|
127
|
+
import type { SliderCompositionFacts } from '../facts/slider_composition.ts';
|
|
128
|
+
import { a34ConstraintTimelineTargets } from '../bodies/a34.ts';
|
|
129
|
+
import { modelConstraintTargets } from './constraint_targets.ts';
|
|
130
|
+
import type { ConstraintTargetFacts } from '../facts/constraint_targets.ts';
|
|
131
|
+
import { a10NoNanAfterStepping } from '../bodies/a10.ts';
|
|
132
|
+
import { modelSteppedPoses } from './stepped_poses.ts';
|
|
133
|
+
import type { SteppedPoseFacts } from '../facts/stepped_poses.ts';
|
|
134
|
+
|
|
135
|
+
/** What the model side is given. */
|
|
136
|
+
export interface ModelValidateInput {
|
|
137
|
+
/** The document `build` writes beside the Spine pair (`skeleton.model.json`). */
|
|
138
|
+
modelText: string;
|
|
139
|
+
/** The directory the document's page names resolve against — the build's `--out`. */
|
|
140
|
+
atlasDir: string;
|
|
141
|
+
profile: AssertionProfile;
|
|
142
|
+
/**
|
|
143
|
+
* Refused (issue #1054): every document states the rig info in its `rig`
|
|
144
|
+
* section, and the model side reads it there (`./declared.ts`). A caller
|
|
145
|
+
* that gives it anyway is a second source for one fact, and each rule that
|
|
146
|
+
* reads the declarations refuses the call by name rather than reading
|
|
147
|
+
* either. Kept on the type so a caller still handing it over is named, not
|
|
148
|
+
* silently ignored.
|
|
149
|
+
*/
|
|
150
|
+
rig?: RigInfo;
|
|
151
|
+
/**
|
|
152
|
+
* The stage and the pages' `pma`, for a `rigc-compiled/2` or `/1` document,
|
|
153
|
+
* which does not hold them (`./given.ts`). A06, A14 and A19 read them; a
|
|
154
|
+
* caller that gives none there has those three refuse by name rather than
|
|
155
|
+
* read a value nobody stated. A `/3` document states both (issue #1026), and
|
|
156
|
+
* `given` beside it is refused by name.
|
|
157
|
+
*/
|
|
158
|
+
given?: ModelGiven;
|
|
159
|
+
/** Refused, as `rig` is (issue #1054): every document states each animation's declared duration, and A09 reads them there. */
|
|
160
|
+
declaredDurations?: Record<string, number>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The model side's report: `validate()`'s shape, over the moved assertions and the two parse rules. */
|
|
164
|
+
export interface ModelReport extends VerdictLists {
|
|
165
|
+
profile: AssertionProfile;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The fact families, composed: one supplier per family under `./`, each a
|
|
170
|
+
* function of the read document. A body names the family it reads, so a later
|
|
171
|
+
* cut that moves an assertion over an existing family adds a body file and a
|
|
172
|
+
* row below, and one over a new family adds a facts file, a supplier file and
|
|
173
|
+
* a field here.
|
|
174
|
+
*/
|
|
175
|
+
export interface ModelSupply {
|
|
176
|
+
skinEntries: (read: ReadDocument) => SkinEntryFacts;
|
|
177
|
+
atlasPages: (read: ReadDocument) => AtlasPageFacts;
|
|
178
|
+
slotColour: (read: ReadDocument) => SlotColourFacts;
|
|
179
|
+
animatedBones: (read: ReadDocument) => AnimatedBoneFacts;
|
|
180
|
+
skinMembers: (read: ReadDocument) => SkinMemberFacts;
|
|
181
|
+
regionJoins: (read: ReadDocument) => RegionJoinFacts;
|
|
182
|
+
atlasRegions: (read: ReadDocument, input: ModelValidateInput) => AtlasRegionFacts;
|
|
183
|
+
stage: (read: ReadDocument, input: ModelValidateInput) => StageFacts;
|
|
184
|
+
meshes: (read: ReadDocument) => MeshFacts;
|
|
185
|
+
polygons: (read: ReadDocument) => PolygonFacts;
|
|
186
|
+
links: (read: ReadDocument) => LinkFacts;
|
|
187
|
+
constraints: (read: ReadDocument) => ConstraintFacts;
|
|
188
|
+
skeletonRoster: (read: ReadDocument) => SkeletonRosterFacts;
|
|
189
|
+
boneTimelines: (read: ReadDocument) => BoneTimelineFacts;
|
|
190
|
+
eventKeys: (read: ReadDocument) => EventKeyFacts;
|
|
191
|
+
deformSurvey: (read: ReadDocument) => DeformSurveyFacts;
|
|
192
|
+
animationDurations: (read: ReadDocument) => AnimationDurationFacts;
|
|
193
|
+
twoColour: (read: ReadDocument) => TwoColourFacts;
|
|
194
|
+
sequences: (read: ReadDocument) => SequenceFacts;
|
|
195
|
+
sliderComposition: (read: ReadDocument) => SliderCompositionFacts;
|
|
196
|
+
constraintTargets: (read: ReadDocument) => ConstraintTargetFacts;
|
|
197
|
+
steppedPoses: (read: ReadDocument) => SteppedPoseFacts;
|
|
198
|
+
/** The stage box the document asks for and what its default skin holds there (issue #1168, `./stage_box.ts`). */
|
|
199
|
+
stageBox: (read: ReadDocument) => StageBoxFacts;
|
|
200
|
+
/** The rig info the document declares (issue #1054, `./declared.ts`); a caller's `rig` beside it is refused by name. */
|
|
201
|
+
rigInfo: (read: ReadDocument, input: ModelValidateInput) => RigInfo;
|
|
202
|
+
/** Each animation's declared duration as the document states it (issue #1054); a caller's `declaredDurations` beside it is refused by name. */
|
|
203
|
+
declaredDurations: (read: ReadDocument, input: ModelValidateInput) => Record<string, number>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The suppliers the model side runs on. */
|
|
207
|
+
export const MODEL_SUPPLY: ModelSupply = {
|
|
208
|
+
skinEntries: modelSkinEntries,
|
|
209
|
+
atlasPages: modelAtlasPages,
|
|
210
|
+
slotColour: modelSlotColour,
|
|
211
|
+
animatedBones: modelAnimatedBones,
|
|
212
|
+
skinMembers: modelSkinMembers,
|
|
213
|
+
regionJoins: modelRegionJoins,
|
|
214
|
+
atlasRegions: (read, input) => modelAtlasRegions(read, input.given),
|
|
215
|
+
stage: (read, input) => modelStage(read, input.given),
|
|
216
|
+
meshes: modelMeshFacts,
|
|
217
|
+
polygons: modelPolygonFacts,
|
|
218
|
+
links: modelLinkFacts,
|
|
219
|
+
constraints: modelConstraintFacts,
|
|
220
|
+
skeletonRoster: modelSkeletonRoster,
|
|
221
|
+
boneTimelines: modelBoneTimelines,
|
|
222
|
+
eventKeys: modelEventKeys,
|
|
223
|
+
deformSurvey: modelDeformSurvey,
|
|
224
|
+
animationDurations: modelAnimationDurations,
|
|
225
|
+
twoColour: modelTwoColour,
|
|
226
|
+
sequences: modelSequences,
|
|
227
|
+
sliderComposition: modelSliderComposition,
|
|
228
|
+
constraintTargets: modelConstraintTargets,
|
|
229
|
+
steppedPoses: modelSteppedPoses,
|
|
230
|
+
stageBox: modelStageBox,
|
|
231
|
+
rigInfo: (read, input) => {
|
|
232
|
+
refuseDeclaredBeside(read, input, 'rig');
|
|
233
|
+
return modelRigInfo(read);
|
|
234
|
+
},
|
|
235
|
+
declaredDurations: (read, input) => {
|
|
236
|
+
refuseDeclaredBeside(read, input, 'declaredDurations');
|
|
237
|
+
return modelDeclaredDurations(read);
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* One moved assertion: its code, and how the model side runs its body once the
|
|
243
|
+
* document is read. The list is the registry of what has moved — a later cut
|
|
244
|
+
* puts its rows where `validate()` runs those rules, not at the end — and the
|
|
245
|
+
* selftest reads it for the codes it compares.
|
|
246
|
+
*/
|
|
247
|
+
export interface MovedAssertion {
|
|
248
|
+
code: string;
|
|
249
|
+
/** The body, over the document. Not called when the model side read no document. */
|
|
250
|
+
run: (verdicts: Verdicts, read: ReadDocument, input: ModelValidateInput, supply: ModelSupply) => void;
|
|
251
|
+
/** What the body SKIPs with when there is no document — the model side's counterpart of the reason `validate()` gives when the round trip produced nothing. */
|
|
252
|
+
unread: string;
|
|
253
|
+
/**
|
|
254
|
+
* The body runs behind the reader alone, not behind the region rule — the
|
|
255
|
+
* model side's place for a rule `validate()` runs BEFORE its round trip
|
|
256
|
+
* (A08, issue #589), so that a region miss is named by the rule's own
|
|
257
|
+
* sentence on both sides while the parse beside it refuses the file.
|
|
258
|
+
*
|
|
259
|
+
* Since cut 4c-4 also every rule `validate()` runs over the skeleton JSON
|
|
260
|
+
* whatever its round trip did (A12, A24, A25, A26, A29, A30, and A32, which
|
|
261
|
+
* runs before it; since cut 4c-5 A34): on the runtime's side their verdict does not wait on the
|
|
262
|
+
* load, so on this side it does not wait on the region rule, and where both
|
|
263
|
+
* parses refuse a build the two sides' lines of these rules are still
|
|
264
|
+
* compared.
|
|
265
|
+
*/
|
|
266
|
+
beforeTheParse?: boolean;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The moved assertions, in `validate()`'s report order — the order its `check`
|
|
271
|
+
* calls stand in, so a model-side report lists its PASS, SKIP and FAIL lines in
|
|
272
|
+
* the order the round trip's does. The seven cuts of issue #1025 appended their
|
|
273
|
+
* rows in landing order; issue #1054 put them back, and the selftest's `VF16`
|
|
274
|
+
* names a row that stands out of that order. `SKIP_NO_ATLAS` for
|
|
275
|
+
* A17 rather than `SKIP_NO_MODEL` because that is what A17 says over a missing
|
|
276
|
+
* atlas on either side: its body reads only the pages, and on this side no
|
|
277
|
+
* document means no pages.
|
|
278
|
+
*/
|
|
279
|
+
export const MOVED_ASSERTIONS: readonly MovedAssertion[] = [
|
|
280
|
+
{ 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 },
|
|
281
|
+
{ code: 'A32_EVENT_KEYS_RESOLVE', run: (v, read, _input, supply) => a32EventKeysResolve(v, supply.eventKeys(read)), unread: SKIP_NO_MODEL, beforeTheParse: true },
|
|
282
|
+
{ code: 'A34_CONSTRAINT_TIMELINE_TARGETS', run: (v, read, _input, supply) => a34ConstraintTimelineTargets(v, supply.constraintTargets(read)), unread: SKIP_NO_MODEL, beforeTheParse: true },
|
|
283
|
+
{ 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 },
|
|
284
|
+
{ code: 'A03_REGION_WIDTH_HEIGHT_FINITE', run: (v, read, _input, supply) => a03RegionWidthHeightFinite(v, supply.skinEntries(read)), unread: SKIP_NO_MODEL },
|
|
285
|
+
{ code: 'A04_MESH_TRIANGLES_AND_ENCODING', run: (v, read, _input, supply) => a04MeshTrianglesAndEncoding(v, supply.meshes(read)), unread: SKIP_NO_MODEL },
|
|
286
|
+
{ code: 'A33_VERTEX_ATTACHMENT_GEOMETRY', run: (v, read, _input, supply) => a33VertexAttachmentGeometry(v, supply.polygons(read)), unread: SKIP_NO_MODEL },
|
|
287
|
+
{ code: 'A11_NO_CLIPPING_ATTACHMENTS', run: (v, read, _input, supply) => a11NoClippingAttachments(v, supply.skinEntries(read)), unread: SKIP_NO_MODEL },
|
|
288
|
+
{ code: 'A13_MESH_BUDGET', run: (v, read, input, supply) => a13MeshBudget(v, supply.meshes(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL },
|
|
289
|
+
{ code: 'A14_NO_FULL_FRAME_MESH', run: (v, read, input, supply) => a14NoFullFrameMesh(v, supply.meshes(read), supply.stage(read, input)), unread: SKIP_NO_MODEL },
|
|
290
|
+
{ 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 },
|
|
291
|
+
{ 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 },
|
|
292
|
+
{ code: 'A21_MESH_RIM_PINNED', run: (v, read, input, supply) => a21MeshRimPinned(v, supply.meshes(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
|
|
293
|
+
{ code: 'A22_MESH_UVS_IN_UNIT_RANGE', run: (v, read, _input, supply) => a22MeshUvsInUnitRange(v, supply.meshes(read)), unread: SKIP_NO_MODEL },
|
|
294
|
+
{ code: 'A39_DEFORM_KEEPS_TRIANGLE_WINDING', run: (v, read, input, supply) => a39DeformKeepsTriangleWinding(v, supply.deformSurvey(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
|
|
295
|
+
{ code: 'A23_PHYSICS_CONSTRAINT_EFFECTIVE', run: (v, read, _input, supply) => a23PhysicsConstraintEffective(v, supply.constraints(read), supply.meshes(read)), unread: SKIP_NO_MODEL },
|
|
296
|
+
{ code: 'A36_PATH_CONSTRAINT_EFFECTIVE', run: (v, read, _input, supply) => a36PathConstraintEffective(v, supply.constraints(read)), unread: SKIP_NO_MODEL },
|
|
297
|
+
{ code: 'A37_SLIDER_CONSTRAINT_EFFECTIVE', run: (v, read, _input, supply) => a37SliderConstraintEffective(v, supply.constraints(read)), unread: SKIP_NO_MODEL },
|
|
298
|
+
{ 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 },
|
|
299
|
+
{ 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 },
|
|
300
|
+
{ 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 },
|
|
301
|
+
{ code: 'A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER', run: (v, read, _input, supply) => a42DrivenConstraintsUpdateAfterTheirDriver(v, supply.constraints(read)), unread: SKIP_NO_MODEL },
|
|
302
|
+
{ code: 'A38_SKIN_MEMBERS_ARE_SKIN_REQUIRED', run: (v, read, _input, supply) => a38SkinMembersAreSkinRequired(v, supply.skinMembers(read)), unread: SKIP_NO_MODEL },
|
|
303
|
+
{ code: 'A09_ANIMATION_DURATION_MATCHES_SPEC', run: (v, read, input, supply) => a09AnimationDurationMatchesSpec(v, supply.animationDurations(read), supply.declaredDurations(read, input)), unread: SKIP_NO_MODEL },
|
|
304
|
+
{ code: 'A10_NO_NAN_AFTER_STEPPING', run: (v, read, _input, supply) => a10NoNanAfterStepping(v, supply.boneTimelines(read), supply.steppedPoses(read)), unread: SKIP_NO_MODEL },
|
|
305
|
+
{ code: 'A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN', run: (v, read, _input, supply) => a43TwoColorTintLoadsAndPosesAsWritten(v, supply.twoColour(read)), unread: SKIP_NO_MODEL },
|
|
306
|
+
{ 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 },
|
|
307
|
+
{ code: 'A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN', run: (v, read, _input, supply) => a44LinkedMeshStatesNoGeometryOfItsOwn(v, supply.links(read)), unread: SKIP_NO_MODEL },
|
|
308
|
+
{ code: 'A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES', run: (v, read, _input, supply) => a46SequenceAttachmentsShowTheFrameTheFileStates(v, supply.sequences(read)), unread: SKIP_NO_MODEL },
|
|
309
|
+
{ code: 'A17_ATLAS_PAGE_FILES_EXIST', run: (v, read, input, supply) => a17AtlasPageFilesExist(v, supply.atlasPages(read), input), unread: SKIP_NO_ATLAS },
|
|
310
|
+
{ 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 },
|
|
311
|
+
{ 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 },
|
|
312
|
+
{ code: 'A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP', run: (v, read, input, supply) => a49PackedFootprintsDoNotOverlap(v, supply.atlasRegions(read, input), supply.regionJoins(read), supply.meshes(read)), unread: SKIP_NO_ATLAS },
|
|
313
|
+
{ code: 'A50_STAGE_BOX_IS_THE_STAGE', run: (v, read, _input, supply) => a50StageBoxIsTheStage(v, supply.stageBox(read)), unread: SKIP_NO_MODEL },
|
|
314
|
+
{ 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 },
|
|
315
|
+
{ 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 },
|
|
316
|
+
{ 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 },
|
|
317
|
+
{ code: 'A27_REGION_NAME_MATCHES_PAGE_FILENAME', run: (v, read, input, supply) => a27RegionNameMatchesPageFilename(v, supply.atlasRegions(read, input)), unread: SKIP_NO_ATLAS },
|
|
318
|
+
{ code: 'A28_RIBBON_ROWS_SHARE_WEIGHTS', run: (v, read, input, supply) => a28RibbonRowsShareWeights(v, supply.meshes(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
|
|
319
|
+
{ 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 },
|
|
320
|
+
{ 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 },
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
/** The codes the model side prints: its two parse rules, then the moved assertions. */
|
|
324
|
+
export const MODEL_SIDE_CODES: readonly string[] = [A00_MODEL_READ, A00_MODEL_REGIONS_ON_PAGES, ...MOVED_ASSERTIONS.map((m) => m.code)];
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* The parse: the reader, then the region rule. Returns what the reader read
|
|
328
|
+
* (`null` when it refused) and whether the region rule held too — a
|
|
329
|
+
* `beforeTheParse` body needs only the first.
|
|
330
|
+
*/
|
|
331
|
+
function parse(h: VerdictHarness, modelText: string): { read: ReadDocument | null; held: boolean } {
|
|
332
|
+
const read = h.check(A00_MODEL_READ, () => modelRead(h.verdicts, modelText)) ?? null;
|
|
333
|
+
if (read === null) {
|
|
334
|
+
h.check(A00_MODEL_REGIONS_ON_PAGES, () => h.skip(A00_MODEL_REGIONS_ON_PAGES, SKIP_NO_MODEL_DOCUMENT));
|
|
335
|
+
return { read: null, held: false };
|
|
336
|
+
}
|
|
337
|
+
return { read, held: h.check(A00_MODEL_REGIONS_ON_PAGES, () => modelRegionsOnPages(h.verdicts, read)) === true };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* The row the rig's two figures are written before (issue #1114): `validate()`
|
|
342
|
+
* sets `rig` and `profile` where its archetype assertions begin, after every
|
|
343
|
+
* figure the bodies above them write and before A29's and A30's, so the figures
|
|
344
|
+
* line lists them in that place on both entries. `RC39` in `selftest.ts`
|
|
345
|
+
* compares the two lines key by key.
|
|
346
|
+
*/
|
|
347
|
+
const RIG_FIGURES_BEFORE = 'A24_AXIS_SPACE_STROKE';
|
|
348
|
+
if (!MOVED_ASSERTIONS.some((m) => m.code === RIG_FIGURES_BEFORE)) throw new Error(`internal: the model side writes the rig's figures before ${RIG_FIGURES_BEFORE}, which it no longer runs`);
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The attachment figures `validate()` counts over the skins it loaded (issue
|
|
352
|
+
* #1114), counted over the document's skins in the file's walk order: a
|
|
353
|
+
* `region` record is a region attachment, and a `mesh` or `linkedmesh` record
|
|
354
|
+
* a mesh attachment — the runtime loads a linked mesh as a mesh. Written only
|
|
355
|
+
* where the document read and its regions held, as the round trip writes them
|
|
356
|
+
* only where it loaded the skeleton, and before any body's figure, which is
|
|
357
|
+
* where `validate()` writes them.
|
|
358
|
+
*/
|
|
359
|
+
function attachmentFigures(stats: Record<string, number | string>, read: ReadDocument): void {
|
|
360
|
+
const kinds = fileOrderedEntries(read).map((entry) => entry.record.kind);
|
|
361
|
+
stats.regionAttachments = kinds.filter((kind) => kind === 'region').length;
|
|
362
|
+
stats.meshAttachments = kinds.filter((kind) => kind === 'mesh' || kind === 'linkedmesh').length;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* `rig` — the archetype the document's rig section states (`validate()` prints
|
|
367
|
+
* its caller's; the model side reads the document's, issue #1054 — read
|
|
368
|
+
* directly rather than through `supply.rigInfo`, whose refusal of a caller's
|
|
369
|
+
* `rig` beside the document is the rules' line to print, not this figure's),
|
|
370
|
+
* left out where there is no document to state one — and `profile`, the profile this
|
|
371
|
+
* run was handed: the same argument both entries read off `--profile`.
|
|
372
|
+
*/
|
|
373
|
+
function rigFigures(stats: Record<string, number | string>, read: ReadDocument | null, input: ModelValidateInput): void {
|
|
374
|
+
if (read !== null) stats.rig = modelRigInfo(read).archetype;
|
|
375
|
+
stats.profile = input.profile;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Run the model side: the parse, then every moved assertion — each over the
|
|
380
|
+
* document, or skipped naming the parse when there is none — and return the
|
|
381
|
+
* report in `validate()`'s shape, which `reportLines` in `src/validate.ts`
|
|
382
|
+
* prints. `plant` replaces a family's supplier; only the selftest's plants
|
|
383
|
+
* pass one.
|
|
384
|
+
*/
|
|
385
|
+
export function validateModel(input: ModelValidateInput, plant: Partial<ModelSupply> = {}): ModelReport {
|
|
386
|
+
const supply: ModelSupply = { ...MODEL_SUPPLY, ...plant };
|
|
387
|
+
const h = verdictHarness(input.profile, { ...ASSERTION_KIND, ...MODEL_PARSE_KIND }, 'the model side');
|
|
388
|
+
const { read, held } = parse(h, input.modelText);
|
|
389
|
+
if (read !== null && held) attachmentFigures(h.stats, read);
|
|
390
|
+
for (const moved of MOVED_ASSERTIONS) {
|
|
391
|
+
if (moved.code === RIG_FIGURES_BEFORE) rigFigures(h.stats, read, input);
|
|
392
|
+
const doc = read !== null && (held || moved.beforeTheParse === true) ? read : null;
|
|
393
|
+
h.check(moved.code, () => (doc === null ? h.skip(moved.code, moved.unread) : moved.run(h.verdicts, doc, input, supply)));
|
|
394
|
+
}
|
|
395
|
+
const { failures, passed, skipped, profileSkipped, stats } = h;
|
|
396
|
+
return { failures, passed, skipped, profileSkipped, stats, profile: input.profile };
|
|
397
|
+
}
|
|
398
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model side's supply of `LinkFacts` (issue #1025, step 4c of #380): the
|
|
3
|
+
* document's `linkedmesh` records, in the order the Spine file spells them —
|
|
4
|
+
* the skins and each skin's slot keys in the file's order (`fileSkinOrder`,
|
|
5
|
+
* issue #1034), each slot's records in table order
|
|
6
|
+
* (`fileSpelledEntries`, `./vertex_polygons.ts`).
|
|
7
|
+
*
|
|
8
|
+
* `encoding` is empty on every link: a link record has no geometry field, so
|
|
9
|
+
* the geometry keys A44 refuses cannot be spelled here (`../facts/linked_meshes.ts`'s 🔒).
|
|
10
|
+
*
|
|
11
|
+
* Links nothing from the runtime.
|
|
12
|
+
*/
|
|
13
|
+
import type { LinkEntry, LinkFacts } from '../facts/linked_meshes.ts';
|
|
14
|
+
import { fileSpelledEntries } from './vertex_polygons.ts';
|
|
15
|
+
import type { ReadDocument } from './parse.ts';
|
|
16
|
+
|
|
17
|
+
export function modelLinkFacts(read: ReadDocument): LinkFacts {
|
|
18
|
+
const links: LinkEntry[] = [];
|
|
19
|
+
for (const { skin, slot, placeholder, record } of fileSpelledEntries(read)) {
|
|
20
|
+
if (record.kind !== 'linkedmesh' || typeof record.source !== 'string') continue;
|
|
21
|
+
links.push({ skin, slot, placeholder, source: record.source, encoding: [] });
|
|
22
|
+
}
|
|
23
|
+
return { links };
|
|
24
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model side's supply of `MeshFacts` (issue #1025, step 4c of #380): the
|
|
3
|
+
* document's mesh and linked-mesh records, in the file's walk order, with the
|
|
4
|
+
* geometry and the weights the runtime holds after its parse.
|
|
5
|
+
*
|
|
6
|
+
* Every value is the document's, through a function rigc already runs:
|
|
7
|
+
*
|
|
8
|
+
* - **The order** is `fileOrderedEntries` (`./skin_entries.ts`), the skins
|
|
9
|
+
* put in the file's order (`fileSkinOrder`, issue #1034).
|
|
10
|
+
* - **A binding's bone** is the index the emitter writes for its name,
|
|
11
|
+
* `boneIndexOf` over the document's bones (`src/emit_spine.ts`), and its
|
|
12
|
+
* weight the float32 the runtime stores a vertex array in (`Math.fround`,
|
|
13
|
+
* `src/model.ts`'s 🔸).
|
|
14
|
+
* - **A link's geometry is its source's**: its vertices are the core's
|
|
15
|
+
* `sourceOfDoc`, its `uvs` and triangles the core's `drawWalkOf(...).meshOf`
|
|
16
|
+
* — the two lookups the core poses a linked mesh through — and its hull
|
|
17
|
+
* the source record's, read where those two read it.
|
|
18
|
+
* - **The counts the parser derives**: `worldVerticesLength` is the length
|
|
19
|
+
* of `uvs` (the length the parser hands its vertex reader for a mesh — the
|
|
20
|
+
* reader refuses a document whose `uvs` are not one pair per vertex, so it is
|
|
21
|
+
* also two per vertex), and
|
|
22
|
+
* `hullLength` twice the record's `hull`, which the parser reads at the
|
|
23
|
+
* mesh row's default of the parser-default table when the key is absent
|
|
24
|
+
* (`PARSER_DEFAULTS` in `src/keyorder.ts`, which the selftest holds to the
|
|
25
|
+
* parser row by row).
|
|
26
|
+
*
|
|
27
|
+
* - **`width` and `height`** are the record's, off the document's JSON — a
|
|
28
|
+
* link's its source record's, the two numbers the runtime's `setSourceMesh`
|
|
29
|
+
* writes over the link's own: the core's geometry reader keeps a mesh's
|
|
30
|
+
* vertices, UVs and triangles and not its size, which no pose reads. Since
|
|
31
|
+
* issue #1054 this is the one family of the meshes; cut 4c-1's
|
|
32
|
+
* `SkinMeshFacts` stated the same entries with these two numbers added.
|
|
33
|
+
*
|
|
34
|
+
* `encoding` is empty on every entry: the document states the weighted form
|
|
35
|
+
* outright and names bones, so the clauses about the flat run have no subject
|
|
36
|
+
* here (`../facts/mesh_attachments.ts`'s 🔒).
|
|
37
|
+
*
|
|
38
|
+
* Links nothing from the runtime.
|
|
39
|
+
*/
|
|
40
|
+
import { boneIndexOf } from '../../emit_spine.ts';
|
|
41
|
+
import { drawWalkOf, shownRow, sourceOfDoc } from '../../core/index.ts';
|
|
42
|
+
import { PARSER_DEFAULTS } from '../../keyorder.ts';
|
|
43
|
+
import type { ModelVertices } from '../../model.ts';
|
|
44
|
+
import type { MeshBinding, MeshEntry, MeshFacts } from '../facts/mesh_attachments.ts';
|
|
45
|
+
import { fileOrderedEntries } from './skin_entries.ts';
|
|
46
|
+
import type { ReadDocument } from './parse.ts';
|
|
47
|
+
import { isObj } from '../values.ts';
|
|
48
|
+
|
|
49
|
+
/** A record's own `width` and `height` as the document states them, found by skin (the first of that name, as a link resolves its skin), slot and placeholder. */
|
|
50
|
+
function statedSize(read: ReadDocument, skin: string, slot: string, placeholder: string): { width: number; height: number } {
|
|
51
|
+
const skins = Array.isArray(read.json.skins) ? read.json.skins : [];
|
|
52
|
+
const found = skins.find((k: unknown) => isObj(k) && k.name === skin);
|
|
53
|
+
const table = isObj(found) && isObj(found.attachments) ? found.attachments[slot] : undefined;
|
|
54
|
+
const record = isObj(table) ? table[placeholder] : undefined;
|
|
55
|
+
if (!isObj(record) || typeof record.width !== 'number' || typeof record.height !== 'number') {
|
|
56
|
+
throw new Error(`internal: skin "${skin}" slot "${slot}" placeholder "${placeholder}" is a mesh record the document states no width and height for`);
|
|
57
|
+
}
|
|
58
|
+
return { width: record.width, height: record.height };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The parser's reading of a mesh with no `hull` key, off the parser-default table. */
|
|
62
|
+
function hullDefault(): number {
|
|
63
|
+
const stated = PARSER_DEFAULTS['mesh attachment']?.hull;
|
|
64
|
+
if (typeof stated !== 'number') throw new Error('internal: the parser-default table has no number for a mesh attachment\'s hull');
|
|
65
|
+
return stated;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function modelMeshFacts(read: ReadDocument): MeshFacts {
|
|
69
|
+
const { doc } = read;
|
|
70
|
+
const indexOf = boneIndexOf(doc.bones);
|
|
71
|
+
const sourceVertices = sourceOfDoc(doc);
|
|
72
|
+
const walk = drawWalkOf(doc, []);
|
|
73
|
+
const slotBone = new Map(doc.slots.map((s) => [s.name, s.bone]));
|
|
74
|
+
const meshes: MeshEntry[] = [];
|
|
75
|
+
for (const entry of fileOrderedEntries(read)) {
|
|
76
|
+
const g = entry.record.geometry;
|
|
77
|
+
if (g === undefined || (g.kind !== 'mesh' && g.kind !== 'linkedmesh')) continue;
|
|
78
|
+
const where = `skin "${entry.skin}" slot "${entry.slot}" placeholder "${entry.placeholder}"`;
|
|
79
|
+
let vertices: ModelVertices;
|
|
80
|
+
let uvs: readonly number[];
|
|
81
|
+
let triangles: readonly number[];
|
|
82
|
+
let hull: number | undefined;
|
|
83
|
+
let size: { width: number; height: number };
|
|
84
|
+
if (g.kind === 'mesh') {
|
|
85
|
+
({ vertices, uvs, triangles, hull } = g);
|
|
86
|
+
size = statedSize(read, entry.skin, entry.slot, entry.placeholder);
|
|
87
|
+
} else {
|
|
88
|
+
// `readModel` resolves every link to a mesh or refuses the document, so a miss here is this module's defect.
|
|
89
|
+
const found = sourceVertices(g.skin, g.slot, g.source);
|
|
90
|
+
const drawn = walk.meshOf(g.skin, g.slot, g.source);
|
|
91
|
+
if (typeof found === 'string' || drawn === undefined) throw new Error(`internal: ${where} is a link the reader returned without a source mesh: ${typeof found === 'string' ? found : 'no uvs'}`);
|
|
92
|
+
vertices = found;
|
|
93
|
+
({ uvs, triangles } = drawn);
|
|
94
|
+
const source = doc.skins.find((k) => k.name === g.skin)?.attachments[g.slot]?.[g.source]?.geometry;
|
|
95
|
+
hull = source?.kind === 'mesh' ? source.hull : undefined;
|
|
96
|
+
size = statedSize(read, g.skin, g.slot, g.source);
|
|
97
|
+
}
|
|
98
|
+
const bone = slotBone.get(entry.slot);
|
|
99
|
+
if (bone === undefined) throw new Error(`internal: ${where} names a slot the reader accepted and the document does not hold`);
|
|
100
|
+
const weights: MeshBinding[][] | null = vertices.weighted ? vertices.bindings.map((influences) => influences.map((b) => ({ bone: indexOf(b.bone), weight: Math.fround(b.weight) }))) : null;
|
|
101
|
+
meshes.push({
|
|
102
|
+
name: shownRow(entry).name,
|
|
103
|
+
skin: entry.skin,
|
|
104
|
+
slot: entry.slot,
|
|
105
|
+
slotBone: bone,
|
|
106
|
+
placeholder: entry.placeholder,
|
|
107
|
+
link: g.kind === 'linkedmesh' ? { source: g.source } : null,
|
|
108
|
+
triangles,
|
|
109
|
+
worldVerticesLength: uvs.length,
|
|
110
|
+
regionUVs: uvs,
|
|
111
|
+
hullLength: 2 * (hull ?? hullDefault()),
|
|
112
|
+
width: size.width,
|
|
113
|
+
height: size.height,
|
|
114
|
+
weights,
|
|
115
|
+
encoding: [],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return { bones: doc.bones.map((b) => b.name), meshes };
|
|
119
|
+
}
|