@kieransimkin/dancemoves 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +338 -0
  2. package/README.md +893 -0
  3. package/RUDIMENTS-API.md +394 -0
  4. package/docs/README.md +73 -0
  5. package/docs/api/adapters.md +400 -0
  6. package/docs/api/downloads.md +158 -0
  7. package/docs/api/effects.md +463 -0
  8. package/docs/api/javascript.md +500 -0
  9. package/docs/api/paper-planes.md +167 -0
  10. package/docs/api/styling.md +292 -0
  11. package/docs/api/surface.json +446 -0
  12. package/docs/api/wordpress.md +365 -0
  13. package/docs/clay-transform-validation.md +84 -0
  14. package/docs/development.md +254 -0
  15. package/docs/guide.md +288 -0
  16. package/docs/javascript-api.md +292 -0
  17. package/docs/javascript-server.md +150 -0
  18. package/docs/react-next.md +185 -0
  19. package/docs/releasing-shared.md +162 -0
  20. package/docs/rudiments-source-integrity.md +80 -0
  21. package/docs/shared-feature-coverage.md +34 -0
  22. package/docs/shared-runtime-migration.md +111 -0
  23. package/docs/source-audit.md +120 -0
  24. package/docs/validation-setup.md +102 -0
  25. package/docs/wordpress-examples.md +206 -0
  26. package/docs/wordpress-shared-runtime.md +133 -0
  27. package/lib/admin.min.js +1 -0
  28. package/lib/assets/DanceRudiments-LICENSE +21 -0
  29. package/lib/assets/DanceRudiments-build-manifest.json +21 -0
  30. package/lib/assets/paper-dreams-plane-atlas.png +0 -0
  31. package/lib/build-manifest.json +117 -0
  32. package/lib/dancemoves.css +9 -0
  33. package/lib/dancemoves.min.js +5 -0
  34. package/lib/index.cjs +4802 -0
  35. package/lib/index.d.ts +71 -0
  36. package/lib/index.mjs +4780 -0
  37. package/lib/react.cjs +154 -0
  38. package/lib/react.d.ts +12 -0
  39. package/lib/react.mjs +135 -0
  40. package/lib/rudiments.d.ts +124 -0
  41. package/lib/server.cjs +411 -0
  42. package/lib/server.d.ts +11 -0
  43. package/lib/server.mjs +367 -0
  44. package/lib/styles/clay-stars-effects.css +1 -0
  45. package/lib/styles/clay-stars-rudiments.css +1 -0
  46. package/lib/styles/dance-moves-core.css +1 -0
  47. package/lib/styles/ks-epk-device-orientation.css +1 -0
  48. package/lib/styles/paper-dreams-flight.css +1 -0
  49. package/lib/wordpress.js +5 -0
  50. package/package.json +101 -0
