rig-c 0.0.0-stage → 2.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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 +1191 -0
- package/src/meshquality.ts +2051 -0
- package/src/meshrasters.ts +944 -0
- package/src/meshreduce.ts +1444 -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
package/src/types.ts
ADDED
|
@@ -0,0 +1,1797 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input and output shapes for rigc.
|
|
3
|
+
*
|
|
4
|
+
* Three inputs, one domain each:
|
|
5
|
+
* - the cut manifest, which owns measured geometry: crop, part offsets, part sizes, mask
|
|
6
|
+
* polygons, the state machine, anchors, the axis and the measured ceilings.
|
|
7
|
+
* Optional — a foreign skeleton has none;
|
|
8
|
+
* - the **rig spec** ([`src/rig.ts`](rig.ts), `spec: "rigc-rig/1"`), which owns
|
|
9
|
+
* skeleton structure: bones, slots, skins, constraints, invariants. Required.
|
|
10
|
+
* It replaced the three hard-coded archetype tables that used to be code;
|
|
11
|
+
* - the motion spec, which owns time: keys, named
|
|
12
|
+
* easings, groups, declared durations.
|
|
13
|
+
*
|
|
14
|
+
* Nothing else is an input, and the compiler never invents a value that is in
|
|
15
|
+
* none of them.
|
|
16
|
+
*/
|
|
17
|
+
import type { AtlasRegion } from './atlas.ts';
|
|
18
|
+
import type { DeformTransform, DeformTransformReport } from './deformgen.ts';
|
|
19
|
+
import type { TurnCeiling } from './depth.ts';
|
|
20
|
+
import type { SpecEnumTable, SpecTypeRow } from './keys.ts';
|
|
21
|
+
import type { MeshKind } from './mesh.ts';
|
|
22
|
+
import type { CompiledModel } from './model.ts';
|
|
23
|
+
import type { TrackDerive, TrackDeriveReport } from './trackgen.ts';
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Cut manifest (face class)
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Mesh declaration for a part — geometry, so it belongs to the manifest and not
|
|
31
|
+
* to the motion spec. It says WHERE the deformable ring
|
|
32
|
+
* is; the motion spec says WHEN it moves, by keying the control bone.
|
|
33
|
+
*/
|
|
34
|
+
export interface FaceManifestMesh {
|
|
35
|
+
/**
|
|
36
|
+
* Which generator builds this mesh. Absent means `ring`, so every manifest
|
|
37
|
+
* written before the joint archetype keeps its meaning.
|
|
38
|
+
*
|
|
39
|
+
* ring — three concentric rings + a hub. The outer two are pinned (region
|
|
40
|
+
* border, then mask contour) and only the aperture ring moves.
|
|
41
|
+
* ribbon — a two-wide strip along a bone chain. Length changes, width does
|
|
42
|
+
* not, because paired vertices carry identical weights.
|
|
43
|
+
*/
|
|
44
|
+
kind?: 'ring' | 'ribbon';
|
|
45
|
+
/** Ring only. The part's own mask polygon, which is the seam. */
|
|
46
|
+
hull?: 'polygon';
|
|
47
|
+
/** Ring only. Aperture centre in CROP pixels (y down) — measured, not guessed. */
|
|
48
|
+
center?: [number, number];
|
|
49
|
+
/** Ring only. Inner ring position between centre (0) and hull (1). */
|
|
50
|
+
inner?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Ring only, legacy form: ONE control bone which the compiler CREATES as a
|
|
53
|
+
* child of the slot bone. Used by archetypes that have no explicit bone tree.
|
|
54
|
+
*/
|
|
55
|
+
control_bone?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Control bones that already exist in the archetype's bone tree — the ring's
|
|
58
|
+
* authority is split between them by angular position, so a four-grip ring
|
|
59
|
+
* can expand asymmetrically without a key per vertex.
|
|
60
|
+
*/
|
|
61
|
+
control_bones?: string[];
|
|
62
|
+
/** Ribbon only. Number of cross rows; triangles = 2 * (rows - 1). */
|
|
63
|
+
rows?: number;
|
|
64
|
+
/** Ribbon only. The bone chain the strip rides, root first. */
|
|
65
|
+
chain?: string[];
|
|
66
|
+
/**
|
|
67
|
+
* Directional weighting. Without it the ring deforms symmetrically about the
|
|
68
|
+
* centre, which moves the upper lip and the upper teeth along with the jaw —
|
|
69
|
+
* anatomically wrong, and the owner spotted it on the first review.
|
|
70
|
+
*
|
|
71
|
+
* `axis_deg` is the mouth line measured on the art (screen degrees, y down).
|
|
72
|
+
* `ramp` is the signed distance across that axis, in part pixels, over which
|
|
73
|
+
* control authority goes 0 -> 1; positive is the jaw side.
|
|
74
|
+
*/
|
|
75
|
+
bias?: { axis_deg: number; ramp: [number, number]; note?: string };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface FaceManifestPart {
|
|
79
|
+
slot: string;
|
|
80
|
+
/**
|
|
81
|
+
* The archetype slot this part joins on, when it differs from `slot`.
|
|
82
|
+
*
|
|
83
|
+
* ⚠️ A cut manifest is often ALSO the record of the pipeline that generated
|
|
84
|
+
* the art, and that pipeline names parts after what they depict while a rig
|
|
85
|
+
* names them after the role they play. The rig's slot table has to stay
|
|
86
|
+
* single-valued — the runtime, the tooling and A26 all join on the emitted slot
|
|
87
|
+
* name, and a second alias for one slot is how a slot vanishes with no error.
|
|
88
|
+
* So the manifest carries the mapping and `slot` keeps meaning what its author
|
|
89
|
+
* meant. Absent = the two are the same name.
|
|
90
|
+
*/
|
|
91
|
+
rig_slot?: string;
|
|
92
|
+
draw_order: number;
|
|
93
|
+
/**
|
|
94
|
+
* Base plate only: the unmodified crop. Explicit `null` (with no `states`)
|
|
95
|
+
* means the manifest is recording a part this cut does NOT carry, and the
|
|
96
|
+
* compiler skips it and reports the absence.
|
|
97
|
+
*/
|
|
98
|
+
image?: string | null;
|
|
99
|
+
/** Top-left of the part window in crop pixels, y down. */
|
|
100
|
+
offset: [number, number];
|
|
101
|
+
/** Part window size in pixels. Absent on the base plate (= the crop size). */
|
|
102
|
+
size?: [number, number];
|
|
103
|
+
/** Which key of `state_machine` drives this slot. */
|
|
104
|
+
state_key?: string;
|
|
105
|
+
/** state name -> PNG path relative to the manifest, or null for "base pixels". */
|
|
106
|
+
states?: Record<string, string | null>;
|
|
107
|
+
/** Mask polygon in CROP pixels (y down). Required when `mesh` is present. */
|
|
108
|
+
polygon?: Array<[number, number]>;
|
|
109
|
+
/** Promote this part's attachments from regions to ring meshes. */
|
|
110
|
+
mesh?: FaceManifestMesh;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface FaceManifest {
|
|
114
|
+
schema: string;
|
|
115
|
+
crop: { x: number; y: number; w: number; h: number; resample: string };
|
|
116
|
+
base: string;
|
|
117
|
+
/** Overlay archetypes only; the joint archetype has no per-slot state list. */
|
|
118
|
+
state_machine?: Record<string, string[]>;
|
|
119
|
+
parts: FaceManifestPart[];
|
|
120
|
+
|
|
121
|
+
// -- articulated-cut fields ------------------------------------------------
|
|
122
|
+
/**
|
|
123
|
+
* Entry point in crop pixels, y down — the origin of the cut's axis frame.
|
|
124
|
+
*/
|
|
125
|
+
insertion?: [number, number];
|
|
126
|
+
/**
|
|
127
|
+
* ⭐ The one value a new cut of this archetype changes.
|
|
128
|
+
* `deg` is SCREEN degrees, y down, the same convention as `mesh.bias.axis_deg`;
|
|
129
|
+
* the compiler negates it into Spine's y-up CCW rotation. `unit` is the same
|
|
130
|
+
* direction as a vector and is cross-checked against `deg`, because a manifest
|
|
131
|
+
* that disagrees with itself is the cheapest bug to catch and the worst to
|
|
132
|
+
* debug later.
|
|
133
|
+
*/
|
|
134
|
+
axis?: { deg: number; unit: [number, number] };
|
|
135
|
+
/**
|
|
136
|
+
* One-way stroke amplitude in axis pixels, the extension the plate covers, and
|
|
137
|
+
* the DERIVED ceiling on inward travel.
|
|
138
|
+
*
|
|
139
|
+
* 🎯 `contact_depth` is the owner's rule of 2026-08-22 made mechanical: the
|
|
140
|
+
* swallow goes at most until the inserting mass touches the occluder. It is a
|
|
141
|
+
* MEASURED fact about two plates (rigc/tools/contact.ts), so it belongs in the
|
|
142
|
+
* manifest for exactly the reason `mesh.center` does — the compiler never
|
|
143
|
+
* re-measures art. Assertion A29 holds every animation to it.
|
|
144
|
+
*/
|
|
145
|
+
stroke?: {
|
|
146
|
+
amplitude?: number;
|
|
147
|
+
extension?: number;
|
|
148
|
+
contact_depth?: number | null;
|
|
149
|
+
/**
|
|
150
|
+
* 🎯 The second, independent ceiling on inward travel: the deepest insert at
|
|
151
|
+
* which the moving part's cap contour is still entirely inside the occluder's
|
|
152
|
+
* opaque footprint. Past it the cap is DRAWN where it should be swallowed.
|
|
153
|
+
*
|
|
154
|
+
* It is not a restatement of `contact_depth`. Contact asks when two masses
|
|
155
|
+
* collide; containment asks when the drawn flesh runs out of patch to hide
|
|
156
|
+
* behind — and a cut can have one without the other. The real tier-2 cut has
|
|
157
|
+
* exactly that shape: no contact ceiling at all, and a containment ceiling of
|
|
158
|
+
* 118px. Measured, like every other art fact in this file. Assertion A30.
|
|
159
|
+
*/
|
|
160
|
+
cap_containment_ceiling?: number | null;
|
|
161
|
+
};
|
|
162
|
+
/** ROI box, recorded for provenance; the compiler does not read it. */
|
|
163
|
+
roi?: { x: number; y: number; w: number; h: number };
|
|
164
|
+
/**
|
|
165
|
+
* Bone positions in crop pixels, y down: `[x, y]`, or `[x, y, facing_deg]`
|
|
166
|
+
* where the third element is a SCREEN-space facing angle that becomes the
|
|
167
|
+
* bone's setup rotation. A grip whose local +X points radially outward turns
|
|
168
|
+
* "expand the ring" into one shared translate key, which is the same trick the
|
|
169
|
+
* `axis` bone plays for the stroke.
|
|
170
|
+
*/
|
|
171
|
+
anchors?: Record<string, number[]>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The type every field of the cut manifest holds, shape by shape — what
|
|
176
|
+
* `refuseValuesOfTheWrongType` refuses a manifest value against (issue #890).
|
|
177
|
+
* The three interfaces above name three rows; the inline object types they
|
|
178
|
+
* declare (`crop`, `axis`, `stroke`, `roi`, `mesh.bias`) are rows named
|
|
179
|
+
* `<interface>.<field>`, and the selftest derives every row's keys and checked
|
|
180
|
+
* types from the declarations here.
|
|
181
|
+
*
|
|
182
|
+
* ⚠️ A manifest has no key scan, and this table does not add one: a manifest is
|
|
183
|
+
* often also the record of the pipeline that made the art and carries fields
|
|
184
|
+
* rigc does not read. A key outside a row is left alone, exactly as before; a
|
|
185
|
+
* key inside one must hold its type. Measured before the walk existed, 62 of 84
|
|
186
|
+
* wrong-typed plants on the three fixture manifests built green —
|
|
187
|
+
* `crop.h: "256"` among them — and 4 threw a TypeError from `node:path`.
|
|
188
|
+
*
|
|
189
|
+
* Unchecked by this walk: `mesh.kind` and `mesh.hull` (`enum`) — see
|
|
190
|
+
* `MANIFEST_ENUMS`, below; `crop` and the other nested shapes, `parts` and
|
|
191
|
+
* `mesh` (`object`) — the readers that walk them.
|
|
192
|
+
*/
|
|
193
|
+
export const MANIFEST_TYPES = {
|
|
194
|
+
FaceManifest: {
|
|
195
|
+
schema: 'string', crop: 'object', base: 'string', state_machine: 'map of string[]', parts: 'object[]',
|
|
196
|
+
insertion: 'number[]', axis: 'object', stroke: 'object', roi: 'object', anchors: 'map of number[]',
|
|
197
|
+
},
|
|
198
|
+
'FaceManifest.crop': { x: 'number', y: 'number', w: 'number', h: 'number', resample: 'string' },
|
|
199
|
+
'FaceManifest.axis': { deg: 'number', unit: 'number[]' },
|
|
200
|
+
'FaceManifest.stroke': {
|
|
201
|
+
amplitude: 'number', extension: 'number', contact_depth: 'number | null', cap_containment_ceiling: 'number | null',
|
|
202
|
+
},
|
|
203
|
+
'FaceManifest.roi': { x: 'number', y: 'number', w: 'number', h: 'number' },
|
|
204
|
+
FaceManifestPart: {
|
|
205
|
+
slot: 'string', rig_slot: 'string', draw_order: 'number', image: 'string | null', offset: 'number[]', size: 'number[]',
|
|
206
|
+
state_key: 'string', states: 'map of string | null', polygon: 'number[][]', mesh: 'object',
|
|
207
|
+
},
|
|
208
|
+
FaceManifestMesh: {
|
|
209
|
+
kind: 'enum', hull: 'enum', center: 'number[]', inner: 'number', control_bone: 'string', control_bones: 'string[]',
|
|
210
|
+
rows: 'number', chain: 'string[]', bias: 'object',
|
|
211
|
+
},
|
|
212
|
+
'FaceManifestMesh.bias': { axis_deg: 'number', ramp: 'number[]', note: 'string' },
|
|
213
|
+
} as const satisfies Record<string, SpecTypeRow>;
|
|
214
|
+
|
|
215
|
+
/** The two mesh generators a manifest part may name (`FaceManifestMesh.kind`); absent means `ring`. */
|
|
216
|
+
export const MANIFEST_MESH_KINDS = ['ring', 'ribbon'] as const satisfies ReadonlyArray<NonNullable<FaceManifestMesh['kind']>>;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Who refuses each `enum` row of `MANIFEST_TYPES` outside its set (issue #900);
|
|
220
|
+
* `SpecEnumTable` in [`keys.ts`](keys.ts) says what an entry means.
|
|
221
|
+
*
|
|
222
|
+
* 🚨 `mesh.kind` was listed as held by "the manifest mesh reader", and it was
|
|
223
|
+
* not held at all: that reader has three places that ask the kind, and they
|
|
224
|
+
* disagreed about a value outside the two. The mesh checks read `"foo"` as a
|
|
225
|
+
* ribbon, while the control-bone reader and the geometry builder read it as a
|
|
226
|
+
* ring — so a ring part was refused as *a ribbon mesh needs mesh.rows and a
|
|
227
|
+
* mesh.chain*, and a ribbon part as *a mesh with no control bone deforms
|
|
228
|
+
* nothing*, both naming a fault the part did not have. `hull` is held: the ring
|
|
229
|
+
* check refuses anything but `"polygon"` by name, and keeps that sentence.
|
|
230
|
+
*/
|
|
231
|
+
export const MANIFEST_ENUMS = {
|
|
232
|
+
FaceManifestMesh: {
|
|
233
|
+
kind: {
|
|
234
|
+
set: MANIFEST_MESH_KINDS,
|
|
235
|
+
readAs:
|
|
236
|
+
'anything else was read as a ribbon by the mesh checks and as a ring by the control-bone reader and the ' +
|
|
237
|
+
'geometry builder, so the refusal it got named a fault the part did not have',
|
|
238
|
+
},
|
|
239
|
+
hull: { owner: 'compileInto' },
|
|
240
|
+
},
|
|
241
|
+
} as const satisfies SpecEnumTable<typeof MANIFEST_TYPES>;
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Motion spec (spec: "rigc-motion/1")
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
/** Graph-view style normalised handles [hx1, hy1, hx2, hy2]. */
|
|
248
|
+
export type EasingHandles = [number, number, number, number];
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* One value per group member, keyed by member name — the map form of `MotionKey.v`.
|
|
252
|
+
*
|
|
253
|
+
* Each entry is exactly what `v` would be for that one member: `[x, y]` on a
|
|
254
|
+
* paired property, `[value]` on a single-axis one, `[r, g, b, a]` on `rgba`, an
|
|
255
|
+
* attachment name on `attachment`. The times, the easings and the key count stay
|
|
256
|
+
* shared, because those being shared is what makes a group a group.
|
|
257
|
+
*/
|
|
258
|
+
export type MotionMemberValues = Record<string, number[] | string | null>;
|
|
259
|
+
|
|
260
|
+
export interface MotionKey {
|
|
261
|
+
/** Time in seconds. */
|
|
262
|
+
t: number;
|
|
263
|
+
/**
|
|
264
|
+
* Value. Meaning depends on the track property:
|
|
265
|
+
* rgba -> [r, g, b, a] in 0..1
|
|
266
|
+
* attachment -> attachment name, or null for "show nothing"
|
|
267
|
+
* translate -> [x, y] in pixels, relative to the bone's setup position
|
|
268
|
+
* scale -> [x, y] as multipliers (1 = setup)
|
|
269
|
+
* rotate -> [degrees]
|
|
270
|
+
* inherit -> a mode name (`normal`, `onlyTranslation`,
|
|
271
|
+
* `noRotationOrReflection`, `noScale`, `noScaleOrReflection`);
|
|
272
|
+
* stepped by the format, so no `ease` and no `curve`
|
|
273
|
+
* mix -> [0..1] physics authority
|
|
274
|
+
* inertia / strength / damping / mass / wind / gravity
|
|
275
|
+
* -> [value]; the physics constraint's own setting, over time
|
|
276
|
+
* reset -> null; the key is the event
|
|
277
|
+
*
|
|
278
|
+
* ⭐ On a track that names a `group`, this may instead be a **map keyed by
|
|
279
|
+
* member name** (`MotionMemberValues`) — the six numbers of a head turn side
|
|
280
|
+
* by side rather than six tracks apart, which is the only arrangement in which
|
|
281
|
+
* a reader notices that one of them has the wrong sign (issue #295). A
|
|
282
|
+
* non-map `v` keeps meaning exactly what it means today: every member gets it.
|
|
283
|
+
*
|
|
284
|
+
* ⚠️ Absent only when the key states a `derive` instead. Every other timeline
|
|
285
|
+
* still refuses a key with no value, by name.
|
|
286
|
+
*/
|
|
287
|
+
v?: number[] | string | null | MotionMemberValues;
|
|
288
|
+
/**
|
|
289
|
+
* The model form of `v`: a named kind whose per-member values the compiler
|
|
290
|
+
* evaluates from stated parameters (`src/trackgen.ts`, AUTHORING §4.5.1).
|
|
291
|
+
*
|
|
292
|
+
* ⭐ A `v` map states six numbers; this states the one line of arithmetic that
|
|
293
|
+
* produced them plus the six **depths** that are the actual decisions. A key
|
|
294
|
+
* carries one or the other and never both — two answers to one question, the
|
|
295
|
+
* same refusal a `deform` key's `transform` has against a `vertices` run.
|
|
296
|
+
*/
|
|
297
|
+
derive?: TrackDerive;
|
|
298
|
+
/** Named easing from `easings`, or "stepped". Absent = linear. */
|
|
299
|
+
ease?: string;
|
|
300
|
+
/**
|
|
301
|
+
* Escape hatch: this key's bezier written out, as ABSOLUTE (time, value)
|
|
302
|
+
* control points — four numbers per value channel, in field order, which is
|
|
303
|
+
* exactly what the emitted JSON holds.
|
|
304
|
+
*
|
|
305
|
+
* ⭐ `ease` stays the recommended path and a key may carry one or the other,
|
|
306
|
+
* never both. A named easing says "this shape, wherever it is used", which is
|
|
307
|
+
* what makes a motion spec readable as intent; this says "these numbers", which
|
|
308
|
+
* is what a transcription of an editor export needs, because an export has a
|
|
309
|
+
* different shape per key per channel.
|
|
310
|
+
*
|
|
311
|
+
* ⚠️ Not the normalised graph-view handles `easings` takes. Those go through
|
|
312
|
+
* `bezierForChannel`; writing them here loads clean and plays a different
|
|
313
|
+
* curve.
|
|
314
|
+
*/
|
|
315
|
+
curve?: number[] | 'stepped';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Bone timelines the compiler emits. Channel counts live in the validator.
|
|
320
|
+
*
|
|
321
|
+
* The single-axis forms are not sugar for the paired ones: Spine keys them as
|
|
322
|
+
* separate timelines, and an export that used `translatex` alone is not
|
|
323
|
+
* reproduced by a `translate` whose y channel happens to be flat — the key
|
|
324
|
+
* counts differ, and so does what a runtime blends against.
|
|
325
|
+
*/
|
|
326
|
+
export type BoneProperty =
|
|
327
|
+
| 'translate'
|
|
328
|
+
| 'translatex'
|
|
329
|
+
| 'translatey'
|
|
330
|
+
| 'scale'
|
|
331
|
+
| 'scalex'
|
|
332
|
+
| 'scaley'
|
|
333
|
+
| 'shear'
|
|
334
|
+
| 'shearx'
|
|
335
|
+
| 'sheary'
|
|
336
|
+
| 'rotate'
|
|
337
|
+
| 'inherit';
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Physics timelines the compiler emits — all eight `SkeletonJson`'s physics
|
|
341
|
+
* branch reads, in the order it reads them (`SkeletonJson.js:1063-1094`).
|
|
342
|
+
*
|
|
343
|
+
* Six of them are one number that overrides the constraint's own setting for
|
|
344
|
+
* the length of an animation: `[inertia]`, `[strength]`, `[damping]`, `[mass]`,
|
|
345
|
+
* `[wind]`, `[gravity]`. `mix` is the constraint's authority and `reset`
|
|
346
|
+
* carries no value at all.
|
|
347
|
+
*
|
|
348
|
+
* ⚠️ A key that omits its value reads **0** on all six, and 1 on `mix` — the
|
|
349
|
+
* per-key default, which is NOT the constraint default (`inertia` 0.5,
|
|
350
|
+
* `strength` 100, `damping` 0.85, `mass` 1). rigc never omits a channel, so the
|
|
351
|
+
* distinction only bites a reader comparing an emitted file with an editor
|
|
352
|
+
* export; `PHYSICS_TRACKS` in `compile.ts` carries the argument.
|
|
353
|
+
*
|
|
354
|
+
* 🔒 **Four of them have a compile-time range** (issue #610): `mass` must be
|
|
355
|
+
* `> 0`, `damping` inside the closed `[0, 1]`, and `mix` and `strength` `0` or
|
|
356
|
+
* more. The bounds are `PHYSICS_POSE_RULES` in `src/timelines.ts`, and each row's
|
|
357
|
+
* `basis` says, per way out, whose they are (issue #798). Two are the
|
|
358
|
+
* runtime's arithmetic: a `mass` of 0 is an infinite `massInverse` and NaN from
|
|
359
|
+
* the first step, and a `damping` below 0 is a negative base under a fractional
|
|
360
|
+
* exponent at any `fps` where `60 / fps` is not whole. The other six are rigc's
|
|
361
|
+
* call — the rig runs, finitely, and runs wrongly: `mass` below 0 and
|
|
362
|
+
* `strength` below 0 run away, `damping` above 1 diverges until it overflows,
|
|
363
|
+
* `mix` below 0 is the jiggle inverted, and a setup `mix` or `strength` of 0 is
|
|
364
|
+
* a constraint muted or pulled back by nothing. `inertia`, `wind`, `gravity`
|
|
365
|
+
* and the top of `mix` are bounded nowhere, because the runtime documents
|
|
366
|
+
* nothing for the first three and documents `mix` as "a percentage (0+)" —
|
|
367
|
+
* which is also the text the bottom of `mix` rests on. The same four rows are what
|
|
368
|
+
* `A23_PHYSICS_CONSTRAINT_EFFECTIVE` judges a setup pose and a foreign file's
|
|
369
|
+
* timeline keys with, which is why they are not stated here as numbers.
|
|
370
|
+
*
|
|
371
|
+
* ⚠️ **Two of the four are wider on a KEY than at rest**, and `A23` holds a
|
|
372
|
+
* constraint's own tuning to the narrower one: a setup `mix` or `strength` of 0
|
|
373
|
+
* is a constraint that does nothing, while a key of 0 is an animation muting or
|
|
374
|
+
* releasing it for a span and the next key restores it (issues #610, #727).
|
|
375
|
+
*/
|
|
376
|
+
export type PhysicsProperty =
|
|
377
|
+
| 'inertia'
|
|
378
|
+
| 'strength'
|
|
379
|
+
| 'damping'
|
|
380
|
+
| 'mass'
|
|
381
|
+
| 'wind'
|
|
382
|
+
| 'gravity'
|
|
383
|
+
| 'mix'
|
|
384
|
+
| 'reset';
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Path constraint timelines. `mix` is three values in one key —
|
|
388
|
+
* `[mixRotate, mixX, mixY]` — because the format writes them as one timeline
|
|
389
|
+
* with three curve channels (`SkeletonJson.ts:1025-1056`).
|
|
390
|
+
*/
|
|
391
|
+
export type PathProperty = 'position' | 'spacing' | 'mix';
|
|
392
|
+
|
|
393
|
+
/** Slider timelines. `time` is the animation time the slider applies. */
|
|
394
|
+
export type SliderProperty = 'time' | 'mix';
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* One physics constraint.
|
|
398
|
+
*
|
|
399
|
+
* Structure, so it could argue for the manifest — but every field here is a
|
|
400
|
+
* tuning number for motion over time, so the starting-parameter table goes into
|
|
401
|
+
* the motion spec. It lives with the keys it competes against.
|
|
402
|
+
*
|
|
403
|
+
* ⚠️ The component fields (`x`/`y`/`rotate`/`scaleX`/`shearX`) all default to 0,
|
|
404
|
+
* which means a constraint that names none of them parses cleanly and does
|
|
405
|
+
* absolutely nothing. That is assertion A23.
|
|
406
|
+
*/
|
|
407
|
+
export interface MotionPhysics {
|
|
408
|
+
bone: string;
|
|
409
|
+
x?: number;
|
|
410
|
+
y?: number;
|
|
411
|
+
rotate?: number;
|
|
412
|
+
scaleX?: number;
|
|
413
|
+
shearX?: number;
|
|
414
|
+
inertia?: number;
|
|
415
|
+
strength?: number;
|
|
416
|
+
damping?: number;
|
|
417
|
+
mass?: number;
|
|
418
|
+
wind?: number;
|
|
419
|
+
gravity?: number;
|
|
420
|
+
mix?: number;
|
|
421
|
+
fps?: number;
|
|
422
|
+
limit?: number;
|
|
423
|
+
note?: string;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* One target, one property, a list of keys.
|
|
428
|
+
*
|
|
429
|
+
* ⭐ **The target field picks the family, not the property.** Three constraint
|
|
430
|
+
* families spell a timeline `group.<constraint>.<timeline>` and all three of them
|
|
431
|
+
* have a timeline called `mix`, so `property` alone cannot say which one a track
|
|
432
|
+
* means — `physics`, `path` and `slider` each name their own constraint, and a
|
|
433
|
+
* track that names none of them is a slot or bone track as before.
|
|
434
|
+
*/
|
|
435
|
+
export interface MotionTrack {
|
|
436
|
+
/** Target one slot... */
|
|
437
|
+
slot?: string;
|
|
438
|
+
/** ...or a named group of slots. */
|
|
439
|
+
group?: string;
|
|
440
|
+
/** ...or one bone, for the mesh tier: the control bone carries every key. */
|
|
441
|
+
bone?: string;
|
|
442
|
+
/** ...or one physics constraint, by name. */
|
|
443
|
+
physics?: string;
|
|
444
|
+
/** ...or one path constraint, by name. */
|
|
445
|
+
path?: string;
|
|
446
|
+
/** ...or one slider, by name. */
|
|
447
|
+
slider?: string;
|
|
448
|
+
property: 'rgba' | 'rgb' | 'alpha' | 'rgba2' | 'rgb2' | 'attachment' | BoneProperty | PhysicsProperty | PathProperty | SliderProperty;
|
|
449
|
+
/** Seconds added to every key time of this track. */
|
|
450
|
+
lag?: number;
|
|
451
|
+
/** Extra per-member delay inside a group, in member order. */
|
|
452
|
+
stagger?: number;
|
|
453
|
+
keys: MotionKey[];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* A track after its per-member values have been resolved **for one target**.
|
|
458
|
+
*
|
|
459
|
+
* ⭐ The type exists to make the resolution order an invariant rather than a
|
|
460
|
+
* convention: a `v` that is a map and a `v` that is a value mean different
|
|
461
|
+
* things, so nothing downstream of `resolveMemberTrack` is allowed to see the
|
|
462
|
+
* first. By the time a key reaches `compileValueTrack`, `v` means one thing —
|
|
463
|
+
* which is the same guarantee `evaluateDeformTransform`'s `setup` array carries,
|
|
464
|
+
* stated in the type system instead of in a comment.
|
|
465
|
+
*/
|
|
466
|
+
export interface MotionValueKey extends Omit<MotionKey, 'v' | 'derive'> {
|
|
467
|
+
v?: number[] | string | null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export interface MotionValueTrack extends Omit<MotionTrack, 'keys'> {
|
|
471
|
+
keys: MotionValueKey[];
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* One slot moved, at one draw-order key: `offset` positions later in the array.
|
|
476
|
+
*
|
|
477
|
+
* ⚠️ The offset is counted against the SETUP order, not against wherever the
|
|
478
|
+
* slot ended up at the previous key — `readDrawOrder` rebuilds the whole
|
|
479
|
+
* permutation from the setup array every time (SkeletonJson.ts:1336-1374). A key
|
|
480
|
+
* is a complete statement of the change, not an edit to the one before it.
|
|
481
|
+
*/
|
|
482
|
+
export interface MotionDrawOrderOffset {
|
|
483
|
+
slot: string;
|
|
484
|
+
/** How many places later this slot is drawn. Negative moves it earlier. */
|
|
485
|
+
offset: number;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* One key of the whole-animation draw-order timeline.
|
|
490
|
+
*
|
|
491
|
+
* A key with **no** `offsets` restores the setup draw order — that is the
|
|
492
|
+
* parser's own encoding (`readDrawOrder` returns null, and the timeline sets the
|
|
493
|
+
* setup array), and it is how an animation that has swapped two slots puts them
|
|
494
|
+
* back.
|
|
495
|
+
*/
|
|
496
|
+
export interface MotionDrawOrderKey {
|
|
497
|
+
/** Time in seconds. */
|
|
498
|
+
t: number;
|
|
499
|
+
offsets?: MotionDrawOrderOffset[];
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* One firing of a declared event, at one time.
|
|
504
|
+
*
|
|
505
|
+
* ⚠️ Like `drawOrder` and unlike a `track`, this timeline names **no target**:
|
|
506
|
+
* 4.3 writes it as `animations.<a>.events` beside `bones` and `slots`
|
|
507
|
+
* (SPEC_COVERAGE part 1-8), and there is one per animation. The `name` picks
|
|
508
|
+
* an entry out of the rig spec's `events` table; the optional payload fields
|
|
509
|
+
* override that entry's defaults for this firing only.
|
|
510
|
+
*
|
|
511
|
+
* A key with no `int`/`float`/`string` inherits the event's setup payload
|
|
512
|
+
* (`:1250-1252`) — which is what the editor writes, and why `{ "t": 0.5,
|
|
513
|
+
* "name": "footstep" }` is the common shape.
|
|
514
|
+
*/
|
|
515
|
+
export interface MotionEventKey {
|
|
516
|
+
/** Time in seconds. */
|
|
517
|
+
t: number;
|
|
518
|
+
/** An event the rig spec declares. A miss throws in the parser; rigc refuses it. */
|
|
519
|
+
name: string;
|
|
520
|
+
/** Payload overrides for this firing. Omit to inherit the event's defaults. */
|
|
521
|
+
int?: number;
|
|
522
|
+
float?: number;
|
|
523
|
+
string?: string;
|
|
524
|
+
/** Read only when the declared event carries an `audio` path — see `RigEvent`. */
|
|
525
|
+
volume?: number;
|
|
526
|
+
balance?: number;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* One key of an IK constraint's timeline (`animations.<a>.ik.<constraint>`).
|
|
531
|
+
*
|
|
532
|
+
* ⚠️ Every field is **optional and absolute**, and that pairing is the trap. The
|
|
533
|
+
* parser reads each one with its own default per key
|
|
534
|
+
* (`SkeletonJson.ts`: `mix` 1, `softness` 0, `bendPositive` true, `compress`
|
|
535
|
+
* false, `stretch` false) — so a key that omits `softness` does not hold the
|
|
536
|
+
* previous key's softness, it snaps to 0. rigc therefore refuses a track whose
|
|
537
|
+
* keys do not all name the SAME set of fields: state the value on every key, or
|
|
538
|
+
* on none of them.
|
|
539
|
+
*
|
|
540
|
+
* `mix` and `softness` are the timeline's two curve channels, in that order.
|
|
541
|
+
* The three booleans are stepped by nature — nothing interpolates them.
|
|
542
|
+
*/
|
|
543
|
+
export interface MotionIkKey {
|
|
544
|
+
/** Time in seconds. */
|
|
545
|
+
t: number;
|
|
546
|
+
/** 0..1: how much of the constrained rotation is applied. Parser default 1. */
|
|
547
|
+
mix?: number;
|
|
548
|
+
/** Distance from full reach at which the bones stop straightening. Default 0. */
|
|
549
|
+
softness?: number;
|
|
550
|
+
/** Two-bone IK bend direction. Default true. */
|
|
551
|
+
bendPositive?: boolean;
|
|
552
|
+
/** One-bone IK: scale the bone down to reach a close target. Default false. */
|
|
553
|
+
compress?: boolean;
|
|
554
|
+
/** Scale the bone up to reach a far target. Default false. */
|
|
555
|
+
stretch?: boolean;
|
|
556
|
+
/** Named easing from `easings`, or "stepped". Absent = linear. */
|
|
557
|
+
ease?: string;
|
|
558
|
+
/** The raw form: 4 absolute (time, value) numbers per channel — 8 here. */
|
|
559
|
+
curve?: number[] | 'stepped';
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* One IK constraint keyed over time.
|
|
564
|
+
*
|
|
565
|
+
* 4.3 writes this as `animations.<a>.ik.<constraint>` — **one unnamed timeline
|
|
566
|
+
* per constraint**, so the constraint name is the only target there is and the
|
|
567
|
+
* group carries no timeline name at all.
|
|
568
|
+
*/
|
|
569
|
+
export interface MotionIkTrack {
|
|
570
|
+
/** An `ik` constraint the rig spec declares. */
|
|
571
|
+
constraint: string;
|
|
572
|
+
keys: MotionIkKey[];
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* One key of a transform constraint's timeline
|
|
577
|
+
* (`animations.<a>.transform.<constraint>`).
|
|
578
|
+
*
|
|
579
|
+
* Six mixes, six curve channels, in the order written here — which is the order
|
|
580
|
+
* the parser reads them and therefore the order a curve array concatenates.
|
|
581
|
+
* The same absent-means-default rule as `MotionIkKey` applies, with one extra
|
|
582
|
+
* quirk: `mixY` defaults to **this key's own `mixX`**, not to 1.
|
|
583
|
+
*/
|
|
584
|
+
export interface MotionTransformKey {
|
|
585
|
+
/** Time in seconds. */
|
|
586
|
+
t: number;
|
|
587
|
+
/** Parser default 1. */
|
|
588
|
+
mixRotate?: number;
|
|
589
|
+
/** Parser default 1. */
|
|
590
|
+
mixX?: number;
|
|
591
|
+
/** Parser default: the same key's `mixX`. */
|
|
592
|
+
mixY?: number;
|
|
593
|
+
/** Parser default 1. */
|
|
594
|
+
mixScaleX?: number;
|
|
595
|
+
/** Parser default 1. */
|
|
596
|
+
mixScaleY?: number;
|
|
597
|
+
/** Parser default 1. */
|
|
598
|
+
mixShearY?: number;
|
|
599
|
+
ease?: string;
|
|
600
|
+
/** 4 absolute (time, value) numbers per channel — 24 here. */
|
|
601
|
+
curve?: number[] | 'stepped';
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** One transform constraint keyed over time. Same shape rule as `MotionIkTrack`. */
|
|
605
|
+
export interface MotionTransformTrack {
|
|
606
|
+
/** A `transform` constraint the rig spec declares. */
|
|
607
|
+
constraint: string;
|
|
608
|
+
keys: MotionTransformKey[];
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* One key of a deform timeline
|
|
613
|
+
* (`animations.<a>.attachments.<skin>.<slot>.<attachment>.deform`).
|
|
614
|
+
*
|
|
615
|
+
* 🚨 This is the only key in the format whose meaning depends on the object it
|
|
616
|
+
* is attached to. The parser builds a zero-filled array as long as the
|
|
617
|
+
* attachment's own deform array, copies this key's `vertices` into it starting at
|
|
618
|
+
* `offset`, and leaves the rest alone — so a key is a **sparse edit of the setup
|
|
619
|
+
* geometry**, and both the length of that array and the meaning of an index into
|
|
620
|
+
* it come from the attachment (`SkeletonJson.ts`, the `deform` branch):
|
|
621
|
+
*
|
|
622
|
+
* - **unweighted** attachment — the array is one `x, y` pair per VERTEX, and
|
|
623
|
+
* the parser adds the setup position back on load. The numbers here are
|
|
624
|
+
* therefore offsets from setup, in the slot bone's space.
|
|
625
|
+
* - **weighted** attachment — the array is one `x, y` pair per BONE INFLUENCE
|
|
626
|
+
* (`vertices.length / 3 * 2`), each in the bind space of that influence's
|
|
627
|
+
* bone. A vertex with three bones on it occupies three pairs.
|
|
628
|
+
*
|
|
629
|
+
* `offset` is an index into that array and works for both. `fromVertex` is
|
|
630
|
+
* rigc's ergonomic form — a VERTEX index, which rigc translates — and it is
|
|
631
|
+
* accepted only where the translation is honest: always on an unweighted
|
|
632
|
+
* attachment, and on a weighted one only when every vertex the run covers has
|
|
633
|
+
* exactly one bone influence. Anything else is refused by name rather than
|
|
634
|
+
* emitted as a plausible-looking lie.
|
|
635
|
+
*/
|
|
636
|
+
export interface MotionDeformKey {
|
|
637
|
+
/** Time in seconds. */
|
|
638
|
+
t: number;
|
|
639
|
+
/**
|
|
640
|
+
* Where the run starts in the attachment's own deform array. Default 0.
|
|
641
|
+
*
|
|
642
|
+
* Any index the array holds, **odd ones included** (issue #576): the parser
|
|
643
|
+
* copies at this index and does no pair arithmetic, so an odd start is what an
|
|
644
|
+
* editor writes when it trims the leading numbers off a delta run.
|
|
645
|
+
*/
|
|
646
|
+
offset?: number;
|
|
647
|
+
/** The same start, given as a vertex index. Never together with `offset`. */
|
|
648
|
+
fromVertex?: number;
|
|
649
|
+
/**
|
|
650
|
+
* The run: consecutive numbers written into the deform array from `offset` on,
|
|
651
|
+
* `x, y` per array slot. Absent or `null` is the parser's own encoding for
|
|
652
|
+
* "back to the setup pose" — the key with no edit.
|
|
653
|
+
*
|
|
654
|
+
* ⚠️ **An ODD count is legal and means something** (issue #576). Both readers
|
|
655
|
+
* copy the run verbatim — `Utils.arrayCopy(vertices, 0, deform, offset,
|
|
656
|
+
* vertices.length)` in `SkeletonJson`, a `for (let v = start; v < end; v++)`
|
|
657
|
+
* fill in `SkeletonBinary` — so a run of three numbers moves one vertex in x
|
|
658
|
+
* and y and the next in x alone, leaving that y at its setup value. Padding a
|
|
659
|
+
* `0` to even it out is a different animation whenever that setup y is
|
|
660
|
+
* non-zero, which is why the even-length rule that stood here could not be kept
|
|
661
|
+
* as a convenience: it left one production export with no spelling in this
|
|
662
|
+
* spec at all.
|
|
663
|
+
*/
|
|
664
|
+
vertices?: number[] | null;
|
|
665
|
+
/**
|
|
666
|
+
* The run stated as a **model** instead, which the compiler evaluates over the
|
|
667
|
+
* attachment's own setup geometry (`src/deformgen.ts`, issue #294).
|
|
668
|
+
*
|
|
669
|
+
* ⭐ The same move `generator` already made for geometry: a table of numbers is
|
|
670
|
+
* the wrong way to say a deformation model. `gallery/portrait`'s held 12° yaw
|
|
671
|
+
* is 160 hand-transcribed floats of one closed form, and none of them is a
|
|
672
|
+
* judgement — so the spec states the model and the compiler states the
|
|
673
|
+
* numbers.
|
|
674
|
+
*
|
|
675
|
+
* Never together with `vertices`, for the same reason `generator` and authored
|
|
676
|
+
* geometry cannot sit on one attachment, and never with `offset` or
|
|
677
|
+
* `fromVertex`: a transform covers **every** vertex, because a model applied
|
|
678
|
+
* to part of an attachment leaves a step at the end of its run.
|
|
679
|
+
*/
|
|
680
|
+
transform?: DeformTransform;
|
|
681
|
+
ease?: string;
|
|
682
|
+
/**
|
|
683
|
+
* One channel, and it interpolates the deform FRACTION from 0 to 1 rather than
|
|
684
|
+
* any value in `vertices` (`readCurve(..., 0, 1, 1)`). So a raw curve is 4
|
|
685
|
+
* numbers whose value axis runs 0..1.
|
|
686
|
+
*/
|
|
687
|
+
curve?: number[] | 'stepped';
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/** One attachment's geometry keyed over time. */
|
|
691
|
+
export interface MotionDeformTrack {
|
|
692
|
+
/** The skin the attachment lives in. Absent = `"default"`. */
|
|
693
|
+
skin?: string;
|
|
694
|
+
slot: string;
|
|
695
|
+
/** The attachment's placeholder name inside that skin and slot. */
|
|
696
|
+
attachment: string;
|
|
697
|
+
keys: MotionDeformKey[];
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* One key of a sequence timeline
|
|
702
|
+
* (`animations.<a>.attachments.<skin>.<slot>.<attachment>.sequence`), which
|
|
703
|
+
* picks the frame of an attachment's `sequence` block (see `RigSequence`).
|
|
704
|
+
*
|
|
705
|
+
* From the key's time on, the frame shown is `index` advanced by one every
|
|
706
|
+
* `delay` seconds and folded back into the series by `mode` —
|
|
707
|
+
* `SequenceTimeline.applyToSlot` (`Animation.js`):
|
|
708
|
+
* `index + floor((time - keyTime) / delay + 0.00001)`, then per mode (`hold`
|
|
709
|
+
* never advances; `once` stops on the last frame; `loop` wraps; `pingpong`
|
|
710
|
+
* bounces; the three `Reverse` modes run from the last frame down). Before the
|
|
711
|
+
* first key the frame is the attachment's `setup`.
|
|
712
|
+
*
|
|
713
|
+
* ⚠️ Three silences the compiler refuses, every one measured on spine-core
|
|
714
|
+
* 4.3.13: a `mode` outside the seven loads as `hold`; a `delay` of 0 under an
|
|
715
|
+
* advancing mode divides by zero and `Infinity | 0` is 0, so the frame never
|
|
716
|
+
* moves; an `index` at or past the series' `count` is clamped to the last frame
|
|
717
|
+
* and a fractional one is truncated (`1.5` showed frame 1).
|
|
718
|
+
*
|
|
719
|
+
* 🔑 Each field is optional in the FORMAT and none is invented here: `mode`
|
|
720
|
+
* defaults to `"hold"` and `index` to 0 in the parser, and `delay` defaults to
|
|
721
|
+
* the PREVIOUS key's delay (0 on the first) — so a key that omits it keeps its
|
|
722
|
+
* neighbour's rate, and the compiler emits exactly the fields the spec states.
|
|
723
|
+
*/
|
|
724
|
+
export interface MotionSequenceKey {
|
|
725
|
+
/** Time in seconds. */
|
|
726
|
+
t: number;
|
|
727
|
+
/** One of the seven `SEQUENCE_MODES`. Parser default `"hold"`. */
|
|
728
|
+
mode?: string;
|
|
729
|
+
/** The frame this key starts on, 0-based. Parser default 0. */
|
|
730
|
+
index?: number;
|
|
731
|
+
/** Seconds per frame. Parser default: the previous key's, 0 on the first. */
|
|
732
|
+
delay?: number;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/** One attachment's frame keyed over time — the `deform` family's triple, a sequence's keys. */
|
|
736
|
+
export interface MotionSequenceTrack {
|
|
737
|
+
/** The skin the attachment lives in. Absent = `"default"`. */
|
|
738
|
+
skin?: string;
|
|
739
|
+
slot: string;
|
|
740
|
+
/** The attachment's placeholder name inside that skin and slot. */
|
|
741
|
+
attachment: string;
|
|
742
|
+
keys: MotionSequenceKey[];
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
export interface MotionAnimation {
|
|
746
|
+
/** Declared, then verified against the compiled result (rule 4). */
|
|
747
|
+
duration: number;
|
|
748
|
+
/**
|
|
749
|
+
* Player hint only; not expressible in skeleton JSON, and therefore optional.
|
|
750
|
+
*
|
|
751
|
+
* ⚠️ It was declared required until issue #307 put a parser in front of this
|
|
752
|
+
* type and the corpus disagreed: 20 of the 37 motion specs in the repository
|
|
753
|
+
* name no `loop` at all. Nothing in the emitted artifact depends on it, so a
|
|
754
|
+
* required-field refusal here would have refused most of the benchmark corpus
|
|
755
|
+
* over a field the compiler never reads.
|
|
756
|
+
*/
|
|
757
|
+
loop?: boolean;
|
|
758
|
+
note?: string;
|
|
759
|
+
tracks: MotionTrack[];
|
|
760
|
+
/**
|
|
761
|
+
* IK constraint timelines, one entry per constraint.
|
|
762
|
+
*
|
|
763
|
+
* ⭐ Not a `track`, and the reason is the key rather than the target. A track's
|
|
764
|
+
* key is one `v` — an array, a name, or nothing — and these three families each
|
|
765
|
+
* carry a shape of their own: five named fields for IK, six for a transform
|
|
766
|
+
* constraint, and for a deform a sparse run whose meaning depends on the
|
|
767
|
+
* attachment. Folding them into `MotionKey` would make `v` mean four different
|
|
768
|
+
* things depending on `property`, and the type would stop documenting any of
|
|
769
|
+
* them. So they sit beside `tracks`, where 4.3 also writes them
|
|
770
|
+
* (`animations.<a>.ik`, `.transform`, `.attachments`).
|
|
771
|
+
*/
|
|
772
|
+
ik?: MotionIkTrack[];
|
|
773
|
+
/** Transform constraint timelines, one entry per constraint. */
|
|
774
|
+
transform?: MotionTransformTrack[];
|
|
775
|
+
/** Deform timelines, one entry per skin/slot/attachment triple. */
|
|
776
|
+
deform?: MotionDeformTrack[];
|
|
777
|
+
/**
|
|
778
|
+
* Sequence timelines, one entry per skin/slot/attachment triple — the other
|
|
779
|
+
* of the two timelines an attachment carries, beside `deform` for the same
|
|
780
|
+
* reason: a key of three named fields aimed at an attachment rather than at a
|
|
781
|
+
* slot.
|
|
782
|
+
*/
|
|
783
|
+
sequence?: MotionSequenceTrack[];
|
|
784
|
+
/**
|
|
785
|
+
* The draw-order timeline. **One per animation, and it names no target** —
|
|
786
|
+
* which is why it is not a `track`: 4.3 writes it as `animations.<a>.drawOrder`
|
|
787
|
+
* beside `bones` and `slots`, not inside either (SPEC_COVERAGE part 1-8).
|
|
788
|
+
*
|
|
789
|
+
* Draw order is the one thing about a slot that the slots array already
|
|
790
|
+
* states (rule R4), so this timeline is the only way to say it changes over
|
|
791
|
+
* time. First needed at ladder rung 5.
|
|
792
|
+
*/
|
|
793
|
+
drawOrder?: MotionDrawOrderKey[];
|
|
794
|
+
/**
|
|
795
|
+
* The event timeline. One per animation, names no target, and for the same
|
|
796
|
+
* reason `drawOrder` is not a `track`. First needed at the spineboy rung.
|
|
797
|
+
*/
|
|
798
|
+
events?: MotionEventKey[];
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Setup pose per slot. Declared, never inferred — rule 5. It decides which of
|
|
803
|
+
* the two overlay mechanisms a slot uses:
|
|
804
|
+
* - an attachment + alpha 0 => the lid tier, driven by rgba timelines;
|
|
805
|
+
* - attachment null => the swap tier, driven by attachment timelines
|
|
806
|
+
* (null = the untouched base pixels show).
|
|
807
|
+
*/
|
|
808
|
+
export interface MotionSetupSlot {
|
|
809
|
+
attachment?: string | null;
|
|
810
|
+
/** [r, g, b, a] in 0..1. Omit for opaque white. */
|
|
811
|
+
color?: [number, number, number, number];
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
export interface MotionSpec {
|
|
815
|
+
spec: 'rigc-motion/1';
|
|
816
|
+
/**
|
|
817
|
+
* The rig this spec was authored against — it must equal the rig spec's
|
|
818
|
+
* `name`, and a mismatch is a compile error rather than a silent pairing.
|
|
819
|
+
*
|
|
820
|
+
* It named a hard-coded table until 2026-08-22; now it names a file's own
|
|
821
|
+
* name, and the file's path comes from the cuts table. The check is kept
|
|
822
|
+
* because the keys in here are aimed at bones by NAME: pair the spec with
|
|
823
|
+
* another rig whose names happen to overlap and every one of them lands on
|
|
824
|
+
* something that means something else.
|
|
825
|
+
*/
|
|
826
|
+
archetype: string;
|
|
827
|
+
cut: string;
|
|
828
|
+
note?: string;
|
|
829
|
+
easings: Record<string, EasingHandles>;
|
|
830
|
+
groups?: Record<string, string[]>;
|
|
831
|
+
setup?: Record<string, MotionSetupSlot>;
|
|
832
|
+
/** Physics constraints by name. Emitted into the 4.3 `constraints` array. */
|
|
833
|
+
physics?: Record<string, MotionPhysics>;
|
|
834
|
+
animations: Record<string, MotionAnimation>;
|
|
835
|
+
/** Player-side AnimationStateData config; not emitted into skeleton JSON. */
|
|
836
|
+
mix?: MotionMix;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* The player-side mix table — a default crossfade and the pairs that override
|
|
841
|
+
* it. Never emitted, which is why nothing had ever looked at it before issue
|
|
842
|
+
* #307's parse.
|
|
843
|
+
*
|
|
844
|
+
* ⭐ Named rather than inline so that `MOTION_KEYS` can pair a key set with it:
|
|
845
|
+
* `CUR17` resolves each set against an interface of the same name, and an
|
|
846
|
+
* anonymous shape is one the pairing cannot reach.
|
|
847
|
+
*/
|
|
848
|
+
export interface MotionMix {
|
|
849
|
+
default: number;
|
|
850
|
+
pairs?: Array<[string, string, number]>;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// ---------------------------------------------------------------------------
|
|
854
|
+
// Emitted Spine 4.3 skeleton JSON
|
|
855
|
+
// ---------------------------------------------------------------------------
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Field order here is the EDITOR's, for every field its exports write — the
|
|
859
|
+
* order `src/keyorder.ts`'s `EDITOR_KEY_ORDER` states per kind and the emitter
|
|
860
|
+
* writes since issue #716 (`length, rotation, x, y` on a bone, `x, y, …, width,
|
|
861
|
+
* height` on a region, `type` before `name` on a constraint). `CUR84` in
|
|
862
|
+
* `selftest.ts` holds each interface below to its row, so this is a checked
|
|
863
|
+
* claim rather than a description. A field no export writes (`shearX`, a
|
|
864
|
+
* region's `name` and `path`) sits where it reads best: the table leaves such a
|
|
865
|
+
* key at the position its constructor gives it.
|
|
866
|
+
*
|
|
867
|
+
* ⚠️ This comment said the opposite until #716 — *"rigc's, not the editor's …
|
|
868
|
+
* changing it would be a byte-level diff that says nothing"*. It says something:
|
|
869
|
+
* a rebuild of an editor export is the export only if it is the same text, and
|
|
870
|
+
* 269 objects over the twelve exports under `examples/` were not.
|
|
871
|
+
*
|
|
872
|
+
* A field is present exactly when the rig spec declared it; see `src/rig.ts`.
|
|
873
|
+
*/
|
|
874
|
+
export interface SpineBone {
|
|
875
|
+
name: string;
|
|
876
|
+
parent?: string;
|
|
877
|
+
length?: number;
|
|
878
|
+
/** Spine degrees, CCW in a y-up world. */
|
|
879
|
+
rotation?: number;
|
|
880
|
+
x?: number;
|
|
881
|
+
y?: number;
|
|
882
|
+
scaleX?: number;
|
|
883
|
+
scaleY?: number;
|
|
884
|
+
shearX?: number;
|
|
885
|
+
shearY?: number;
|
|
886
|
+
/** 4.2+ name. 4.0/4.1's `transform` still loads and is silently ignored — A02. */
|
|
887
|
+
inherit?: string;
|
|
888
|
+
skin?: boolean;
|
|
889
|
+
color?: string;
|
|
890
|
+
/** Editor-only affordance, read at `SkeletonJson.ts:121-126`. */
|
|
891
|
+
icon?: string;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
export interface SpineSlot {
|
|
895
|
+
name: string;
|
|
896
|
+
bone: string;
|
|
897
|
+
color?: string;
|
|
898
|
+
attachment?: string;
|
|
899
|
+
dark?: string;
|
|
900
|
+
blend?: string;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* The attachment's own name, as distinct from the placeholder it is filed under.
|
|
905
|
+
*
|
|
906
|
+
* `readAttachment` reads `const name = getValue(map, "name", placeholder)`
|
|
907
|
+
* (`SkeletonJson.ts:526`), so an absent field means "the placeholder is also the
|
|
908
|
+
* name". Nothing in the format resolves by it — skins, setup attachments,
|
|
909
|
+
* attachment and deform timelines and a linked mesh's `source` are keyed by the
|
|
910
|
+
* placeholder — so several skins' entries under one placeholder may carry one
|
|
911
|
+
* name, which is what the editor itself exports (issue #796).
|
|
912
|
+
*
|
|
913
|
+
* 🚨 It moves a second field's default with it. For the three types that carry
|
|
914
|
+
* texture art, `path` defaults to **`name`**, not to the placeholder
|
|
915
|
+
* (`:529`, `:559`), so an attachment given a name and no path resolves the region
|
|
916
|
+
* that name spells. rigc emits it exactly when the rig spec states it
|
|
917
|
+
* (`RigAttachmentName` in `rig.ts`); from #541 to #796 it composed
|
|
918
|
+
* `<skin>/<placeholder>` for a placeholder several skins fill, and that is gone.
|
|
919
|
+
*
|
|
920
|
+
* ⚠️ And the **`default` skin may never be one of the skins sharing a
|
|
921
|
+
* placeholder** — a fact about the editor rather than about the format (issue
|
|
922
|
+
* #567, Spine 4.3.26, round trips 7 and 8), and a `CompileError` rather than a
|
|
923
|
+
* spelling. The editor's named skins hold *skin placeholders*, a key holding a
|
|
924
|
+
* named attachment, and come back untouched. Its default skin holds no
|
|
925
|
+
* placeholders: an attachment there hangs on the slot and is known by its name
|
|
926
|
+
* alone. So writing a name there gets it re-keyed by that name on export and
|
|
927
|
+
* the slot's setup `attachment` stops resolving (trip 7), and NOT writing one
|
|
928
|
+
* makes that attachment's name collide with the named skins' placeholder of the
|
|
929
|
+
* same name, which the editor refuses at import (trip 8). Both spellings are
|
|
930
|
+
* measured, so there is no third; `refuseDefaultSkinContest` in `compile.ts` is
|
|
931
|
+
* where that lives.
|
|
932
|
+
*/
|
|
933
|
+
export interface SpineRegionAttachment {
|
|
934
|
+
name?: string;
|
|
935
|
+
path?: string;
|
|
936
|
+
x?: number;
|
|
937
|
+
y?: number;
|
|
938
|
+
scaleX?: number;
|
|
939
|
+
scaleY?: number;
|
|
940
|
+
/**
|
|
941
|
+
* Cancels the bone's world rotation so a plate authored in screen space stays
|
|
942
|
+
* screen-upright under a rotated bone. Without it every slot hanging off the
|
|
943
|
+
* `axis` bone would render tilted by the axis angle.
|
|
944
|
+
*/
|
|
945
|
+
rotation?: number;
|
|
946
|
+
/** Required. Omitting these yields NaN with no error. */
|
|
947
|
+
width: number;
|
|
948
|
+
height: number;
|
|
949
|
+
color?: string;
|
|
950
|
+
sequence?: SpineSequence;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/** `readSequence`'s four fields, emitted as the spec stated them (`RigSequence`). */
|
|
954
|
+
export interface SpineSequence {
|
|
955
|
+
count: number;
|
|
956
|
+
start?: number;
|
|
957
|
+
digits?: number;
|
|
958
|
+
setup?: number;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Weighted mesh. `triangles` and `uvs` are not optional in practice: a missing
|
|
963
|
+
* `triangles` loads as `undefined` and `uvs` is
|
|
964
|
+
* what decides `worldVerticesLength`.
|
|
965
|
+
*/
|
|
966
|
+
export interface SpineMeshAttachment {
|
|
967
|
+
type: 'mesh';
|
|
968
|
+
/** See `SpineRegionAttachment.name` — and it takes `path` with it. */
|
|
969
|
+
name?: string;
|
|
970
|
+
/** `path` and `color` are written right after `type` — `compile.ts`'s `meshTextureKeys` says where that is measured. */
|
|
971
|
+
path?: string;
|
|
972
|
+
color?: string;
|
|
973
|
+
uvs: number[];
|
|
974
|
+
triangles: number[];
|
|
975
|
+
/** Weighted encoding: boneCount, (boneIndex, bindX, bindY, weight)*n, repeated. */
|
|
976
|
+
vertices: number[];
|
|
977
|
+
/**
|
|
978
|
+
* Hull vertex count — the outline polygon is the first `hull` vertices, in
|
|
979
|
+
* order. The loader stores this doubled, and the binary reader derives the
|
|
980
|
+
* triangle count from it, so it is always the count the triangles state
|
|
981
|
+
* (`traceOutline` in mesh.ts) and never 0.
|
|
982
|
+
*/
|
|
983
|
+
hull: number;
|
|
984
|
+
/**
|
|
985
|
+
* Nonessential edge list the editor draws: vertex index pairs, each index
|
|
986
|
+
* TIMES TWO (`meshEdges` in mesh.ts). Always written — authored edges are
|
|
987
|
+
* carried through, every other mesh gets its triangle edges — because a mesh
|
|
988
|
+
* without one imports with its interior edges reported lost.
|
|
989
|
+
*/
|
|
990
|
+
edges: number[];
|
|
991
|
+
/** Nonessential, but they make the mesh budget assertions readable. */
|
|
992
|
+
width: number;
|
|
993
|
+
height: number;
|
|
994
|
+
sequence?: SpineSequence;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* A mesh that borrows another mesh's geometry (`RigLinkedMeshAttachment`).
|
|
999
|
+
*
|
|
1000
|
+
* Only the keys the parser reads, and only where they differ from its defaults:
|
|
1001
|
+
* `slot` defaults to the link's own slot, `skin` to the default skin and
|
|
1002
|
+
* `timelines` to true (`SkeletonJson.js:571-581`), so writing one at its default
|
|
1003
|
+
* would be a byte the editor's own export does not carry. `width`/`height` are
|
|
1004
|
+
* emitted for the editor and overwritten by the source's at load
|
|
1005
|
+
* (`MeshAttachment.setSourceMesh`), which is why nothing reads them back.
|
|
1006
|
+
*/
|
|
1007
|
+
export interface SpineLinkedMeshAttachment {
|
|
1008
|
+
type: 'linkedmesh';
|
|
1009
|
+
/** See `SpineRegionAttachment.name` — and it takes `path` with it. */
|
|
1010
|
+
name?: string;
|
|
1011
|
+
path?: string;
|
|
1012
|
+
source: string;
|
|
1013
|
+
slot?: string;
|
|
1014
|
+
skin?: string;
|
|
1015
|
+
timelines?: boolean;
|
|
1016
|
+
width: number;
|
|
1017
|
+
height: number;
|
|
1018
|
+
color?: string;
|
|
1019
|
+
sequence?: SpineSequence;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* The two vertex-only attachments: a polygon and nothing else.
|
|
1024
|
+
*
|
|
1025
|
+
* `vertexCount` is not optional the way a mesh's is absent-by-design: the parser
|
|
1026
|
+
* reads `map.vertexCount << 1`, so an omission is `0` and `readVertices` decodes
|
|
1027
|
+
* the coordinate array as a weight run and stores nothing.
|
|
1028
|
+
*/
|
|
1029
|
+
export interface SpineBoundingBoxAttachment {
|
|
1030
|
+
type: 'boundingbox';
|
|
1031
|
+
/** See `SpineRegionAttachment.name`. No `path`: this type reads none. */
|
|
1032
|
+
name?: string;
|
|
1033
|
+
vertexCount: number;
|
|
1034
|
+
/** Unweighted x/y pairs, or the weighted run — same encoding as a mesh's. */
|
|
1035
|
+
vertices: number[];
|
|
1036
|
+
color?: string;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
export interface SpineClippingAttachment {
|
|
1040
|
+
type: 'clipping';
|
|
1041
|
+
/** See `SpineRegionAttachment.name`. No `path`: this type reads none. */
|
|
1042
|
+
name?: string;
|
|
1043
|
+
/** The last slot the clip applies to. Absent = to the bottom of the order. */
|
|
1044
|
+
end?: string;
|
|
1045
|
+
convex?: boolean;
|
|
1046
|
+
inverse?: boolean;
|
|
1047
|
+
vertexCount: number;
|
|
1048
|
+
vertices: number[];
|
|
1049
|
+
color?: string;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* A composite cubic Bezier, for a path constraint to slide bones along.
|
|
1054
|
+
*
|
|
1055
|
+
* `lengths` is the cumulative length at the end of each curve in the setup pose,
|
|
1056
|
+
* measured **the way `PathConstraint` measures it** — a four-sample forward
|
|
1057
|
+
* difference per curve (`PathConstraint.js:301-320`), which is also what the
|
|
1058
|
+
* Spine editor exports and which reads about **0.5 % below the true arc**.
|
|
1059
|
+
* `vertexCount / 3` entries on both shapes — one per curve on a closed path, and
|
|
1060
|
+
* one more than the curves on an open one, the wrap-around curve's cumulative,
|
|
1061
|
+
* which nothing reads (below). It has no parser default and the parser
|
|
1062
|
+
* dereferences `map.lengths.length` unconditionally, so an absent array is one of
|
|
1063
|
+
* the format's few loud failures. Since issue #804 rigc emits a stated array as
|
|
1064
|
+
* stated and measures only an omitted one — see `buildRigPath`.
|
|
1065
|
+
*
|
|
1066
|
+
* ⚠️ That sentence read *"the cumulative **arc** length"* until issue #560, and
|
|
1067
|
+
* the word was load-bearing in the wrong direction: this is not an arc length,
|
|
1068
|
+
* and no refinement of the integral converges on it. It is the number the
|
|
1069
|
+
* field's own consumer computes when it is not given one. ⇒ Do not derive a
|
|
1070
|
+
* physical quantity from it — how far a wheel rolls, how long a ribbon is.
|
|
1071
|
+
* `position` is stated against it; arc length is not it.
|
|
1072
|
+
*
|
|
1073
|
+
* ⚠️ **The EDITOR writes `vertexCount / 3` entries on BOTH — measured**
|
|
1074
|
+
* (round trip 6, 2026-09-16, Spine 4.3.26). It computes the wrap-around curve
|
|
1075
|
+
* even for an open path: `gallery/ride`'s 12-vertex open path came back with
|
|
1076
|
+
* **four** entries on 2026-09-04 (4.3.23) and the fourth, `2136.228`, is the
|
|
1077
|
+
* *closed*-chain cumulative. ⚠️ Neither array is wrong, and this is not a case
|
|
1078
|
+
* of the editor knowing something rigc does not. `SkeletonJson.js:601` allocates
|
|
1079
|
+
* `Utils.newArray(vertexCount / 3, 0)` and copies whatever is there, while
|
|
1080
|
+
* `PathConstraint` reads at most `lengths[curveCount]` with
|
|
1081
|
+
* `curveCount = verticesLength / 6 − (closed ? 1 : 2)` — index 2 on that path.
|
|
1082
|
+
* The trailing entry the editor adds to an open path is never read by anything.
|
|
1083
|
+
* Since issue #804 rigc writes it too, over the closed chain, and the `ride`
|
|
1084
|
+
* build ends on the editor's `2136.228`: a rebuild is the file the editor
|
|
1085
|
+
* writes rather than one entry short of it.
|
|
1086
|
+
*
|
|
1087
|
+
* 🚨 **What the editor writes INTO those entries is its own measurement, and it
|
|
1088
|
+
* is the runtime's, not calculus'** (issue #560, measured on the same trip).
|
|
1089
|
+
* `PathConstraint`'s `constantSpeed` re-measure is a four-sample forward
|
|
1090
|
+
* difference per curve — its constants are `0.1875 = 3t²`, `0.09375 = 6t³` and
|
|
1091
|
+
* `(cx1 − x1) · 0.75 = 3t` at **t = 1/4**, accumulating four `Math.sqrt` terms —
|
|
1092
|
+
* and the editor's stored `lengths` are that same computation. A 4-sample chord
|
|
1093
|
+
* sum over the same control points reproduces the editor to every digit it
|
|
1094
|
+
* prints, on two rigs and two editor builds: `pathmodes` (closed, 4.3.26) came
|
|
1095
|
+
* back `[152.7006, 305.4012, 458.1019, 610.8025]` and `ride` (open, 4.3.23)
|
|
1096
|
+
* `[430.8389, 838.0142, 1127.736, …]`. It is a recomputation at **export** — the
|
|
1097
|
+
* inflated `.spine` project holds the imported numbers verbatim — so a path rig
|
|
1098
|
+
* is re-parameterised by the trip rather than corrupted by it.
|
|
1099
|
+
*
|
|
1100
|
+
* ⇒ **rigc emits that computation, not a sampler aimed at it** (issue #560).
|
|
1101
|
+
* `pathCurveLengths` in [`compile.ts`](compile.ts) measures with the core's
|
|
1102
|
+
* curve table (`curveLengthTable` in `src/core/constraints_path.ts`, issue
|
|
1103
|
+
* #1015), down to `Math.sqrt(dx * dx + dy * dy)` rather than `Math.hypot` and
|
|
1104
|
+
* `0.16666667` rather than `1 / 6`; `PS67`, `PS68` and `PS186` in `selftest.ts`
|
|
1105
|
+
* hold it there by requiring it to reproduce a real `PathConstraint.curves`
|
|
1106
|
+
* array **bit for bit** on the runtime's own posed chain. Measured after the change, all
|
|
1107
|
+
* seven entries of both editor exports above come back at the precision the
|
|
1108
|
+
* editor prints them.
|
|
1109
|
+
*
|
|
1110
|
+
* ⚠️ This paragraph used to point at a constant — `PATH_LENGTH_SAMPLES` — and ask
|
|
1111
|
+
* *how finely rigc should sample*. There is no such constant now, and the
|
|
1112
|
+
* question was the wrong one. The chord-sum reading above is true and it is not
|
|
1113
|
+
* sufficient: a 4-sample chord sum agrees with the forward difference to about
|
|
1114
|
+
* **nine significant digits**, which is *below* what float32 can hold — so no
|
|
1115
|
+
* editor export can tell the two apart, and that reading can only settle the
|
|
1116
|
+
* MODEL — and *above* rigc's six-decimal rounding, so the emitted file can. On
|
|
1117
|
+
* both rigs above the two spellings round apart on the **last** curve, where the
|
|
1118
|
+
* running total has accumulated most: `610.802519` against `610.802520`, and
|
|
1119
|
+
* `1127.735817` against `1127.735818`. So the editor is the evidence for what is
|
|
1120
|
+
* being computed and only the runtime is evidence for how.
|
|
1121
|
+
*
|
|
1122
|
+
* 📌 For the record of what the disagreement cost when it was found: the build
|
|
1123
|
+
* rigc **0.21.0** emitted for `pathmodes` sat a uniform **0.70 %** above the
|
|
1124
|
+
* editor's four numbers, and `check` read **4.9612 mean MAE** against 0.0000 on
|
|
1125
|
+
* the seven rigs of that run without a path. ⚠️ What decides whether that moves a
|
|
1126
|
+
* pixel is the POSITION mode, not the spacing mode: `pathmodes` is
|
|
1127
|
+
* `positionMode: fixed`, where an absolute `position` is compared against a total
|
|
1128
|
+
* that scaled, so the bone slides. Under `positionMode: percent` a uniform scale
|
|
1129
|
+
* cancels out of both the position and the spacing — `gallery/ride` is
|
|
1130
|
+
* percent/percent and every one of its 74 rendered frames came back **byte
|
|
1131
|
+
* identical** across this change, on an emitted array all three of whose numbers
|
|
1132
|
+
* moved. `A33_VERTEX_ATTACHMENT_GEOMETRY` asks only that the array strictly
|
|
1133
|
+
* increase, which both arrays do, and `diff` does not compare it at all.
|
|
1134
|
+
*/
|
|
1135
|
+
export interface SpinePathAttachment {
|
|
1136
|
+
type: 'path';
|
|
1137
|
+
/** See `SpineRegionAttachment.name`. No texture `path`: this type reads none. */
|
|
1138
|
+
name?: string;
|
|
1139
|
+
closed?: boolean;
|
|
1140
|
+
constantSpeed?: boolean;
|
|
1141
|
+
vertexCount: number;
|
|
1142
|
+
/** Unweighted x/y pairs, or the weighted run — same encoding as a mesh's. */
|
|
1143
|
+
vertices: number[];
|
|
1144
|
+
lengths: number[];
|
|
1145
|
+
color?: string;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
export type SpineAttachment =
|
|
1149
|
+
| SpineRegionAttachment
|
|
1150
|
+
| SpineMeshAttachment
|
|
1151
|
+
| SpineLinkedMeshAttachment
|
|
1152
|
+
| SpineBoundingBoxAttachment
|
|
1153
|
+
| SpineClippingAttachment
|
|
1154
|
+
| SpinePathAttachment;
|
|
1155
|
+
|
|
1156
|
+
export type SpineTimelineKey = Record<string, unknown>;
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* 4.3 puts every constraint type in ONE top-level `constraints` array and
|
|
1160
|
+
* branches on `type` (SkeletonJson.js:129-350). The 4.1-era per-type arrays
|
|
1161
|
+
* (`physics: [...]`, `ik: [...]`) are not read at all — the constraint vanishes
|
|
1162
|
+
* with no error, which is assertion A01.
|
|
1163
|
+
*/
|
|
1164
|
+
export type SpineConstraint = { type: string; name: string } & Record<string, unknown>;
|
|
1165
|
+
|
|
1166
|
+
export interface SpineSkeletonJson {
|
|
1167
|
+
skeleton: {
|
|
1168
|
+
spine: string;
|
|
1169
|
+
/**
|
|
1170
|
+
* The setup-pose bounding box — and since issue #907 that is what `build`
|
|
1171
|
+
* writes here, computed by rigc's core (`headerBoundsOf` in `compile.ts`);
|
|
1172
|
+
* it used to copy the stage. All four together or none of them: a rig spec
|
|
1173
|
+
* that declares no stage (`skeleton.width`/`height` stated `null` — see
|
|
1174
|
+
* `RigSkeletonHeader`) emits a header without any of them, which is what an
|
|
1175
|
+
* export of a skeleton whose bounds were never set carries (issue #578).
|
|
1176
|
+
*
|
|
1177
|
+
* ⚠️ Optional here because the *runtime* leaves them `undefined` when they
|
|
1178
|
+
* are absent, not 0. `SkeletonData` declares `x = 0 … height = 0`
|
|
1179
|
+
* (`SkeletonData.js:55-61`), and `SkeletonJson` then overwrites all four
|
|
1180
|
+
* unconditionally — `skeletonData.x = skeletonMap.x` (`SkeletonJson.js:70-73`,
|
|
1181
|
+
* no `getValue` default) — so an absent field lands as `undefined` on a
|
|
1182
|
+
* `SkeletonData` whose own `.d.ts` types it `number`. Anything reading these
|
|
1183
|
+
* back off a parsed skeleton guards for it; `validate.ts`'s `data.width || 0`
|
|
1184
|
+
* is why A14 and A19 were already right about a stage-less file.
|
|
1185
|
+
*/
|
|
1186
|
+
x?: number;
|
|
1187
|
+
y?: number;
|
|
1188
|
+
width?: number;
|
|
1189
|
+
height?: number;
|
|
1190
|
+
fps?: number;
|
|
1191
|
+
referenceScale?: number;
|
|
1192
|
+
images?: string;
|
|
1193
|
+
/** Nonessential, carried from `RigSkeletonHeader.audio` as stated — `null` included. */
|
|
1194
|
+
audio?: string | null;
|
|
1195
|
+
};
|
|
1196
|
+
bones: SpineBone[];
|
|
1197
|
+
slots: SpineSlot[];
|
|
1198
|
+
constraints?: SpineConstraint[];
|
|
1199
|
+
/**
|
|
1200
|
+
* Field order inside a skin entry is `readSkeletonData`'s reading order —
|
|
1201
|
+
* `bones`, then the five constraint lists, then `attachments` — and every one
|
|
1202
|
+
* of them but `name` and `attachments` is emitted only when the rig declared
|
|
1203
|
+
* it, so a spec that names no per-skin member emits exactly what it always did.
|
|
1204
|
+
*/
|
|
1205
|
+
skins: Array<{
|
|
1206
|
+
name: string;
|
|
1207
|
+
/** Bone names this skin activates. Each one carries `skin: true`. */
|
|
1208
|
+
bones?: string[];
|
|
1209
|
+
ik?: string[];
|
|
1210
|
+
transform?: string[];
|
|
1211
|
+
path?: string[];
|
|
1212
|
+
physics?: string[];
|
|
1213
|
+
slider?: string[];
|
|
1214
|
+
attachments: Record<string, Record<string, SpineAttachment>>;
|
|
1215
|
+
}>;
|
|
1216
|
+
/**
|
|
1217
|
+
* Event definitions, keyed by name (`SkeletonJson.ts:451-464`). An object, not
|
|
1218
|
+
* an array — one of the **two** top-level collections in the format that are,
|
|
1219
|
+
* `animations` being the other.
|
|
1220
|
+
*
|
|
1221
|
+
* ⚠️ This sentence said "the one" until issue #535, and the collection it was
|
|
1222
|
+
* overlooking is where the defect that card is about lived. The distinction is
|
|
1223
|
+
* not cosmetic: the binary format addresses both of these by ORDINAL
|
|
1224
|
+
* (`SkeletonBinary`: `animations[readInt()]` for a slider's animation,
|
|
1225
|
+
* `events[readInt()]` for an event key), and an editor round trip was measured
|
|
1226
|
+
* to re-key every name-keyed object while returning the arrays it was taken
|
|
1227
|
+
* over in the order they were given. So a reference into either of these two
|
|
1228
|
+
* is a reference whose ordinal an editor can move.
|
|
1229
|
+
*
|
|
1230
|
+
* ✅ **`events` does not need what `animations` needed, and that is measured
|
|
1231
|
+
* rather than owed.** This comment said *unmeasured* until issue #539 carried
|
|
1232
|
+
* three of them through the editor on the same session's discriminator rigs:
|
|
1233
|
+
* `zebra, mike, alpha` came back keyed `alpha, mike, zebra`, and every firing
|
|
1234
|
+
* still resolved **by name** — `0.3 -> mike`, `0.6 -> alpha`, payloads intact.
|
|
1235
|
+
* So the editor re-keys `events` and repoints nothing, while the same re-key
|
|
1236
|
+
* of `animations` repoints every slider (#535). `animations` is emitted in the
|
|
1237
|
+
* editor's order for that reason (`compile.ts`'s `editorAnimationOrder`);
|
|
1238
|
+
* `events` is emitted in the order the rig spec declares them.
|
|
1239
|
+
*
|
|
1240
|
+
* ⚠️ Two more of this paragraph's claims were falsified by the same session,
|
|
1241
|
+
* and both stood here for a release because nothing re-read them (#544):
|
|
1242
|
+
*
|
|
1243
|
+
* - **The re-key is not in codepoint order.** It is natural and
|
|
1244
|
+
* case-insensitive: `Turn, sweep, wave` came back `sweep, Turn, wave` and
|
|
1245
|
+
* `turn10, turn2, zoom` came back `turn2, turn10, zoom` (#539). rigc emits
|
|
1246
|
+
* `animations` in **that** comparator's order since issue #543, and since
|
|
1247
|
+
* #728 the comparator itself is measured rather than quantified over —
|
|
1248
|
+
* five stored round trips, folders and all — so only what those files leave
|
|
1249
|
+
* open is refused by name. See `compile.ts`'s `editorNameOrder` and
|
|
1250
|
+
* `refuseNamesTheEditorCouldKeyDifferently`. It emitted codepoint until
|
|
1251
|
+
* #543, with the refusal widened to cover every pair codepoint could order
|
|
1252
|
+
* differently; that refused both rigs above, which are the only two anybody
|
|
1253
|
+
* had measured then, and it moved no byte to stop doing so.
|
|
1254
|
+
* - 🚨 **`skins` is NOT an array the editor leaves alone. It is the first one
|
|
1255
|
+
* measured moved** (#541). A four-skin rig built `default, zulu, mike,
|
|
1256
|
+
* alpha` came back `default, alpha, mike, zulu`: `default` is pinned first
|
|
1257
|
+
* and the rest are re-sorted, and the deform timelines came back keyed
|
|
1258
|
+
* `mike, zulu` rather than `zulu, mike` with it. `SkeletonBinary` addresses
|
|
1259
|
+
* skins by ORDINAL — `skins[readInt()]` for an attachment timeline,
|
|
1260
|
+
* `skins[skinIndex]` for a linked mesh — so this is #535 in the collection
|
|
1261
|
+
* nobody had checked. rigc emits `default` first and the rest in that
|
|
1262
|
+
* order since #541; see `compile.ts`'s `editorSkinOrder`.
|
|
1263
|
+
*
|
|
1264
|
+
* ⚠️ **The two readings this replaces, kept because the second is the one
|
|
1265
|
+
* that cost something.** #537's pull request called `skins` "measured
|
|
1266
|
+
* preserved"; #544 corrected that to *unmeasured*, on the grounds that the
|
|
1267
|
+
* arrays the round trip actually returned element for element were `bones`
|
|
1268
|
+
* (30), `slots` (24) and `constraints` (3), that every rig in this tree
|
|
1269
|
+
* declares exactly ONE skin, and that a one-element array comes back in
|
|
1270
|
+
* order whatever the editor does with it. Both readings were reached by
|
|
1271
|
+
* generalising from the three arrays that *were* measured — "an editor does
|
|
1272
|
+
* not move arrays" — and the generalisation is what was false. #544 also
|
|
1273
|
+
* said the measurement could not be taken, because the editor refused a
|
|
1274
|
+
* four-skin rig on import without a word: that refusal was rigc's own
|
|
1275
|
+
* harness discarding the editor's stderr, and the editor had named the
|
|
1276
|
+
* cause all along.
|
|
1277
|
+
*
|
|
1278
|
+
* - ✅ **What round trip 6 added to the `constraints` line is TYPES, not
|
|
1279
|
+
* order** (2026-09-16, Spine 4.3.26, eight rigs). The three that stood here
|
|
1280
|
+
* were `gallery/look`'s, and they are two types: `yaw` and `tilt`
|
|
1281
|
+
* (**slider**) and `whip` (**physics**). The trip carried a `transform`
|
|
1282
|
+
* with its whole 4.3 `source` + `properties` map, a `path` with three
|
|
1283
|
+
* non-default modes, another `physics` and another `slider`, and every one
|
|
1284
|
+
* came back field for field — so the array is now measured over **four**
|
|
1285
|
+
* of the format's types. `ik` is in none of the rigs anybody has
|
|
1286
|
+
* round-tripped, which is why the count is four and not five.
|
|
1287
|
+
*
|
|
1288
|
+
* ⚠️ **None of round 6's own constraint arrays can tell order preserved
|
|
1289
|
+
* from a name sort, and reading them as if they could would be #537's
|
|
1290
|
+
* mistake in a second collection.** The three rigs that carry constraints
|
|
1291
|
+
* hold `aim, hold` (xform), `hold, knob` (physlider) and `ride` alone
|
|
1292
|
+
* (pathmodes). All three came back in the order they were given — and all
|
|
1293
|
+
* three were *already* in name order, so a re-sort and a preservation are
|
|
1294
|
+
* the same picture there, exactly as a one-element array is for `skins`
|
|
1295
|
+
* above. ⇒ The order claim still rests entirely on `gallery/look`, whose
|
|
1296
|
+
* build order `yaw, tilt, whip` is **not** name order (`tilt < whip < yaw`)
|
|
1297
|
+
* and which #539 read back unchanged. That one rig is load-bearing and
|
|
1298
|
+
* nothing in this tree re-takes it.
|
|
1299
|
+
*/
|
|
1300
|
+
events?: Record<string, SpineEvent>;
|
|
1301
|
+
animations: Record<
|
|
1302
|
+
string,
|
|
1303
|
+
{
|
|
1304
|
+
slots?: Record<string, Record<string, SpineTimelineKey[]>>;
|
|
1305
|
+
bones?: Record<string, Record<string, SpineTimelineKey[]>>;
|
|
1306
|
+
/**
|
|
1307
|
+
* `ik.<constraint> = keys[]` — the constraint IS the timeline, so there is
|
|
1308
|
+
* no timeline name between the two. Same for `transform`.
|
|
1309
|
+
*/
|
|
1310
|
+
ik?: Record<string, SpineTimelineKey[]>;
|
|
1311
|
+
transform?: Record<string, SpineTimelineKey[]>;
|
|
1312
|
+
/** `path.<constraint>.<position|spacing|mix> = keys[]` — the physics shape. */
|
|
1313
|
+
path?: Record<string, Record<string, SpineTimelineKey[]>>;
|
|
1314
|
+
physics?: Record<string, Record<string, SpineTimelineKey[]>>;
|
|
1315
|
+
/** `slider.<constraint>.<time|mix> = keys[]`. */
|
|
1316
|
+
slider?: Record<string, Record<string, SpineTimelineKey[]>>;
|
|
1317
|
+
/** `attachments.<skin>.<slot>.<attachment>.<timeline> = keys[]` — four deep. */
|
|
1318
|
+
attachments?: Record<string, Record<string, Record<string, Record<string, SpineTimelineKey[]>>>>;
|
|
1319
|
+
/** Whole-animation timeline: no target name, one array per animation. */
|
|
1320
|
+
drawOrder?: SpineTimelineKey[];
|
|
1321
|
+
/** The other whole-animation timeline; same shape, same reason. */
|
|
1322
|
+
events?: SpineTimelineKey[];
|
|
1323
|
+
}
|
|
1324
|
+
>;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/** One entry of the emitted `skins` array — what `emitSkins` in `src/emit_spine.ts` writes per skin. */
|
|
1328
|
+
export type SpineSkin = SpineSkeletonJson['skins'][number];
|
|
1329
|
+
|
|
1330
|
+
/** One animation of the emitted `animations` object — what `emitAnimations` in `src/emit_spine.ts` writes per animation. */
|
|
1331
|
+
export type SpineAnimation = SpineSkeletonJson['animations'][string];
|
|
1332
|
+
|
|
1333
|
+
/** One entry of the emitted `events` map: the payload a firing inherits. */
|
|
1334
|
+
export interface SpineEvent {
|
|
1335
|
+
int?: number;
|
|
1336
|
+
float?: number;
|
|
1337
|
+
string?: string;
|
|
1338
|
+
audio?: string;
|
|
1339
|
+
volume?: number;
|
|
1340
|
+
balance?: number;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// ---------------------------------------------------------------------------
|
|
1344
|
+
// Compiler result
|
|
1345
|
+
// ---------------------------------------------------------------------------
|
|
1346
|
+
|
|
1347
|
+
export interface CompiledImage {
|
|
1348
|
+
/** Region name = attachment name = PNG basename. */
|
|
1349
|
+
region: string;
|
|
1350
|
+
/** Atlas page name: the PNG path relative to the atlas file. */
|
|
1351
|
+
page: string;
|
|
1352
|
+
/** Absolute path on disk, for the size assertions. */
|
|
1353
|
+
absPath: string;
|
|
1354
|
+
width: number;
|
|
1355
|
+
height: number;
|
|
1356
|
+
/**
|
|
1357
|
+
* A per-pixel alpha channel, and only that — colour types 4 and 6.
|
|
1358
|
+
*
|
|
1359
|
+
* ⚠️ Not "this part can be transparent": indexed and greyscale art keeps its
|
|
1360
|
+
* transparency in a `tRNS` chunk and reads `false` here. Anything asking
|
|
1361
|
+
* whether the art can draw a transparent pixel wants `PngInfo.hasTransparency`
|
|
1362
|
+
* ([`src/png.ts`](png.ts)), which is the distinction A19 got wrong (#215).
|
|
1363
|
+
*/
|
|
1364
|
+
hasAlpha: boolean;
|
|
1365
|
+
isBase: boolean;
|
|
1366
|
+
/**
|
|
1367
|
+
* The atlas region this part was resolved FROM, when it came out of a
|
|
1368
|
+
* pre-packed atlas (`build --atlas-in`). Absent for the ordinary case, where
|
|
1369
|
+
* the part is a loose PNG and its region covers its page exactly.
|
|
1370
|
+
*
|
|
1371
|
+
* Carried rather than flattened because a packed region says things a loose
|
|
1372
|
+
* PNG cannot: where on the page it sits, how much border the packer trimmed,
|
|
1373
|
+
* whether it is turned. `width`/`height` above are the untrimmed DRAWING's
|
|
1374
|
+
* size — the region's `originalWidth`/`originalHeight` divided by the page's
|
|
1375
|
+
* `scale:` — so every existing reader of this interface keeps the meaning it
|
|
1376
|
+
* had; this field is for the two that need the rectangle itself, in the page's
|
|
1377
|
+
* own texels (lifting the drawing back off the page, and reporting the pack).
|
|
1378
|
+
*
|
|
1379
|
+
* ⚠️ So these two are in DIFFERENT units whenever the page declares a `scale:`
|
|
1380
|
+
* other than 1: `width` is world/art size, `atlas.width` is texels. See
|
|
1381
|
+
* `atlasScale`.
|
|
1382
|
+
*/
|
|
1383
|
+
atlas?: AtlasRegion;
|
|
1384
|
+
/**
|
|
1385
|
+
* The `scale:` of the page the region above sits on, when it declares one
|
|
1386
|
+
* other than 1. Absent otherwise, and absent for a loose PNG.
|
|
1387
|
+
*
|
|
1388
|
+
* Present so a message can show its work: `width`/`height` are already
|
|
1389
|
+
* descaled, and a refusal that says "region X is 746" without saying it read
|
|
1390
|
+
* 373 texels at `scale: 0.5` names a number that is in neither file.
|
|
1391
|
+
*
|
|
1392
|
+
* And so a figure taken off the lifted texels can be stated in the drawing's
|
|
1393
|
+
* pixels, which is what `width`/`height` are in (issue #762): a distance
|
|
1394
|
+
* measured on this page's grid is `texels / atlasScale` pixels of the drawing,
|
|
1395
|
+
* and a sheet made at the drawing's size is read at the same ratio. It is the
|
|
1396
|
+
* value the atlas states, never one rigc measured.
|
|
1397
|
+
*/
|
|
1398
|
+
atlasScale?: number;
|
|
1399
|
+
/**
|
|
1400
|
+
* The page this region sits on, when that page's file is not the size the
|
|
1401
|
+
* atlas declares for it (issue #750): `said` is `pageGridSaid`'s clause and
|
|
1402
|
+
* `sentence` is `A06`'s whole sentence (`pageGridSentence`). Absent for a
|
|
1403
|
+
* page whose file agrees, and for a loose PNG, which is its own page.
|
|
1404
|
+
*
|
|
1405
|
+
* Carried because the region lift (`partPlate`) addresses the page at the
|
|
1406
|
+
* coordinates the atlas states, and on such a file those are not where the
|
|
1407
|
+
* part's texels are: every reader of the lift has to know that before it
|
|
1408
|
+
* takes a figure off it.
|
|
1409
|
+
*/
|
|
1410
|
+
pageGrid?: { said: string; sentence: string };
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* Structural expectations the validator cannot read out of skeleton JSON.
|
|
1415
|
+
*
|
|
1416
|
+
* Some invariants of a rig are simply not written down in the artifact:
|
|
1417
|
+
* nothing in the file says "this mesh is a ribbon" or "this emitter must not
|
|
1418
|
+
* hang off the part that released it". The compiler knows, because the rig
|
|
1419
|
+
* spec's `invariants` block says so, and it hands the knowledge over rather than
|
|
1420
|
+
* letting the validator guess. Mutants stay honest because a mutant edits the
|
|
1421
|
+
* ARTIFACT while this block keeps saying what the rig was supposed to be.
|
|
1422
|
+
*/
|
|
1423
|
+
export interface RigInfo {
|
|
1424
|
+
/** The rig spec's `name`. Reported by the validator so a green names its rig. */
|
|
1425
|
+
archetype: string;
|
|
1426
|
+
/** The bone whose setup rotation carries the cut's axis, if the rig has one. */
|
|
1427
|
+
axisBone: string | null;
|
|
1428
|
+
/** Bones under the axis bone, whose translate keys must stay on the axis. */
|
|
1429
|
+
axisSubtree: string[];
|
|
1430
|
+
/** [bone, ancestor it must never have] — see `invariants.detached`. */
|
|
1431
|
+
detached: Array<[string, string]>;
|
|
1432
|
+
/** Canonical draw order (the rig's slot array), or null if it declares none. */
|
|
1433
|
+
slotOrder: string[] | null;
|
|
1434
|
+
/**
|
|
1435
|
+
* slot -> what built this mesh, for the kind-aware mesh assertions.
|
|
1436
|
+
*
|
|
1437
|
+
* `ring`, `ribbon` and `contour` are rigc's own generators, whose topology it
|
|
1438
|
+
* therefore knows: where the rim is, which edge is the entry row, that the
|
|
1439
|
+
* rows pair up, that a contour's hull IS every vertex it has.
|
|
1440
|
+
* **`authored`** is geometry that came in through the rig spec — drawn by an
|
|
1441
|
+
* animator, transcribed from an export — and rigc knows nothing about its
|
|
1442
|
+
* topology at all. An assertion that measures generator topology has nothing
|
|
1443
|
+
* to say about one, so it SKIPs with that as the reason rather than checking
|
|
1444
|
+
* a ring the mesh was never supposed to be.
|
|
1445
|
+
*/
|
|
1446
|
+
meshKinds: Record<string, MeshKind | 'authored'>;
|
|
1447
|
+
/**
|
|
1448
|
+
* slot -> every bone the compiler bound this mesh to, in the order the `MESH`
|
|
1449
|
+
* report line prints them: the slot bone first, then the control bones the
|
|
1450
|
+
* generator named.
|
|
1451
|
+
*
|
|
1452
|
+
* ⭐ A20 reads it to ask the one question a weighted run cannot answer about
|
|
1453
|
+
* itself — whether a bone the mesh DECLARES is bound by any vertex at all. A
|
|
1454
|
+
* ring that named two grips and bound one was a green build on every
|
|
1455
|
+
* per-vertex rule there is, because each of those rules reads a vertex and the
|
|
1456
|
+
* missing bone is in none of them (issue #684).
|
|
1457
|
+
*
|
|
1458
|
+
* ⚠️ On an `authored` mesh this is the set the weights themselves name, so it
|
|
1459
|
+
* is a tautology there and A20's clause skips it with the rest of the
|
|
1460
|
+
* generator policy — rigc did not choose those bindings.
|
|
1461
|
+
*/
|
|
1462
|
+
meshDeclaredBones: Record<string, string[]>;
|
|
1463
|
+
/**
|
|
1464
|
+
* Slots whose mesh carries a SOFT region on a second bone, and which bone —
|
|
1465
|
+
* from a generator's `soft` block.
|
|
1466
|
+
*
|
|
1467
|
+
* ⭐ A21 reads it. A rim vertex the mask CARRIED is supposed to move — that
|
|
1468
|
+
* is what a soft region is — so the rule splits by declaration rather than
|
|
1469
|
+
* being relaxed, exactly as it already splits for a ribbon's entry row: on
|
|
1470
|
+
* such a mesh the invariant is that a vertex is either pinned to the slot
|
|
1471
|
+
* bone or shared between it and the declared bone, and never anything else.
|
|
1472
|
+
*/
|
|
1473
|
+
meshSoftBones: Record<string, string>;
|
|
1474
|
+
/**
|
|
1475
|
+
* Slots whose deform timelines may turn a triangle inside out, from
|
|
1476
|
+
* `invariants.deformMayFold`. Empty is the ordinary case, and A39 gates every
|
|
1477
|
+
* mesh not named here — see `RigInvariants.deformMayFold` for why the default
|
|
1478
|
+
* is on.
|
|
1479
|
+
*/
|
|
1480
|
+
deformMayFold: string[];
|
|
1481
|
+
/**
|
|
1482
|
+
* Ik and transform constraints whose mix the consumer sets, from
|
|
1483
|
+
* `invariants.consumerDrivenMix`, in the rig spec's order. `A47` / `A48` do
|
|
1484
|
+
* not measure these: a declared constraint is named on the stats line, and it
|
|
1485
|
+
* is the SKIP's subject when nothing else of its kind is left to measure. A
|
|
1486
|
+
* bare `validate <dir>` has no rig and so no declaration, and refuses every
|
|
1487
|
+
* muted constraint as before (issue #784).
|
|
1488
|
+
*/
|
|
1489
|
+
consumerDrivenMix: Array<{ type: 'ik' | 'transform'; constraint: string; why: string }>;
|
|
1490
|
+
/**
|
|
1491
|
+
* The `why` of `invariants.idleDrivesMeshes`, or null when the rig does not
|
|
1492
|
+
* declare that its `idle` deforms meshes on purpose. Declared, `A15` SKIPs
|
|
1493
|
+
* with the renderer cost it measured, or FAILs when the `idle` keys no
|
|
1494
|
+
* mesh-driving bone and the declaration switches off nothing. A bare
|
|
1495
|
+
* `validate <dir>` has no rig, so null, and A15 refuses per bone as before.
|
|
1496
|
+
*/
|
|
1497
|
+
idleDrivesMeshes: string | null;
|
|
1498
|
+
/**
|
|
1499
|
+
* The atlas region(s) the build names as its base plate — the one image
|
|
1500
|
+
* `A19_OVERLAY_PNGS_HAVE_ALPHA` lets be opaque — in compile order.
|
|
1501
|
+
*
|
|
1502
|
+
* A cut manifest states it: the part whose window IS the crop
|
|
1503
|
+
* (`CompiledImage.isBase`), with no stage box involved. A rig spec has no way
|
|
1504
|
+
* to state it, so a build from one names none and this is empty. `A19` reads
|
|
1505
|
+
* this first and falls back to "an attachment at least the stage's size" only
|
|
1506
|
+
* when it is empty (issue #770) — two readings that can disagree need an
|
|
1507
|
+
* order, and the statement outranks the measurement.
|
|
1508
|
+
*/
|
|
1509
|
+
basePlates: string[];
|
|
1510
|
+
/** Mesh slots this rig budgets for, or null when it declares no budget. */
|
|
1511
|
+
meshSlotBudget: number | null;
|
|
1512
|
+
/** Triangles one mesh may carry, or null when the rig declares no budget. */
|
|
1513
|
+
meshTriangleBudget: number | null;
|
|
1514
|
+
/** Deepest inward advance the two masses allow, from the manifest. */
|
|
1515
|
+
contactDepth: number | null;
|
|
1516
|
+
/**
|
|
1517
|
+
* Deepest inward advance at which the cap contour is still covered, from the
|
|
1518
|
+
* manifest. Null when the cut has not measured one — A30 then says nothing
|
|
1519
|
+
* rather than inventing a wall.
|
|
1520
|
+
*/
|
|
1521
|
+
capContainmentCeiling: number | null;
|
|
1522
|
+
/**
|
|
1523
|
+
* The bone the inserting mass hangs on. Its own inward keys spend the same
|
|
1524
|
+
* clearance the stroke does: if both move in, both close the gap.
|
|
1525
|
+
*/
|
|
1526
|
+
massBone: string | null;
|
|
1527
|
+
/** Inward unit vector in SPINE world (y up), for projecting off-axis keys. */
|
|
1528
|
+
inwardUnit: [number, number] | null;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/**
|
|
1532
|
+
* A state the manifest lists whose art was not where the manifest said.
|
|
1533
|
+
*
|
|
1534
|
+
* Named rather than written inline in `CompileResult` because it outlives the
|
|
1535
|
+
* result: a compile that REFUSES returns nothing, and the drops it recorded on
|
|
1536
|
+
* the way to that refusal are facts about the inputs the caller still has to be
|
|
1537
|
+
* told (issue #671). `CompileError.droppedStates` carries them, and it can only
|
|
1538
|
+
* do that if the shape has a name.
|
|
1539
|
+
*/
|
|
1540
|
+
export interface DroppedState {
|
|
1541
|
+
slot: string;
|
|
1542
|
+
state: string;
|
|
1543
|
+
path: string;
|
|
1544
|
+
/**
|
|
1545
|
+
* What was consulted and came up empty, when it was not a file on disk.
|
|
1546
|
+
*
|
|
1547
|
+
* Absent on the ordinary path, where "no PNG at <path>" says everything. An
|
|
1548
|
+
* `--atlas-in` build opened no such file — it looked for a REGION — so
|
|
1549
|
+
* reporting the path would send the reader to a directory instead of to the
|
|
1550
|
+
* pack that is missing it.
|
|
1551
|
+
*/
|
|
1552
|
+
why?: string;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
export interface CompileResult {
|
|
1556
|
+
/**
|
|
1557
|
+
* The compiled model (`src/model.ts`): what the skeleton below was emitted
|
|
1558
|
+
* from. This cut fills its `bones` and `setupWorld`; every other field is
|
|
1559
|
+
* this result's own, carried by reference.
|
|
1560
|
+
*/
|
|
1561
|
+
model: CompiledModel;
|
|
1562
|
+
/**
|
|
1563
|
+
* The Spine 4.3 skeleton: `emitSkeleton`'s value over `model` (issue #922),
|
|
1564
|
+
* the one call the assembly makes. `build` writes its text, and beside it
|
|
1565
|
+
* `modelDocument(model)` as `skeleton.model.json`.
|
|
1566
|
+
*/
|
|
1567
|
+
skeleton: SpineSkeletonJson;
|
|
1568
|
+
skeletonText: string;
|
|
1569
|
+
atlasText: string;
|
|
1570
|
+
images: CompiledImage[];
|
|
1571
|
+
/**
|
|
1572
|
+
* Every page of an `--atlas-in` pack whose file is not the size the atlas
|
|
1573
|
+
* declares, in the pack's page order, each with `pageGridSaid`'s clause.
|
|
1574
|
+
* Empty on the loose and packing routes, and on a pack whose pages agree.
|
|
1575
|
+
*/
|
|
1576
|
+
pageGrids: Array<{ page: string; said: string }>;
|
|
1577
|
+
/** States listed in the manifest whose PNG is not on disk. */
|
|
1578
|
+
droppedStates: DroppedState[];
|
|
1579
|
+
/**
|
|
1580
|
+
* Parts the manifest declares and the cut does not carry (`image: null`, no
|
|
1581
|
+
* states). Reported rather than swallowed: "the optional slots are optional" is
|
|
1582
|
+
* a claim about the emit path, so the emit path says out loud which ones it
|
|
1583
|
+
* left out.
|
|
1584
|
+
*/
|
|
1585
|
+
absentParts: Array<{ slot: string; why: string }>;
|
|
1586
|
+
/** Declared durations, carried into the validator (rule 4). */
|
|
1587
|
+
declaredDurations: Record<string, number>;
|
|
1588
|
+
/** Bones that drive a mesh attachment: the slot bone plus its control bone. */
|
|
1589
|
+
meshBones: string[];
|
|
1590
|
+
/** Mesh slots emitted, with triangle counts — reported by `build`. */
|
|
1591
|
+
meshes: Array<{
|
|
1592
|
+
slot: string;
|
|
1593
|
+
kind: MeshKind | 'authored';
|
|
1594
|
+
attachments: string[];
|
|
1595
|
+
vertices: number;
|
|
1596
|
+
triangles: number;
|
|
1597
|
+
bones: string[];
|
|
1598
|
+
/**
|
|
1599
|
+
* Share of the part's own art the triangles cover, 0..1.
|
|
1600
|
+
*
|
|
1601
|
+
* Measured for every mesh that names an `image`, generated or authored: it is
|
|
1602
|
+
* a number between two things the compiler has in front of it — the emitted
|
|
1603
|
+
* triangles and the PNG — and it assumes nothing about how the vertices are
|
|
1604
|
+
* arranged (issue #277). Absent on a mesh with no `image`, which has nothing
|
|
1605
|
+
* to be measured against, and on a `ring` or `ribbon`, whose window size
|
|
1606
|
+
* comes from the spec rather than from art.
|
|
1607
|
+
*/
|
|
1608
|
+
coverage?: number;
|
|
1609
|
+
/**
|
|
1610
|
+
* How far past the silhouette that mesh reaches, in the DRAWING's pixels —
|
|
1611
|
+
* the unit `CompiledImage.width` is in, and the one an author draws in.
|
|
1612
|
+
*
|
|
1613
|
+
* The distance is measured on the grid the part's alpha is read off, and on
|
|
1614
|
+
* a page that declares a `scale:` that grid is the page's texels, so it is
|
|
1615
|
+
* divided by the stated scale here (issue #762). It printed 8.00px on a
|
|
1616
|
+
* `scale: 0.5` page for a mesh that reaches 16.00px past the same drawing
|
|
1617
|
+
* on the page it was packed from. `pageScale` says when that happened.
|
|
1618
|
+
*/
|
|
1619
|
+
overshoot?: number;
|
|
1620
|
+
/**
|
|
1621
|
+
* The `scale:` of the page the fit above was measured on, when it is not 1
|
|
1622
|
+
* (`CompiledImage.atlasScale`). Absent on a loose part and on a page at
|
|
1623
|
+
* scale 1, where the texel grid is the drawing's. Carried so the report can
|
|
1624
|
+
* say the figure was taken on a grid whose step is `1 / pageScale` pixels
|
|
1625
|
+
* of the drawing, which is the precision it has.
|
|
1626
|
+
*/
|
|
1627
|
+
pageScale?: number;
|
|
1628
|
+
/**
|
|
1629
|
+
* Why `coverage` and `overshoot` are absent on a mesh that names an image:
|
|
1630
|
+
* its part sits on a packed page whose file is not the size the atlas
|
|
1631
|
+
* declares, and this is `pageGridSaid`'s clause for that page (issue #750).
|
|
1632
|
+
* The fit is a measurement between the triangles and the part's texels,
|
|
1633
|
+
* and the coordinates the atlas states do not locate those texels on such
|
|
1634
|
+
* a file, so the figure is withheld rather than taken off the wrong ones.
|
|
1635
|
+
*/
|
|
1636
|
+
fitWithheld?: string;
|
|
1637
|
+
/**
|
|
1638
|
+
* Transparent pixels the traced outline encloses — inside the mesh, drawing
|
|
1639
|
+
* nothing. Only a `contour` has one: it is a property of the trace, and an
|
|
1640
|
+
* authored mesh was not traced.
|
|
1641
|
+
*/
|
|
1642
|
+
holePixels?: number;
|
|
1643
|
+
/**
|
|
1644
|
+
* The soft region a `soft` block carried to its own bone, when one was
|
|
1645
|
+
* named — the mask, its digest, and how many vertices it reached.
|
|
1646
|
+
*
|
|
1647
|
+
* ⚠️ Separate from `depth` on purpose. It WAS a depth threshold and that
|
|
1648
|
+
* conflated two properties: the most prominent thing on a face is the nose,
|
|
1649
|
+
* and a nose does not wobble.
|
|
1650
|
+
*/
|
|
1651
|
+
soft?: { mask: string; digest: string; bone: string; carried: number; ramped: number };
|
|
1652
|
+
/**
|
|
1653
|
+
* How a `segments` mesh's bones share its vertices — the figures `build`
|
|
1654
|
+
* and `explain` print under its line. Only `segments` has one: it is the
|
|
1655
|
+
* generator whose weights are the whole point, and every other generator's
|
|
1656
|
+
* weighting is fixed by its kind.
|
|
1657
|
+
*/
|
|
1658
|
+
influence?: {
|
|
1659
|
+
/** Most bones any one vertex binds, and the mean over the mesh. */
|
|
1660
|
+
maxBones: number;
|
|
1661
|
+
meanBones: number;
|
|
1662
|
+
/** Vertices bound to exactly one bone. */
|
|
1663
|
+
singleBone: number;
|
|
1664
|
+
/** The bones some vertex binds, in the order `bones` names them. */
|
|
1665
|
+
bound: string[];
|
|
1666
|
+
/** The lattice: its cell, cells across and down, cells with art, cells kept, islands joined. */
|
|
1667
|
+
cell: number;
|
|
1668
|
+
cols: number;
|
|
1669
|
+
rows: number;
|
|
1670
|
+
artCells: number;
|
|
1671
|
+
keptCells: number;
|
|
1672
|
+
islands: number;
|
|
1673
|
+
};
|
|
1674
|
+
/**
|
|
1675
|
+
* What a depth map put on this mesh's vertices, when one was named.
|
|
1676
|
+
*
|
|
1677
|
+
* The digest is over the levels rather than the file, so a re-encode of the
|
|
1678
|
+
* same sheet reports the same provenance; `range` is what was actually
|
|
1679
|
+
* sampled. Absent when no map was named — never zeroes, which would read as
|
|
1680
|
+
* "sampled and found flat".
|
|
1681
|
+
*
|
|
1682
|
+
* ⚠️ This comment sat above `soft` rather than above the field it describes
|
|
1683
|
+
* until issue #449 came to add to it, which is the same drift `CUR07` was
|
|
1684
|
+
* built for one file over — nothing derives a doc comment's neighbour.
|
|
1685
|
+
*/
|
|
1686
|
+
depth?: {
|
|
1687
|
+
/** The sheet, as written in the spec. */
|
|
1688
|
+
image: string;
|
|
1689
|
+
/** First 16 hex of a sha256 over width, height and levels. */
|
|
1690
|
+
digest: string;
|
|
1691
|
+
near: 'white' | 'black';
|
|
1692
|
+
zScale: number;
|
|
1693
|
+
/** The stated curve, in full — `1 / 1 / 0` is the straight line. */
|
|
1694
|
+
tone: { gamma: number; contrast: number; bias: number };
|
|
1695
|
+
/** Least and greatest `z` over the mesh's vertices, in attachment units. */
|
|
1696
|
+
range: [number, number];
|
|
1697
|
+
/**
|
|
1698
|
+
* How many of the mesh's vertices took their depth from a texel **the
|
|
1699
|
+
* part image does not draw** (issue #449).
|
|
1700
|
+
*
|
|
1701
|
+
* ⚠️ Not what `range` says, and this is the field that exists because
|
|
1702
|
+
* `range` was claimed to say it. A map that is half background has
|
|
1703
|
+
* exactly as full a range as one that is all subject, because a
|
|
1704
|
+
* background level is a legitimate depth — so a full-frame sheet over a
|
|
1705
|
+
* cut-out part reports a healthy `[0, 223.97]` of 224 with 54 % of the
|
|
1706
|
+
* mesh reading background.
|
|
1707
|
+
*
|
|
1708
|
+
* A count and never a refusal: the same defect is already a named refusal
|
|
1709
|
+
* when the sheet's alpha is cut to the art, and a sheet that is opaque
|
|
1710
|
+
* everywhere is a statement rigc has no authority to guess away. Zero is
|
|
1711
|
+
* a real answer here rather than an absence — every mesh that names a
|
|
1712
|
+
* depth map also names an image, so the measurement is taken whenever
|
|
1713
|
+
* the image's texels can be located.
|
|
1714
|
+
*
|
|
1715
|
+
* `null` is the one case they cannot (issue #750): a part lifted off a
|
|
1716
|
+
* packed page whose file is not the size its atlas declares, where the
|
|
1717
|
+
* coordinates the atlas states are not where the part's texels are. The
|
|
1718
|
+
* count is withheld rather than taken off whatever sits there — a
|
|
1719
|
+
* number measured over the wrong pixels reads exactly like a right one.
|
|
1720
|
+
*/
|
|
1721
|
+
undrawn: number | null;
|
|
1722
|
+
/** `pageGridSaid`'s clause for the page, exactly when `undrawn` is `null`. */
|
|
1723
|
+
unlocated?: string;
|
|
1724
|
+
/**
|
|
1725
|
+
* The turn this geometry takes on this sheet before a triangle reverses,
|
|
1726
|
+
* per axis and per direction — `src/depth.ts`'s `turnCeiling`.
|
|
1727
|
+
*
|
|
1728
|
+
* ⭐ It is what an author needs BEFORE writing a key, and the loop it
|
|
1729
|
+
* replaces is "pick an angle, build, read `A39`'s refusal, guess again".
|
|
1730
|
+
* A report and never a refusal: `A39` owns the refusal, from the artifact.
|
|
1731
|
+
*/
|
|
1732
|
+
ceiling: TurnCeiling;
|
|
1733
|
+
};
|
|
1734
|
+
}>;
|
|
1735
|
+
/** Structural expectations handed to the validator. */
|
|
1736
|
+
rig: RigInfo;
|
|
1737
|
+
/** Physics constraints emitted, with the bone each one drives. */
|
|
1738
|
+
physics: Array<{ name: string; bone: string; components: string[]; mix: number; drivesMesh: boolean }>;
|
|
1739
|
+
/**
|
|
1740
|
+
* Deform keys that stated a `transform` instead of a run, one entry per key in
|
|
1741
|
+
* emit order — reported by `explain` (issue #294).
|
|
1742
|
+
*
|
|
1743
|
+
* ⭐ The report carries the offsets it **emitted**, not a second evaluation of
|
|
1744
|
+
* the same model, so the printed audit and the artifact cannot disagree — and
|
|
1745
|
+
* where the two are not one array, `expanded` carries the second (issue #389).
|
|
1746
|
+
* An empty array is the ordinary case: a spec whose deform keys are all
|
|
1747
|
+
* authored runs generated nothing to report.
|
|
1748
|
+
*/
|
|
1749
|
+
deformTransforms: Array<
|
|
1750
|
+
DeformTransformReport & {
|
|
1751
|
+
animation: string;
|
|
1752
|
+
skin: string;
|
|
1753
|
+
slot: string;
|
|
1754
|
+
attachment: string;
|
|
1755
|
+
/** The key's own time, as emitted. */
|
|
1756
|
+
time: number;
|
|
1757
|
+
/**
|
|
1758
|
+
* The deform array actually written, when it is not `offsets` itself
|
|
1759
|
+
* (issue #389).
|
|
1760
|
+
*
|
|
1761
|
+
* Present only on a multi-influence attachment, where the model is
|
|
1762
|
+
* evaluated at setup **world** positions and `offsets` is therefore one
|
|
1763
|
+
* world displacement per vertex, while the array holds one `Mᵢ⁻¹ · D` pair
|
|
1764
|
+
* per bone INFLUENCE. Absent everywhere else, because there the two are
|
|
1765
|
+
* the same numbers and a second copy of them could only ever drift.
|
|
1766
|
+
*/
|
|
1767
|
+
expanded?: number[];
|
|
1768
|
+
}
|
|
1769
|
+
>;
|
|
1770
|
+
/**
|
|
1771
|
+
* Group-track keys whose per-member values were **stated as a map** or
|
|
1772
|
+
* **derived from a model**, one entry per key in emit order — reported by
|
|
1773
|
+
* `explain` (issue #295).
|
|
1774
|
+
*
|
|
1775
|
+
* ⭐ Both spellings are here, and that is the point of the report rather than
|
|
1776
|
+
* an accident of it: what an author needs to see is the members' values *side
|
|
1777
|
+
* by side*, and whether they were transcribed or derived is one column of that
|
|
1778
|
+
* table. The values carried are the **emitted** ones, so the printed audit and
|
|
1779
|
+
* the artifact cannot disagree.
|
|
1780
|
+
*/
|
|
1781
|
+
trackDerivations: Array<{
|
|
1782
|
+
animation: string;
|
|
1783
|
+
/** The group the track named, or the bone if a bone track stated a model. */
|
|
1784
|
+
target: string;
|
|
1785
|
+
/** `group` or `bone` — which field carried the target. */
|
|
1786
|
+
targetKind: 'group' | 'bone';
|
|
1787
|
+
property: string;
|
|
1788
|
+
/** The key's own time, as emitted for the FIRST member (before any `stagger`). */
|
|
1789
|
+
time: number;
|
|
1790
|
+
/** The authored key time, which is what the spec names. */
|
|
1791
|
+
authoredTime: number;
|
|
1792
|
+
/** Absent when the key stated a `v` map rather than a model. */
|
|
1793
|
+
model: TrackDeriveReport | null;
|
|
1794
|
+
/** Every member's emitted value, in member order. */
|
|
1795
|
+
members: Array<{ member: string; value: number[] | string | null }>;
|
|
1796
|
+
}>;
|
|
1797
|
+
}
|