@kieransimkin/dance-rudiments 0.2.4 → 0.4.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/AGENTS.md +72 -0
- package/CONTRIBUTING.md +8 -0
- package/README.md +261 -45
- package/dist/recording/recording.wasm +0 -0
- package/dist/recording/recording.wasm.json +5 -0
- package/dist/typescript/index.d.ts +2 -1
- package/dist/typescript/index.js +1 -0
- package/dist/typescript/motion-packages.d.ts +125 -0
- package/dist/typescript/motion-packages.js +625 -0
- package/docs/blender-motion-import.md +228 -0
- package/docs/mesh-demos.md +163 -0
- package/docs/midi-harness.md +180 -0
- package/docs/motion-packages.md +324 -0
- package/docs/release-demo.md +197 -0
- package/docs/rig-motion.md +285 -0
- package/docs/rigify-catalogue.md +246 -0
- package/examples/motion-packages/inspect_package.py +21 -0
- package/examples/motion-packages/page-motion.mjs +168 -0
- package/package.json +25 -4
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Optional rig motion packages
|
|
2
|
+
|
|
3
|
+
Rig recordings are independent data packages. They are **not default rudiments,
|
|
4
|
+
dependencies of the base package, or site-wide assets**. A character page opts
|
|
5
|
+
into one exact rig profile and loads selected recordings. Leaving the page can
|
|
6
|
+
release its network requests, cached recordings and native instances.
|
|
7
|
+
|
|
8
|
+
The existing 1,731-pattern catalogue is unchanged. Importing the Python authoring
|
|
9
|
+
package or TypeScript module does not read any optional motion package. NumPy is
|
|
10
|
+
an optional **offline BVH import** dependency; Blender is only required for the
|
|
11
|
+
Blender import/retarget bridge. Neither is a browser runtime dependency.
|
|
12
|
+
|
|
13
|
+
These APIs are development source until a matching stable release is published.
|
|
14
|
+
|
|
15
|
+
## Contents and identity
|
|
16
|
+
|
|
17
|
+
An independent package directory contains:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
manifest.json
|
|
21
|
+
profile.json
|
|
22
|
+
clips/<clip-id>.json.gz
|
|
23
|
+
README.md
|
|
24
|
+
LICENSE.txt
|
|
25
|
+
THIRD_PARTY_NOTICES.md
|
|
26
|
+
SOURCES.json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The manifest has format `dancerudiments.motion-package`, schema version 1, and an
|
|
30
|
+
independently versioned package ID. It declares one exact rig profile, its
|
|
31
|
+
SHA-256, canonical semantic type URI, ordered channel names, licence/provenance,
|
|
32
|
+
and descriptors for individual clips. Every descriptor includes a SHA-256,
|
|
33
|
+
stored/decoded byte lengths, encoding, absolute `start_pip`, `sample_count`, and
|
|
34
|
+
optional finite metadata. Catalogue reads need only this manifest.
|
|
35
|
+
|
|
36
|
+
`profile.json` is canonical ASCII JSON without a trailing newline. Its file
|
|
37
|
+
SHA-256 equals the existing `RigProfile.fingerprint`; consumers hash the actual
|
|
38
|
+
bytes instead of reserializing floats differently in JavaScript. Clip files
|
|
39
|
+
contain the standard `dancerudiments.recording` JSON, optionally gzip compressed.
|
|
40
|
+
The shared profile is stored once per package rather than repeated in every
|
|
41
|
+
clip. There is no second binary recording schema or second live sampler.
|
|
42
|
+
|
|
43
|
+
Compatibility requires **all three** of the scene's type URI, profile hash and
|
|
44
|
+
ordered dimensions. A matching humanoid label or similar bone names is not an
|
|
45
|
+
adequate rig binding. Source BVH/FBX rigs use the separate
|
|
46
|
+
`syn://type/danceflow/motion/skeletal/source/...` namespace. They must not be
|
|
47
|
+
represented as Rigify control recordings until explicitly retargeted.
|
|
48
|
+
|
|
49
|
+
The manifest is limited to 8 MiB and 10,000 descriptors; each profile, stored clip
|
|
50
|
+
and decoded clip is limited to 64 MiB. Each clip also obeys the native maximum of
|
|
51
|
+
8,388,608 scalar values and signed 32-bit pip bounds. Relative paths cannot
|
|
52
|
+
traverse directories, contain URLs, collide case-insensitively, or replace root
|
|
53
|
+
notices. Importers reject duplicate JSON keys, non-finite samples, incomplete
|
|
54
|
+
quaternions and invalid discrete selectors.
|
|
55
|
+
|
|
56
|
+
## Explicit local installation and loading
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
dancerudiments-motion-pack install robot-1.0.0.zip --root ./motion-packs --sha256 <archive-sha256>
|
|
60
|
+
dancerudiments-motion-pack list --root ./motion-packs
|
|
61
|
+
dancerudiments-motion-pack inspect ./motion-packs/robot/1.0.0
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
From a source checkout, use `python tools/motion_package.py` in place of the
|
|
65
|
+
installed command. Installation verifies a local ZIP and atomically creates
|
|
66
|
+
`<root>/<id>/<version>`; it preserves existing versions. It neither downloads nor
|
|
67
|
+
executes code from a package. Archive traversal, symlinks, undeclared files,
|
|
68
|
+
duplicates and configured size violations fail before installation. Every
|
|
69
|
+
declared payload is hash-checked; full recording validation occurs on loading.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from dancerudiments_authoring import MotionPackage, load_profile
|
|
73
|
+
|
|
74
|
+
scene_profile = load_profile("my-character.profile.json")
|
|
75
|
+
with MotionPackage("motion-packs/my_character/1.0.0",
|
|
76
|
+
expected_profile=scene_profile) as package:
|
|
77
|
+
choices = package.catalogue() # metadata only
|
|
78
|
+
motion = package.load(choices[0]["id"])
|
|
79
|
+
player = motion.player() # existing native SampledRecording
|
|
80
|
+
physical_channels = player.by_channel(player.start_pip)
|
|
81
|
+
package.unload(motion.name)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Python caches at most one clip by default; `cache_size=0` disables caching.
|
|
85
|
+
`close()` drops the package's references. Objects deliberately retained by the
|
|
86
|
+
caller remain that caller's responsibility. `discover_motion_packages(root)`
|
|
87
|
+
only searches the directory the caller explicitly supplies; no global install
|
|
88
|
+
scan or default-library registration runs automatically.
|
|
89
|
+
|
|
90
|
+
`MotionPackageBuilder` creates packages from validated `RigMotion` objects.
|
|
91
|
+
`add_bvh_chunk()` accepts the explicitly validated vectorized offline import
|
|
92
|
+
result. `export_motion_package()` writes a deterministic independent ZIP, and
|
|
93
|
+
`resume=True` only resumes a package with identical identity, profile, licence
|
|
94
|
+
and provenance. Existing clip IDs cannot silently be overwritten.
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
dancerudiments-motion-pack create first.motion.json second.motion.json \
|
|
98
|
+
--output ./motion-packs/my_character/1.0.0 --id my_character \
|
|
99
|
+
--title "My character movements" --license CC0-1.0 \
|
|
100
|
+
--license-url https://creativecommons.org/publicdomain/zero/1.0/ \
|
|
101
|
+
--attribution "My own capture" --source-url https://example.org/my-capture \
|
|
102
|
+
--timing-kind music
|
|
103
|
+
dancerudiments-motion-pack export ./motion-packs/my_character/1.0.0 my-character.zip
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Use the real source's licence and timing status. These command arguments record
|
|
107
|
+
the caller's declarations; a converter cannot grant rights or establish beats.
|
|
108
|
+
|
|
109
|
+
## Browser pages and the beat clock
|
|
110
|
+
|
|
111
|
+
The package exposes `@kieransimkin/dance-rudiments/motion-packages` separately.
|
|
112
|
+
Dynamic import it inside the character page's lifecycle. The base module also
|
|
113
|
+
exports the APIs for existing consumers, with no asset-fetch side effects.
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
const {createMotionPackageLoader, createRecordingWasmAdapter} =
|
|
117
|
+
await import('@kieransimkin/dance-rudiments/motion-packages');
|
|
118
|
+
|
|
119
|
+
const response = await fetch(characterPage.recordingWasmUrl);
|
|
120
|
+
if (!response.ok) throw new Error(`WASM HTTP ${response.status}`);
|
|
121
|
+
const nativeModule = await WebAssembly.compile(await response.arrayBuffer());
|
|
122
|
+
const motions = createMotionPackageLoader({
|
|
123
|
+
manifestUrl: characterPage.motionManifestUrl,
|
|
124
|
+
packageId: characterPage.motionPackageId,
|
|
125
|
+
expectedProfile: characterRig.target,
|
|
126
|
+
nativeFactory: createRecordingWasmAdapter(nativeModule),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const catalogue = await motions.catalogue();
|
|
130
|
+
const clip = await motions.load(catalogue.clips[0].id);
|
|
131
|
+
// hostPip is an absolute integer from the existing 64-pip beat transport.
|
|
132
|
+
if (hostPip >= clip.startPip && hostPip < clip.startPip + clip.sampleCount) {
|
|
133
|
+
characterRig.applyChannels(clip.profile, clip.sample(hostPip));
|
|
134
|
+
}
|
|
135
|
+
// On character/page teardown:
|
|
136
|
+
motions.dispose();
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`characterRig.target` must come from the actual saved rig. Copying an untrusted
|
|
140
|
+
manifest's identity into this field does not establish compatibility. Applying
|
|
141
|
+
channels requires the host's rig evaluator; Rigify constraints and IK require a
|
|
142
|
+
capable evaluator or an explicitly baked/deformation-rig export. This library
|
|
143
|
+
does not silently add a mesh renderer to the XYZ visualizer.
|
|
144
|
+
|
|
145
|
+
The raw native recording accessor is exposed at the `./recording-wasm` package
|
|
146
|
+
subpath. Configure its hosted URL using the consuming app's build system. It is
|
|
147
|
+
compiled from the existing C++ recording implementation and hash-checked against
|
|
148
|
+
its sources when packaged. It is not fetched or instantiated on module import.
|
|
149
|
+
|
|
150
|
+
`createMotionPackageLoader()` is inert until called. `catalogue()` loads metadata;
|
|
151
|
+
`load(id)` fetches the profile once and only the selected clip. Handles provide
|
|
152
|
+
`sampleNormalized(pip)` for native values and `sample(pip)` for decoded physical
|
|
153
|
+
channels. In-flight requests are deduplicated, failed requests may be retried,
|
|
154
|
+
and each caller's abort only cancels that caller's wait. When no waiters remain,
|
|
155
|
+
the request can be cancelled. `unload(id)` invalidates that clip and releases its
|
|
156
|
+
native instance; `dispose()` releases everything owned by that loader.
|
|
157
|
+
|
|
158
|
+
The general browser loader retains requested clips until explicitly unloaded.
|
|
159
|
+
The complete [page example](../examples/motion-packages/page-motion.mjs) maintains
|
|
160
|
+
**one current chunk**, cancels obsolete requests on seeking, ignores late
|
|
161
|
+
results, subscribes to the host clock and cleans up on route exit. Repeated
|
|
162
|
+
frame ticks reuse a pending chunk request. Failures require explicit retry,
|
|
163
|
+
avoiding a request loop on every animation frame. The host keeps the beat clock;
|
|
164
|
+
the example creates no independent clock or sampler.
|
|
165
|
+
|
|
166
|
+
For a static server, serve `.json.gz` assets as raw `application/gzip`, **without
|
|
167
|
+
`Content-Encoding: gzip`**. Otherwise HTTP auto-decompression changes the stored
|
|
168
|
+
bytes before integrity validation. JSON may use `application/json`; WASM may
|
|
169
|
+
use `application/wasm`. Cross-origin hosting also needs ordinary CORS permission.
|
|
170
|
+
No signed-in fetches are required by the package format.
|
|
171
|
+
|
|
172
|
+
## Source timing, complete actions and chunks
|
|
173
|
+
|
|
174
|
+
Unknown music timing is represented explicitly as `timing_kind: "time-only"`.
|
|
175
|
+
Its 60 BPM clock is a coordinate in which one beat equals one second, sampled at
|
|
176
|
+
64 pips per second. This value is **not an estimated musical tempo**. The original
|
|
177
|
+
frame time, FPS, frame count, duration and source hash remain in provenance.
|
|
178
|
+
|
|
179
|
+
To match a dance to music, supply source BPM **and** beat-zero phase, or use the
|
|
180
|
+
existing `BeatMap`/`compile_capture` path with measured variable-tempo beat
|
|
181
|
+
anchors. Tempo alone does not determine phase. Target tempo changes then follow
|
|
182
|
+
the same absolute native pip clock. Foot-contact correction, transition design
|
|
183
|
+
and automatic audio beat detection are separate authoring work.
|
|
184
|
+
|
|
185
|
+
Long actions are split at contiguous pip boundaries into chunks with at most
|
|
186
|
+
500,000 values. All chunks of one action stay together in one package. Metadata
|
|
187
|
+
records `original_clip_id`, `chunk_index`, `chunk_count`, `total_sample_count`
|
|
188
|
+
and exact source-time spans. Counts of source actions and recording chunks are
|
|
189
|
+
reported separately; splitting does not create new distinct performances.
|
|
190
|
+
|
|
191
|
+
Compilation includes every integer pip inside the valid original time interval,
|
|
192
|
+
including negative prebeat pips when phase requires them. It never extends or
|
|
193
|
+
holds the endpoint. The sub-pip head/tail remainder is recorded. Recordings stay
|
|
194
|
+
non-looping; a caller must deliberately author any loop or transition.
|
|
195
|
+
|
|
196
|
+
## Importing optional source collections
|
|
197
|
+
|
|
198
|
+
Install offline acceleration only when needed:
|
|
199
|
+
|
|
200
|
+
```sh
|
|
201
|
+
python -m pip install '.[motion-import]'
|
|
202
|
+
python tools/import_motion_collection.py download --sources accad
|
|
203
|
+
python tools/import_motion_collection.py build --sources accad
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The explicit source plan is [`motion-packages/sources.json`](../motion-packages/sources.json).
|
|
207
|
+
It records original public URLs, licence/attribution, byte/checksum constraints,
|
|
208
|
+
coordinate policies and output groups. Downloads go to the ignored project
|
|
209
|
+
cache. Converted packages go to ignored `motion-packs/`. Neither is included in
|
|
210
|
+
the base npm package, Python wheel/sdist or native default catalogue. A download
|
|
211
|
+
is an explicit command, never an installation hook.
|
|
212
|
+
|
|
213
|
+
The same commands accept `--sources lifelike_agility style100` for the dog
|
|
214
|
+
capture and 100STYLE locomotion collections. These are general motion sources;
|
|
215
|
+
100STYLE's 100 styles are not 100 dance genres. Source and converted data can
|
|
216
|
+
require several gigabytes, so select collections deliberately.
|
|
217
|
+
|
|
218
|
+
Space-XR publishes individual files with two coordinate conventions. Its
|
|
219
|
+
pinned acquisition recipe keeps their original bytes, performer names, titles,
|
|
220
|
+
aliases and hashes, then assembles two deterministic local source ZIPs:
|
|
221
|
+
|
|
222
|
+
```sh
|
|
223
|
+
python tools/acquire_space_xr.py --download
|
|
224
|
+
python tools/import_motion_collection.py build --sources space_xr_yup space_xr_zup
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Omit `--download` to require already cached originals. The 17 distinct files
|
|
228
|
+
include nine dances/improvisations, seven Laban exercises and one walk/arm
|
|
229
|
+
movement; repeated listing links are not counted as additional performances.
|
|
230
|
+
The adapted data remains **CC BY-SA 4.0**, with the licence and attribution
|
|
231
|
+
inside each optional package. Source plan entries marked `local_archive` are
|
|
232
|
+
assembled by their acquisition recipe, not fetched from a fictitious ZIP URL.
|
|
233
|
+
|
|
234
|
+
The CMU dance recipe selects 89 dance-specific files from a pinned public mirror
|
|
235
|
+
of the documented cgspeed conversion. Exact deduplication yields 84 source
|
|
236
|
+
sequences, including modern dance, salsa, Charleston and other named dances:
|
|
237
|
+
|
|
238
|
+
```sh
|
|
239
|
+
python tools/acquire_cmu_dance.py --download
|
|
240
|
+
python tools/import_motion_collection.py build --sources cmu_dance
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
It retains the converter's calibration first frame and custom CMU terms,
|
|
244
|
+
including **no direct resale of the data**, even after conversion. These terms
|
|
245
|
+
are independent of the software licence. Per-file original URLs, Git blobs,
|
|
246
|
+
SHA-256 values, titles and timing evidence are preserved. The 30 salsa trials
|
|
247
|
+
use the explicitly evidenced 1/60-second correction described below; the other
|
|
248
|
+
files retain their declared frame intervals. Musical beats remain unverified.
|
|
249
|
+
|
|
250
|
+
The Quaternius robot recipe extracts all 14 Actions from the explicitly supplied
|
|
251
|
+
original CC0 archive, including Dance. Run it in the pinned Blender runtime:
|
|
252
|
+
|
|
253
|
+
```sh
|
|
254
|
+
blender --background --factory-startup --python tools/import_quaternius_robot.py -- \
|
|
255
|
+
--archive /path/to/Animated-Robot-Oct-2018.zip \
|
|
256
|
+
--work-dir .cache/motion-sources/robot-rebuild \
|
|
257
|
+
--output-dir motion-packs/quaternius_robot/1.0.0 \
|
|
258
|
+
--package-archive optional-motion-packs/quaternius_robot-1.0.0.zip
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Use a new work/output directory. This recipe downloads nothing, preserves the
|
|
262
|
+
source rig and forms one exact profile for its Actions. The separately supplied
|
|
263
|
+
Blender demonstration retains the original skinned robot and bakes native
|
|
264
|
+
DanceRudiments samples; the base library contains no robot mesh or recordings.
|
|
265
|
+
|
|
266
|
+
Bulk import publishes a complete original action in one atomic manifest update.
|
|
267
|
+
A failed later chunk leaves the prior complete catalogue intact. `--resume`
|
|
268
|
+
requires the same package identity, profile, licence and source plan; interrupted
|
|
269
|
+
clip assets are reused only if regenerated bytes match exactly. A private build
|
|
270
|
+
checkpoint makes interruption before the first manifest recoverable and is
|
|
271
|
+
excluded from exported packages. A new version/output directory is required
|
|
272
|
+
when changing conversion settings or source identity.
|
|
273
|
+
|
|
274
|
+
The importer parses bounded BVH text, preserves the declared hierarchy and Euler
|
|
275
|
+
orders, converts local rotations to continuous unit quaternions, and resamples
|
|
276
|
+
offline with linear translation and shortest-path SLERP. It reports malformed
|
|
277
|
+
files and deduplicates exact source-byte duplicates. Source FPS is read from
|
|
278
|
+
each file, not guessed from the collection name.
|
|
279
|
+
|
|
280
|
+
An explicitly reviewed per-file `frame_time_override` can correct a known bad
|
|
281
|
+
header. `override_bvh_frame_time(clip, frame_time_seconds="1/60", evidence={...})`
|
|
282
|
+
requires nonempty source evidence and retains the original byte hash, declared
|
|
283
|
+
header interval/FPS/duration, pose arrays and profile. Effective timing then
|
|
284
|
+
drives compilation, with the correction recorded in every chunk. It does not
|
|
285
|
+
infer or replace musical tempo. The CMU conversion is one concrete use: its
|
|
286
|
+
converter rewrote headers uniformly, while some original trials ran at 60 fps.
|
|
287
|
+
|
|
288
|
+
BVH does not declare physical units. Scale 1 preserves original numerical units
|
|
289
|
+
and is labelled `source-units-unverified`; an explicit caller scale is recorded.
|
|
290
|
+
Axis conventions and fixed location bounds are part of the profile identity.
|
|
291
|
+
The importer supports both additive BVH positions and Blender's absolute-local
|
|
292
|
+
position convention. Sources with position channels equal to their rest offsets
|
|
293
|
+
must use the latter to avoid doubled bone lengths. This is an explicit source
|
|
294
|
+
policy, not a silent heuristic.
|
|
295
|
+
|
|
296
|
+
The opt-in `root_offset_policy: "motion"` moves only the initial ROOT rest
|
|
297
|
+
translation into recorded root motion. It retains original root offsets in each
|
|
298
|
+
clip's provenance and leaves child proportions unchanged. Identical bodies can
|
|
299
|
+
then share a profile even when captures started at different floor positions.
|
|
300
|
+
The default `"retain"` preserves exact original rest origins. Both policies are
|
|
301
|
+
checked against Blender/native world-space poses; this is not anatomical retargeting.
|
|
302
|
+
|
|
303
|
+
For FBX, glTF/GLB and BLEND Actions, use the
|
|
304
|
+
[Blender import and explicit retarget guide](blender-motion-import.md). Imported
|
|
305
|
+
source rigs remain separately typed, and the exact source rest hierarchy is
|
|
306
|
+
available to reconstruct a compatible carrier.
|
|
307
|
+
|
|
308
|
+
## Validation and ecosystem ownership
|
|
309
|
+
|
|
310
|
+
The package tests cover lazy reads, identity, tampering, bounded decompression,
|
|
311
|
+
atomic installation, path handling and native playback. TypeScript tests use
|
|
312
|
+
the actual checked-in C++ recording WASM, including cancellation and page/chunk
|
|
313
|
+
lifetime. Python-built gzip packages are consumed by that same browser API.
|
|
314
|
+
Pinned Blender tests compare source and reconstructed world transforms, exercise
|
|
315
|
+
multi-action/slot extraction and verify explicit Rigify FK mappings with native
|
|
316
|
+
skin deformation after saving/reopening.
|
|
317
|
+
|
|
318
|
+
DanceRudiments owns the reusable profile, package and recording APIs. Its existing
|
|
319
|
+
native playback engine is sufficient and remains shared. StemLab beat maps can
|
|
320
|
+
supply timing when music exists; the present motion-only sources do not warrant
|
|
321
|
+
running audio inference. DanceMoves/FrameRudiments can consume the page API after
|
|
322
|
+
a compatible shared release; this work does not claim those consumers were
|
|
323
|
+
released or deployed. Motion packages and program releases have separate version
|
|
324
|
+
identities, and each source's licence remains attached to its data.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Versioned release demos and Pages
|
|
2
|
+
|
|
3
|
+
The **Release demo** workflow builds the exact tagged native catalogue, its
|
|
4
|
+
standalone visualizers, and a separate [3D mesh dance gallery](mesh-demos.md).
|
|
5
|
+
It packages the root visualizer and `meshes/` together, attaches verified
|
|
6
|
+
downloads to the same GitHub Release, and can deploy that complete site to
|
|
7
|
+
GitHub Pages. Building, attaching, deploying and checking the live site are
|
|
8
|
+
separate gates.
|
|
9
|
+
|
|
10
|
+
## What changes on the website
|
|
11
|
+
|
|
12
|
+
The root remains the existing native pattern visualizer. A link in its header
|
|
13
|
+
opens `meshes/`, where two original low-poly skinned characters perform different
|
|
14
|
+
recorded dances. The standalone HTML download remains self-contained; the mesh
|
|
15
|
+
gallery is a separate HTTP-served bundle with locally bundled JavaScript, WASM,
|
|
16
|
+
rig profiles and selectively loaded motion files.
|
|
17
|
+
|
|
18
|
+
Earlier skeletal-motion changes added APIs, Rigify profiles, import tools and
|
|
19
|
+
optional packages. Those changes did not turn the XYZ pattern visualizer into a
|
|
20
|
+
mesh renderer. Before this release policy, a successful **Pattern visualizer**
|
|
21
|
+
main-branch deployment could republish the existing 2D viewer with no visible
|
|
22
|
+
3D page. That result does not show that the skeletal API patch failed.
|
|
23
|
+
|
|
24
|
+
With this change, **Pattern visualizer** builds and tests main/PR previews as
|
|
25
|
+
artifacts only. **Release demo** is the sole Pages publisher. It deploys the
|
|
26
|
+
combined `dist/pages` tree, so a later main-branch preview cannot remove the
|
|
27
|
+
mesh gallery.
|
|
28
|
+
|
|
29
|
+
## Trigger and source identity
|
|
30
|
+
|
|
31
|
+
The workflow runs on pushes of `vMAJOR.MINOR.PATCH` tags. It also accepts a manual
|
|
32
|
+
`release_tag` for an existing published release whose tagged source contains
|
|
33
|
+
these tools. It does not use a `release.published` event generated by another
|
|
34
|
+
workflow's `GITHUB_TOKEN`.
|
|
35
|
+
|
|
36
|
+
For a tag push, the checked-out commit must equal both the peeled tag and the
|
|
37
|
+
triggering push commit. A manual run checks that the requested release exists
|
|
38
|
+
and is published. Both paths validate the native, Python and npm versions with
|
|
39
|
+
`tools/release_version.py verify-tag`. Before attaching assets, the workflow
|
|
40
|
+
checks the remote tag again, including annotated tags, against the tested
|
|
41
|
+
commit.
|
|
42
|
+
|
|
43
|
+
Publish a **fresh release containing this change** through the existing release
|
|
44
|
+
process. Tags predating the gallery, including `v0.3.0`, do not contain its
|
|
45
|
+
builders or workflow. Manually rerunning one of those tags cannot add the new
|
|
46
|
+
code, and existing tags must remain unchanged.
|
|
47
|
+
|
|
48
|
+
GitHub's [workflow-trigger documentation](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow)
|
|
49
|
+
explains why ordinary events created using `GITHUB_TOKEN` do not start another
|
|
50
|
+
workflow. The tag-triggered build works alongside the existing tag-release
|
|
51
|
+
workflow and waits for that workflow's GitHub Release before attaching assets.
|
|
52
|
+
|
|
53
|
+
## Build and browser gates
|
|
54
|
+
|
|
55
|
+
The tagged job verifies all generated default collections, installs that
|
|
56
|
+
checkout's native Python extension, and calls the native `catalogue()` and
|
|
57
|
+
`sample()` APIs. It enumerates the actual catalogue rather than maintaining a
|
|
58
|
+
second list of movements. Required default names are checked against
|
|
59
|
+
`collections/defaults.json`, so a stale installed binary cannot silently
|
|
60
|
+
generate a smaller viewer.
|
|
61
|
+
|
|
62
|
+
The general catalogue builder embeds native samples, source notices, interface
|
|
63
|
+
code, CSS and a freshly compiled C++ WebAssembly snapshot sampler into one
|
|
64
|
+
offline HTML file. The renderer checks every pattern's native sample digest,
|
|
65
|
+
negative and extreme pip wrapping, desktop/mobile rendering, filters and
|
|
66
|
+
playback. The MIDI harness runs against the same full viewer. Club and
|
|
67
|
+
Dancefloor audition pages keep their separate existing browser gates.
|
|
68
|
+
|
|
69
|
+
The mesh stage then builds only the two pinned dance sources selected in
|
|
70
|
+
`tools/mesh_demo_sources.json`. This is an explicit workflow download, bounded
|
|
71
|
+
to those two hashed files. It uses the optional package loader and the existing
|
|
72
|
+
native recording WASM sampler. Browser QA checks:
|
|
73
|
+
|
|
74
|
+
- Both weighted triangle skins and their changing deformed vertices.
|
|
75
|
+
- Every normalized and physical channel at four reference pips per dance, plus
|
|
76
|
+
all source-space joint matrices against independently composed references.
|
|
77
|
+
- Play, pause, integer-pip seeking, preview speed, restart and complete-action
|
|
78
|
+
endpoint behavior.
|
|
79
|
+
- No initial rig/motion/WASM download, selected-package-only loading, and native
|
|
80
|
+
handle, geometry and cache disposal on switch and unload.
|
|
81
|
+
- Desktop and mobile layout, local-only runtime requests and browser errors.
|
|
82
|
+
|
|
83
|
+
`tools/package_release_pages.py prepare` adds only the root navigation link;
|
|
84
|
+
it preserves the original standalone HTML and its script/data bytes. The
|
|
85
|
+
complete-site browser report binds the resulting root and mesh files to their
|
|
86
|
+
SHA-256 hashes. `seal` accepts only the exact report schema, matching version
|
|
87
|
+
and commit, `scope: complete-site`, and successful coverage of both `boxbot`
|
|
88
|
+
and `prism`. Changed, missing, unchecked or symlinked assets fail sealing. A
|
|
89
|
+
local `--gallery-only` receipt cannot seal a release site.
|
|
90
|
+
|
|
91
|
+
## Release downloads and reruns
|
|
92
|
+
|
|
93
|
+
The workflow retains the existing native viewer downloads and adds distinct
|
|
94
|
+
site/mesh assets:
|
|
95
|
+
|
|
96
|
+
| Asset | Contents |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| `DanceRudiments-demo-vX.Y.Z.html` and associated JSON, ZIP, PNG and checksum files | The tested standalone native catalogue viewer. |
|
|
99
|
+
| `DanceRudiments-visualizer.html`, `.json`, `.sha256` | Stable download names within that version's release. |
|
|
100
|
+
| `DanceRudiments-pages-vX.Y.Z.zip` | The complete root visualizer plus `meshes/`, notices and site manifest. |
|
|
101
|
+
| `DanceRudiments-pages-vX.Y.Z.json` and `.sha256` | Release identity, every site asset's size/hash, and download checksums. |
|
|
102
|
+
| `DanceRudiments-mesh-demos-vX.Y.Z.zip` | The mesh route under `meshes/`, including both optional motion packages and source notices. |
|
|
103
|
+
| `DanceRudiments-mesh-demos-vX.Y.Z-browser.json`, desktop/mobile PNGs and `.sha256` | Browser evidence for both skins, bound to the packaged bytes. |
|
|
104
|
+
|
|
105
|
+
Only the attachment job receives `contents: write`. It downloads every existing
|
|
106
|
+
same-name asset and compares its bytes **before uploading anything missing**.
|
|
107
|
+
Identical assets are retained; a different existing asset stops the run. New
|
|
108
|
+
attachments are downloaded again and compared. No existing release assets are
|
|
109
|
+
deleted or overwritten. A changed renderer/toolchain may produce different
|
|
110
|
+
bytes on a later rebuild; that does not authorize replacing an immutable
|
|
111
|
+
release attachment.
|
|
112
|
+
|
|
113
|
+
Pages ZIPs are written beside the site, never inside it. This avoids recursively
|
|
114
|
+
including a previous download in the next site artifact. The archives use
|
|
115
|
+
deterministic paths, timestamps and permissions.
|
|
116
|
+
|
|
117
|
+
## Pages deployment and verification
|
|
118
|
+
|
|
119
|
+
Deployment uses the existing repository variable
|
|
120
|
+
`DANCERUDIMENTS_PAGES_ENABLED == 'true'`, the `github-pages` environment and its
|
|
121
|
+
approval rules. An already configured Pages site continues to use those
|
|
122
|
+
settings. The workflow reads existing Pages configuration with
|
|
123
|
+
`enablement: false`; it does not enable Pages or change protections, credentials
|
|
124
|
+
or environment settings.
|
|
125
|
+
|
|
126
|
+
Only the latest stable official GitHub Release may deploy the production site.
|
|
127
|
+
The job checks this before entering deployment, then checks again after waiting
|
|
128
|
+
for the shared `dancerudiments-pages` concurrency slot. Older releases may still
|
|
129
|
+
build and attach their own downloads; the workflow records a visible Pages
|
|
130
|
+
skip when their tag is no longer latest. This prevents a delayed historical
|
|
131
|
+
rebuild from replacing the newer site.
|
|
132
|
+
|
|
133
|
+
The deploy job alone has `pages: write` and `id-token: write`. It deploys the
|
|
134
|
+
already tested Pages artifact. Afterward, the verifier requests both directory
|
|
135
|
+
routes (`/` and `/meshes/`) and every public file, comparing the returned bytes
|
|
136
|
+
with the exact version/commit manifest. It also verifies the hosted manifest
|
|
137
|
+
itself. Precompressed `.json.gz` files must have identity content encoding;
|
|
138
|
+
HTTP decompression must not silently change the bytes the browser loader
|
|
139
|
+
hashes. `.nojekyll` is deployment metadata and need not have a public URL.
|
|
140
|
+
|
|
141
|
+
The `deployed-pages-verification` artifact records this separate live check. A
|
|
142
|
+
successful source test, browser run, upload or deployment action alone is not
|
|
143
|
+
reported as a verified live site. See GitHub's
|
|
144
|
+
[custom Pages workflow guide](https://docs.github.com/en/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages)
|
|
145
|
+
for the existing environment and artifact requirements.
|
|
146
|
+
|
|
147
|
+
## Local build
|
|
148
|
+
|
|
149
|
+
Use a checkout with matching package versions and the native build tools,
|
|
150
|
+
including `clang++` and `wasm-ld` for the standalone catalogue snapshot. Follow
|
|
151
|
+
the temporary-directory preflight in [AGENTS.md](../AGENTS.md) before expensive
|
|
152
|
+
builds. The workflow pins Playwright to the version shown below.
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
python -m pip install '.[motion-import]' playwright==1.64.0
|
|
156
|
+
python -m playwright install chromium
|
|
157
|
+
npm ci
|
|
158
|
+
|
|
159
|
+
DEMO_VERSION=$(node -p 'require("./package.json").version')
|
|
160
|
+
DEMO_COMMIT=$(git rev-parse HEAD)
|
|
161
|
+
python tools/release_demo.py --version "$DEMO_VERSION" --commit "$DEMO_COMMIT" --output-dir dist/demo
|
|
162
|
+
python tools/test_midi_harness.py "dist/demo/DanceRudiments-demo-v$DEMO_VERSION.html"
|
|
163
|
+
python tools/render_release_demo.py "dist/demo/DanceRudiments-demo-v$DEMO_VERSION.html"
|
|
164
|
+
|
|
165
|
+
python tools/build_mesh_demo_assets.py --output-dir generated/mesh-demo-assets --cache-root .cache/motion-sources/cmu-dance --download
|
|
166
|
+
node tools/build_mesh_demo.mjs --assets-dir generated/mesh-demo-assets --output-dir dist/pages/meshes --version "$DEMO_VERSION" --commit "$DEMO_COMMIT"
|
|
167
|
+
python tools/package_release_pages.py prepare --pages-dir dist/pages --visualizer "dist/demo/DanceRudiments-demo-v$DEMO_VERSION.html" --version "$DEMO_VERSION" --commit "$DEMO_COMMIT"
|
|
168
|
+
python tools/test_mesh_demo_browser.py --site-dir dist/pages --output-dir dist/demo --prefix "DanceRudiments-mesh-demos-v$DEMO_VERSION"
|
|
169
|
+
python tools/package_release_pages.py seal --pages-dir dist/pages --output-dir dist/demo --qa-report "dist/demo/DanceRudiments-mesh-demos-v$DEMO_VERSION-browser.json" --version "$DEMO_VERSION" --commit "$DEMO_COMMIT"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The asset builder requires a fresh output directory and uses only verified
|
|
173
|
+
cached sources when `--download` is omitted. Mesh-only development and the
|
|
174
|
+
`--chromium` override are documented in [mesh-demos.md](mesh-demos.md).
|
|
175
|
+
|
|
176
|
+
The standalone visualizer's default renderer checks `file://` navigation. Its
|
|
177
|
+
explicit `--in-memory` option records that narrower mode for environments that
|
|
178
|
+
forbid file navigation. The mesh gallery uses HTTP because its ES modules and
|
|
179
|
+
optional package loader fetch separate files.
|
|
180
|
+
|
|
181
|
+
## Catalogue presentation and snapshot storage
|
|
182
|
+
|
|
183
|
+
The full catalogue remains available through collection, family, search and
|
|
184
|
+
review filters. Only visible cards animate; offscreen canvas backing stores are
|
|
185
|
+
released and restored while their phase comes from the shared clock. Personal
|
|
186
|
+
notes, comparison, local audio/BPM synchronization, oblique 3D paths and a
|
|
187
|
+
separate Z curve remain available. This generated viewer is a read-only native
|
|
188
|
+
snapshot; authoring export controls stay hidden.
|
|
189
|
+
|
|
190
|
+
Schema 2 stores movement tables once inside WASM, with a SHA-256 digest of the
|
|
191
|
+
native-exported little-endian float64 XYZ values per pattern. Signed zero is
|
|
192
|
+
normalized for those digests. The renderer reads samples in bounded batches and
|
|
193
|
+
also accepts legacy schema-1 snapshots containing decimal tables. Base64 is
|
|
194
|
+
decoded in bounded chunks. The general snapshot permits up to 4,096 patterns
|
|
195
|
+
and 4,194,304 samples; untrusted authoring packs retain their separate existing
|
|
196
|
+
limits. The 68-study Dancefloor page retains all 16 beat mappings, four-slot
|
|
197
|
+
comparison, synthesized audio and its exact scalar checks.
|