spine-rigc 1.9.2 → 1.9.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.
Files changed (49) hide show
  1. package/cli.ts +28 -4397
  2. package/docs/AUTHORING.md +4 -4
  3. package/package.json +1 -1
  4. package/src/assertions/bodies/a06.ts +53 -3
  5. package/src/assertions/bodies/a13.ts +3 -3
  6. package/src/assertions/bodies/a14.ts +3 -3
  7. package/src/assertions/bodies/a15.ts +5 -5
  8. package/src/assertions/bodies/a19.ts +44 -2
  9. package/src/assertions/bodies/a22.ts +3 -3
  10. package/src/assertions/facts/animated_bones.ts +4 -2
  11. package/src/assertions/facts/mesh_attachments.ts +33 -4
  12. package/src/assertions/model/animated_bones.ts +13 -5
  13. package/src/assertions/model/bone_timelines.ts +5 -3
  14. package/src/assertions/model/declared.ts +127 -0
  15. package/src/assertions/model/index.ts +63 -40
  16. package/src/assertions/model/mesh_attachments.ts +31 -2
  17. package/src/assertions/model/parse.ts +9 -1
  18. package/src/assertions/model/runtime_timelines.ts +78 -0
  19. package/src/assertions/model/slider_composition.ts +10 -8
  20. package/src/assertions/model/stepped_poses.ts +3 -2
  21. package/src/bonedist.ts +4 -5
  22. package/src/check.ts +3 -2
  23. package/src/checkpics.ts +1 -1
  24. package/src/cli/core_commands.ts +1532 -0
  25. package/src/cli/shared.ts +1935 -0
  26. package/src/cli/spine_commands.ts +1141 -0
  27. package/src/core/additive.ts +52 -62
  28. package/src/core/constraints.ts +26 -3
  29. package/src/core/constraints_physics.ts +11 -1
  30. package/src/core/constraints_slider.ts +136 -6
  31. package/src/core/index.ts +27 -3
  32. package/src/core/skins.ts +28 -3
  33. package/src/core/walk.ts +9 -36
  34. package/src/correspondence.ts +15 -0
  35. package/src/deformbuild.ts +60 -0
  36. package/src/deformmeasure.ts +20 -48
  37. package/src/deformreport.ts +2 -2
  38. package/src/framing.ts +1 -1
  39. package/src/pose.ts +1 -1
  40. package/src/preview.ts +1 -1
  41. package/src/region_joins.ts +54 -0
  42. package/src/render.ts +206 -2836
  43. package/src/render_core.ts +19 -36
  44. package/src/render_shared.ts +2887 -0
  45. package/src/slots.ts +1 -1
  46. package/src/spine_side.ts +123 -0
  47. package/src/validate.ts +11 -91
  48. package/src/assertions/facts/skin_meshes.ts +0 -49
  49. package/src/assertions/model/skin_meshes.ts +0 -90
@@ -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. */
@@ -16,12 +16,21 @@
16
16
  * — the two lookups the core poses a linked mesh through — and its hull
17
17
  * the source record's, read where those two read it.
18
18
  * - **The counts the parser derives**: `worldVerticesLength` is the length
19
- * of `uvs` (the length the parser hands its vertex reader for a mesh), and
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
20
22
  * `hullLength` twice the record's `hull`, which the parser reads at the
21
23
  * mesh row's default of the parser-default table when the key is absent
22
24
  * (`PARSER_DEFAULTS` in `src/keyorder.ts`, which the selftest holds to the
23
25
  * parser row by row).
24
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
+ *
25
34
  * `encoding` is empty on every entry: the document states the weighted form
26
35
  * outright and names bones, so the clauses about the flat run have no subject
27
36
  * here (`../facts/mesh_attachments.ts`'s 🔒).
@@ -35,6 +44,19 @@ import type { ModelVertices } from '../../model.ts';
35
44
  import type { MeshBinding, MeshEntry, MeshFacts } from '../facts/mesh_attachments.ts';
36
45
  import { fileOrderedEntries } from './skin_entries.ts';
37
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
+ }
38
60
 
39
61
  /** The parser's reading of a mesh with no `hull` key, off the parser-default table. */