@@ -0,0 +1,394 @@
1
+ # DanceMoves rudiment animation API
2
+
3
+ API **1.0.0**, introduced by the **2.9.0 integration candidate**, based on released DanceMoves **2.8.0** (`4cb6a71f60459b5579be87d7e55ac8b1426c578e`). This source patch is not a published release or evidence of a WordPress deployment.
4
+
5
+ The integration pins DanceRudiments **0.1.3**, commit `0df000db8753f04f397311f5b7c6ae6561e1d260`. The pinned C++ source, header and MIT licence are under [vendor/dancerudiments](vendor/dancerudiments/). [UPSTREAM.json](vendor/dancerudiments/UPSTREAM.json) records their Git blob identities. [TypeScript declarations](RUDIMENTS-API.d.ts) describe the complete public interface.
6
+
7
+ ## Contents
8
+
9
+ [Architecture](#architecture-and-native-authority) · [Quick start](#quick-start) · [Catalogue](#catalogue) · [Service API](#service-api) · [Animation options](#animation-options) · [Controller API](#controller-api) · [Frame payload](#render-callback-payload) · [Clocks](#clock-and-lifecycle-contract) · [CSS](#css-and-html-contract) · [Events](#events-and-errors) · [Clay migration](#claystars-integration) · [WordPress](#wordpress-and-packaging) · [Build and validation](#building-and-testing) · [Limitations](#limits-and-extension-policy)
10
+
11
+ ## Architecture and native authority
12
+
13
+ DanceRudiments defines positions; DanceMoves supplies musical time, lifecycle and rendering. There is no second audio clock, no beat detector, no LRC reparser and no JavaScript copy of a movement formula.
14
+
15
+ The backend is a **complete-integer-domain C++/WASM lookup build**, not the upstream npm/Emscripten wrapper. This distinction matters:
16
+
17
+ 1. The build compiles and executes the unmodified upstream C++17 library. It samples **every** integer pip of **every** catalogue period, checking positive and negative wrap parity.
18
+ 2. The resulting IEEE-double values are emitted as hexadecimal C++ literals. Clang compiles those tables and a tiny C++ lookup ABI into import-free WebAssembly.
19
+ 3. The generated browser asset contains the native module as base64 plus catalogue metadata. JavaScript selects the pattern and musical pip; native WASM returns the three coordinates.
20
+
21
+ For this pinned upstream API, each function depends only on a name and an integer pip, with a finite periodic domain. Exhaustively compiling that domain preserves the upstream sampling contract; it is not sparse curve fitting or a JS approximation. The 15 patterns comprise 1,856 unique positions. The shipped native module is 44,921 bytes, with fixed 128 KiB linear memory and no host imports. It does not call JavaScript trigonometry. The build compares all three axes at 5,568 wrapped positions against the native exporter, with tolerance `1e-12` for platform floating-point variation.
22
+
23
+ The compiled browser asset is checked in. **No npm install, CDN access, Python interpreter, compiler or WASM MIME configuration is needed on the WordPress server.** Compilation is lazy and occurs once per document when `ready()` or `animate()` is used. If WASM is unavailable or blocked, loading rejects; there is deliberately no fallback JS motion implementation. Clay retains its existing CSS instead.
24
+
25
+ This backend covers the pinned **integer-position** API only. Future upstream parameterised, stateful, curve or event APIs must receive a separately versioned binding. They must not silently be reduced to this fixed lookup contract.
26
+
27
+ ## Quick start
28
+
29
+ Enqueue your page adapter with `dance-moves-rudiments` as a WordPress script dependency. That ensures the synchronous API object exists before the adapter executes; `ready()` handles asynchronous WASM compilation.
30
+
31
+ ```js
32
+ const rudiments = window.DanceMovesRudiments;
33
+ // window.DanceMoves.rudiments is the same object.
34
+ const controller = rudiments.animate({
35
+ id: 'my-release:hero-sway',
36
+ rudiment: 'sway',
37
+ root: '.my-release',
38
+ target: '.my-release .moving-subject',
39
+ clock: 'audio',
40
+ audio: '.my-release audio',
41
+ amplitude: { x: 12, y: 0, z: 0 }
42
+ });
43
+ controller.ready.catch(error => {
44
+ console.warn('Rudiment unavailable; keep the static artwork.', error);
45
+ controller.destroy();
46
+ });
47
+ // On page-adapter teardown, call controller.destroy().
48
+ ```
49
+
50
+ ```css
51
+ .my-release .moving-subject {
52
+ transform: translate3d(
53
+ var(--dance-moves-rudiment-x, 0px),
54
+ var(--dance-moves-rudiment-y, 0px),
55
+ var(--dance-moves-rudiment-z, 0px)
56
+ );
57
+ }
58
+ @media (prefers-reduced-motion: reduce), (forced-colors: active) {
59
+ .my-release .moving-subject { transform: none; }
60
+ }
61
+ ```
62
+
63
+ The target must already exist. Target a bounded decorative child, not player controls or a full-page colour layer. The default CSS renderer writes variables, **not** a `transform`, so it cannot overwrite an existing transform accidentally. A page may compose those variables with its own CSS transform or use a separate child for motion.
64
+
65
+ For manual/native sampling without an animation controller:
66
+
67
+ ```js
68
+ await window.DanceMovesRudiments.ready();
69
+ const offset = window.DanceMovesRudiments.sample('clay_background', 100);
70
+ // Approximately { x: -1, y: 0.9, z: 0 }; caller selects units.
71
+ const pip = window.DanceMovesRudiments.pipsFromTicks(16); // 64, not 16
72
+ ```
73
+
74
+ ## Catalogue
75
+
76
+ Use names as identifiers. `index` is native catalogue order for this pin, not a stable identifier across upgrades. Dimensions indicate occupied axes, not automatic mapping to a particular CSS direction. In particular, upstream's `bounce` description says vertical, but its single coordinate is returned in **x**; a vertical consumer must map x to its y translation.
77
+
78
+ | Name | Period pips | Beats at rate 1 | Dimensions |
79
+ | --- | ---: | ---: | ---: |
80
+ | `bounce` | 64 | 1 | 1 |
81
+ | `sway` | 128 | 2 | 1 |
82
+ | `circle` | 64 | 1 | 2 |
83
+ | `figure_eight` | 128 | 2 | 2 |
84
+ | `step_touch` | 128 | 2 | 2 |
85
+ | `box_step` | 256 | 4 | 2 |
86
+ | `helix` | 256 | 4 | 3 |
87
+ | `clay_background` | 256 | 4 | 2 |
88
+ | `single_stroke_roll` | 64 | 1 | 2 |
89
+ | `double_stroke_roll` | 64 | 1 | 2 |
90
+ | `multiple_bounce_roll` | 64 | 1 | 2 |
91
+ | `single_paradiddle` | 128 | 2 | 2 |
92
+ | `flam` | 64 | 1 | 2 |
93
+ | `drag` | 64 | 1 | 2 |
94
+ | `five_stroke_roll` | 128 | 2 | 2 |
95
+
96
+ ## Service API
97
+
98
+ The frozen service is available as both `window.DanceMovesRudiments` and `window.DanceMoves.rudiments` after the integration script executes. It does not replace `DanceMoves`, `KieranEpkMotion` or `DanceMovesEffects`.
99
+
100
+ ### Properties
101
+
102
+ | Property | Value / meaning |
103
+ | --- | --- |
104
+ | `version` | `'1.0.0'`: this API's contract version, not the plugin version |
105
+ | `upstreamVersion` | `'0.1.3'`: the bundled library version |
106
+ | `upstreamCommit` | Exact pinned source commit |
107
+ | `pipsPerBeat` | `64` |
108
+ | `ticksPerBeat` | `16`: DanceMoves timing ticks |
109
+ | `pipsPerTick` | `4` |
110
+
111
+ ### `ready(): Promise<RudimentApi>`
112
+
113
+ Lazily compile and validate the bundled WASM ABI, pattern count and periods. Repeated calls return the same promise and never create duplicate native instances. Resolves with the service. Rejects when data is absent/incompatible, WASM compilation is unavailable/blocked or the native ABI disagrees. A rejected load is cached; reload the document after correcting the cause rather than creating a retry loop.
114
+
115
+ `catalogue()`, `describe()` and unit conversion work before native compilation. `sample()` requires readiness. `animate()` obtains readiness internally and exposes its own `controller.ready`.
116
+
117
+ ### `catalogue(): readonly RudimentInfo[]`
118
+
119
+ Return the frozen catalogue array; each record is also frozen. Fields: `name`, `description`, `periodPips`, `dimensions` and `index`. No WASM compilation is started by this call.
120
+
121
+ ### `describe(name: string): RudimentInfo | null`
122
+
123
+ Exact, case-sensitive lookup. Returns the same frozen catalogue record or `null` for an unknown name. Does not create an instance.
124
+
125
+ ### `sample(name: RudimentName, pip: number): Offset3`
126
+
127
+ Return a fresh `{ x, y, z }` native offset for an **integer** musical pip. Coordinates are dimensionless and normally within `[-1,1]`. Negative inputs wrap around the period. JavaScript safe integers larger than signed 32-bit are reduced to the period **before** entering the native signed-int ABI.
128
+
129
+ Unknown names throw `RangeError`; non-numeric/non-finite inputs throw `TypeError`; fractions or unsafe integers throw `RangeError`; an unready backend throws `Error`. There is no implicit numeric-string coercion, interpolation, amplitude scaling, DOM write, timestamp conversion or registration.
130
+
131
+ ### `pipsFromTicks(ticks: number): number`
132
+
133
+ Convert DanceMoves ticks to an integer pip: `floor(ticks * 4)`. Finite fractions and negative positions are allowed. A tiny `1e-9` pip tolerance prevents floating-point roundoff just below an exact musical boundary from selecting the preceding pip. The result must be a safe integer. Do **not** pass this value through `DanceMoves.quantizeTicks()`.
134
+
135
+ ### `pipsFromSeconds(seconds: number, bpm?: number): number`
136
+
137
+ Convert seconds to pips: `floor(seconds * bpm * 64 / 60)`, with the same boundary tolerance. BPM defaults to the page's `DanceMoves.bpm`; explicit BPM must be finite and within `[20,400]`. Seconds must be a finite number. This is a pure conversion; it does not inspect audio playback or change the page BPM.
138
+
139
+ ### `animate(options: RudimentOptions): RudimentAnimation`
140
+
141
+ Synchronously validate the registration, reserve its ID and return a controller with a `ready` promise. Mounting does not call `audio.play()` or change playback rate. The first eligible pose is rendered after native readiness; hidden, offscreen or manually paused mounts defer drawing until eligible. A destroyed pending mount cannot revive itself when compilation completes.
142
+
143
+ Duplicate IDs are rejected, **not** silently replaced. Destroy the old instance first. Only one automatic CSS-output owner may target a given element. Multiple custom-render instances may share a target with `css:false`, but the page must keep their outputs separate.
144
+
145
+ ### `get(id: string): RudimentAnimation | null`
146
+
147
+ Return the existing controller, including a pending or failed controller, or `null`. The ID is the exact trimmed registration ID. No new controller is constructed.
148
+
149
+ ### `snapshot(): object`
150
+
151
+ Return `{ version, upstreamVersion, loaded, error, framePending, instances }`. `loaded` means the shared WASM backend compiled. `error` is the native-loading error or `null`; per-controller rendering errors are in `instances`. `framePending` refers only to this integration's shared scheduler, not all DanceMoves or theme animation. `instances` contains fresh controller snapshots, not live DOM references. No frame history, audio history, logging or telemetry is retained by the service.
152
+
153
+ ### `destroyAll(): void`
154
+
155
+ Destroy every currently registered rudiment controller. It does not pause audio, destroy the core DanceMoves runtime or unload the shared WASM backend. The singleton's visibility/page-lifecycle listeners remain available for subsequent registrations. Prefer per-controller destruction when several page adapters coexist.
156
+
157
+ ## Animation options
158
+
159
+ Options are fixed after construction except `enabled` and the local pause state. Change name, rate, amplitude, phase, target or audio by destroying and remounting. The provided object is not a supported live configuration channel.
160
+
161
+ | Option | Type / default | Contract |
162
+ | --- | --- | --- |
163
+ | `id` | Required string | Trimmed, 1–160 characters, unique per document. Use a release/effect-specific stable ID. |
164
+ | `rudiment` | Required catalogue name | Exact case-sensitive name. |
165
+ | `target` | Required Element or selector | Must resolve now to one element. Selectors use `document.querySelector`. |
166
+ | `root` | Element or selector; defaults to target | Lifecycle/visibility root; target must equal it or be its descendant. |
167
+ | `clock` | `'page'` by default; `'audio'` or `'auto'` | Clock semantics below. |
168
+ | `audio` | HTMLAudioElement or selector | Required for `audio` and `auto`. Ignored for `page`. Cannot be a video or arbitrary clock object. |
169
+ | `rate` | Number; `1` | Rudiment cycles per native musical period; strictly greater than 0, at most 64. `0.5` doubles cycle duration. It is **not** `audio.playbackRate`. |
170
+ | `phasePips` | Safe integer; `0` | Signed native-pip offset, applied after rate conversion. |
171
+ | `amplitude` | Number or `{x?,y?,z?}`; `1` | Pixel gains. A scalar applies to all axes. Missing object axes are zero. Finite signed gains within ±10,000 are allowed; the page must choose appropriately bounded motion. |
172
+ | `enabled` | Boolean; `true` | `false` displays a neutral zero-offset pose and stops sampling. |
173
+ | `css` | Boolean; `true` | Write scoped x/y/z pixel custom properties. `false` leaves visual output entirely to `render`. |
174
+ | `cssPrefix` | String; `'--dance-moves-rudiment'` | Prefix for `-x`, `-y`, `-z`; must match `^--[A-Za-z][A-Za-z0-9_-]{0,75}$`. |
175
+ | `offscreen` | Boolean; `true` | Suspend outside the root's intersection when IntersectionObserver is available. No polyfill or offscreen suspension is promised without it. |
176
+ | `resetOnCue` | Boolean or cue name; `false` | `true` subscribes to all core cues; a non-empty string selects one normalised core cue name. Matching audio or manual cues reset the phase origin. Unrelated players are ignored. |
177
+ | `renderEveryFrame` | Boolean; `false` | Default suppresses unchanged-pip frame callbacks. `true` delivers continuous phase for companion art direction; native positions still use discrete integer pips. |
178
+ | `render` | Optional function | Receives the frame payload after optional CSS output. Called synchronously; do not return a promise or create a second clock. |
179
+
180
+ Invalid types/numbers/selectors throw synchronously before registration. A failed native or stylesheet mount rejects `controller.ready` and reports an error event. Runtime callback failures stop that instance, release its CSS ownership and report the error; unrelated instances continue.
181
+
182
+ The controller snapshots the options used by the contract. Do not mutate callback payloads or use them as writable state; their TypeScript interfaces are readonly. Callback objects are short-lived observations.
183
+
184
+ ## Controller API
185
+
186
+ `id` is readonly. `ready: Promise<RudimentAnimation>` resolves to the same controller when native/CSS setup is complete (drawing can remain deferred by lifecycle state); it can resolve to an already-destroyed controller when teardown happened during a successful shared load. It rejects on native, CSS-output or initial-render failure. Catch it even though the implementation internally marks rejection handled for fire-and-forget use.
187
+
188
+ | Method | Result and lifecycle effect |
189
+ | --- | --- |
190
+ | `pause(): void` | Locally hold the last pose and remove the instance from the shared frame scheduler. Does not pause audio. |
191
+ | `resume(): void` | Clear local pause and resample the current clock when permitted. Does not bypass `enabled:false`, reduced motion, forced colours, hidden/offscreen state or paused audio. |
192
+ | `setEnabled(boolean): void` | Disable to neutral zero output and stop work; enable to reconstruct current position. Suitable for a quality-tier gate. Non-boolean values throw. |
193
+ | `reset(): void` | Set the current source beat as the new phase origin; the next eligible sample starts at `phasePips`. Does not seek audio or replay events. |
194
+ | `refresh(): void` | Reconcile lifecycle and render the current permitted pose, including a paused audio seek result. Local `pause`, hidden/offscreen and in-progress seeking continue to hold. |
195
+ | `snapshot(): RudimentAnimationSnapshot` | Return serialisable diagnostic state; fields below. |
196
+ | `destroy(): void` | Idempotently stop work, release ID/CSS ownership, remove audio/media-query/cue/intersection listeners and restore the target's original CSS cascade. |
197
+ | `teardown(): void` | Exact alias of `destroy()`, for consistency with other DanceMoves adapters. |
198
+
199
+ After destruction, lifecycle mutators are no-ops; `snapshot()` still works. `setEnabled()` still validates its argument. Do not remount from a stale destroyed handle; call the service with a new registration. The service automatically destroys registrations when their root or target is removed (MutationObserver when available, otherwise while a running frame is reconciled).
200
+
201
+ Controller snapshot fields are `id`, `rudiment`, `status`, `enabled`, configured `clock`, `rate`, `phasePips`, `destroyed`, `loaded`, `error`, `updates`, last `pip`, last `sourceClock`, last pixel `position`, and `framePending`. `updates` counts payload publications, including lifecycle neutral updates; it is not the display's rendered FPS. Last-position fields are `null` before a first render. `loaded` remains true after a previously loaded controller is destroyed; consult `destroyed`/`status` for activity.
202
+
203
+ Status values: `loading`, `running`, `paused`, `disabled`, `hidden`, `offscreen`, `reduced-motion`, `forced-colors`, `audio-paused`, `seeking`, `ended`, `error`, `destroyed`. They are observations, not a writable enum.
204
+
205
+ ## Render callback payload
206
+
207
+ | Field | Meaning |
208
+ | --- | --- |
209
+ | `id`, `rudiment` | Registration ID and pattern name |
210
+ | `root`, `target` | Actual elements; never serialise these into telemetry |
211
+ | `audio` | Selected audio when using the audio clock; otherwise `null` |
212
+ | `clock` | Resolved `'page'` or `'audio'`, never `'auto'` |
213
+ | `sourceBeats` | Current source position in page-configured beats |
214
+ | `bpm`, `rate` | Effective page BPM and rudiment rate |
215
+ | `pip` | Wrapped integer in `[0,periodPips)`; `null` for neutral accessibility/disabled updates |
216
+ | `positionPips` | Unwrapped integer pip after origin/rate/phase; likewise `null` for neutral updates |
217
+ | `loopProgress` | Continuous wrapped phase `[0,1)` from the same clock; zero for a neutral update |
218
+ | `durationMilliseconds` | One pattern cycle at page BPM and rudiment rate, expressed for audio playbackRate 1 |
219
+ | `offset` | Native normalised `{x,y,z}`, or all zero for a neutral update |
220
+ | `position` | Pixel `{x,y,z}` after amplitude scaling |
221
+ | `reason` | `'frame'` or the status that caused a lifecycle publication |
222
+
223
+ `loopProgress` exists for companion CSS/WAAPI art direction. It is not an interpolated native coordinate. `sample()` and `offset` remain exactly integer-pip based. A slow display skips visual samples and renders the latest pip; the API does not dispatch one event for each skipped pip or attempt timer catch-up.
224
+
225
+ A custom renderer can consume normalised coordinates in another unit with `css:false`. It remains responsible for hiding/stopping its custom drawing on neutral updates and for cleaning up its own objects after controller destruction. `destroy()` removes integration resources; it cannot reverse arbitrary DOM/canvas mutations performed by a callback. Use CSSOM or WAAPI for visual updates where possible: writing inline `style` attributes inside the catalogue-owned root can invoke the existing catalogue timing observer.
226
+
227
+ ## Clock and lifecycle contract
228
+
229
+ One DanceMoves tick is `1/16` beat; one native pip is `1/64` beat. The clocks are converted explicitly:
230
+
231
+ ```text
232
+ page source beats = DanceMoves.currentTick({clock:'page'}) / 16
233
+ audio source beats = audio.currentTime * DanceMoves.bpm / 60
234
+ precise native pips = (source beats - reset origin) * 64 * rate + phasePips
235
+ sample pip = positive modulo(floor(precise native pips + 1e-9), periodPips)
236
+ loop duration ms = (periodPips / 64 / rate) * (60000 / page BPM)
237
+ ```
238
+
239
+ `page` follows the existing page clock independently of audio. `audio` always reads the explicit player's current time, even while paused. It does **not** call `DanceMoves.currentTick({clock:'audio'})`, whose core implementation can fall back to the page clock. The new API therefore cannot accidentally resume ambient motion when a selected audio player pauses.
240
+
241
+ `auto` uses page time until it observes the first playback of its selected audio (or the audio is already playing at mount). It then latches to that audio, including pauses/end; it does not switch back to page time. This preserves an ambient introduction without inventing a second timer. Auto's first page-to-audio transition clears the prior page-clock reset origin.
242
+
243
+ `audio.playbackRate` already affects the rate at which `audio.currentTime` advances. Multiplying by it again would double-count the tempo. The `rate` option independently compresses/stretches the rudiment's musical period.
244
+
245
+ On `seeking`, clear the old reset origin and hold the previous visual until `seeked`. On landing, reconstruct from the player's actual time; no skipped pattern samples or cue events are replayed. An explicitly enabled core cue reset may establish a new origin at a cue landing through the existing core event path. Pause holds the audio pose, and end stops frame scheduling. Replay/seek uses the same absolute-time rules.
246
+
247
+ Hidden documents, pagehide, offscreen roots, reduced motion, forced colours and disabled instances do not keep an integration-owned RAF alive. When work becomes eligible again, sample the actual current position rather than integrating a wall-clock delta. With several eligible instances there is **one** shared RAF, not one per rudiment.
248
+
249
+ `pause()` is a local pose hold, not transport time preservation: if the source continues, `resume()` jumps to its correct current phase. `reset()` is an explicit phase-origin change, not a tempo change. Existing DanceMoves core cue resets and the new `resetOnCue` option are distinct mechanisms; use the latter only when a page deliberately wants its native cycle to restart at cues.
250
+
251
+ ## CSS and HTML contract
252
+
253
+ Automatic output creates a stylesheet rule targeting the generated `data-dance-moves-rudiment-owner` attribute on the exact target. It sets `<prefix>-x`, `<prefix>-y`, `<prefix>-z` as six-decimal **pixel** values with `!important`. It does not set `transform`, `translate`, colour, opacity, dimensions or positioning.
254
+
255
+ The defaults are `--dance-moves-rudiment-x`, `--dance-moves-rudiment-y` and `--dance-moves-rudiment-z`. A target must not already contain the reserved owner attribute. Use `css:false` to opt out of this ownership contract. Do not use competing inline-important declarations for these reserved output variables.
256
+
257
+ The stylesheet is `#dance-moves-rudiment-values`. Hot-path values update `CSSStyleRule.style`, **not** `target.style`; this avoids activating the existing catalogue adopter's subtree `style`-attribute observer on every sample. This does not mean CSS changes are free: rendering/style costs still need profiling on the full page. Only changed strings are written. The owned rule and attribute are removed on destroy, revealing the original author cascade; no original inline style is deleted or rewritten.
258
+
259
+ Reduced motion and forced colours publish zero-offset output and stop scheduling. Retain page-level CSS fallbacks as well, especially for custom renderers and environments where the integration script never loads.
260
+
261
+ ## Events and errors
262
+
263
+ Events are non-bubbling `CustomEvent`s dispatched on `document`.
264
+
265
+ | Event | Detail | When |
266
+ | --- | --- | --- |
267
+ | `dance-moves-rudiments-ready` | `{api, version, upstreamVersion, upstreamCommit}` | Once after successful lazy native compilation |
268
+ | `dance-moves-rudiments-error` | `{id, message}` | Shared load failure (`id:null`) or a specific mount/render failure |
269
+
270
+ Subscribe before calling `ready()` when event order matters; events are not replayed to late listeners. Prefer the readiness promise for mounting. No per-pip DOM event is emitted, avoiding event storms.
271
+
272
+ Synchronous registration errors throw. Asynchronous initial errors reject `controller.ready`; ongoing render errors set its state to `error`, stop its frame work and release CSS ownership. Failed controllers remain inspectable in the registry until destroyed. Native-loading failure can produce both the shared error and per-controller errors; consumers should not treat those as separate user-facing incidents.
273
+
274
+ CSP can block WASM compilation or the generated stylesheet. A compatible policy must permit the bundled script, WebAssembly compilation and this style creation. Do not weaken a site's policy automatically. Correctly retain static/legacy rendering on failure and obtain a reviewed site-policy change where needed. No remote executable text, timing-file text or caller-provided CSS source is evaluated by this API.
275
+
276
+ ## Clay/Stars integration
277
+
278
+ The existing release source assigns `ks-particle-dance` to `.ks-clay-stars-v2 .epk-atmosphere::after`, using `--dance-moves-128t`: **eight beats**. DanceRudiments' `clay_background` represents the positional waypoints in a 256-pip, **four-beat** period. Direct rate-1 substitution would double the visual speed.
279
+
280
+ The bundled adapter therefore uses:
281
+
282
+ ```js
283
+ // This is already mounted by assets/clay-stars-rudiments.js on page 252.
284
+ // Do not paste a second copy into the WordPress page.
285
+ const settings = {
286
+ id: 'clay-stars:background-rudiment',
287
+ rudiment: 'clay_background',
288
+ rate: 0.5,
289
+ amplitude: { x: 14, y: 10, z: 0 },
290
+ clock: 'auto',
291
+ cssPrefix: '--ks-clay-rudiment',
292
+ resetOnCue: true,
293
+ renderEveryFrame: true
294
+ };
295
+ ```
296
+
297
+ At page BPM 90 the cycle remains approximately `5333.333333ms`. The ±14px x and ±10px y gains restore the original positional scale; `--ks-master-intensity` continues to control the existing non-positional treatment rather than silently multiplying native translation.
298
+
299
+ A new, release-scoped `ks-clay-rudiment-treatment` CSS animation preserves the original rotation, scale and opacity keyframes **without their translations**. CSS individual `translate` consumes the native position. The companion animation stays paused and its `currentTime` is explicitly scrubbed from the rudiment's `loopProgress`. Thus it does not introduce a separate free-running phase or leave the old translation active underneath the new one. Cloud drift, cover tilt, button choreography, lyrics, artwork, player behaviour and warm-light input are not replaced.
300
+
301
+ The positional path now follows the **library's discrete, smoothstep-generated table**, rather than the old CSS's continuous linear interpolation between percentage waypoints. Source key percentages are also quantised to native pips. The waypoint vocabulary, spatial scale and eight-beat pace are preserved; byte-for-byte or pixel-for-pixel continuous trajectory identity is not claimed.
302
+
303
+ Only a successful native mount sets `data-dance-moves-rudiment-clay="active"`. Required DOM/capabilities: the existing `.ks-epk.ks-clay-stars-v2` root, `.epk-atmosphere`, the existing `DanceMovesClayStars` runtime, CSS individual translate support and `getAnimations()`. The first root audio supplies `auto` playback timing; a missing audio explicitly selects the page clock. If prerequisites are absent, no migration is mounted and the existing CSS remains.
304
+
305
+ The marker's states are `active`, `suspended` and `fallback`; it is absent before successful mounting and after teardown. `suspended` follows the existing Clay quality profile or disabled setting. In constrained/minimal profiles the existing static quality CSS remains authoritative and the native controller is disabled. Recovery re-samples the current clock. Reduced motion/forced colours also remove both translation and accents. This adapter does not create a competing quality sampler.
306
+
307
+ ### `window.DanceMovesClayRudiment`
308
+
309
+ Available only after a compatible Clay mount attempt. It exposes `root`, `snapshot()` and `teardown()`.
310
+
311
+ `snapshot()` returns `{status, error, destroyed, loaded, rudiment, cycleBeats, rate, controller}`. `status` is `loading`, `active`, `suspended`, `fallback` or `destroyed`; `controller` is the underlying controller snapshot or `null`. `loaded` means a native controller was mounted, not that it is currently running. Inspect `controller.status` for accessibility/transport state.
312
+
313
+ `teardown()` is idempotent. It disconnects the adapter observer/error listener, destroys its native controller, removes the marker and restores the original CSS fallback. Root removal or teardown of the original Clay runtime also tears down this adapter. This is an adapter teardown, not a WordPress plugin deactivation.
314
+
315
+ The underlying controller is available through `DanceMovesRudiments.get('clay-stars:background-rudiment')`. For normal page integration, let the bundled adapter manage its enabled state and teardown. Do not mount another controller on the same atmosphere element.
316
+
317
+ ### Adoption and rollback
318
+
319
+ No WordPress page-content edit or new metadata is required for the existing page-252 adapter. The plugin's PHP integration enqueues the new assets behind the existing legacy-plugin coexistence guard. The compiled backend must be present before packaging; the package gate verifies it.
320
+
321
+ Before publishing, check full/constrained/minimal, reduced motion, forced colours, hidden/visible, offscreen, pause/resume, both seek directions, exact chapter landings, playback-rate changes, long playback, deliberate WASM failure and teardown on the real page. Confirm that only one translation owner exists and that no catalogue rescan occurs per frame. Test signed out at desktop, tablet and narrow-mobile sizes; profile the complete effect stack.
322
+
323
+ On native-load failure the original CSS remains active. To remove this migration from a staged/live build, disable its PHP enqueue module or restore the prior approved plugin package through the normal release procedure. `DanceMovesClayRudiment.teardown()` is a useful in-document diagnostic rollback; it is not a persistent deployment rollback.
324
+
325
+ ## WordPress and packaging
326
+
327
+ The entrypoint requires `dance-moves-rudiments.php`. It defines `DANCE_MOVES_RUDIMENTS_API_VERSION` (`1.0.0`) and registers `dance_moves_enqueue_rudiments(): void` on `wp_enqueue_scripts` at priority **25**, after the existing core runtime's priority 20.
328
+
329
+ | Handle | Type | Dependencies / scope |
330
+ | --- | --- | --- |
331
+ | `dance-moves-rudiments-native` | Footer script | Bundled C++/WASM asset; all WordPress Pages |
332
+ | `dance-moves-rudiments` | Footer script | `dance-moves-core`, `dance-moves-rudiments-native`; all Pages |
333
+ | `dance-moves-clay-rudiments` | Style | `dance-moves-clay-stars`; existing Clay page only |
334
+ | `dance-moves-clay-rudiments` | Footer script | `dance-moves-rudiments`, `dance-moves-clay-stars`; existing Clay page only |
335
+
336
+ The API can be used on any Page; only the release adapter is tied to the existing Clay page ID. Non-Page requests receive none of these assets. When `KS_CLAY_STARS_EFFECTS_VERSION` indicates the legacy Clay effects plugin, the reusable API still loads on Pages but the new Clay migration assets are suppressed.
337
+
338
+ Cache versions combine the plugin version and API version. The patch increments the plugin candidate to **2.9.0** and aligns the known version-sensitive contract and phone-fixture cache keys. It does not publish that version, regenerate migration manifests or replace existing `dist` releases.
339
+
340
+ `tools/package.ps1` verifies the native bundle before packaging and includes the new PHP module, both API-reference files and the pinned vendor directory, alongside the existing assets. The package retains `kieran-epk-device-orientation` as its WordPress upgrade slug. Tests and build tooling remain repository-only. The MIT licence accompanies both the vendored C++ and generated native browser asset.
341
+
342
+ ## Building and testing
343
+
344
+ Normal consumers use the checked-in generated native bundle. Rebuild only when reviewing an upstream/library/backend change.
345
+
346
+ ```sh
347
+ # Host GCC or Clang in C++17 mode; Clang must support wasm32 and have wasm-ld.
348
+ # Python orchestrates compilation; it does not implement motion functions.
349
+ python tools/build-rudiments.py --cxx g++ --clang clang++
350
+
351
+ # Compare the committed backend against freshly executed upstream C++ samplers.
352
+ # --check writes no repository files and needs only the host C++ compiler and Node.
353
+ python tools/build-rudiments.py --check --cxx g++
354
+
355
+ # Read-only package/hash/ABI gate; no compiler required.
356
+ node tools/verify-rudiments.cjs
357
+
358
+ node tests/rudiments-runtime.test.cjs
359
+ node tests/clay-rudiments.test.cjs
360
+ node tests/rudiments-source.test.cjs
361
+ php tests/rudiments-wordpress.php
362
+ tsc --noEmit --strict --lib es2020,dom RUDIMENTS-API.d.ts
363
+ ```
364
+
365
+ `--cxx` and `--clang` accept executable paths; the host compiler needs GCC/Clang-style command-line arguments. The generator verifies vendored Git blob identities before compilation. It rejects unreviewed source edits rather than fetching a moving branch. Review a new exact upstream commit and source hashes deliberately, rebuild, rerun exhaustive parity, then update both the plugin release version and evidence.
366
+
367
+ The build's local repeatability can be checked with output hashes. Different native math libraries/compilers can differ by floating-point roundoff for transcendental functions; cross-toolchain **bitwise** reproducibility is not guaranteed. The committed native asset and recorded digest are the release artifact; regeneration validates coordinates to `1e-12` and records compiler identities without wall-clock timestamps.
368
+
369
+ The normal Unit validator discovers the new `.test.cjs` files and explicitly runs the PHP enqueue test. It remains necessary to run the existing DanceMoves suite. Package mode has its pre-existing migration/release-workspace prerequisites.
370
+
371
+ [Browser fixture](tests/rudiments-browser.html) uses real integration code and WASM with explicitly labelled legacy API test doubles and locally generated silent audio. It is not the live Clay EPK. With Python Playwright and a permitted Chromium installation:
372
+
373
+ ```sh
374
+ python tests/test-rudiments-browser.py --chromium /path/to/chromium --output qa/rudiments-browser
375
+ ```
376
+
377
+ The fixture exercises pseudo-element translation, companion phase, real audio seeking, quality/accessibility state, failure fallback and teardown. It also counts inline style mutations. A browser navigation failure means the rendering tests did not run; it is not a pass. Headless results do not establish physical-device FPS, GPU cost, input latency or WordPress compatibility.
378
+
379
+ ## Limits and extension policy
380
+
381
+ This API does not import timing files, infer BPM, synchronise multiple players, emit drumming stroke events, expose Python tooling in the browser or wrap unmerged future curve/event infrastructure. Each controller has an explicit clock/target; a new controller is required to change them. A pattern is sampled only at integer native pips, with no implicit interpolation.
382
+
383
+ The core's animation-reset API does not own this controller's CSSOM variables; use `resetOnCue` deliberately. Conversely, the new controller does not own or stop unrelated core/theme RAF loops. `snapshot().framePending === false` means the rudiment scheduler is idle, not that the whole page is idle.
384
+
385
+ CSSOM output still has rendering cost; callbacks allocate payload objects and may be expensive. Clay also updates a paused CSS animation's current time. Full-page performance must be measured before deployment. Browser rendering was not proven merely by native or mocked-DOM tests.
386
+
387
+ As the upstream library grows, retain C++ as movement authority. For a future API whose domain cannot be exhaustively precompiled, add a direct compiled binding rather than inventing JavaScript formulas. Keep any new backend behind the same readiness, unit, lifecycle and capability-error contracts and document its compatibility separately.
388
+
389
+ ## Source references
390
+
391
+ - [DanceMoves 2.8.0 source](https://github.com/kieransimkin/DanceMoves/tree/4cb6a71f60459b5579be87d7e55ac8b1426c578e): core clock, existing Clay adapter, stylesheet, enqueue and packaging contracts.
392
+ - [Clay stylesheet and original keyframes](https://github.com/kieransimkin/DanceMoves/blob/4cb6a71f60459b5579be87d7e55ac8b1426c578e/assets/clay-stars-effects.css).
393
+ - [Pinned DanceRudiments source](https://github.com/kieransimkin/DanceRudiments/tree/0df000db8753f04f397311f5b7c6ae6561e1d260): integer-pip contract, catalogue, C++ functions and MIT licence.
394
+ - [Target public page](https://kieransimkin.co.uk/made-from-the-clay-and-the-stars-anunnaki/). The public page text was inspected; the integration is based on the pinned release source, not a claim that the modified runtime has been deployed there.
package/docs/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # DanceMoves documentation
2
+
3
+ **Implemented baseline:** DanceMoves **2.8.0**, release tag `v2.8.0`, commit
4
+ `4cb6a71f60459b5579be87d7e55ac8b1426c578e` (28 September 2026).
5
+ This reference was checked against that source on 29 September 2026. It documents
6
+ implemented behaviour, including limitations, rather than treating historical QA
7
+ notes or planned harness work as a shipping API.
8
+
9
+ DanceMoves is **Kieran Simkin's WordPress EPK motion runtime**, part of the wider
10
+ **DanceFlow** BPM and motion-response workflow. It supplies shared timing, cue and
11
+ lyric state, shared effect primitives, signed MP3 downloads, configurable paper-plane
12
+ effects, CSS timing adoption, and permission-aware orientation input. A release
13
+ supplies its own artwork, markup, players and visual language.
14
+
15
+ The default branch is not the release baseline: `main` was still at 2.4.0 when
16
+ reviewed. The staging branch `timed-lyrics-staging-2026-09-20` at
17
+ `c95f24f326a5f5334e4fe63c1e705ec13b1d59d6` adds only publication evidence
18
+ after `v2.8.0`; runtime and README are identical. This reference covers the release,
19
+ not a hypothetical 2.9. See [source provenance](source-audit.md).
20
+
21
+ ## Read by task
22
+
23
+ | Task | Reference |
24
+ | --- | --- |
25
+ | Install, configure a Page, prepare timing files, enable lyrics | [User and integration guide](guide.md) |
26
+ | Mount pointer, pulse, cue-class, cue-timeline or quality effects | [Shared effect API](api/effects.md) |
27
+ | Enable or integrate the paper-plane renderer | [Paper-plane API](api/paper-planes.md) |
28
+ | Create reliable MP3 download links and understand the HTTP endpoint | [Download API](api/downloads.md) |
29
+ | Call the musical clock, schedule work, subscribe to cues/lyrics | [JavaScript API](api/javascript.md) |
30
+ | Read/write metadata, validate files, integrate PHP, submit captures | [WordPress, PHP and REST API](api/wordpress.md) |
31
+ | Use timing variables, style lyrics, inspect DOM state | [CSS and HTML contract](api/styling.md) |
32
+ | Integrate orientation, catalogue timing or Clay/Stars effects | [Adapters and orientation API](api/adapters.md) |
33
+ | Run tests, use local harnesses, package and troubleshoot | [Development and operations](development.md) |
34
+ | Check reference coverage and important implementation caveats | [Source audit](source-audit.md) and [symbol inventory](api/surface.json) |
35
+
36
+ ## Feature map
37
+
38
+ | Feature | What ships | Important boundary |
39
+ | --- | --- | --- |
40
+ | Page properties | BPM, lyric attachment, cue attachment, lyric-pop-up opt-in, ambient-effect selection; hidden master duration | Attachment content validation on the editor save path is stronger than REST metadata sanitisation. |
41
+ | Musical clock | 16 ticks per beat, duration quantisation, page and audio clocks, beat/bar helpers | A bar helper means four beats; this is not a meter or tempo-map engine. |
42
+ | Cue timing | LRC-style timed labels, named/wildcard handlers, scoped animation resets, legacy event alias | `.cue` means the plugin's timestamped text format, not a CD cuesheet. |
43
+ | Timed lyrics | One neutral visual layer, blank clears, playback/seek following, immediate-next and next-visible previews, callbacks and DOM events | Off by default; a release-specific design review is required before adoption. |
44
+ | Shared effects | `pointer`, `playbackPulse`, `cueClass`, `cueTimeline`, `quality`; stable-ID instance management | Page code owns appearance; callback, transport and accessibility contracts differ between primitives. |
45
+ | Signed MP3 downloads | Attachment-only GET/HEAD endpoint and rewriting of explicit upload download links | HMAC is not user authorisation or an expiring URL; players keep their original URLs. |
46
+ | Paper planes | Page-configured hero effect, atlas sprites, compact mode, offscreen/hidden pause | DOM structure, CSS containment and count limits must be preserved; no user-facing tuning panel. |
47
+ | Catalogue timing | EPK-owned stylesheet, inline, computed and pseudo-element timing conversion | It cannot rewrite canvas/WebGL phase logic or supply an arbitrary new root registration API. |
48
+ | Orientation | Nine release adapters, permission UI, rolling mapping, latest-sample frame coalescing and two-tick target writes | Mobile/capability detection and reduced motion gate the production input runtime. |
49
+ | Clay/Stars | Cover/light response, particle release, cue state and recoverable performance tiers | Page 252 only; the legacy effects-plugin constant suppresses takeover. |
50
+ | Motion capture | Bounded, sanitised JSON saved as private WordPress posts | Dedicated submission endpoint, not automatic tracking or a secure user-authenticated telemetry service. |
51
+ | Diagnostics | Explicitly enabled synchronous timing sink and bounded catalogue evidence | No core diagnostic retention/transmission; external consumers own their storage and privacy obligations. |
52
+ | Development | Contract tests, local harnesses, deployment evidence and package tooling | Tests and harness globals are not front-end production APIs. |
53
+
54
+ ## API scope and compatibility
55
+
56
+ The documented integration surface is `window.DanceMoves` (also exposed as
57
+ `window.KieranEpkMotion`), `window.DanceMovesEffects`, the documented conditional adapter globals, the PHP
58
+ helpers and WordPress metadata/REST contracts. The internal orientation lifecycle
59
+ object is documented for diagnostics but is not a general adapter registry.
60
+
61
+ There are **no plugin shortcodes, Gutenberg blocks, WP-CLI commands, custom
62
+ DanceMoves action/filter extension points, stem-separation models, automatic BPM
63
+ analysis, track-loop extraction, or public capture-reading endpoint** in this
64
+ baseline. The plugin consumes prepared timing data; it does not call StemLab or a
65
+ React timeline service. Those projects can participate upstream in a DanceFlow
66
+ workflow without implying an implemented network integration here.
67
+
68
+ All source links in these documents are repository-relative. The exact audited
69
+ blob identifiers are recorded in [surface.json](api/surface.json). When reading a
70
+ newer checkout, compare the exports and configuration against that inventory.
71
+ The root README contains current usage and troubleshooting. Dated evidence in Git,
72
+ `migration/`, `qa/` and the [test plan](../TEST-PLAN.md) does not establish the status
73
+ of a newer build or deployment.