rig-c 0.0.0-stage → 2.20.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +19 -0
- package/.claude-plugin/plugin.json +13 -0
- package/LICENSE +30 -0
- package/NOTICE.md +145 -0
- package/README.md +817 -3
- package/bin/rigc.cjs +83 -0
- package/cli.ts +61 -0
- package/cli_core.ts +46 -0
- package/docs/AUTHORING.md +9923 -0
- package/docs/FACE.md +1948 -0
- package/docs/INGEST.md +1488 -0
- package/docs/MOTION.md +1241 -0
- package/docs/PROMPTING.md +109 -0
- package/docs/RIGGING.md +1441 -0
- package/docs/SPEC_COVERAGE.md +357 -0
- package/package.json +108 -4
- package/skills/rigc/SKILL.md +133 -0
- package/skills/rigc-face/SKILL.md +60 -0
- package/skills/rigc-ingest/SKILL.md +78 -0
- package/skills/rigc-motion/SKILL.md +51 -0
- package/skills/rigc-rigging/SKILL.md +49 -0
- package/src/areaband.ts +159 -0
- package/src/assertions/bodies/a01.ts +23 -0
- package/src/assertions/bodies/a02.ts +21 -0
- package/src/assertions/bodies/a03.ts +27 -0
- package/src/assertions/bodies/a04.ts +40 -0
- package/src/assertions/bodies/a05.ts +56 -0
- package/src/assertions/bodies/a06.ts +245 -0
- package/src/assertions/bodies/a07.ts +68 -0
- package/src/assertions/bodies/a08.ts +76 -0
- package/src/assertions/bodies/a09.ts +82 -0
- package/src/assertions/bodies/a10.ts +116 -0
- package/src/assertions/bodies/a11.ts +15 -0
- package/src/assertions/bodies/a12.ts +30 -0
- package/src/assertions/bodies/a13.ts +51 -0
- package/src/assertions/bodies/a14.ts +35 -0
- package/src/assertions/bodies/a15.ts +97 -0
- package/src/assertions/bodies/a16.ts +24 -0
- package/src/assertions/bodies/a17.ts +26 -0
- package/src/assertions/bodies/a18.ts +62 -0
- package/src/assertions/bodies/a19.ts +404 -0
- package/src/assertions/bodies/a20.ts +122 -0
- package/src/assertions/bodies/a21.ts +190 -0
- package/src/assertions/bodies/a22.ts +39 -0
- package/src/assertions/bodies/a23.ts +305 -0
- package/src/assertions/bodies/a24.ts +68 -0
- package/src/assertions/bodies/a25.ts +39 -0
- package/src/assertions/bodies/a26.ts +61 -0
- package/src/assertions/bodies/a27.ts +33 -0
- package/src/assertions/bodies/a28.ts +70 -0
- package/src/assertions/bodies/a29.ts +34 -0
- package/src/assertions/bodies/a30.ts +50 -0
- package/src/assertions/bodies/a31.ts +61 -0
- package/src/assertions/bodies/a32.ts +44 -0
- package/src/assertions/bodies/a33.ts +110 -0
- package/src/assertions/bodies/a34.ts +133 -0
- package/src/assertions/bodies/a35.ts +160 -0
- package/src/assertions/bodies/a36.ts +81 -0
- package/src/assertions/bodies/a37.ts +77 -0
- package/src/assertions/bodies/a38.ts +73 -0
- package/src/assertions/bodies/a39.ts +303 -0
- package/src/assertions/bodies/a40.ts +128 -0
- package/src/assertions/bodies/a42.ts +97 -0
- package/src/assertions/bodies/a43.ts +181 -0
- package/src/assertions/bodies/a44.ts +23 -0
- package/src/assertions/bodies/a45.ts +172 -0
- package/src/assertions/bodies/a46.ts +224 -0
- package/src/assertions/bodies/a47.ts +126 -0
- package/src/assertions/bodies/a48.ts +83 -0
- package/src/assertions/bodies/a49.ts +81 -0
- package/src/assertions/bodies/a50.ts +97 -0
- package/src/assertions/constraint_words.ts +169 -0
- package/src/assertions/emitted/index.ts +148 -0
- package/src/assertions/facts/animated_bones.ts +30 -0
- package/src/assertions/facts/animation_durations.ts +37 -0
- package/src/assertions/facts/atlas_pages.ts +19 -0
- package/src/assertions/facts/atlas_regions.ts +52 -0
- package/src/assertions/facts/bone_timelines.ts +37 -0
- package/src/assertions/facts/constraint_targets.ts +56 -0
- package/src/assertions/facts/constraints.ts +155 -0
- package/src/assertions/facts/deform_survey.ts +27 -0
- package/src/assertions/facts/event_keys.ts +55 -0
- package/src/assertions/facts/linked_meshes.ts +38 -0
- package/src/assertions/facts/mesh_attachments.ts +100 -0
- package/src/assertions/facts/region_joins.ts +34 -0
- package/src/assertions/facts/sequences.ts +85 -0
- package/src/assertions/facts/skeleton_roster.ts +45 -0
- package/src/assertions/facts/skin_entries.ts +37 -0
- package/src/assertions/facts/skin_members.ts +53 -0
- package/src/assertions/facts/slider_composition.ts +78 -0
- package/src/assertions/facts/slot_colour.ts +43 -0
- package/src/assertions/facts/stage.ts +27 -0
- package/src/assertions/facts/stage_box.ts +65 -0
- package/src/assertions/facts/stepped_poses.ts +74 -0
- package/src/assertions/facts/two_colour.ts +52 -0
- package/src/assertions/facts/vertex_polygons.ts +53 -0
- package/src/assertions/footprints.ts +367 -0
- package/src/assertions/harness.ts +109 -0
- package/src/assertions/inward_advance.ts +58 -0
- package/src/assertions/kinds.ts +105 -0
- package/src/assertions/mesh_kinds.ts +56 -0
- package/src/assertions/model/animated_bones.ts +38 -0
- package/src/assertions/model/animation_durations.ts +57 -0
- package/src/assertions/model/atlas_pages.ts +15 -0
- package/src/assertions/model/atlas_regions.ts +76 -0
- package/src/assertions/model/bone_timelines.ts +58 -0
- package/src/assertions/model/constraint_targets.ts +82 -0
- package/src/assertions/model/constraints.ts +233 -0
- package/src/assertions/model/declared.ts +125 -0
- package/src/assertions/model/deform_survey.ts +24 -0
- package/src/assertions/model/event_keys.ts +45 -0
- package/src/assertions/model/given.ts +45 -0
- package/src/assertions/model/index.ts +398 -0
- package/src/assertions/model/linked_meshes.ts +24 -0
- package/src/assertions/model/mesh_attachments.ts +119 -0
- package/src/assertions/model/parse.ts +146 -0
- package/src/assertions/model/region_joins.ts +67 -0
- package/src/assertions/model/runtime_timelines.ts +78 -0
- package/src/assertions/model/sequences.ts +157 -0
- package/src/assertions/model/skeleton_roster.ts +23 -0
- package/src/assertions/model/skin_entries.ts +69 -0
- package/src/assertions/model/skin_members.ts +64 -0
- package/src/assertions/model/slider_composition.ts +193 -0
- package/src/assertions/model/slot_colour.ts +81 -0
- package/src/assertions/model/stage.ts +28 -0
- package/src/assertions/model/stage_box.ts +51 -0
- package/src/assertions/model/stepped_poses.ts +105 -0
- package/src/assertions/model/two_colour.ts +61 -0
- package/src/assertions/model/vertex_polygons.ts +72 -0
- package/src/assertions/reasons.ts +129 -0
- package/src/assertions/region_lookups.ts +61 -0
- package/src/assertions/report.ts +189 -0
- package/src/assertions/values.ts +39 -0
- package/src/atlas.ts +2870 -0
- package/src/ballot.ts +866 -0
- package/src/bonedist.ts +643 -0
- package/src/chainfit.ts +2752 -0
- package/src/chains.ts +170 -0
- package/src/check.ts +4303 -0
- package/src/checkpics.ts +295 -0
- package/src/cli/core_commands.ts +1627 -0
- package/src/cli/repack.ts +414 -0
- package/src/cli/shared.ts +2776 -0
- package/src/cli/spine_commands.ts +820 -0
- package/src/compile.ts +9414 -0
- package/src/core/additive.ts +458 -0
- package/src/core/animation.ts +1050 -0
- package/src/core/clipping.ts +696 -0
- package/src/core/constraints.ts +1876 -0
- package/src/core/constraints_path.ts +964 -0
- package/src/core/constraints_physics.ts +881 -0
- package/src/core/constraints_slider.ts +635 -0
- package/src/core/deform.ts +613 -0
- package/src/core/draw_order.ts +125 -0
- package/src/core/events.ts +135 -0
- package/src/core/hooks.ts +249 -0
- package/src/core/index.ts +1400 -0
- package/src/core/raw.ts +739 -0
- package/src/core/skins.ts +129 -0
- package/src/core/uvs.ts +469 -0
- package/src/core/vertices.ts +490 -0
- package/src/core/walk.ts +197 -0
- package/src/core/world.ts +289 -0
- package/src/correspondence.ts +15 -0
- package/src/deformbuild.ts +60 -0
- package/src/deformgen.ts +630 -0
- package/src/deformmeasure.ts +732 -0
- package/src/deformreport.ts +373 -0
- package/src/deformstructure.ts +386 -0
- package/src/deformsurvey.ts +2162 -0
- package/src/depth.ts +784 -0
- package/src/diff.ts +2252 -0
- package/src/emit.ts +134 -0
- package/src/emit_spine.ts +854 -0
- package/src/errors.ts +53 -0
- package/src/framing.ts +819 -0
- package/src/generation.ts +139 -0
- package/src/ingest.ts +2293 -0
- package/src/json-position.ts +253 -0
- package/src/keyorder.ts +587 -0
- package/src/keys.ts +486 -0
- package/src/ladder.ts +121 -0
- package/src/mesh.ts +2382 -0
- package/src/meshcompare.ts +1188 -0
- package/src/meshquality.ts +2042 -0
- package/src/meshrasters.ts +944 -0
- package/src/meshreduce.ts +1425 -0
- package/src/model.ts +1245 -0
- package/src/motion.ts +809 -0
- package/src/nonfinite.ts +54 -0
- package/src/package_meta.ts +48 -0
- package/src/png.ts +297 -0
- package/src/pose.ts +2324 -0
- package/src/preview.ts +434 -0
- package/src/region_joins.ts +54 -0
- package/src/render.ts +1013 -0
- package/src/render_core.ts +871 -0
- package/src/render_shared.ts +2958 -0
- package/src/repack.ts +495 -0
- package/src/rig.ts +2941 -0
- package/src/slots.ts +892 -0
- package/src/spine_side.ts +138 -0
- package/src/timelines.ts +837 -0
- package/src/trackgen.ts +364 -0
- package/src/transform.ts +310 -0
- package/src/types.ts +1797 -0
- package/src/validate.ts +3875 -0
- package/tools/contact.ts +126 -0
- package/tools/editor_roundtrip.ts +1641 -0
- package/tools/font5x7.ts +101 -0
- package/tools/measure_contact_depth.ts +105 -0
- package/tools/plate.ts +508 -0
- package/tools/png_probe.mjs +72 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What kind of rule each assertion is, and which profile runs which kind
|
|
3
|
+
* (issue #1025, step 4c of #380).
|
|
4
|
+
*
|
|
5
|
+
* The table lives here rather than in `src/validate.ts` because two harnesses
|
|
6
|
+
* read it now: `validate()`'s `check`, which runs an assertion over what
|
|
7
|
+
* spine-core loaded, and the model side's (`./harness.ts`), which runs the
|
|
8
|
+
* same body over the model document and the core. A profile has to exclude
|
|
9
|
+
* the same rules on both sides, and two copies of the table is how one of
|
|
10
|
+
* them would come to exclude a rule the other runs. This module links nothing
|
|
11
|
+
* from the runtime, which is the condition for the model side to read it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** The two rulebooks — `ValidateProfile` in `src/validate.ts`, where the choice between them is argued. */
|
|
15
|
+
export type AssertionProfile = 'spine' | 'spine-html';
|
|
16
|
+
|
|
17
|
+
/** The three kinds a rule can be; the table's own comment says what each means. */
|
|
18
|
+
export type AssertionKind = 'validity' | 'renderer' | 'archetype';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What kind of rule each assertion is. Every assertion has an entry, and
|
|
22
|
+
* `check()` throws on a name that has none — a new assertion must state its kind
|
|
23
|
+
* rather than defaulting into one, because the default would decide, silently,
|
|
24
|
+
* whether it runs on foreign data.
|
|
25
|
+
*
|
|
26
|
+
* validity — the file is wrong for any consumer. Runs under every profile.
|
|
27
|
+
* renderer — valid Spine that this project's renderer or frame budget refuses.
|
|
28
|
+
* archetype — a structural rule about rigc's own formations, meaningless to a
|
|
29
|
+
* skeleton rigc did not compile.
|
|
30
|
+
*
|
|
31
|
+
* Two assertions are MIXED and are marked `validity` here because their
|
|
32
|
+
* validity half must never stop running; their policy clauses are gated inside
|
|
33
|
+
* the assertion body against `profile`, and each such clause says so where it
|
|
34
|
+
* lives. They are A06 (size-vs-PNG and every region's rectangle inside the page
|
|
35
|
+
* it names are validity; pma and rotation are policy — the rectangle moved
|
|
36
|
+
* across that line in issue #694, and the reason is stated where it now sits;
|
|
37
|
+
* the policy clause about two regions over the same texels left A06 for
|
|
38
|
+
* `A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP` in issue #1099, a renderer rule of
|
|
39
|
+
* its own) and A20 (weight coherence is validity; requiring
|
|
40
|
+
* a mesh to be weighted at all is policy).
|
|
41
|
+
*
|
|
42
|
+
* A08 was the third until issue #574 retired its policy clause. It required a
|
|
43
|
+
* skin entry's placeholder to be spelled like the region it resolves to, under
|
|
44
|
+
* a renderer that resolves art by `path` and has never read a placeholder — so
|
|
45
|
+
* restating it as the renderer's own join made it a tautology over the validity
|
|
46
|
+
* half. The argument, with the renderer lines it is measured against, sits above
|
|
47
|
+
* `check('A08_…')`.
|
|
48
|
+
*/
|
|
49
|
+
export const ASSERTION_KIND: Readonly<Record<string, AssertionKind>> = {
|
|
50
|
+
A00_ROUNDTRIP_PARSE: 'validity',
|
|
51
|
+
A01_NO_LEGACY_TOPLEVEL_CONSTRAINT_ARRAYS: 'validity',
|
|
52
|
+
A02_NO_BONE_TRANSFORM_KEY: 'validity',
|
|
53
|
+
A03_REGION_WIDTH_HEIGHT_FINITE: 'validity',
|
|
54
|
+
A04_MESH_TRIANGLES_AND_ENCODING: 'validity',
|
|
55
|
+
A05_CURVE_ARRAY_LENGTH: 'validity',
|
|
56
|
+
A06_ATLAS_PAGE_SIZE_MATCHES_PNG: 'validity', // mixed — see above
|
|
57
|
+
A07_ATLAS_TEXT_SHAPE: 'validity',
|
|
58
|
+
A08_REGION_NAMES_MATCH_ATTACHMENTS: 'validity', // no longer mixed — see above (#574)
|
|
59
|
+
A09_ANIMATION_DURATION_MATCHES_SPEC: 'validity',
|
|
60
|
+
A10_NO_NAN_AFTER_STEPPING: 'validity',
|
|
61
|
+
A11_NO_CLIPPING_ATTACHMENTS: 'renderer',
|
|
62
|
+
A12_NO_DARK_COLOR: 'renderer',
|
|
63
|
+
A13_MESH_BUDGET: 'renderer',
|
|
64
|
+
A14_NO_FULL_FRAME_MESH: 'renderer',
|
|
65
|
+
A15_IDLE_NO_MESH_BONE_KEYS: 'renderer',
|
|
66
|
+
A16_SKELETON_VERSION_4_3: 'validity',
|
|
67
|
+
A17_ATLAS_PAGE_FILES_EXIST: 'validity',
|
|
68
|
+
A18_DETERMINISTIC_EMIT: 'validity',
|
|
69
|
+
A19_OVERLAY_PNGS_HAVE_ALPHA: 'renderer',
|
|
70
|
+
A20_MESH_WEIGHTS_COHERENT: 'validity', // mixed — see above
|
|
71
|
+
A21_MESH_RIM_PINNED: 'archetype',
|
|
72
|
+
A22_MESH_UVS_IN_UNIT_RANGE: 'validity',
|
|
73
|
+
A23_PHYSICS_CONSTRAINT_EFFECTIVE: 'validity',
|
|
74
|
+
A24_AXIS_SPACE_STROKE: 'archetype',
|
|
75
|
+
A25_DETACHED_BONE_PARENTAGE: 'archetype',
|
|
76
|
+
A26_SLOT_DRAW_ORDER: 'archetype',
|
|
77
|
+
A27_REGION_NAME_MATCHES_PAGE_FILENAME: 'renderer',
|
|
78
|
+
A28_RIBBON_ROWS_SHARE_WEIGHTS: 'archetype',
|
|
79
|
+
A29_STROKE_WITHIN_CONTACT_DEPTH: 'archetype',
|
|
80
|
+
A30_STROKE_WITHIN_CAP_CONTAINMENT: 'archetype',
|
|
81
|
+
A31_DRAW_ORDER_OFFSETS_RESOLVE: 'validity',
|
|
82
|
+
A32_EVENT_KEYS_RESOLVE: 'validity',
|
|
83
|
+
A33_VERTEX_ATTACHMENT_GEOMETRY: 'validity',
|
|
84
|
+
A34_CONSTRAINT_TIMELINE_TARGETS: 'validity',
|
|
85
|
+
A35_DEFORM_KEYS_FIT_THE_ATTACHMENT: 'validity',
|
|
86
|
+
A36_PATH_CONSTRAINT_EFFECTIVE: 'validity',
|
|
87
|
+
A37_SLIDER_CONSTRAINT_EFFECTIVE: 'validity',
|
|
88
|
+
A38_SKIN_MEMBERS_ARE_SKIN_REQUIRED: 'validity',
|
|
89
|
+
A39_DEFORM_KEEPS_TRIANGLE_WINDING: 'archetype',
|
|
90
|
+
A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET: 'validity',
|
|
91
|
+
A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER: 'validity',
|
|
92
|
+
A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN: 'validity',
|
|
93
|
+
A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN: 'validity',
|
|
94
|
+
A45_SEPARABLE_COLOR_TIMELINES_OWN_THEIR_CHANNELS_AND_POSE_AS_WRITTEN: 'validity',
|
|
95
|
+
A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES: 'validity',
|
|
96
|
+
A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT: 'validity',
|
|
97
|
+
A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT: 'validity',
|
|
98
|
+
A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP: 'renderer',
|
|
99
|
+
A50_STAGE_BOX_IS_THE_STAGE: 'validity',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Whether `profile` runs a rule of `kind`: `spine` runs validity alone, `spine-html` every kind. */
|
|
103
|
+
export function kindRunsUnder(kind: AssertionKind, profile: AssertionProfile): boolean {
|
|
104
|
+
return kind === 'validity' || profile === 'spine-html';
|
|
105
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What built a mesh, as the bodies that hold generator topology to its own
|
|
3
|
+
* rules ask it (issue #1025, step 4c of #380) — `A20`, `A21` and `A28`. Moved
|
|
4
|
+
* here from `validate()`'s closures unchanged but for what they read: a mesh
|
|
5
|
+
* entry of `./facts/mesh_attachments.ts` rather than a loaded attachment and
|
|
6
|
+
* the prelude's link map. Links nothing from the runtime.
|
|
7
|
+
*/
|
|
8
|
+
import type { RigInfo } from '../types.ts';
|
|
9
|
+
import type { MeshEntry } from './facts/mesh_attachments.ts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* What built this mesh. ring unless the rig says otherwise; absent rig info
|
|
13
|
+
* reads as ring (legacy).
|
|
14
|
+
*
|
|
15
|
+
* 🚨 `authored` is not a third topology, it is the ABSENCE of one rigc may
|
|
16
|
+
* assume. Geometry that came in through the rig spec was drawn by somebody
|
|
17
|
+
* with an editor, and its rim, its row pairing and its entry edge are
|
|
18
|
+
* whatever that person made them. The `||` fallback below used to hand such
|
|
19
|
+
* a mesh the string `ring`, and A21 then checked ring topology on a shape
|
|
20
|
+
* that was never a ring — 40 failures on correct data (issue #44).
|
|
21
|
+
*
|
|
22
|
+
* 🔗 A LINKED mesh borrows another attachment's geometry, so no generator
|
|
23
|
+
* topology is a claim about THIS attachment — and the ARTIFACT says which
|
|
24
|
+
* ones they are (the entry's `link`, read off the file's own `source` keys).
|
|
25
|
+
* It is read from there rather than off `meshKinds` for two reasons, and the
|
|
26
|
+
* second is the load-bearing one.
|
|
27
|
+
*
|
|
28
|
+
* 1. `meshKinds` is keyed by SLOT, and a link's slot is not its
|
|
29
|
+
* geometry's: a link to a `ring` in another slot looked up the LINK's
|
|
30
|
+
* slot, found nothing, and took the `|| 'ring'` fallback — issue #44's
|
|
31
|
+
* own default, reached by a new route. Measured before this clause on
|
|
32
|
+
* a correct rig (a ring on slot "sa", a link to it on slot "sb"):
|
|
33
|
+
* **8 A21 failures**, `mesh "sb" rim vertex 0 is pinned to "a", not
|
|
34
|
+
* the slot bone "b"`, one per hull vertex. The rim is pinned exactly
|
|
35
|
+
* where the ring's own slot put it, which is the only place it could
|
|
36
|
+
* be.
|
|
37
|
+
* 2. Writing the link into `meshKinds` instead would have overwritten
|
|
38
|
+
* the source's kind wherever the two share a slot, which is the
|
|
39
|
+
* commonest linked mesh there is — silencing A21 on the mesh rigc
|
|
40
|
+
* DID build. A gate turned off by a feature is worse than a gate
|
|
41
|
+
* that skips something it cannot measure.
|
|
42
|
+
*/
|
|
43
|
+
export function meshKindOf(mesh: MeshEntry, rig: RigInfo | undefined): RigInfo['meshKinds'][string] {
|
|
44
|
+
if (mesh.link !== null) return 'authored';
|
|
45
|
+
return rig?.meshKinds[mesh.slot] || 'ring';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Meshes whose topology is not rigc's to have an opinion about, by name and
|
|
50
|
+
* with the reason each one is on the list — the string several skips need.
|
|
51
|
+
*/
|
|
52
|
+
export function authoredMeshNames(list: readonly MeshEntry[], rig: RigInfo | undefined): string[] {
|
|
53
|
+
return list
|
|
54
|
+
.filter((m) => meshKindOf(m, rig) === 'authored')
|
|
55
|
+
.map((m) => (m.link === null ? `"${m.name}"` : `"${m.name}" (linked to "${m.link.source}")`));
|
|
56
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model side's supply of `AnimatedBoneFacts` (issue #1025, step 4c of
|
|
3
|
+
* #380): an animation's bone timelines as the document lists them.
|
|
4
|
+
*
|
|
5
|
+
* The document writes each animation's `bones` as a list in the model's map
|
|
6
|
+
* order, and the emitter writes the Spine file's `bones` group from that same
|
|
7
|
+
* map, entry for entry, leaving the group out when it is empty
|
|
8
|
+
* (`emitAnimation`'s `if (animation.bones.size)`) — so an empty list is a file
|
|
9
|
+
* with no `bones` group, and an animation the document does not hold is one the
|
|
10
|
+
* file does not key. The first animation of a name answers, as the file's
|
|
11
|
+
* object would hold only one.
|
|
12
|
+
*
|
|
13
|
+
* 🔢 **The order is `keyedOrder` over the list's, not the list's** (issue
|
|
14
|
+
* #1039). The group is an object keyed by bone name, so the file lists an
|
|
15
|
+
* array-index name (`5`, `10`) first whatever order the emitter filled it in —
|
|
16
|
+
* measured on a walk whose tracks key `-a, 5, 2b, 10, …`: the document lists
|
|
17
|
+
* that, the file and spine-core `5, 10, -a, 2b, …`, and A15 prints one line per
|
|
18
|
+
* keyed bone in the file's order.
|
|
19
|
+
*
|
|
20
|
+
* Links nothing from the runtime.
|
|
21
|
+
*/
|
|
22
|
+
import { keyedOrder } from '../../compile.ts';
|
|
23
|
+
import type { AnimatedBoneFacts } from '../facts/animated_bones.ts';
|
|
24
|
+
import { isObj } from '../values.ts';
|
|
25
|
+
import type { ReadDocument } from './parse.ts';
|
|
26
|
+
|
|
27
|
+
export function modelAnimatedBones(read: ReadDocument): AnimatedBoneFacts {
|
|
28
|
+
const animations = Array.isArray(read.json.animations) ? read.json.animations.filter(isObj) : [];
|
|
29
|
+
return {
|
|
30
|
+
bonesKeyedBy: (name) => {
|
|
31
|
+
const animation = animations.find((a) => a.name === name);
|
|
32
|
+
if (animation === undefined) return undefined;
|
|
33
|
+
const bones = Array.isArray(animation.bones) ? animation.bones.filter(isObj) : [];
|
|
34
|
+
if (bones.length === 0) return null;
|
|
35
|
+
return keyedOrder(bones.map((bone) => String(bone.name)));
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model side's supply of `AnimationDurationFacts` (issue #1025, cut 4c-3
|
|
3
|
+
* of step 4c of #380): the document's animations in the file's order, each
|
|
4
|
+
* with the runtime's duration and every timeline's own duration.
|
|
5
|
+
*
|
|
6
|
+
* Every number is the core's reading, through functions rigc already runs:
|
|
7
|
+
*
|
|
8
|
+
* - **The roster's order** is the file's, `fileAnimationOrder` (`src/compile.ts`,
|
|
9
|
+
* issue #1034): a `rigc-compiled/3` document's stated `editorOrder`, else the
|
|
10
|
+
* emitter's own rule over the document's names — integer-like names first,
|
|
11
|
+
* as the file keys them.
|
|
12
|
+
* - **An animation's duration** is `CoreAnimationTimelines.duration`, the last
|
|
13
|
+
* key time of every timeline as float32 — the value `tools/pose_oracle.ts
|
|
14
|
+
* compare` holds equal to spine-core's on its `animations` roster.
|
|
15
|
+
* - **A timeline's own duration** is its last key's time as the core read it
|
|
16
|
+
* (float32): each bone and slot timeline, each constraint timeline in the
|
|
17
|
+
* order the runtime builds them (`modelConstraintTimelineSources`), each
|
|
18
|
+
* attachment's deform and sequence timeline, the draw order and the events.
|
|
19
|
+
* How many timelines that makes is the count the runtime builds
|
|
20
|
+
* (`timelineCount`, which the selftest's `VF10` holds equal to the runtime's
|
|
21
|
+
* on every slider animation); a walk that lists another count is this
|
|
22
|
+
* module's defect and throws by name rather than handing A09 a list of the
|
|
23
|
+
* wrong length.
|
|
24
|
+
*
|
|
25
|
+
* Links nothing from the runtime.
|
|
26
|
+
*/
|
|
27
|
+
import { fileAnimationOrder } from '../../compile.ts';
|
|
28
|
+
import type { AnimationDuration, AnimationDurationFacts } from '../facts/animation_durations.ts';
|
|
29
|
+
import { modelConstraintTimelineSources, timelineCount } from './constraints.ts';
|
|
30
|
+
import type { ReadDocument } from './parse.ts';
|
|
31
|
+
|
|
32
|
+
/** The last of `times`, which the core read in key order. */
|
|
33
|
+
const lastOf = (times: ReadonlyArray<{ time: number }>): number[] => (times.length === 0 ? [] : [times[times.length - 1].time]);
|
|
34
|
+
|
|
35
|
+
export function modelAnimationDurations(read: ReadDocument): AnimationDurationFacts {
|
|
36
|
+
const constraintTimelines = modelConstraintTimelineSources(read);
|
|
37
|
+
const animations: AnimationDuration[] = [];
|
|
38
|
+
for (const name of fileAnimationOrder(read.doc)) {
|
|
39
|
+
const anim = read.doc.animations.find((a) => a.name === name);
|
|
40
|
+
if (anim === undefined) continue;
|
|
41
|
+
const t = anim.timelines;
|
|
42
|
+
const durations: number[] = [];
|
|
43
|
+
for (const target of [...t.bones, ...t.slots]) for (const tl of target.timelines) durations.push(...lastOf(tl.keys));
|
|
44
|
+
for (const source of constraintTimelines) if (source.animation === name) durations.push(...lastOf(source.keys));
|
|
45
|
+
for (const a of t.attachments) {
|
|
46
|
+
if (a.deform !== null) durations.push(...lastOf(a.deform));
|
|
47
|
+
if (a.sequence !== null) durations.push(...lastOf(a.sequence));
|
|
48
|
+
}
|
|
49
|
+
durations.push(...lastOf(t.drawOrder), ...lastOf(t.events));
|
|
50
|
+
const built = timelineCount(t);
|
|
51
|
+
if (durations.length !== built) {
|
|
52
|
+
throw new Error(`internal: animation "${name}": the durations walk lists ${durations.length} timeline(s) and the core reads ${built}`);
|
|
53
|
+
}
|
|
54
|
+
animations.push({ name, duration: t.duration, timelineDurations: durations });
|
|
55
|
+
}
|
|
56
|
+
return { animations };
|
|
57
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model side's supply of `AtlasPageFacts` (issue #1025, step 4c of #380):
|
|
3
|
+
* the document's `pages` section, which `build` spells from the atlas text it
|
|
4
|
+
* writes (`pagesOfAtlas` in `src/model.ts`), so the page names are the ones
|
|
5
|
+
* the written atlas names, in its order and trimmed as the parser trims them.
|
|
6
|
+
* A `rigc-compiled/1` document states no pages, and reads as no atlas.
|
|
7
|
+
*
|
|
8
|
+
* Links nothing from the runtime.
|
|
9
|
+
*/
|
|
10
|
+
import type { AtlasPageFacts } from '../facts/atlas_pages.ts';
|
|
11
|
+
import type { ReadDocument } from './parse.ts';
|
|
12
|
+
|
|
13
|
+
export function modelAtlasPages(read: ReadDocument): AtlasPageFacts {
|
|
14
|
+
return { atlas: read.doc.pages === null ? null : { pages: read.doc.pages.map((page) => ({ name: page.name })) } };
|
|
15
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model side's supply of `AtlasRegionFacts` (issue #1025, step 4c of
|
|
3
|
+
* #380): the document's `pages` section — which `build` spells from the atlas
|
|
4
|
+
* text it writes (`pagesOfAtlas` in `src/model.ts`, the reader `PKR01` holds
|
|
5
|
+
* field for field to spine-core's `TextureAtlas`) — with each page's `pma` as
|
|
6
|
+
* a `rigc-compiled/3` document states it (issue #1026), or, for a `/2`
|
|
7
|
+
* document, which does not, as the caller gives it.
|
|
8
|
+
*
|
|
9
|
+
* - The pages and their regions in the document's order, which is the atlas
|
|
10
|
+
* file's; each region carries the page it sits on.
|
|
11
|
+
* - `findRegion` is the core's lookup, called: `documentPageLookup`
|
|
12
|
+
* (`src/core/uvs.ts`), the first region of exactly that spelling with pages
|
|
13
|
+
* in order — the runtime's `findRegion`, measured there.
|
|
14
|
+
* - A `rigc-compiled/1` document states no pages, and reads as no atlas.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ On a `/2` document `pma` is the one value here the document does not
|
|
17
|
+
* hold. A caller that gives none, or gives a list that is not one value per
|
|
18
|
+
* page, is refused by name rather than read as `false`: a page's claim the
|
|
19
|
+
* caller did not state is not a claim of straight alpha. On a `/3` document a
|
|
20
|
+
* caller that gives one anyway is refused too (`refuseGivenBeside`): the
|
|
21
|
+
* document is the statement, and a second source for the same fact is how a
|
|
22
|
+
* verdict comes to read one value while the document says another.
|
|
23
|
+
*
|
|
24
|
+
* Links nothing from the runtime.
|
|
25
|
+
*/
|
|
26
|
+
import { documentPageLookup } from '../../core/uvs.ts';
|
|
27
|
+
import type { ModelPageRegion } from '../../model.ts';
|
|
28
|
+
import type { AtlasRegionEntry, AtlasRegionFacts, AtlasRegionPage } from '../facts/atlas_regions.ts';
|
|
29
|
+
import type { ReadDocument } from './parse.ts';
|
|
30
|
+
import { refuseGivenBeside, type ModelGiven } from './given.ts';
|
|
31
|
+
|
|
32
|
+
export function modelAtlasRegions(read: ReadDocument, given: ModelGiven | undefined): AtlasRegionFacts {
|
|
33
|
+
refuseGivenBeside(read, given);
|
|
34
|
+
const pages = read.doc.pages;
|
|
35
|
+
if (pages === null) return { atlas: null };
|
|
36
|
+
// A rigc-compiled/3 document states each page's pma (issue #1026); `readModel` requires it on every /3 page.
|
|
37
|
+
const pma = read.doc.stated !== null ? pages.map((page) => page.pma) : given?.pma;
|
|
38
|
+
if (pma === undefined || pma.length !== pages.length || pma.some((v) => typeof v !== 'boolean')) {
|
|
39
|
+
throw new Error(`the caller gave ${pma === undefined ? 'no' : pma.length} page pma value(s) for the document's ${pages.length} page(s); a ${read.doc.spec} document does not state pma, so the model side cannot read a page without it`);
|
|
40
|
+
}
|
|
41
|
+
const outPages: AtlasRegionPage[] = [];
|
|
42
|
+
const regions: AtlasRegionEntry[] = [];
|
|
43
|
+
const byRecord = new Map<ModelPageRegion, AtlasRegionEntry>();
|
|
44
|
+
pages.forEach((page, i) => {
|
|
45
|
+
const at: AtlasRegionPage = { name: page.name, width: page.width, height: page.height, pma: pma[i] === true };
|
|
46
|
+
outPages.push(at);
|
|
47
|
+
for (const region of page.regions) {
|
|
48
|
+
const entry: AtlasRegionEntry = {
|
|
49
|
+
name: region.name,
|
|
50
|
+
page: at,
|
|
51
|
+
x: region.x,
|
|
52
|
+
y: region.y,
|
|
53
|
+
width: region.width,
|
|
54
|
+
height: region.height,
|
|
55
|
+
degrees: region.degrees,
|
|
56
|
+
offsetX: region.offsetX,
|
|
57
|
+
offsetY: region.offsetY,
|
|
58
|
+
originalWidth: region.originalWidth,
|
|
59
|
+
originalHeight: region.originalHeight,
|
|
60
|
+
};
|
|
61
|
+
regions.push(entry);
|
|
62
|
+
byRecord.set(region, entry);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
const lookup = documentPageLookup(pages);
|
|
66
|
+
return {
|
|
67
|
+
atlas: {
|
|
68
|
+
pages: outPages,
|
|
69
|
+
regions,
|
|
70
|
+
findRegion: (name) => {
|
|
71
|
+
const found = lookup(name);
|
|
72
|
+
return found === null ? null : (byRecord.get(found.region) ?? null);
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model side's supply of `BoneTimelineFacts` (issue #1025, cut 4c-4 of
|
|
3
|
+
* #380): every animation's bone timelines as the Spine file states them.
|
|
4
|
+
*
|
|
5
|
+
* The same reading A45's slot timelines are given (`fileSlotTimelines` in
|
|
6
|
+
* `./slot_colour.ts`), over the `bones` group: the animations put in the
|
|
7
|
+
* file's order by `editorAnimationOrder` (`src/compile.ts`, the function the
|
|
8
|
+
* emitter is handed), each animation's bones and their timelines laid out as
|
|
9
|
+
* the file lays them (`animations.<name>.bones.<bone>.<timeline> = keys`,
|
|
10
|
+
* filled in the document's order, which is the order `emitAnimation` writes
|
|
11
|
+
* the model's map in — and read back out of that object, so an array-index
|
|
12
|
+
* bone name comes first as it does in the file: issue #1039 measured this
|
|
13
|
+
* reader already walking the file's order), and the whole handed to the emitter's own `withoutParserDefaults`
|
|
14
|
+
* and `inEditorKeyOrder` (`src/keyorder.ts`) — so a key field the emitter
|
|
15
|
+
* leaves out at the parser's default (`time: 0`, a translate key's `y: 0`) is
|
|
16
|
+
* left out here too, and a body printing a key's `time` prints what it prints
|
|
17
|
+
* over the file. Nothing here restates either rule.
|
|
18
|
+
*
|
|
19
|
+
* Links nothing from the runtime.
|
|
20
|
+
*/
|
|
21
|
+
import { editorAnimationOrder } from '../../compile.ts';
|
|
22
|
+
import { inEditorKeyOrder, withoutParserDefaults } from '../../keyorder.ts';
|
|
23
|
+
import type { BoneTimelineFacts, BoneTimelines } from '../facts/bone_timelines.ts';
|
|
24
|
+
import { isObj, type Json } from '../values.ts';
|
|
25
|
+
import type { ReadDocument } from './parse.ts';
|
|
26
|
+
|
|
27
|
+
const list = (v: unknown): Json[] => (Array.isArray(v) ? v.filter(isObj) : []);
|
|
28
|
+
|
|
29
|
+
/** Every animation's bone timelines as the Spine file states them: `animations.<name>.bones.<bone>.<timeline> = keys`. */
|
|
30
|
+
export function fileBoneTimelines(read: ReadDocument): BoneTimelines[] {
|
|
31
|
+
const byName = new Map<string, Json>();
|
|
32
|
+
for (const anim of list(read.json.animations)) if (typeof anim.name === 'string') byName.set(anim.name, anim);
|
|
33
|
+
const animations: Json = {};
|
|
34
|
+
for (const name of editorAnimationOrder([...byName.keys()])) {
|
|
35
|
+
const bones: Json = {};
|
|
36
|
+
for (const bone of list((byName.get(name) as Json).bones)) {
|
|
37
|
+
if (typeof bone.name !== 'string') continue;
|
|
38
|
+
const timelines: Json = {};
|
|
39
|
+
for (const timeline of list(bone.timelines)) {
|
|
40
|
+
if (typeof timeline.name === 'string') timelines[timeline.name] = list(timeline.keys).map((key) => ({ ...key }));
|
|
41
|
+
}
|
|
42
|
+
bones[bone.name] = timelines;
|
|
43
|
+
}
|
|
44
|
+
// An animation keying no bone writes no `bones` group — `emitAnimation`'s `if (animation.bones.size)`.
|
|
45
|
+
animations[name] = Object.keys(bones).length > 0 ? { bones } : {};
|
|
46
|
+
}
|
|
47
|
+
const file = inEditorKeyOrder(withoutParserDefaults({ animations }));
|
|
48
|
+
const out: BoneTimelines[] = [];
|
|
49
|
+
for (const [animation, anim] of Object.entries(file.animations)) {
|
|
50
|
+
if (!isObj(anim) || !isObj(anim.bones)) continue;
|
|
51
|
+
for (const [bone, timelines] of Object.entries(anim.bones)) out.push({ animation, bone, timelines });
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function modelBoneTimelines(read: ReadDocument): BoneTimelineFacts {
|
|
57
|
+
return { boneTimelines: fileBoneTimelines(read) };
|
|
58
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model side's supply of `ConstraintTargetFacts` (issue #1025, cut 4c-5
|
|
3
|
+
* of step 4c of #380): the document's constraints and every animation's
|
|
4
|
+
* constraint groups, laid out as the Spine file lays them.
|
|
5
|
+
*
|
|
6
|
+
* - **The constraints**: the document's, in its order — which the emitter
|
|
7
|
+
* writes the `constraints` array in — each its name and its kind, the word
|
|
8
|
+
* the file spells its `type` with.
|
|
9
|
+
* - **The animations**: in the file's order (`fileAnimationOrder`, issue
|
|
10
|
+
* #1034); in each the five groups in A34's fixed order, a group only where
|
|
11
|
+
* the document holds an entry for it (`emitAnimation` writes a group only
|
|
12
|
+
* when the model holds something for it); each group's entries in the order
|
|
13
|
+
* the file keys them — an object the emitter fills in the document's order,
|
|
14
|
+
* so its names land as `keyedOrder` lands them, built here as that object;
|
|
15
|
+
* the timeline naming no physics constraint under the empty name the
|
|
16
|
+
* emitter spells it with, where the document spells it `*`
|
|
17
|
+
* (`EVERY_GLOBAL_PHYSICS`); each entry's timelines in the document's order,
|
|
18
|
+
* which the emitter writes unchanged (a name-keyed map no key-order pass
|
|
19
|
+
* touches, `src/keyorder.ts`). A document the reader accepted states every
|
|
20
|
+
* key array as a non-empty list, a named group's entry as named timelines,
|
|
21
|
+
* and only constraints of the kind its group names, so the clauses that
|
|
22
|
+
* refuse those shapes have nothing to fire on here.
|
|
23
|
+
* - **The reach** of a physics timeline naming none: the core's own reading,
|
|
24
|
+
* `unnamedReach` (`./constraints.ts`, which poses the timeline through
|
|
25
|
+
* `posedPhysics` and reads which records took the key), over the document's
|
|
26
|
+
* physics records in its order. The reader refuses a physics timeline name
|
|
27
|
+
* it does not know, so every name asked of it is one the core poses.
|
|
28
|
+
*
|
|
29
|
+
* Links nothing from the runtime.
|
|
30
|
+
*/
|
|
31
|
+
import { fileAnimationOrder } from '../../compile.ts';
|
|
32
|
+
import { EVERY_GLOBAL_PHYSICS, PHYSICS_TIMELINE_KINDS, type CorePhysicsRecord } from '../../core/constraints_physics.ts';
|
|
33
|
+
import type { ConstraintTargetFacts, TargetAnimation, TargetEntry, TargetKeyArray } from '../facts/constraint_targets.ts';
|
|
34
|
+
import { isObj, type Json } from '../values.ts';
|
|
35
|
+
import { unnamedReach } from './constraints.ts';
|
|
36
|
+
import type { ReadDocument } from './parse.ts';
|
|
37
|
+
|
|
38
|
+
const list = (v: unknown): Json[] => (Array.isArray(v) ? v.filter(isObj) : []);
|
|
39
|
+
const keyArray = (timeline: string, keys: unknown): TargetKeyArray => ({ timeline, keys: Array.isArray(keys) ? { count: keys.length } : { spelled: `${JSON.stringify(keys)}` } });
|
|
40
|
+
|
|
41
|
+
/** One group's entries as the file keys them: an object filled in the document's order, read back in its key order. */
|
|
42
|
+
function fileEntries(group: 'ik' | 'transform' | 'path' | 'physics' | 'slider', entries: readonly Json[]): TargetEntry[] {
|
|
43
|
+
const keyed: Record<string, TargetEntry> = {};
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (typeof entry.name !== 'string') continue;
|
|
46
|
+
const name = group === 'physics' && entry.name === EVERY_GLOBAL_PHYSICS ? '' : entry.name;
|
|
47
|
+
keyed[name] =
|
|
48
|
+
group === 'ik' || group === 'transform'
|
|
49
|
+
? { name, bare: null, keyArrays: [keyArray('', entry.keys)] }
|
|
50
|
+
: { name, bare: null, keyArrays: list(entry.timelines).flatMap((t) => (typeof t.name === 'string' ? [keyArray(t.name, t.keys)] : [])) };
|
|
51
|
+
}
|
|
52
|
+
return Object.values(keyed);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function modelConstraintTargets(read: ReadDocument): ConstraintTargetFacts {
|
|
56
|
+
const { doc } = read;
|
|
57
|
+
const byName = new Map<string, Json>();
|
|
58
|
+
for (const anim of list(read.json.animations)) if (typeof anim.name === 'string') byName.set(anim.name, anim);
|
|
59
|
+
const animations: TargetAnimation[] = [];
|
|
60
|
+
for (const name of fileAnimationOrder(doc)) {
|
|
61
|
+
const anim = byName.get(name);
|
|
62
|
+
if (anim === undefined) continue;
|
|
63
|
+
const groups: TargetAnimation['groups'][number][] = [];
|
|
64
|
+
const constraints = isObj(anim.constraints) ? anim.constraints : {};
|
|
65
|
+
for (const group of ['ik', 'transform', 'path', 'physics', 'slider'] as const) {
|
|
66
|
+
const entries = list(constraints[group]);
|
|
67
|
+
if (entries.length > 0) groups.push({ group, entries: fileEntries(group, entries) });
|
|
68
|
+
}
|
|
69
|
+
animations.push({ name, groups });
|
|
70
|
+
}
|
|
71
|
+
const records = doc.constraints.flatMap((c): CorePhysicsRecord[] => (c.record?.kind === 'physics' ? [c.record] : []));
|
|
72
|
+
return {
|
|
73
|
+
constraints: doc.constraints.map((c) => ({ name: c.name, type: c.kind, spelled: c.name })),
|
|
74
|
+
groupsByAnimation: animations,
|
|
75
|
+
reach: (timeline) => {
|
|
76
|
+
const kind = PHYSICS_TIMELINE_KINDS.find((k) => k === timeline);
|
|
77
|
+
if (kind === undefined) return null;
|
|
78
|
+
const reached = unnamedReach(records, kind);
|
|
79
|
+
return { resets: kind === 'reset', reached: records.filter((r) => reached.has(r.name)).map((r) => r.name) };
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|