40
62
  function hullDefault(): number {
@@ -58,8 +80,10 @@ export function modelMeshFacts(read: ReadDocument): MeshFacts {
58
80
  let uvs: readonly number[];
59
81
  let triangles: readonly number[];
60
82
  let hull: number | undefined;
83
+ let size: { width: number; height: number };
61
84
  if (g.kind === 'mesh') {
62
85
  ({ vertices, uvs, triangles, hull } = g);
86
+ size = statedSize(read, entry.skin, entry.slot, entry.placeholder);
63
87
  } else {
64
88
  // `readModel` resolves every link to a mesh or refuses the document, so a miss here is this module's defect.
65
89
  const found = sourceVertices(g.skin, g.slot, g.source);
@@ -69,19 +93,24 @@ export function modelMeshFacts(read: ReadDocument): MeshFacts {
69
93
  ({ uvs, triangles } = drawn);
70
94
  const source = doc.skins.find((k) => k.name === g.skin)?.attachments[g.slot]?.[g.source]?.geometry;
71
95
  hull = source?.kind === 'mesh' ? source.hull : undefined;
96
+ size = statedSize(read, g.skin, g.slot, g.source);
72
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`);
73
100
  const weights: MeshBinding[][] | null = vertices.weighted ? vertices.bindings.map((influences) => influences.map((b) => ({ bone: indexOf(b.bone), weight: Math.fround(b.weight) }))) : null;
74
101
  meshes.push({
75
102
  name: shownRow(entry).name,
76
103
  skin: entry.skin,
77
104
  slot: entry.slot,
78
- slotBone: slotBone.get(entry.slot) ?? '',
105
+ slotBone: bone,
79
106
  placeholder: entry.placeholder,
80
107
  link: g.kind === 'linkedmesh' ? { source: g.source } : null,
81
108
  triangles,
82
109
  worldVerticesLength: uvs.length,
83
110
  regionUVs: uvs,
84
111
  hullLength: 2 * (hull ?? hullDefault()),
112
+ width: size.width,
113
+ height: size.height,
85
114
  weights,
86
115
  encoding: [],
87
116
  });
@@ -40,6 +40,7 @@
40
40
  import { CORE_REGION_KINDS, CoreInputError, readModel, shownRow, type CompiledDocument } from '../../core/index.ts';
41
41
  import { documentPageLookup, frameRegionName, readUvSequences } from '../../core/uvs.ts';
42
42
  import type { AssertionKind } from '../kinds.ts';
43
+ import { rigSectionProblems } from './declared.ts';
43
44
  import type { Verdicts } from '../harness.ts';
44
45
 
45
46
  /** The reader's refusal, as a line. */
@@ -76,7 +77,14 @@ export function modelRead({ fail }: Verdicts, modelText: string): ReadDocument |
76
77
  fail(A00_MODEL_READ, err.message);
77
78
  return null;
78
79
  }
79
- return { doc, json: JSON.parse(modelText) as Record<string, unknown> };
80
+ const json = JSON.parse(modelText) as Record<string, unknown>;
81
+ // Issue #1054: the rig section is a source the model side reads (`./declared.ts`), so it is read here with the rest — refused by its paths, never cast.
82
+ const rigProblems = rigSectionProblems(json);
83
+ if (rigProblems.length > 0) {
84
+ fail(A00_MODEL_READ, `the model document: ${rigProblems.length} problem(s) in the rig section the model side reads its declarations from: ${rigProblems.join('; ')}`);
85
+ return null;
86
+ }
87
+ return { doc, json };
80
88
  }
81
89
 
82
90
  /**
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The runtime's words for each timeline a document's animation can hold
3
+ * (issue #1054): the class it loads as, the property ids it registers and what
4
+ * those ids carry — the values A40's sentence prints, which the document has no
5
+ * field for and the core has no reason to know.
6
+ *
7
+ * ⭐ **Why they live here and not beside the core's table.** Until issue #1054
8
+ * this table stood in `src/core/additive.ts`, beside the mode each kind's
9
+ * application has: 34 runtime class names and their property names as string
10
+ * literals in a module that otherwise speaks the document's words (#1011's
11
+ * second ask). The mode is the core's — it decides what the probe computes —
12
+ * and stays there, keyed by the document's own words (`ADDITIVE_MODE`); what
13
+ * the runtime calls the timeline is a fact about the validator's other
14
+ * supplier, and sits here with the constraint classes' names
15
+ * (`RUNTIME_CONSTRAINT_CLASS`, `./constraints.ts`). Both tables are keyed by
16
+ * the same spelling (`additiveSpelling`), and the selftest's `CO27` holds
17
+ * every row of both against the timeline objects spine-core loads, over a
18
+ * seeded population reaching every spelling.
19
+ *
20
+ * Read off the loaded timeline objects, never written from the runtime's
21
+ * source: the class is the object's constructor name, the properties its
22
+ * registered ids' names.
23
+ *
24
+ * Links nothing from the runtime.
25
+ */
26
+
27
+ /** What a timeline's property ids are followed by: its target's index (and, for an attachment's, the attachment), or nothing. */
28
+ export type RuntimeTimelineAddress = 'bone' | 'slot' | 'attachment' | 'constraint' | 'none';
29
+
30
+ /** One timeline spelling, as the runtime loads it: its class, the properties it registers in order, and what its ids carry. */
31
+ export interface RuntimeTimelineRow {
32
+ runtimeClass: string;
33
+ properties: readonly string[];
34
+ address: RuntimeTimelineAddress;
35
+ }
36
+
37
+ const row = (runtimeClass: string, properties: readonly string[], address: RuntimeTimelineAddress): RuntimeTimelineRow => ({ runtimeClass, properties, address });
38
+
39
+ /** Every timeline spelling a document's animation can hold (`additiveSpelling`'s keys), as the runtime loads it. */
40
+ export const RUNTIME_TIMELINE: Readonly<Record<string, RuntimeTimelineRow>> = {
41
+ 'bone rotate': row('RotateTimeline', ['rotate'], 'bone'),
42
+ 'bone translate': row('TranslateTimeline', ['x', 'y'], 'bone'),
43
+ 'bone translatex': row('TranslateXTimeline', ['x'], 'bone'),
44
+ 'bone translatey': row('TranslateYTimeline', ['y'], 'bone'),
45
+ 'bone scale': row('ScaleTimeline', ['scaleX', 'scaleY'], 'bone'),
46
+ 'bone scalex': row('ScaleXTimeline', ['scaleX'], 'bone'),
47
+ 'bone scaley': row('ScaleYTimeline', ['scaleY'], 'bone'),
48
+ 'bone shear': row('ShearTimeline', ['shearX', 'shearY'], 'bone'),
49
+ 'bone shearx': row('ShearXTimeline', ['shearX'], 'bone'),
50
+ 'bone sheary': row('ShearYTimeline', ['shearY'], 'bone'),
51
+ 'bone inherit': row('InheritTimeline', ['inherit'], 'bone'),
52
+ 'slot attachment': row('AttachmentTimeline', ['attachment'], 'slot'),
53
+ 'slot rgba': row('RGBATimeline', ['rgb', 'alpha'], 'slot'),
54
+ 'slot rgb': row('RGBTimeline', ['rgb'], 'slot'),
55
+ 'slot alpha': row('AlphaTimeline', ['alpha'], 'slot'),
56
+ 'slot rgba2': row('RGBA2Timeline', ['rgb', 'alpha', 'rgb2'], 'slot'),
57
+ 'slot rgb2': row('RGB2Timeline', ['rgb', 'rgb2'], 'slot'),
58
+ 'attachment deform': row('DeformTimeline', ['deform'], 'attachment'),
59
+ 'attachment sequence': row('SequenceTimeline', ['sequence'], 'attachment'),
60
+ ik: row('IkConstraintTimeline', ['ikConstraint'], 'constraint'),
61
+ transform: row('TransformConstraintTimeline', ['transformConstraint'], 'constraint'),
62
+ 'path position': row('PathConstraintPositionTimeline', ['pathConstraintPosition'], 'constraint'),
63
+ 'path spacing': row('PathConstraintSpacingTimeline', ['pathConstraintSpacing'], 'constraint'),
64
+ 'path mix': row('PathConstraintMixTimeline', ['pathConstraintMix'], 'constraint'),
65
+ 'physics inertia': row('PhysicsConstraintInertiaTimeline', ['physicsConstraintInertia'], 'constraint'),
66
+ 'physics strength': row('PhysicsConstraintStrengthTimeline', ['physicsConstraintStrength'], 'constraint'),
67
+ 'physics damping': row('PhysicsConstraintDampingTimeline', ['physicsConstraintDamping'], 'constraint'),
68
+ 'physics mass': row('PhysicsConstraintMassTimeline', ['physicsConstraintMass'], 'constraint'),
69
+ 'physics wind': row('PhysicsConstraintWindTimeline', ['physicsConstraintWind'], 'constraint'),
70
+ 'physics gravity': row('PhysicsConstraintGravityTimeline', ['physicsConstraintGravity'], 'constraint'),
71
+ 'physics mix': row('PhysicsConstraintMixTimeline', ['physicsConstraintMix'], 'constraint'),
72
+ // A reset registers its property with no constraint index, named or not (measured: both read `physicsConstraintReset`).
73
+ 'physics reset': row('PhysicsConstraintResetTimeline', ['physicsConstraintReset'], 'none'),
74
+ 'slider time': row('SliderTimeline', ['sliderTime'], 'constraint'),
75
+ 'slider mix': row('SliderMixTimeline', ['sliderMix'], 'constraint'),
76
+ drawOrder: row('DrawOrderTimeline', ['drawOrder'], 'none'),
77
+ events: row('EventTimeline', ['event'], 'none'),
78
+ };
@@ -16,16 +16,17 @@
16
16
  * timelines at their three levels in the order the file keys them
17
17
  * (`keyedOrder` over the document's order, deform before sequence —
18
18
  * `emitAttachmentTimelines`' order), then the draw order and the events.
19
- * - **The words.** Each timeline's class name and properties are the core's
20
- * table (`ADDITIVE_APPLY`, `src/core/additive.ts`), the one value here that
21
- * is not the document's, measured off the loaded timelines; an id's index is
19
+ * - **The words.** Each timeline's class name and properties are the
20
+ * validator's table (`RUNTIME_TIMELINE`, `./runtime_timelines.ts`; the
21
+ * core's until issue #1054), the one value here that is not the document's,
22
+ * measured off the loaded timelines; an id's index is
22
23
  * the target's position in the document's bones, slots or constraints —
23
24
  * which the emitter writes in that order — `-1` for a physics timeline
24
25
  * naming none; a deform's attachment is named as the runtime names it, the
25
26
  * record's `name`, else its placeholder.
26
27
  * - **The behaviour** is the core's probe over the document under no skin set
27
- * (`noSkinView`, which refuses by name a document whose skins it was not
28
- * measured over) and under each skin (`underSkin`): `additiveCells`, and the
28
+ * (`noSkinView`, the view measured in issue #1051) and under each skin
29
+ * (`underSkin`): `additiveCells`, and the
29
30
  * class `additiveBehaviour` reads off them. The core suite's `CO27` holds
30
31
  * both to the runtime's probe; the selftest asks this supplier every
31
32
  * question the body asks the runtime's (`VF14`).
@@ -33,7 +34,8 @@
33
34
  * Links nothing from the runtime.
34
35
  */
35
36
  import { fileSkinOrder, keyedOrder } from '../../compile.ts';
36
- import { ADDITIVE_APPLY, additiveBehaviour, additiveCells, additiveSpelling, type AdditiveTimeline, type AdditiveView } from '../../core/additive.ts';
37
+ import { additiveBehaviour, additiveCells, additiveSpelling, type AdditiveTimeline, type AdditiveView } from '../../core/additive.ts';
38
+ import { RUNTIME_TIMELINE } from './runtime_timelines.ts';
37
39
  import { EVERY_GLOBAL_PHYSICS, type PhysicsTimelineKind } from '../../core/constraints_physics.ts';
38
40
  import { underSkin, type CompiledDocument } from '../../core/index.ts';
39
41
  import type { BoneTimelineKind, SlotTimelineKind } from '../../core/animation.ts';
@@ -57,8 +59,8 @@ const keyedFirsts = (names: readonly string[]): string[] => keyedOrder(names.fil
57
59
 
58
60
  /** The words of one timeline: the table's class and properties, each id addressed by `index` (and `attachment`), and the sentence's naming. */
59
61
  function factOf(doc: CompiledDocument, core: AdditiveTimeline): SliderTimelineFact {
60
- const row = ADDITIVE_APPLY[additiveSpelling(core)];
61
- if (row === undefined) throw new Error(`internal: "${additiveSpelling(core)}" is no row of the additive table`);
62
+ const row = RUNTIME_TIMELINE[additiveSpelling(core)];
63
+ if (row === undefined) throw new Error(`internal: "${additiveSpelling(core)}" is no row of the runtime's timeline table`);
62
64
  const property = (p: string, index: number | null, attachment: string | null, names: string) => ({
63
65
  id: [p, ...(index === null ? [] : [String(index)]), ...(attachment === null ? [] : [attachment])].join('|'),
64
66
  property: p,
@@ -21,8 +21,9 @@
21
21
  * name, so on this side every bone of every pose holds one and the answer is
22
22
  * `null` — derived from the pose rather than assumed, so a reader that
23
23
  * started admitting such a spelling would show up here as a bone with none.
24
- * - **Under no skin set**, as `validate()` poses (`noSkinView`), which refuses
25
- * by name a document whose default skin it cannot read that way.
24
+ * - **Under no skin set**, as `validate()` poses (`noSkinView`, the view
25
+ * measured in issue #1051: no skin's bones or constraints applied, a slot
26
+ * looked up in the default skin alone).
26
27
  *
27
28
  * Links nothing from the runtime.
28
29
  */
package/src/bonedist.ts CHANGED
@@ -84,14 +84,13 @@ import {
84
84
  type BoneSnapshot,
85
85
  type Posable,
86
86
  } from './render.ts';
87
+ import { BONEDIST_SPEC, IDENTITY_CORRESPONDENCE } from './correspondence.ts';
87
88
 
88
89
  export class BoneDistError extends Error {}
89
90
 
90
- /** The sidecar spec a correspondence file declares, and the report's own. */
91
- export const BONEDIST_SPEC = 'rigc-bonedist/1';
92
-
93
- /** What `--bones identity` is spelled as, where a file path would go. */
94
- export const IDENTITY_CORRESPONDENCE = 'identity';
91
+ // `BONEDIST_SPEC` and `IDENTITY_CORRESPONDENCE` are `./correspondence.ts`'s since issue #1052: the CLI's help names
92
+ // both, and an entry that links nothing of the runtime prints that help. Re-exported here.
93
+ export { BONEDIST_SPEC, IDENTITY_CORRESPONDENCE };
95
94
 
96
95
  /** How many bones the report's per-bone table shows before it says "and N more". */
97
96
  export const BONE_TABLE_ROWS = 8;
package/src/check.ts CHANGED
@@ -92,13 +92,14 @@ import {
92
92
  type FramesSidecar,
93
93
  type FrameSet,
94
94
  type MakeCorePoser,
95
- type Posable,
96
95
  type PoseOptions,
97
96
  type Poser,
98
97
  type PoserName,
99
98
  type TextureSubstitution,
100
99
  type Viewport,
101
- } from './render.ts';
100
+ } from './render_shared.ts';
101
+ // The one name here that is the runtime's: a candidate's pages, read off the shape spine-core's loader returns.
102
+ import type { Posable } from './render.ts';
102
103
  import {
103
104
  applyFit,
104
105
  boxHeight,
package/src/checkpics.ts CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  type Framing,
49
49
  type FrameCheck,
50
50
  } from './check.ts';
51
- import { FRAMES_SIDECAR, FRAMES_SPEC } from './render.ts';
51
+ import { FRAMES_SIDECAR, FRAMES_SPEC } from './render_shared.ts';
52
52
  import { isContent } from './framing.ts';
53
53
  import { isAttributable } from './slots.ts';
54
54
  import { encodePng, Plate, type RGBA } from '../tools/plate.ts';