rig-play 1.10.0 → 1.11.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/NOTICE.md CHANGED
@@ -11,20 +11,24 @@ document `rigc build` writes. No part of the
11
11
  [Spine Runtimes](https://github.com/EsotericSoftware/spine-runtimes) is in it,
12
12
  imported by it, or required beside it.
13
13
 
14
- **This repository links no Spine runtime either**, since #63.
15
- `@esotericsoftware/spine-core` and `@esotericsoftware/spine-webgl` are not
16
- dependencies of any kind — not `dependencies`, not `peerDependencies`, not
17
- `devDependencies` — and no lockfile entry names them. The test suite and the
18
- benchmark compare the package's two render modes with each other; the poses
19
- are rig-c's, held to the Spine runtime by rig-c's own CI, not here.
20
- `tests/package.spec.ts` asserts all of that, and that nothing the published
21
- package contains names an `@esotericsoftware/` package.
14
+ **Development tooling uses the official Spine WebGL runtime.** Issue #39
15
+ reintroduces `@esotericsoftware/spine-webgl` 4.3.13 as a pinned devDependency,
16
+ with its matching `@esotericsoftware/spine-core` dependency, for the independent
17
+ pixel oracle and comparison/benchmark tooling. These are licensed under the
18
+ [Spine Runtimes License Agreement](https://esotericsoftware.com/spine-runtimes-license),
19
+ updated April 5, 2025, copyright 2013–2025 Esoteric Software LLC; they are not
20
+ covered by this project's MIT licence. Their licence and copyright notices
21
+ apply to use and redistribution of that tooling. The earlier removal in #63
22
+ remains the history of the production player: no runtime is added back to its
23
+ public API or published library. `tests/package.spec.ts` checks the exact
24
+ development exception and the runtime-free shipped code and declarations.
22
25
 
23
26
  One thing in this repository is Spine data, and it is worth stating where it
24
27
  sits. `rigc build`, which `scripts/fetch-assets.sh` runs to make the fixtures,
25
28
  writes **Spine skeleton data** (`skeleton.json` and `skeleton.atlas`) beside
26
- each model document, into the gitignored `public/rigs/`. Nothing here reads
27
- it, and it is neither committed nor published.
29
+ each model document, into the gitignored `public/rigs/`. The development oracle
30
+ and comparison tooling read that paired export independently of the model
31
+ reader. These generated files are neither committed nor published in the package.
28
32
 
29
33
  ### What that means, as a chain of facts
30
34
 
package/README.md CHANGED
@@ -38,6 +38,81 @@ links to either earlier name keep working.
38
38
 
39
39
  ### Measured
40
40
 
41
+ **Standing comparison against `@esotericsoftware/spine-webgl 4.3.13` (`npm run bench`, #39)** —
42
+ measured on 2026-10-17 in **headed** Chromium `151.0.7922.34` on an Apple M4 GPU
43
+ (`ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)`, macOS `Darwin 25.6.0`,
44
+ 10 cores, 32 GiB RAM, `1280×720` viewport, `devicePixelRatio: 1`, 100 warm-up frames +
45
+ 600 measured frames per row, `loadPerCoreBefore: 0.33`, `loadPerCoreAfter: 0.33` under the
46
+ `1.0` load guard; each row is its own page load with only that runtime animating, and grid
47
+ scenes batch the official skeletons through one shared WebGL canvas while `small players`
48
+ mounts independent player instances):
49
+
50
+ | scene | runtime | rig | clipped to | fps | pose ms | render ms | drawn players | triangles | draw calls | origin Δpx | live contexts |
51
+ | --- | --- | --- | --- | --: | --: | --: | --: | --: | --: | --: | --: |
52
+ | 1 meshed rig animating | dom | `portrait-packed` `turn` | pane | 59.99 | 0.17 | 0.16 | —/1 | 48 | — | 0.007 | —/1 |
53
+ | 1 meshed rig animating | canvas | `portrait-packed` `turn` | pane | 59.99 | 0.23 | 0.18 | —/1 | 0 | 0 | 0.007 | —/1 |
54
+ | 1 meshed rig animating | official | `portrait-packed` `turn` | pane | 60.00 | 0.05 | 0.13 | 1/1 | 88 | 1 | 0.000 | 1/1 |
55
+ | 4 meshed rigs animating | dom | `portrait-packed` `turn` | pane | 60.01 | 0.46 | 0.48 | —/4 | 96 | — | 0.009 | —/1 |
56
+ | 4 meshed rigs animating | canvas | `portrait-packed` `turn` | pane | 60.00 | 0.60 | 0.49 | —/4 | 176 | 2 | 0.009 | —/1 |
57
+ | 4 meshed rigs animating | official | `portrait-packed` `turn` | pane | 59.99 | 0.13 | 0.17 | 4/4 | 352 | 1 | 0.000 | 1/1 |
58
+ | 16 meshed rigs animating | dom | `portrait-packed` `turn` | pane | 60.00 | 0.66 | 0.74 | —/16 | 384 | — | 0.010 | —/1 |
59
+ | 16 meshed rigs animating | canvas | `portrait-packed` `turn` | pane | 59.87 | 0.84 | 0.50 | —/16 | 704 | 8 | 0.010 | —/1 |
60
+ | 16 meshed rigs animating | official | `portrait-packed` `turn` | pane | 60.00 | 0.21 | 0.30 | 16/16 | 1408 | 1 | 0.000 | 1/1 |
61
+ | 36 meshed rigs animating | dom | `portrait-packed` `turn` | pane | 48.28 | 1.24 | 1.42 | —/36 | 1104 | — | 0.010 | —/1 |
62
+ | 36 meshed rigs animating | canvas | `portrait-packed` `turn` | pane | 60.11 | 1.15 | 0.85 | —/36 | 1760 | 20 | 0.010 | —/1 |
63
+ | 36 meshed rigs animating | official | `portrait-packed` `turn` | pane | 60.00 | 0.43 | 0.49 | 36/36 | 3168 | 1 | 0.000 | 1/1 |
64
+ | 64 meshed rigs animating | dom | `portrait-packed` `turn` | pane | 29.07 | 2.13 | 2.69 | —/64 | 1824 | — | 0.010 | —/1 |
65
+ | 64 meshed rigs animating | canvas | `portrait-packed` `turn` | pane | 53.99 | 1.62 | 1.17 | —/64 | 3168 | 36 | 0.010 | —/1 |
66
+ | 64 meshed rigs animating | official | `portrait-packed` `turn` | pane | 59.99 | 0.42 | 0.54 | 64/64 | 5632 | 1 | 0.000 | 1/1 |
67
+ | 1 meshed rig animating (canvas2d) | dom | `portrait-packed` `turn` | pane | 60.00 | 0.18 | 0.28 | —/1 | 48 | — | 0.007 | —/0 |
68
+ | 1 meshed rig animating (canvas2d) | canvas | `portrait-packed` `turn` | pane | 60.00 | 0.20 | 0.18 | —/1 | 88 | 1 | 0.007 | —/1 |
69
+ | 1 meshed rig animating (canvas2d) | official | `portrait-packed` `turn` | pane | 59.99 | 0.06 | 0.14 | 1/1 | 88 | 1 | 0.000 | 1/1 |
70
+ | 16 meshed rigs animating (canvas2d) | dom | `portrait-packed` `turn` | pane | 59.89 | 0.65 | 0.99 | —/16 | 384 | — | 0.010 | —/0 |
71
+ | 16 meshed rigs animating (canvas2d) | canvas | `portrait-packed` `turn` | pane | 60.00 | 0.93 | 0.59 | —/16 | 792 | 9 | 0.010 | —/1 |
72
+ | 16 meshed rigs animating (canvas2d) | official | `portrait-packed` `turn` | pane | 60.00 | 0.18 | 0.31 | 16/16 | 1408 | 1 | 0.000 | 1/1 |
73
+ | 1 held pose | dom | `portrait-packed` `turn` | pane | 60.11 | 0.00 | 0.15 | —/1 | 0 | — | 0.007 | —/1 |
74
+ | 1 held pose | canvas | `portrait-packed` `turn` | pane | 60.02 | 0.01 | 0.11 | —/1 | 0 | 0 | 0.007 | —/1 |
75
+ | 1 held pose | official | `portrait-packed` `turn` | pane | 60.00 | 0.00 | 0.13 | 1/1 | 88 | 1 | 0.000 | 1/1 |
76
+ | 16 held poses | dom | `portrait-packed` `turn` | pane | 60.09 | 0.00 | 0.43 | —/16 | 0 | — | 0.010 | —/1 |
77
+ | 16 held poses | canvas | `portrait-packed` `turn` | pane | 59.99 | 0.01 | 0.55 | —/16 | 0 | 0 | 0.010 | —/1 |
78
+ | 16 held poses | official | `portrait-packed` `turn` | pane | 60.08 | 0.00 | 0.35 | 16/16 | 1408 | 1 | 0.000 | 1/1 |
79
+ | 64 held poses | dom | `portrait-packed` `turn` | pane | 59.99 | 0.02 | 2.43 | —/64 | 0 | — | 0.010 | —/1 |
80
+ | 64 held poses | canvas | `portrait-packed` `turn` | pane | 60.10 | 0.02 | 2.56 | —/64 | 0 | 0 | 0.010 | —/1 |
81
+ | 64 held poses | official | `portrait-packed` `turn` | pane | 60.10 | 0.00 | 0.90 | 64/64 | 5632 | 1 | 0.000 | 1/1 |
82
+ | 1 rigid rig animating | dom | `walk-packed` `walk` | pane | 59.99 | 0.30 | 0.15 | —/1 | 0 | — | 0.007 | —/1 |
83
+ | 1 rigid rig animating | canvas | `walk-packed` `walk` | pane | 59.99 | 0.33 | 0.21 | —/1 | 30 | 1 | 0.007 | —/1 |
84
+ | 1 rigid rig animating | official | `walk-packed` `walk` | pane | 60.00 | 0.04 | 0.16 | 1/1 | 30 | 1 | 0.000 | 1/1 |
85
+ | 16 rigid rigs animating | dom | `walk-packed` `walk` | pane | 60.00 | 0.77 | 0.65 | —/16 | 0 | — | 0.010 | —/1 |
86
+ | 16 rigid rigs animating | canvas | `walk-packed` `walk` | pane | 60.00 | 0.62 | 0.47 | —/16 | 480 | 16 | 0.010 | —/1 |
87
+ | 16 rigid rigs animating | official | `walk-packed` `walk` | pane | 60.00 | 0.15 | 0.22 | 16/16 | 480 | 1 | 0.000 | 1/1 |
88
+ | 64 rigid rigs animating | dom | `walk-packed` `walk` | pane | 59.89 | 1.58 | 1.63 | —/64 | 0 | — | 0.010 | —/1 |
89
+ | 64 rigid rigs animating | canvas | `walk-packed` `walk` | pane | 42.00 | 1.50 | 1.21 | —/64 | 1920 | 64 | 0.010 | —/1 |
90
+ | 64 rigid rigs animating | official | `walk-packed` `walk` | pane | 60.00 | 0.14 | 0.13 | 64/64 | 1920 | 1 | 0.000 | 1/1 |
91
+ | 8 small players | dom | `walk-packed` `walk` | cell | 60.00 | 0.24 | 0.18 | —/8 | 0 | — | 0.012 | —/1 |
92
+ | 8 small players | canvas | `walk-packed` `walk` | cell | 60.00 | 0.33 | 0.30 | —/8 | 240 | 8 | 0.012 | —/1 |
93
+ | 8 small players | official | `walk-packed` `walk` | cell | 60.00 | 0.09 | 0.24 | 8/8 | 240 | 8 | 0.007 | 8/8 |
94
+ | 20 small players | dom | `walk-packed` `walk` | cell | 60.00 | 1.03 | 0.84 | —/20 | 0 | — | 0.015 | —/1 |
95
+ | 20 small players | canvas | `walk-packed` `walk` | cell | 59.99 | 0.69 | 0.54 | —/20 | 600 | 20 | 0.015 | —/1 |
96
+ | 20 small players | official | `walk-packed` `walk` | cell | 60.00 | 0.13 | 0.24 | 16/20 | 480 | 16 | 0.006 | 16/20 |
97
+
98
+ Where each side wins and loses on that table:
99
+
100
+ - **High-count meshed grids (`36` and `64 meshed rigs animating`) — `official` wins**: batching
101
+ all 64 skeletons into one WebGL canvas holds `59.99 fps` (`5,632 triangles` in `1 draw call`),
102
+ while `canvas` mode drops to `53.99 fps` (per-player `drawImage` blits from the shared WebGL
103
+ context) and `dom` mode drops to `48.28 fps` at 36 rigs and `29.07 fps` at 64 rigs (per-part
104
+ mesh canvas blits + DOM compositing). Up to 16 animating meshed rigs all three hold `60 fps`.
105
+ - **Held poses (`1`, `16`, `64 held poses`) — `dom` and `canvas` skip rasterization**: both
106
+ modes reuse their rasters (`0 triangles`, `0 draw calls`), while `official` redraws
107
+ `88` / `1,408` / `5,632 triangles` every frame.
108
+ - **Rigid-heavy grids (`64 rigid rigs animating`) — `dom` and batched `official` hold `60 fps`**:
109
+ `dom` updates `<img>` CSS transforms (`0 triangles`, `59.89 fps`), whereas `canvas` pays 64
110
+ per-player `drawImage` blits (`42.00 fps`).
111
+ - **Many independent players on one page (`20 small players`) — `dom` and `canvas` draw all 20;
112
+ `official` loses 4 to the browser's 16-context WebGL cap**: both `@rig-c/play` modes share at
113
+ most one WebGL context across the page, while mounting 20 independent `spine-webgl` players
114
+ evicts 4 contexts (`16/20 drawn players`, `16/20 live contexts`).
115
+
41
116
  **The element mode's mesh backend is `'webgl'` by default since 1.9 (#109)**, read off
42
117
  macOS Safari on 2026-10-13 (1.8.2, gallery rig `look-packed`: 4 meshes, 464
43
118
  triangles, `sweep`; the demo's stats line after a 30 s settle; the Mac was also
@@ -131,9 +206,11 @@ default stays `dom`.
131
206
  not expressible this way and stays out of scope
132
207
  - ✅ DPR-aware mesh canvas backing store: `renderer.pixelRatio` (defaults to
133
208
  `devicePixelRatio`; if you scale the root element, fold that scale in)
134
- - ✅ Dirty-skip: a mesh whose canvas-space vertices didn't change reuses last frame's
135
- raster — the CSS translate still tracks it, so parts that hold a pose (or move by
136
- whole pixels) pay zero raster. 10 frozen spineboys: WebKit ~141 → ~0.5 ms/frame
209
+ - ✅ Dirty-skip: a mesh whose canvas-space vertices match the last raster reuses it;
210
+ whole-pixel translations allow at most half a Float32 ULP at the current world
211
+ coordinate, strictly less than half a pixel. CSS still tracks the translation, while
212
+ stationary axes stay exact and the comparison anchor changes only on a redraw.
213
+ 10 frozen spineboys: WebKit ~141 → ~0.5 ms/frame
137
214
  - ✅ Clipping attachments as element-level CSS `clip-path`: spine-core's semantics
138
215
  (one active clip at a time, applied from the clip's slot through its end slot
139
216
  inclusive), with the polygon expressed in each element's own local frame — the
@@ -294,6 +371,11 @@ layer answers the same world, and a `seek` replays under the environment as it
294
371
  stands — no history is kept. A rig with no physics ignores both.
295
372
  `tests/readme-page.spec.ts` runs this block in the minimal page.
296
373
 
374
+ The demo's `wind.html` is the same thing with controls: wind and gravity
375
+ sliders, a drag on the stage, and the rig named by `?model=` (the `cloth` test
376
+ rig by default; `bun run dev`, then `/wind.html?model=rigs/<name>/skeleton.model.json`).
377
+ `tests/wind-page.spec.ts` holds it.
378
+
297
379
  The player owns one track: `play(name, { loop })` opens an animation at its first
298
380
  pose, `frame(dt)` steps it (or `advance(dt)` then `draw()`, to time the two
299
381
  apart), `pause()` / `resume()` hold and release it, `timeScale` scales every
@@ -392,6 +474,52 @@ const front = new DomRenderer(frontRoot, assets.regionImages, assets.pageImages)
392
474
  front.render(player.posedFrame(['hand_f', 'arm_f_lo']));
393
475
  ```
394
476
 
477
+ ### Attachment sets (`sets.json`)
478
+
479
+ A build from `@rig-c/parts` (or authored alongside a rig) may ship a `sets.json`
480
+ sidecar beside `skeleton.model.json` declaring named attachment sets — an
481
+ expression, a viseme, a side view — under spec `"rig-parts-sets/1"` or
482
+ `"spine-parts-sets/1"`:
483
+
484
+ ```json
485
+ {
486
+ "spec": "rig-parts-sets/1",
487
+ "sets": {
488
+ "smile": {
489
+ "slots": { "face": "face_smile", "prop": "prop_star" },
490
+ "hides": []
491
+ },
492
+ "wink": {
493
+ "slots": { "face": "face_wink" },
494
+ "hides": ["prop"]
495
+ }
496
+ }
497
+ }
498
+ ```
499
+
500
+ `loadModel` probes `sets.json` in the model document's directory by default
501
+ (treating a 404 as "no sets"; pass `setsUrl: null` to skip the probe, or a URL
502
+ string to require one at another path) and validates every slot and attachment
503
+ name against the model document. `player.sets()` (and `assets.sets`) returns the
504
+ declared `readonly RigSet[]` (`{ name, slots, hides }`), and `player.setSet(name)`
505
+ switches the active set at runtime (#134):
506
+
507
+ ```ts
508
+ player.sets(); // [{ name: 'smile', slots: { ... }, hides: [] }, ...]
509
+ player.setSet('smile'); // covered slots take the set's attachments; <slot>_still and hides are hidden
510
+ player.set; // 'smile'
511
+ player.setSet(null); // restore base / track attachments
512
+ ```
513
+
514
+ The active set is applied immediately to the held pose (including the setup pose
515
+ before `play`) and after track evaluation on every subsequent `advance`, so a
516
+ track key on a covered slot is overridden while the set is active and restored
517
+ on the next frame when cleared with `setSet(null)`. Uncovered slots keep their
518
+ track attachments, mesh deforms and sequence frames untouched; when no set is
519
+ active (`null`), track evaluation runs with zero extra work and produces
520
+ byte-identical poses. An unknown set name throws by name; on a build without
521
+ `sets.json`, `sets()` is empty (`[]`) and `setSet` throws.
522
+
395
523
  ### Several tracks
396
524
 
397
525
  A base on track 0, a layer over it, a one-shot played once and mixed back out —
@@ -937,23 +1065,17 @@ deterministic captures.
937
1065
  bun run test # builds + serves the demo, then runs chromium + webkit
938
1066
  ```
939
1067
 
940
- Playwright drives the demo (`tests/`, config in `playwright.config.ts`; CI
941
- runs the same suite on ubuntu, one job). Every scene is a rig built by
942
- `rigc build` and posed by `rig-c/core` through `RigPlayer` — rigc's gallery rigs,
943
- and three test rigs authored in this repository (`tests/rigs/`) for what the
944
- gallery does not carry: `figure` (clipping attachments in rigc's format —
945
- convex, inverse, nested, whole-skeleton — rigid parts on turned bones, two grid
946
- meshes), `glow` (additive slots with slot colours over two weighted meshes) and
947
- `turned` (a region packed turned 90° on a hand-packed page). No Spine runtime
948
- is installed or compared against: the pose is rig-c's, held to spine-core at
949
- tolerance 0 by rig-c's own CI, and a pose doubt is a rigc issue. The visual
950
- checks are **A/B within one run**: no golden snapshots are committed (they rot
951
- across platforms/GPUs) — instead the same deterministic pose (`?time` +
952
- `?timescale=0`) is screenshotted twice in the same engine and the buffers are
953
- diffed directly with a shift-tolerant comparison, so missing parts, wrong
954
- colors, and tint/blend/clip divergence fail regardless of platform.
955
-
956
- **The picture gate** is the two render modes drawing the same picture: every
1068
+ Playwright drives rigc-built gallery and procedural test rigs through the
1069
+ production `RigPlayer`; its pose comes from `rig-c/core`. CI runs the same
1070
+ suite on Ubuntu with Chromium and WebKit. Internal parity captures the same
1071
+ deterministic pose in two modes/backends within one engine. An independent
1072
+ development oracle also reads the paired Spine export through the official
1073
+ WebGL runtime. No snapshot goldens are committed. Residue limits are measured
1074
+ per engine, case and renderer; an A/B design alone does not make a measured
1075
+ budget portable to another platform. Pose discrepancies remain rigc issues;
1076
+ the independent oracle checks the resulting raster.
1077
+
1078
+ **Internal picture parity** checks the two render modes drawing the same picture: every
957
1079
  scene captured in the DOM mode and in the canvas mode and diffed — bad pixels at
958
1080
  most 1.5% of drawn content on the test-rig scenes (measured floors 0.007–0.524%,
959
1081
  macOS Chromium; each scene's own feature dropped from the canvas mode — tint,
@@ -989,16 +1111,58 @@ resolving, so blob ownership — every unpacked URL freed, nothing the caller
989
1111
  owns touched, nothing stranded by a failed load — is asserted rather than
990
1112
  assumed.
991
1113
 
992
- ## Benchmark: DOM mode vs canvas mode
1114
+ ### Independent official raster oracle
1115
+
1116
+ `tests/oracle.spec.ts` compares real DOM WebGL, DOM canvas2d and whole-canvas
1117
+ output with `@esotericsoftware/spine-webgl` 4.3.13. The official reader parses
1118
+ the same build's Spine JSON, atlas and original PNGs independently; it never
1119
+ receives a `PosedFrame` or the player's alpha-corrected textures. JSON SHA-256
1120
+ binds the export to the compiled model, and generation/page dimensions fail
1121
+ closed. Both posers receive the same fixed-step history, crop, origin, scale
1122
+ and DPR 1; world geometry must agree exactly before raster acceptance.
1123
+
1124
+ Captures include the actual images, element canvases, CSS transforms, tint
1125
+ and clipping. Transparent RGB remains diagnostic, except for additional narrow
1126
+ raw checks on the whole-canvas PMA case. Acceptance compares those
1127
+ same captured bytes on black and white backgrounds with the shared pixel
1128
+ differ, and separately constrains alpha coverage, sum, unequal-pixel count
1129
+ and maximum. `tests/oracleLimits.ts` stores measured engine/case/mode floors:
1130
+ zero stays exact; nonzero counts receive 10% plus two units, and nonzero byte
1131
+ maxima one byte. macOS and Linux have separately measured tables; other
1132
+ platforms are refused as uncalibrated. Tests never regenerate limits from the
1133
+ image under test.
1134
+
1135
+ Cases cover geometry, tint, clipping, draw order with multiply/screen,
1136
+ rotated/named regions, additive glow and an original-PMA twin. Named UV and
1137
+ rotation coverage uses native scale: measured half-scale checker sampling
1138
+ has additional residue, so arbitrary-scale pixel identity is not claimed.
1139
+ The shared #37 negative control serves false PMA flags only in the player's
1140
+ model response, leaving original files and the official reader unchanged.
1141
+ Each renderer must first pass its healthy capture, then exceed the same
1142
+ healthy black-composite budget with the altered response. This runs on both
1143
+ CI browser projects. Original PMA PNG bytes are encoded before either reader
1144
+ sees them.
1145
+
1146
+ The reference uses `antialias:false`, `preserveDrawingBuffer:true` and the
1147
+ official `pmaAdditiveBatching:false` option to retain additive coverage in
1148
+ transparent captures. Initialization finishes model/region extraction before
1149
+ official page uploads, avoiding a measured concurrent WebKit initialization
1150
+ artifact. Use the required nova-run guard and a unique `TEST_PORT` for a
1151
+ focused `bun run test tests/oracle.spec.ts --workers=1`; full-suite acceptance
1152
+ remains CI. Headless results are raster evidence, never device performance.
1153
+
1154
+ ## Benchmark: DOM, canvas and official WebGL
993
1155
 
994
1156
  ```bash
995
1157
  bun run bench:dev # the page, for a person to watch (and for Safari)
996
1158
  bun run bench # drives it in a headed Chromium and writes a report
1159
+ bun run bench --headless # counters only; all timing fields are null
997
1160
  ```
998
1161
 
999
1162
  `bench/` is a separate vite project — not part of the library, and not part of
1000
- what `bun run build` emits — that plays the same rigc gallery build in both
1001
- render modes side by side, each with its own stats line in the demo's style.
1163
+ what `bun run build` emits — that compares DOM, whole-canvas and the independent
1164
+ official WebGL reader, each with its own stats line. The paired JSON hash must
1165
+ match the model before an official comparison can publish readings.
1002
1166
  Scenes by query string: `?scene=mesh` (`portrait-packed` turning its head, the
1003
1167
  deform tier working every frame), `?scene=held` (the same pose frozen, where
1004
1168
  unchanged meshes reuse their raster and an unchanged frame is reused),
@@ -1009,7 +1173,7 @@ something you can see), laid out on one grid whose per-rig scale shrinks with
1009
1173
  the column count, so a step up in count adds rigs rather than cropping the ones
1010
1174
  already there.
1011
1175
 
1012
- **The two sides draw the same picture.** Both place rigs with one derivation and
1176
+ **The comparison uses the same framing.** All paths place rigs with one derivation and
1013
1177
  clip each to the same rect — the pane for a grid, the rig's own cell for `many`
1014
1178
  — with `overflow: hidden` on a box, the way a page embeds a player, and then
1015
1179
  *check* it: each side reports `origin Δpx`, the largest gap between where the
@@ -1017,9 +1181,12 @@ grid puts a rig and where that side drew it, and the report table carries it per
1017
1181
  row. (`contain: layout paint` is deliberately not used: paint containment clips
1018
1182
  to the same edge `overflow: hidden` does, and what it adds past the clip — a
1019
1183
  stacking context, a containing block, an independent formatting context —
1020
- changes how one side is composited and buys the comparison nothing.) Both modes
1021
- share the package's single WebGL context however many players there are; the
1022
- canvas side's stats line says how many contexts the page holds.
1184
+ changes how one side is composited and buys the comparison nothing.) Production
1185
+ modes retain the package's shared WebGL context. Official grid scenes batch
1186
+ all skeletons through one context; only `many` uses independent contexts.
1187
+ Official surviving contexts and drawn players are counted separately from
1188
+ requested players. Production survival counters are null where its public
1189
+ API cannot measure them, rather than treating requested counts as survivors.
1023
1190
 
1024
1191
  `scripts/bench.mjs` walks `bench/scenes.json`, opens each scene in a **headed**
1025
1192
  Chromium (a hidden page throttles `requestAnimationFrame`, so a headless
@@ -1027,15 +1194,20 @@ reading would be the browser's power policy rather than either mode's cost),
1027
1194
  runs a fixed number of frames per mode — one at a time, because rAF has a single
1028
1195
  cadence per document — and writes JSON plus a markdown table with the
1029
1196
  conditions attached: browser version, device pixel ratio, window size, GPU
1030
- string, and the load average before and after. Over a threshold it stamps the
1197
+ string, and the load average before and after. A software or unidentified GPU
1198
+ invalidates a headed timing report. Over a load threshold it stamps the
1031
1199
  whole report `INVALID: load average too high` and exits non-zero, because a
1032
1200
  benchmark on a busy machine measures the machine. `--corpus <dir>` points it at
1033
- local rigc builds that never enter this repository.
1201
+ local rigc builds that never enter this repository. `--headless` instead steps
1202
+ fixed frames and records counters only: FPS, pose/render timing and GPU fields
1203
+ are null, with “측정 불가: 헤드리스” in JSON and the table. The official
1204
+ additive-batching and drawing-buffer settings are recorded as conditions.
1034
1205
 
1035
- > **Numbers pending.** No comparison table is published yet. A reading taken on
1206
+ > **Performance numbers pending.** Counter reports establish the instrument,
1207
+ > not a performance comparison. A timing reading taken on
1036
1208
  > a loaded machine, a CI runner or a headless browser would not be worth
1037
1209
  > printing. The table will be taken the way every other number in this README
1038
- > was: the two stats lines read on a real device, on a quiet machine, per scene
1210
+ > was: the stats lines read on a real device, on a quiet machine, per scene
1039
1211
  > and per engine — including Safari, which stays manual because no automation
1040
1212
  > preserves what is being measured there. Until then the instrument exists and
1041
1213
  > the measurement does not.
@@ -1079,12 +1251,16 @@ so here is what is true of this package and this repository, stated plainly:
1079
1251
  `rig-c/core` (MIT), which poses rigc's model document; no part of the
1080
1252
  [Spine Runtimes](https://github.com/EsotericSoftware/spine-runtimes) is in it or
1081
1253
  required beside it.
1082
- 2. This repository links no Spine runtime either, since #63: no package of Esoteric
1083
- Software's npm scope is a dependency of any kind, and the lockfile names none.
1084
- `tests/package.spec.ts` asserts both.
1254
+ 2. Development tooling pins `@esotericsoftware/spine-webgl` 4.3.13 and its matching
1255
+ spine-core dependency for the independent oracle and comparison/benchmark.
1256
+ The [Spine Runtimes License Agreement](https://esotericsoftware.com/spine-runtimes-license)
1257
+ covers those runtimes, not this project's MIT license. They remain outside
1258
+ production source, the public API and the library package; package guards
1259
+ and the library-only pack allowlist hold that boundary.
1085
1260
  3. `rigc build`, which this repository runs to make its fixtures, also writes Spine
1086
1261
  skeleton data (`skeleton.json` and `skeleton.atlas`) beside each model document.
1087
- Nothing here reads it, and it is neither committed nor published.
1262
+ Development tooling reads this paired export independently. Generated fixtures
1263
+ remain gitignored and excluded from the library package. See [NOTICE.md](NOTICE.md).
1088
1264
  4. A product that plays that Spine data with a Spine Runtime has integrated the Spine
1089
1265
  Runtimes, whose licence permits it **under Section 2 of the
1090
1266
  [Spine Editor License Agreement](https://esotericsoftware.com/spine-editor-license)**,
@@ -1 +1 @@
1
- {"version":3,"file":"DomRenderer.d.ts","sourceRoot":"","sources":["../src/DomRenderer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DomRenderer.d.ts","sourceRoot":"","sources":["../src/DomRenderer.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { type CanvasBackend, CanvasRenderer, type CanvasViewport } from './CanvasRenderer.js';
2
2
  export { DomRenderer, type MeshBackend, type MeshSourceRect } from './DomRenderer.js';
3
3
  export { type PageLayout, type PageRegion, type RegionEncoding, type RegionImage, revokeRegions, type UnpackRegionsOptions, unpackRegions } from './DomTexture.js';
4
- export { type LoadModelOptions, loadModel, type ModelAssets, type ModelDocument, type RegionLoading } from './loadModel.js';
4
+ export { type LoadModelOptions, loadModel, type ModelAssets, type ModelDocument, type RegionLoading, type RigSet } from './loadModel.js';
5
5
  export { type AdjustableBones, type BoneAdjust, type BoneLocals, type BoneWorld, type PlayOptions, type PoseBounds, type QueueOptions, type RenderMode, type RigComplete, type RigEnvironment, type RigEvent, RigPlayer, type RigPlayerOptions, type SlotFilter, type TrackEntry } from './player.js';
6
6
  export type { BlendMode, PosedClip, PosedFrame, PosedMesh, PosedRegion, PosedSlot, } from './posedFrame.js';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnK,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC5H,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtS,YAAY,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnK,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACzI,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtS,YAAY,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC"}