@rig-c/core 2.71.0 → 2.73.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/README.md CHANGED
@@ -51,6 +51,16 @@ Everything in that table needs Bun and this package — `preview` and `vote` als
51
51
  `@esotericsoftware/spine-core` installed beside it (see below) — and no clone, no
52
52
  reference art, no art pipeline, no server.
53
53
 
54
+ ### Alpha-derived clipping
55
+
56
+ Alpha-derived clipping takes a named source slot/skin/attachment plus explicit
57
+ alpha threshold, drawing-pixel simplification tolerance and mapped vertex budget.
58
+ Region, authored/resolved linked mesh and generated grid/contour depth sources use
59
+ ordinary weighted clipping, with the existing validation gate before emission.
60
+ See [AUTHORING: Alpha-derived clipping](docs/AUTHORING.md#alpha-derived-clipping)
61
+ for source-motion limits, fit reports and named refusals, and
62
+ [the face guide](docs/FACE.md#a-measured-alpha-aperture) for the depth-yaw flow.
63
+
54
64
  ## What rigc is, and what it is not
55
65
 
56
66
  rigc emits **Spine's own skeleton data format**. That is the whole positioning, and
package/docs/AUTHORING.md CHANGED
@@ -2620,6 +2620,7 @@ attachment rather than a second set of art.
2620
2620
  | --- | --- |
2621
2621
  | `type` | `"clipping"`. **Required** |
2622
2622
  | `name`, `vertexCount`, `vertices`, `weights`, `boneIndexing`, `color` | exactly as on a bounding box |
2623
+ | `alphaClip` | Optional generated clipping geometry from a named source plate. Declare `skin`, `slot`, `attachment`, `alpha`, `tolerance`, and `maxVertices` in the object; all six are required. This form excludes authored vertex geometry and `convex`/`inverse`. See **Alpha-derived clipping** for source, carrier, budget, and deform refusals. |
2623
2624
  | `end` | the last slot the clip applies to, **by name**. Absent is the parser's own encoding for *clip everything after this one*, which is why a typo cannot be told from an omission once the file is loaded: `findSlot` returns null on a miss and the parser assigns that null without a word, so the clip runs to the bottom of the draw order and takes every slot below it with it. rigc refuses a name the rig does not declare — `end names slot "X", which this rig does not declare` — and `A33` refuses it again on a skeleton rigc did not write |
2624
2625
  | `convex` | default **false**. True tells the runtime the polygon is convex so it can clip without triangulating it, and a polygon that deforms concave is clipped by its convex hull instead (`ClippingAttachment.convex`). Nothing here checks that the polygon is in fact convex |
2625
2626
  | `inverse` | default **false**. True makes everything **outside** the polygon visible instead of everything inside, and inverse clipping is always treated as convex (`ClippingAttachment.inverse`) |
@@ -2935,8 +2936,9 @@ rather than being checked afterwards.
2935
2936
 
2936
2937
  #### 3.4.5 `depth` — give every vertex its own z, instead of one cylinder radius
2937
2938
 
2938
- **On a `contour` or a `grid`.** A grid is the one it was built for: a turn
2939
- needs interior vertices to move, and a contour has none of its own.
2939
+ **On a `contour` or a `grid` generator, or directly on an authored mesh**
2940
+ (including named or raw weighted geometry). A grid supplies interior vertices;
2941
+ an authored mesh keeps its existing triangulation and weights.
2940
2942
 
2941
2943
  A `yaw` or `pitch` key (§4.12) turns a part by treating it as painted on a
2942
2944
  cylinder: a vertex `u` off the axis gets `z = √(radius² − u²)`. That is the right
@@ -2954,6 +2956,35 @@ off a greyscale sheet in the part's own pixel grid:
2954
2956
  }
2955
2957
  ```
2956
2958
 
2959
+ For authored geometry, put the same block beside `weights` or `vertices`:
2960
+
2961
+ ```json
2962
+ "depth": { "image": "strand_depth.png", "near": "white", "zScale": 12 }
2963
+ ```
2964
+
2965
+ The attachment must name an `image`. Each vertex's **setup world position**
2966
+ (the weighted sum of its setup bind positions) is located in the same window
2967
+ as the generators: the part is centred on the slot bone's setup world origin,
2968
+ x right and y up, while the sheet is in part pixels, x right and y down.
2969
+ UVs do not locate these samples. On a scaled atlas page the same drawing-sized
2970
+ sheet is read through the existing page-to-drawing conversion. A sheet whose
2971
+ size differs from the part is refused by `CE_COMPILE_GREY_SHEET_SIZE_MISMATCH`.
2972
+ A top-level `depth` beside a generator is refused; use that generator's depth
2973
+ field for generated geometry.
2974
+
2975
+ An authored `yaw` or `pitch` key with `depth: true` evaluates in **setup world**,
2976
+ including `about` in world units. Its driving-axis displacement is
2977
+ `(coordinate - about) * (cos(angle) - 1) - z * sin(angle)`; with
2978
+ `narrowing: false` only `-z * sin(angle)` remains. Each rounded world offset is
2979
+ written through every influencing bone's inverse setup linear transform.
2980
+ Subsequent bone animation and physics carry those bind offsets with the mesh:
2981
+ the displacement after posing is the weighted composition of the posed bones
2982
+ with their inverse setup transforms, rather than a fixed global translation.
2983
+ The reported turn ceiling measures the setup-world triangles on both axes;
2984
+ A39 still measures the actual posed artifact. Other transform kinds and meshes
2985
+ without this new input keep their existing evaluation spaces and emitted bytes.
2986
+ Naming a sheet without a depth turn changes no skeleton or atlas byte.
2987
+
2957
2988
  | Field | Meaning |
2958
2989
  | --- | --- |
2959
2990
  | `image` | **required.** The sheet, relative to the rig's `images` directory, and the **same pixel size as this attachment's `image`**. It is not packed into the atlas — it is a measurement rigc reads at compile time, not art anything draws. Under `--atlas-in` that is still the drawing's size, on a page that declares a `scale:` too: the sheet is read at each vertex's texel position over the scale the atlas states, so one sheet serves the loose parts and every pack of them |
@@ -7330,7 +7361,7 @@ retain the minimal inputs and report that code rather than inventing author data
7330
7361
  | <a id="CE_COMPILE_DEPTH_IMAGE_MISSING"></a>`CE_COMPILE_DEPTH_IMAGE_MISSING` | “&lt;value&gt;: the depth block has no "image"; it is the sheet to sample, relative to the rig's images directory” | State the depth image relative to the rig images directory. |
7331
7362
  | <a id="CE_COMPILE_DEPTH_NEAR_INVALID"></a>`CE_COMPILE_DEPTH_NEAR_INVALID` | “&lt;value&gt;: the depth block says "near": &lt;value&gt;; it is "white" or "black". Both conventions are in use, and a sheet read with the wrong one turns the part inside out with every gate green.” | State near as white or black to declare the sheet convention. |
7332
7363
  | <a id="CE_COMPILE_DEPTH_TONE_SCALE_INVALID"></a>`CE_COMPILE_DEPTH_TONE_SCALE_INVALID` | “&lt;value&gt;: "zScale" is &lt;value&gt;; it is a finite number of world units”<br>“&lt;value&gt;: "zScale" is &lt;value&gt;; it is how many units the map's full range spans, so a positive number. To put the near end at the back, say "near": "black" — a negative scale states the same thing twice and the two can then disagree.”<br>“&lt;value&gt;: depth tone "&lt;value&gt;" is &lt;value&gt;; it is a finite number”<br>“&lt;value&gt;: depth tone "contrast" is &lt;value&gt;; a contrast at or below 0 collapses the range onto the midpoint (or turns it inside out), so the map would describe a flat part. It is a positive number, and 1 leaves the range alone.”<br>“&lt;value&gt;: depth tone "gamma" is &lt;value&gt;; a gamma at or below 0 maps every level to the same nearness, so the map would describe a flat part. It is a positive number, and 1 is the straight line.”<br>Here the values named in the message include: “bias”. | Use finite depth tone values, positive gamma/contrast and a positive finite zScale. |
7333
- | <a id="CE_COMPILE_DEPTH_COVERAGE_INCOMPLETE"></a>`CE_COMPILE_DEPTH_COVERAGE_INCOMPLETE` | “&lt;value&gt;: the depth map "&lt;value&gt;" does not cover &lt;value&gt; of the mesh's &lt;value&gt; vertices — the first is vertex &lt;value&gt; at (&lt;value&gt;, &lt;value&gt;)[ / in the page's texels, pixel (&lt;value&gt;, &lt;value&gt;) of the drawing-sized sheet]. &lt;value&gt; Sampling anyway would give those vertices the sheet's background depth and fold them away from the turn.”<br>“[A contour mesh puts every vertex ON the silhouette and pushes it out by the margin, so a sheet cut to the art stops short of every one of them. Dilate the sheet past the mesh margin, or lower the margin. / A grid spans the whole part window, corners included, while a sheet is usually cut to the art — so the lattice reaches past it wherever the art does not fill the window. Dilate the sheet to the window, or state "us"/"vs" that keep the lattice inside the art.]” | Supply a depth sheet covering every mesh vertex in the part's drawing-sized pixel grid. |
7364
+ | <a id="CE_COMPILE_DEPTH_COVERAGE_INCOMPLETE"></a>`CE_COMPILE_DEPTH_COVERAGE_INCOMPLETE` | “&lt;value&gt;: the depth map "&lt;value&gt;" does not cover &lt;value&gt; of the mesh's &lt;value&gt; vertices — the first is vertex &lt;value&gt; at (&lt;value&gt;, &lt;value&gt;)[ / in the page's texels, pixel (&lt;value&gt;, &lt;value&gt;) of the drawing-sized sheet]. &lt;value&gt; Sampling anyway would give those vertices the sheet's background depth and fold them away from the turn.”<br>“[A contour mesh puts every vertex ON the silhouette and pushes it out by the margin, so a sheet cut to the art stops short of every one of them. Dilate the sheet past the mesh margin, or lower the margin. / A grid spans the whole part window, corners included, while a sheet is usually cut to the art — so the lattice reaches past it wherever the art does not fill the window. Dilate the sheet to the window, or state "us"/"vs" that keep the lattice inside the art. / The authored setup vertices reach outside the sheet coverage. Extend the sheet to cover their bilinear taps, or correct the setup geometry.]” | Supply a depth sheet covering every mesh vertex in the part's drawing-sized pixel grid. |
7334
7365
  | <a id="CE_COMPILE_MESH_BONE_SETUP_TRANSFORM_MISSING"></a>`CE_COMPILE_MESH_BONE_SETUP_TRANSFORM_MISSING` | “&lt;value&gt;: bone "&lt;value&gt;" has no setup transform” | Correct the bone/setup hierarchy; report a missing compiled transform for a declared bone. |
7335
7366
  | <a id="CE_COMPILE_MESH_BONE_NOT_DECLARED"></a>`CE_COMPILE_MESH_BONE_NOT_DECLARED` | “&lt;value&gt;: bone "&lt;value&gt;" is not in the rig's bone list” | Use a bone name declared in the rig. |
7336
7367
  | <a id="CE_COMPILE_GREY_SHEET_IMAGE_MISSING"></a>`CE_COMPILE_GREY_SHEET_IMAGE_MISSING` | “&lt;value&gt;: the "&lt;value&gt;" block has no image to read” | State the image in the named depth/soft block. |
@@ -7467,7 +7498,7 @@ retain the minimal inputs and report that code rather than inventing author data
7467
7498
  | <a id="CE_COMPILE_SEQUENCE_KEY_INDEX_OUTSIDE_SERIES"></a>`CE_COMPILE_SEQUENCE_KEY_INDEX_OUTSIDE_SERIES` | “&lt;value&gt; (t=&lt;value&gt;): index &lt;value&gt; is past the end of a &lt;value&gt;-frame series (frames 0 to &lt;value&gt;). `Sequence.resolveIndex` clamps it to the last frame, so the key would start on a frame it does not name. `index` is 0-based.” | Use a zero-based index within the frame series. |
7468
7499
  | <a id="CE_COMPILE_DEFORM_TRANSFORM_AND_VERTICES"></a>`CE_COMPILE_DEFORM_TRANSFORM_AND_VERTICES` | “&lt;value&gt; (t=&lt;value&gt;): the key carries both a "transform" and a "vertices" run, and they are two answers to one question. Authored offsets and a stated model do not combine, for the same reason a mesh cannot carry both a "generator" and authored geometry — drop one.”<br>Also: “animation "&lt;value&gt;" deform &lt;value&gt;/&lt;value&gt;/&lt;value&gt;”<br>Here the values named in the message include: “default”. | Use a transform model or authored vertex offsets. |
7469
7500
  | <a id="CE_COMPILE_DEFORM_TRANSFORM_START_INDEX"></a>`CE_COMPILE_DEFORM_TRANSFORM_START_INDEX` | “&lt;value&gt; (t=&lt;value&gt;): the key states a "transform" and a start index. A transform is a model of the whole attachment and is evaluated over all &lt;value&gt; of its vertices, so it always starts at deform index 0. A model applied to part of a run leaves a step at the run's edge — write the partial run by hand if that is what you mean.”<br>Also: “animation "&lt;value&gt;" deform &lt;value&gt;/&lt;value&gt;/&lt;value&gt;”<br>Here the values named in the message include: “default”. | Apply a transform over the whole attachment without a start index, or author the partial run. |
7470
- | <a id="CE_COMPILE_DEFORM_TRANSFORM_SETUP_UNRESOLVED"></a>`CE_COMPILE_DEFORM_TRANSFORM_SETUP_UNRESOLVED` | “&lt;value&gt; (t=&lt;value&gt;): a "transform" states where every vertex of this attachment goes, and this one has a vertex the arithmetic cannot place — &lt;value&gt;. Fix the weights, or write the bind-space pairs yourself and start the run with "offset".”<br>“vertex &lt;value&gt;'s &lt;value&gt; weights sum to &lt;value&gt; rather than 1. A stated model becomes a deform run by writing each vertex's world displacement D into every influence as Mᵢ⁻¹·D, which moves the vertex by D·Σwᵢ — so a sum that is not 1 has no displacement that lands it where the model says. A20_MESH_WEIGHTS_COHERENT is the assertion that requires that sum, at the same 1e-3”<br>Also: “animation "&lt;value&gt;" deform &lt;value&gt;/&lt;value&gt;/&lt;value&gt;”<br>Here the values named in the message include: “default”. | Fix weights so setup vertices can be positioned, or author bind-space pairs using offset. |
7501
+ | <a id="CE_COMPILE_DEFORM_TRANSFORM_SETUP_UNRESOLVED"></a>`CE_COMPILE_DEFORM_TRANSFORM_SETUP_UNRESOLVED` | “&lt;value&gt; (t=&lt;value&gt;): a "transform" states where every vertex of this attachment goes, and this one has a vertex the arithmetic cannot place — &lt;value&gt;. Fix the weights, or write the bind-space pairs yourself and start the run with "offset".”<br>“vertex &lt;value&gt;'s &lt;value&gt; weights sum to &lt;value&gt; rather than 1. A stated model becomes a deform run by writing each vertex's world displacement D into every influence as Mᵢ⁻¹·D, which moves the vertex by D·Σwᵢ — so a sum that is not 1 has no displacement that lands it where the model says. A20_MESH_WEIGHTS_COHERENT is the assertion that requires that sum, at the same 1e-3”<br>Also: “animation "&lt;value&gt;" deform &lt;value&gt;/&lt;value&gt;/&lt;value&gt;”<br>Here the values named in the message include: “default”.<br>“&lt;value&gt;: depth needs resolved setup positions — &lt;value&gt;” | Fix weights so setup vertices can be positioned, or author bind-space pairs using offset. |
7471
7502
  | <a id="CE_COMPILE_DEFORM_TRANSFORM_OFFSET_COUNT"></a>`CE_COMPILE_DEFORM_TRANSFORM_OFFSET_COUNT` | “&lt;value&gt; (t=&lt;value&gt;): the transform produced &lt;value&gt; numbers for &lt;value&gt; vertices”<br>Also: “animation "&lt;value&gt;" deform &lt;value&gt;/&lt;value&gt;/&lt;value&gt;”<br>Here the values named in the message include: “default”. | Report the transform model and compiler defect: the produced offset count does not match the vertices. |
7472
7503
  | <a id="CE_COMPILE_DEFORM_TRANSFORM_RUN_LENGTH"></a>`CE_COMPILE_DEFORM_TRANSFORM_RUN_LENGTH` | “&lt;value&gt; (t=&lt;value&gt;): the transform expanded to &lt;value&gt; numbers and this attachment's deform array is &lt;value&gt; long”<br>Also: “animation "&lt;value&gt;" deform &lt;value&gt;/&lt;value&gt;/&lt;value&gt;”<br>Here the values named in the message include: “default”. | Report the transform model and compiler defect: the expanded offsets do not match the deform array. |
7473
7504
  | <a id="CE_COMPILE_DEFORM_START_SOURCES_CONFLICT"></a>`CE_COMPILE_DEFORM_START_SOURCES_CONFLICT` | “&lt;value&gt; (t=&lt;value&gt;): a key gives its start as "offset" (an index into the deform array) or as "fromVertex" (a vertex index rigc translates), never both”<br>Also: “animation "&lt;value&gt;" deform &lt;value&gt;/&lt;value&gt;/&lt;value&gt;”<br>Here the values named in the message include: “default”. | Use offset or fromVertex, rather than both. |
@@ -7645,7 +7676,9 @@ retain the minimal inputs and report that code rather than inventing author data
7645
7676
  | <a id="CE_DEFORM_TRANSFORM_OBJECT"></a>`CE_DEFORM_TRANSFORM_OBJECT` | “&lt;where&gt;: "transform" is &lt;JSON.stringify(transform)&gt;; it is an object with a "kind"” | Write transform as an object with a kind. |
7646
7677
  | <a id="CE_DEFORM_TRANSFORM_KIND"></a>`CE_DEFORM_TRANSFORM_KIND` | “&lt;where&gt;: "transform" has kind &lt;JSON.stringify(kind)&gt;; this spec evaluates &lt;DEFORM_TRANSFORM_KINDS.join(', ')&gt;. A model that is none of those is still authorable as a "vertices" run.”<br>Here the values named in the message include: “yaw”, “bend”, “wave”, “pitch”, “affine”. | Choose a supported transform kind, or author the deformation as a vertices run. |
7647
7678
  | <a id="CE_DEFORM_DEPTH_RADIUS_CONFLICT"></a>`CE_DEFORM_DEPTH_RADIUS_CONFLICT` | “&lt;where&gt;: transform &lt;kind&gt; states both "depth": true and a radius &lt;JSON.stringify(t.radius)&gt;. They are two answers to how far forward each vertex sits — a cylinder derives it, a map states it — and a key carrying both leaves a reader unable to say which one the output came from. Drop one.” | Choose depth:true or a radius and remove the other. |
7648
- | <a id="CE_DEFORM_DEPTH_MAP_REQUIRED"></a>`CE_DEFORM_DEPTH_MAP_REQUIRED` | “&lt;where&gt;: transform &lt;kind&gt; says "depth": true and this attachment has no depth map. The map is named on the attachment's generator, as `"depth": { "image": …, "near": …, "zScale": … }`, because it describes the art rather than this key. Name it there, or give this key a radius.” | Declare the attachment generator depth map or give this transform a radius. |
7679
+ | <a id="CE_DEFORM_DEPTH_MAP_REQUIRED"></a>`CE_DEFORM_DEPTH_MAP_REQUIRED` | “&lt;where&gt;: transform &lt;kind&gt; says "depth": true and this attachment has no depth map. The map is named on an authored attachment or its contour/grid generator, as `"depth": { "image": …, "near": …, "zScale": … }`, because it describes the art rather than this key. Name it there, or give this key a radius.” | Declare the authored attachment or contour/grid generator depth map, or give this transform a radius. |
7680
+ | <a id="CE_COMPILE_DEPTH_AUTHORED_REQUIRED"></a>`CE_COMPILE_DEPTH_AUTHORED_REQUIRED` | “&lt;where&gt;: attachment "depth" describes authored geometry; a generated mesh states its depth on the contour or grid generator instead” | Put depth on the contour/grid generator or remove the generator and author the geometry. |
7681
+ | <a id="CE_COMPILE_DEPTH_IMAGE_REQUIRED"></a>`CE_COMPILE_DEPTH_IMAGE_REQUIRED` | “&lt;where&gt;: an authored mesh with "depth" needs an "image" to locate the part's pixel grid” | Name the part image the sheet describes. |
7649
7682
  | <a id="CE_DEFORM_DEPTH_SAMPLE_COUNT"></a>`CE_DEFORM_DEPTH_SAMPLE_COUNT` | “&lt;where&gt;: transform &lt;kind&gt; has &lt;depth.length&gt; sampled depths for &lt;count&gt; vertices” | Report this compiler defect with the inputs and printed sampled-depth and vertex counts; do not edit generated samples. |
7650
7683
  | <a id="CE_DEFORM_RADIUS_POSITIVE"></a>`CE_DEFORM_RADIUS_POSITIVE` | “&lt;where&gt;: transform &lt;kind&gt; has radius &lt;radius&gt;; it is the radius of the cylinder the part is painted on, so a positive number” | State a positive cylinder radius. |
7651
7684
  | <a id="CE_DEFORM_VERTEX_OUTSIDE_CYLINDER"></a>`CE_DEFORM_VERTEX_OUTSIDE_CYLINDER` | “&lt;where&gt;: transform &lt;kind&gt; has radius &lt;radius&gt;, and vertex &lt;v&gt; sits at &lt;kind === 'yaw' ? 'x' : 'y'&gt;=&lt;setup[2 * v + along]&gt;, which is &lt;round(Math.abs(u) - radius)&gt; past it (about=&lt;about&gt;). The cylinder has no surface there, so its depth would be 0 and the projection would be a different model at that vertex. Raise the radius to where the part actually sits, or move the vertex.” | Raise radius to include the measured vertex or move that vertex within the cylinder. |
@@ -7749,7 +7782,7 @@ retain the minimal inputs and report that code rather than inventing author data
7749
7782
 
7750
7783
  | Code | Fixed message text and alternatives | What to change |
7751
7784
  | --- | --- | --- |
7752
- | <a id="CE_KEYS_UNKNOWN_FIELD"></a>`CE_KEYS_UNKNOWN_FIELD` | “&lt;where&gt;: &lt;what&gt; has &lt;strays.length === 1 ? 'a key' : `${strays.length} keys`&gt; this compiler does not read: &lt;named.join(', ')&gt;. Nothing reads such a key, so it would be dropped from the emitted skeleton in silence — fix the spelling or remove it. {This shape has no fields at all. / Known here: &lt;[...known].sort().join(', ')&gt;.}”<br>“{"&lt;key&gt;" / "&lt;key&gt;" (did you mean &lt;near.map((n) =&gt; `"${n}"`).join(', ')&gt;?)}”<br>Also: “skin "&lt;value&gt;" slot "&lt;value&gt;" attachment "&lt;value&gt;"”<br>“&lt;value&gt; generator.bones[&lt;value&gt;]”<br>“{constraint "&lt;value&gt;" / constraints[&lt;value&gt;]}”<br>“&lt;value&gt;.transform.envelope”<br>“&lt;value&gt; weights[&lt;value&gt;][&lt;value&gt;]”<br>“invariants.deformMayFold[&lt;value&gt;]”<br>“animations."&lt;value&gt;"”<br>“&lt;value&gt;.offsets[&lt;value&gt;]”<br>“{stop "&lt;value&gt;" / stops[&lt;value&gt;]}”<br>“&lt;value&gt; properties."&lt;value&gt;".to."&lt;value&gt;"”<br>“this rig spec”<br>“&lt;value&gt;.derive”<br>“setup."&lt;value&gt;"”<br>“physics."&lt;value&gt;"”<br>“&lt;value&gt;'s "lower"”<br>“&lt;value&gt; "sequence"”<br>“&lt;value&gt; generator.bias”<br>“&lt;value&gt; generator.depth”<br>“&lt;value&gt;.events[&lt;value&gt;]”<br>“&lt;value&gt;.tracks[&lt;value&gt;]”<br>“invariants.axes[&lt;value&gt;]”<br>“&lt;value&gt; generator.falloff”<br>“&lt;value&gt;.drawOrder[&lt;value&gt;]”<br>“&lt;value&gt; generator.influence”<br>“invariants.detached[&lt;value&gt;]”<br>“{slot "&lt;value&gt;" / slots[&lt;value&gt;]}”<br>“invariants.consumerDrivenMix[&lt;value&gt;]”<br>“event "&lt;value&gt;"”<br>“&lt;value&gt;'s "from"”<br>“this motion spec”<br>“{absent / &lt;value&gt;}”<br>“{region / &lt;value&gt;}”<br>“&lt;value&gt;.keys[&lt;value&gt;]”<br>“&lt;value&gt; generator.soft”<br>“{linkedmesh / &lt;value&gt;}”<br>“&lt;value&gt;.deform[&lt;value&gt;]”<br>“&lt;value&gt;.sequence[&lt;value&gt;]”<br>“&lt;value&gt; generator (&lt;value&gt;)”<br>“{bone "&lt;value&gt;" / bones[&lt;value&gt;]}”<br>Here the values named in the message include: “uvs”, “mixX”, “blend”, “scaleX”, “groups”, “radius”, “images”, “axisBone”, “maxBones”, “massBone”, “softness”, “timelines”, “archetype”, “rotateMode”, “windGlobal”, “localTarget”, “spacingMode”, “positionMode”, “inertiaGlobal”, “dampingGlobal”, “gravityGlobal”, “strengthGlobal”, “skeleton.stageBox”, “invariants.idleDrivesMeshes”, “ik”, “lag”, “why”, “cut”, “fps”, “icon”, “dark”, “cols”, “kind”, “hull”, “cell”, “path”, “loop”, “mask”, “ease”, “ramp”, “note”, “mode”, “size”, “rows”, “mixY”, “alpha”, “pairs”, “inner”, “curve”, “limit”, “edges”, “float”, “start”, “audio”, “along”, “clamp”, “width”, “skins”, “links”, “chain”, “phase”, “about”, “power”, “count”, “color”, “gamma”, “index”, “delay”, “shearY”, “source”, “convex”, “target”, “amount”, “volume”, “shearX”, “slider”, “string”, “center”, “parent”, “closed”, “zScale”, “inward”, “margin”, “across”, “height”, “digits”, “scaleY”, “anchor”, “degrees”, “inverse”, “stretch”, “easings”, “against”, “default”, “inherit”, “lengths”, “carried”, “balance”, “stagger”, “outward”, “through”, “vertices”, “axis_deg”, “contrast”, “compress”, “notUnder”, “property”, “duration”, “controls”, “additive”, “rotation”, “triangles”, “mixScaleY”, “mixScaleX”, “mixShearY”, “tolerance”, “mixRotate”, “meshSlots”, “minWeight”, “mixGlobal”, “amplitude”, “massGlobal”, “meshCenter”, “fromVertex”, “slotWindow”, “wavelength”, “"skeleton"”, “attachments”, “vertexCount”, “maxVertices”, “localSource”, “"invariants"”, “bendPositive”, “boneIndexing”, “constantSpeed”, “meshTriangles”, “referenceScale”, “narrowing”. | Fix each printed stray key's spelling using the suggested or known names, or remove it. |
7785
+ | <a id="CE_KEYS_UNKNOWN_FIELD"></a>`CE_KEYS_UNKNOWN_FIELD` | “&lt;where&gt;: &lt;what&gt; has &lt;strays.length === 1 ? 'a key' : `${strays.length} keys`&gt; this compiler does not read: &lt;named.join(', ')&gt;. Nothing reads such a key, so it would be dropped from the emitted skeleton in silence — fix the spelling or remove it. {This shape has no fields at all. / Known here: &lt;[...known].sort().join(', ')&gt;.}”<br>“{"&lt;key&gt;" / "&lt;key&gt;" (did you mean &lt;near.map((n) =&gt; `"${n}"`).join(', ')&gt;?)}”<br>Also: “skin "&lt;value&gt;" slot "&lt;value&gt;" attachment "&lt;value&gt;"”<br>“&lt;value&gt; generator.bones[&lt;value&gt;]”<br>“{constraint "&lt;value&gt;" / constraints[&lt;value&gt;]}”<br>“&lt;value&gt;.transform.envelope”<br>“&lt;value&gt; weights[&lt;value&gt;][&lt;value&gt;]”<br>“invariants.deformMayFold[&lt;value&gt;]”<br>“animations."&lt;value&gt;"”<br>“&lt;value&gt;.offsets[&lt;value&gt;]”<br>“{stop "&lt;value&gt;" / stops[&lt;value&gt;]}”<br>“&lt;value&gt; properties."&lt;value&gt;".to."&lt;value&gt;"”<br>“this rig spec”<br>“&lt;value&gt;.derive”<br>“setup."&lt;value&gt;"”<br>“physics."&lt;value&gt;"”<br>“&lt;value&gt;'s "lower"”<br>“&lt;value&gt; "sequence"”<br>“&lt;value&gt; generator.bias”<br>“&lt;value&gt; generator.depth”<br>“&lt;value&gt; depth”<br>“&lt;value&gt;.events[&lt;value&gt;]”<br>“&lt;value&gt;.tracks[&lt;value&gt;]”<br>“invariants.axes[&lt;value&gt;]”<br>“&lt;value&gt; generator.falloff”<br>“&lt;value&gt;.drawOrder[&lt;value&gt;]”<br>“&lt;value&gt; generator.influence”<br>“invariants.detached[&lt;value&gt;]”<br>“{slot "&lt;value&gt;" / slots[&lt;value&gt;]}”<br>“invariants.consumerDrivenMix[&lt;value&gt;]”<br>“event "&lt;value&gt;"”<br>“&lt;value&gt;'s "from"”<br>“this motion spec”<br>“{absent / &lt;value&gt;}”<br>“{region / &lt;value&gt;}”<br>“&lt;value&gt;.keys[&lt;value&gt;]”<br>“&lt;value&gt; generator.soft”<br>“{linkedmesh / &lt;value&gt;}”<br>“&lt;value&gt;.deform[&lt;value&gt;]”<br>“&lt;value&gt;.sequence[&lt;value&gt;]”<br>“&lt;value&gt; generator (&lt;value&gt;)”<br>“{bone "&lt;value&gt;" / bones[&lt;value&gt;]}”<br>Here the values named in the message include: “uvs”, “mixX”, “blend”, “scaleX”, “groups”, “radius”, “images”, “axisBone”, “maxBones”, “massBone”, “softness”, “timelines”, “archetype”, “rotateMode”, “windGlobal”, “localTarget”, “spacingMode”, “positionMode”, “inertiaGlobal”, “dampingGlobal”, “gravityGlobal”, “strengthGlobal”, “skeleton.stageBox”, “invariants.idleDrivesMeshes”, “ik”, “lag”, “why”, “cut”, “fps”, “icon”, “dark”, “cols”, “kind”, “hull”, “cell”, “path”, “loop”, “mask”, “ease”, “ramp”, “note”, “mode”, “size”, “rows”, “mixY”, “alpha”, “pairs”, “inner”, “curve”, “limit”, “edges”, “float”, “start”, “audio”, “along”, “clamp”, “width”, “skins”, “links”, “chain”, “phase”, “about”, “power”, “count”, “color”, “gamma”, “index”, “delay”, “shearY”, “source”, “convex”, “target”, “amount”, “volume”, “shearX”, “slider”, “string”, “center”, “parent”, “closed”, “zScale”, “inward”, “margin”, “across”, “height”, “digits”, “scaleY”, “anchor”, “degrees”, “inverse”, “stretch”, “easings”, “against”, “default”, “inherit”, “lengths”, “carried”, “balance”, “stagger”, “outward”, “through”, “vertices”, “axis_deg”, “contrast”, “compress”, “notUnder”, “property”, “duration”, “controls”, “additive”, “rotation”, “triangles”, “mixScaleY”, “mixScaleX”, “mixShearY”, “tolerance”, “mixRotate”, “meshSlots”, “minWeight”, “mixGlobal”, “amplitude”, “massGlobal”, “meshCenter”, “fromVertex”, “slotWindow”, “wavelength”, “"skeleton"”, “attachments”, “vertexCount”, “maxVertices”, “localSource”, “"invariants"”, “bendPositive”, “boneIndexing”, “constantSpeed”, “meshTriangles”, “referenceScale”, “narrowing”.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦ alphaClip⟧<br>⟦alphaClip⟧ | Fix each printed stray key's spelling using the suggested or known names, or remove it. |
7753
7786
  | <a id="CE_KEYS_NUMBER_FLOAT32_FINITE"></a>`CE_KEYS_NUMBER_FLOAT32_FINITE` | “&lt;where&gt;: &lt;place(path)&gt; is &lt;String(node)&gt;; a number in this file is finite at float32 precision, at most ±&lt;String(FLOAT32_MAX)&gt;, because the skeleton is written as float32 — past that it would be emitted as Infinity, which JSON writes as null, a value the file never stated.&lt;why&gt;”<br>“{ / { This one is finite as a double and has no float32 but Infinity. / JSON has no spelling for Infinity: a literal past ±1.7976931348623157e308, such as 1e309, parses to it.}}” | State a finite number whose float32 rounding stays finite; inspect the printed path and magnitude. |
7754
7787
  | <a id="CE_KEYS_FIELD_VALUE_TYPE"></a>`CE_KEYS_FIELD_VALUE_TYPE` | “&lt;where&gt;: &lt;visit.name([key, ...wrong.tail])&gt; is &lt;typeFound(wrong.value)&gt;; &lt;wrong.required&gt; is required”<br>“a boolean (true or false)”<br>“a number”<br>“a number or null”<br>“a string”<br>“a string or null”<br>“an array of arrays of numbers”<br>“an array of numbers”<br>“an array of numbers, or null”<br>“an array of strings”<br>“an object whose every value is a string or null”<br>“an object whose every value is an array of numbers”<br>“an object whose every value is an array of strings” | Replace the named field or element with the required JSON type printed in the message. |
7755
7788
  | <a id="CE_KEYS_ENUM_VALUE"></a>`CE_KEYS_ENUM_VALUE` | “&lt;where&gt;: &lt;visit.name([key])&gt; is &lt;JSON.stringify(value) ?? String(value)&gt;; one of &lt;rule.set.map((name) =&gt; JSON.stringify(name)).join(', ')&gt; — &lt;rule.readAs&gt;”<br>“anything else was read as a ribbon by the mesh checks and as a ring by the control-bone reader and the geometry builder, so the refusal it got named a fault the part did not have”<br>“anything else was read as no rotation source, and the bone was emitted without the setup rotation it asked for”<br>“anything else was read as the default "name" in silence: a weighted "vertices" run was refused as unflagged, and every other attachment built byte for byte as if the key were absent” | Choose one of the printed names for the named field; the readAs explanation describes why another spelling changes its meaning. |
@@ -12491,7 +12524,93 @@ nonselected output is the stored source geometry through the unchanged setup
12491
12524
  matrices; it does not borrow a different selection's deform. The control
12492
12525
  checks both results and the source of the difference, without an epsilon.
12493
12526
 
12494
- ## 15. Posing in place for a player — the runtime poser (stage 0)
12527
+ ### Alpha-derived clipping
12528
+
12529
+ A clipping attachment may replace authored `vertexCount`/`vertices`/`weights` with
12530
+ `alphaClip`. The two forms are exclusive. This creates ordinary weighted clipping
12531
+ geometry; it introduces no runtime alpha mask or new projection model.
12532
+
12533
+ ```json
12534
+ {"type":"clipping","end":"content","alphaClip":{
12535
+ "skin":"default","slot":"aperture","attachment":"aperture",
12536
+ "alpha":128,"tolerance":0.5,"maxVertices":256
12537
+ }}
12538
+ ```
12539
+
12540
+ All six fields are required. `skin`, `slot`, `attachment` name the source's exact
12541
+ placeholder identity, independent of draw order; no current-skin/setup-attachment
12542
+ guess is made. `alpha` is an integer in 1..255, `tolerance` is a positive distance
12543
+ in the drawing's pixels, and `maxVertices` is an integer >=3. These example values
12544
+ are declarations, not defaults. The measured alpha is the decoded source plate,
12545
+ not the source slot's animated opacity. A hidden source slot may supply the plate.
12546
+
12547
+ Sources are regions, meshes (generated grid/contour included), or linked meshes
12548
+ whose ordinary source resolution succeeded. Atlas rotation/trim extraction and
12549
+ **declared** scale apply before tracing. The source must have one nonempty island,
12550
+ transparent exterior and no holes; a single clipping polygon cannot encode holes.
12551
+ A sequence has no single immutable plate and is refused. Missing or ambiguous UV
12552
+ carriers refuse the build instead of extrapolating the source surface.
12553
+
12554
+ The simplified outline is split at every source UV-triangle crossing. Every
12555
+ partitioned open interval also requires a unique carrier: carried endpoints alone
12556
+ cannot authorize a straight bridge across a hole in the source UV domain. UV
12557
+ barycentrics locate the source's setup position, per-bone weighted first moments
12558
+ and sampled depth. Those crossings count against `maxVertices`. The resulting
12559
+ named weighted bindings follow the source bones even if the clip slot belongs to
12560
+ a different bone. They do **not** inherit another attachment's deform timeline.
12561
+ For a source that deforms, explicitly give source and clip identical affine or
12562
+ `depth:true` yaw/pitch transform keys in compatible source/clip `DeformGeometry`
12563
+ frames. Existing
12564
+ `compileDeformTrack` evaluates both; clip depth comes from interpolation of the
12565
+ source's sampled vertex depths, not a second sample of a nonlinear depth image.
12566
+ The frame is derived by `deformGeometryOf`: a bind-space frame requires exactly
12567
+ one influence per vertex and exactly one bone; duplicate influences on that same
12568
+ bone instead evaluate in world space. A world-source/bind-clip mismatch is refused
12569
+ by name. Compatible world/world affine or depth transforms are accepted when
12570
+ source influence shares are constant across vertices, so interpolation commutes
12571
+ with both projection and moving bone matrices. Dense keys, envelopes/nonlinear
12572
+ transforms and varying world-space shares cannot silently leave a clip static;
12573
+ they receive a named representability refusal. Static multi-bone sources
12574
+ use their existing named weighted geometry without a new solver.
12575
+
12576
+ `build` on both entries and `explain` print a `CLIP` row: source/target identity,
12577
+ threshold/tolerance, traced/simplified/emitted vertex counts and budget,
12578
+ `coveredArt/artPixels`, coverage, false-positive/negative **pixel-centre cells**,
12579
+ maximum original trace-corner distance to the simplified outline, overshoot, and
12580
+ `maxBindingDeviation` (maximum world-space discrepancy from source-carrier setup
12581
+ positions after final float32 binding/world rounding).
12582
+ Trace/overshoot distances are drawing pixels, including on a scaled atlas;
12583
+ binding deviation explicitly uses world units. Final float32 adjacent duplicate,
12584
+ foldback and zero-area polygons are refused, not silently pruned. This is a threshold
12585
+ silhouette fit, not soft-alpha pixel equality. Coverage/distance scalars display
12586
+ six decimal places (display rounding envelope 0.5e-6); counts are exact. Do not
12587
+ compare the shortened display against exact float32 equality. No mesh-specific
12588
+ 99.5% coverage cutoff or offset margin is applied to a clipping boundary.
12589
+
12590
+ The ordinary validation gate still precedes artifact writes. `A18` compares
12591
+ independent emits and `A33` reads clipping geometry; neither alone proves the
12592
+ posed mask is the intended aperture. The controlled proof separately compares
12593
+ the threshold plate on the actual posed source surface, clip boundary and both
12594
+ renderers at four gaze directions × depth yaw ±15°. It fixes viewport/background,
12595
+ uses output pixel centres and the existing premultiplied bilinear reader, and
12596
+ separates soft-edge quantization from simplification/projection/filtering error.
12597
+ A real licensed editor import/export and nonempty per-state pixel/binding checks
12598
+ remain separate requirements; editor harness exit 0 or parser tests are not a
12599
+ verdict on those figures.
12600
+
12601
+ | Code | Named refusal and repair |
12602
+ | --- | --- |
12603
+ | <a id="CE_COMPILE_ALPHA_CLIP_PARAMETERS"></a>`CE_COMPILE_ALPHA_CLIP_PARAMETERS` | Alpha/tolerance/scale/budget names its actual value and required range. Declare every parameter; never choose a threshold by eye during a run.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦: alphaClip.alpha is ⟧<br>⟦; required an integer in 1..255. Declare the threshold.⟧<br>⟦: alphaClip.tolerance=⟧<br>⟦, page scale=⟧<br>⟦required finite positive drawing-pixel tolerance and page scale.⟧<br>⟦: alphaClip.maxVertices is ⟧<br>⟦; required an integer &gt;=3. ⟧<br>⟦Declare the complete mapped polygon budget.⟧ |
12604
+ | <a id="CE_COMPILE_ALPHA_CLIP_NO_ALPHA"></a>`CE_COMPILE_ALPHA_CLIP_NO_ALPHA` | `slotwithnoalpha`: the actual pixel count above threshold is zero or the whole window. Supply a nonempty silhouette with transparent exterior. An opaque RGBA channel is not a silhouette.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦: slotwithnoalpha: ⟧<br>⟦ of ⟧<br>⟦ pixels reach alpha ⟧<br>⟦; ⟧<br>⟦required a nonempty silhouette with transparent exterior. Supply alpha art or declare a threshold that measures a silhouette.⟧ |
12605
+ | <a id="CE_COMPILE_ALPHA_CLIP_TOPOLOGY"></a>`CE_COMPILE_ALPHA_CLIP_TOPOLOGY` | Trace cannot form one simple outline, has holes/islands/pinches, or simplifies below three vertices. Repair the source or lower the declared tolerance; holes are never silently filled.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦: clipping has ⟧<br>⟦ vertices; required &gt;=3 after final coordinate rounding.⟧<br>⟦: adjacent clipping vertices ⟧<br>⟦ and ⟧<br>⟦[⟧<br>⟦] and [⟧<br>⟦]; required finite distinct neighbours after final coordinate rounding. ⟧<br>⟦Lower the source coordinate magnitude or simplify the outline.⟧<br>⟦: adjacent edges ⟧<br>⟦ fold back after rounding; ⟧<br>⟦required a nondegenerate simple clipping boundary.⟧<br>⟦: clipping doubled area is ⟧<br>⟦ after coordinate rounding; required nonzero area.⟧<br>⟦: alpha outline cannot be represented: ⟧<br>⟦bad part size ⟧<br>⟦x⟧<br>⟦the alpha mask holds ⟧<br>⟦ bytes for a ⟧<br>⟦ part⟧<br>⟦no pixel of the ⟧<br>⟦ part reaches alpha ⟧<br>⟦; there is no silhouette to trace⟧<br>⟦the alpha silhouette pinches to a single point at pixel corner (⟧<br>⟦), where two parts of the art ⟧<br>⟦meet diagonally — one outline cannot pass through one point twice. Move the alpha threshold so the ⟧<br>⟦pinch opens (raise it) or closes (lower it), or author the geometry as weights⟧<br>⟦the alpha trace did not close; the mask is not a closed region⟧<br>⟦the alpha trace reached a dead end at corner (⟧<br>⟦); the mask is not a closed region⟧<br>⟦; required one simple connected outline.⟧<br>⟦: alpha ⟧<br>⟦ islands and ⟧<br>⟦ hole pixels; ⟧<br>⟦required one island without holes. Separate the source or author clipping geometry.⟧ |
12606
+ | <a id="CE_COMPILE_ALPHA_CLIP_SELF_INTERSECTION"></a>`CE_COMPILE_ALPHA_CLIP_SELF_INTERSECTION` | `selfintersectionaftersimplification`: names intersecting edges. Lower tolerance or repair the source topology. The forged-crossing helper control is not evidence of a traced raster counterexample.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦: selfintersectionaftersimplification: edges ⟧<br>⟦ and ⟧<br>⟦intersect; required one simple outline. Lower tolerance or repair the source topology.⟧ |
12607
+ | <a id="CE_COMPILE_ALPHA_CLIP_CARRIER"></a>`CE_COMPILE_ALPHA_CLIP_CARRIER` | Source UV carrier missing/ambiguous at the named outline vertex or partitioned open interval. Extend coverage or repair overlapping source triangles; no extrapolation.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦: source UV carrier is ambiguous: ⟧<br>⟦COMPARE_UV_CARRIER_NOT_UNIQUE⟧<br>⟦required unique source geometry. Repair overlapping UV triangles.⟧<br>⟦: source has no UV carrier for outline vertex ⟧<br>⟦ at [⟧<br>⟦]; ⟧<br>⟦required coverage of the complete clipping outline. Extend the source geometry or lower tolerance.⟧<br>⟦: outline interval ⟧<br>⟦ from [⟧<br>⟦] to [⟧<br>⟦] ⟧<br>⟦has no source UV carrier at open midpoint [⟧<br>⟦]; required complete interval coverage. ⟧<br>⟦Fill the source UV gap; carried endpoints cannot authorize a bridge.⟧ |
12608
+ | <a id="CE_COMPILE_ALPHA_CLIP_VERTEX_BUDGET"></a>`CE_COMPILE_ALPHA_CLIP_VERTEX_BUDGET` | `polygonoverdeclaredvertexbudget`: actual mapped count exceeds declared limit, including source-triangle crossings. Raise the budget or simplify the outline/source.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦ alphaClip from "⟧<br>⟦"⟧<br>⟦: polygonoverdeclaredvertexbudget: ⟧<br>⟦ vertices after source carrier crossings (⟧<br>⟦ simplified), required &lt;=⟧<br>⟦. Raise the declared budget or simplify the source/outline.⟧ |
12609
+ | <a id="CE_COMPILE_ALPHA_CLIP_SOURCE"></a>`CE_COMPILE_ALPHA_CLIP_SOURCE` | Names missing identity/plate, wrong source kind, sequence or unlocated atlas grid. Supply exact source identity and static plate geometry. A clipping source cannot serve as its own dependency.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦: generated clipping target was not filed in its skin; required a resolved attachment.⟧<br>⟦: alphaClip.⟧<br>⟦ is ⟧<br>⟦; required an explicit nonempty source name.⟧<br>⟦ alphaClip from "⟧<br>⟦"⟧<br>⟦: source attachment is absent; required a declared slot/skin/attachment.⟧<br>⟦: source kind is ⟧<br>⟦; required a region or resolved mesh with pixels.⟧<br>⟦: linked source resolves to ⟧<br>⟦nothing⟧<br>⟦; required a resolved mesh.⟧<br>⟦: source is ⟧<br>⟦; required region or mesh geometry with a plate (a clipping source would be a dependency cycle).⟧<br>⟦: source has a ⟧<br>⟦-frame sequence; required one immutable source plate. Select a separate named attachment for the measured frame.⟧<br>⟦: region "⟧<br>⟦" has no source plate; required image/atlas pixels.⟧<br>⟦: source plate grid cannot be located. ⟧<br>⟦page "⟧<br>⟦" declares ⟧<br>⟦ and its PNG is ⟧<br>⟦ — ⟧<br>⟦ of the declared width and ⟧<br>⟦ of the declared height⟧<br>⟦A runtime does not read the file's own size anywhere in the region mapping — &#96;TextureAtlas&#96; computes every ⟧<br>⟦region's UVs as a fraction of the DECLARED size (&#96;region.u = region.x / page.width&#96;, spine-core ⟧<br>⟦&#96;dist/TextureAtlas.js:162-171&#96;) — so a page whose PNG is the declared page RESCALED draws the same picture at ⟧<br>⟦the file's resolution, and one whose PNG is anything else draws whatever sits at those fractions. What a ⟧<br>⟦declared size that disagrees with the file breaks is every reader that addresses the page in TEXELS: this ⟧<br>⟦validator's own &#96;A19&#96; alpha scan, the region lift rigc's mesh generators trace, and a canvas renderer that ⟧<br>⟦cuts each part out of the page by source rectangle.⟧<br>⟦The &#96;scale:&#96; header states one ratio for both axes, so a page whose axes differ cannot be ⟧<br>⟦declared honestly at all: re-export the page at ⟧<br>⟦, or repack it.⟧<br>⟦The format states coarser texels with the &#96;scale:&#96; header, but this page cannot be re-declared ⟧<br>⟦that way: ⟧<br>⟦region ⟧<br>⟦'s &#96;⟧<br>⟦&#96; of ⟧<br>⟦ becomes ⟧<br>⟦ on the file's own grid, which is not a whole texel⟧<br>⟦. Re-export the page at ⟧<br>⟦The format states coarser texels with the &#96;scale:&#96; header and rigc builds that: declare ⟧<br>⟦&#96;size: ⟧<br>⟦&#96; with &#96;scale: ⟧<br>⟦&#96; and multiply every ⟧<br>⟦&#96;bounds&#96;/&#96;offsets&#96; on this page by ⟧<br>⟦, and every part keeps the size it ⟧<br>⟦has now.⟧ |
12610
+ | <a id="CE_COMPILE_ALPHA_CLIP_CONFLICT"></a>`CE_COMPILE_ALPHA_CLIP_CONFLICT` | Names authored fields or convex/inverse flags conflicting with generated inside-clipping geometry. Use one authored/generated form.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦: alphaClip conflicts with [⟧<br>⟦vertexCount⟧<br>⟦vertices⟧<br>⟦weights⟧<br>⟦boneIndexing⟧<br>⟦, ⟧<br>⟦], convex=⟧<br>⟦, inverse=⟧<br>⟦; required ordinary inside clipping with generated geometry only. Remove authored geometry/flags.⟧ |
12611
+ | <a id="CE_COMPILE_ALPHA_CLIP_DEFORM"></a>`CE_COMPILE_ALPHA_CLIP_DEFORM` | Names animation and source/clip key/frame counts that cannot share the existing projection contract. Give matching explicit supported transform keys in compatible actual frames, with constant world shares where needed, or author the corresponding clip geometry and motion; weighted binding does not copy source deform.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦ alphaClip from "⟧<br>⟦"⟧<br>⟦: animation "⟧<br>⟦" has ⟧<br>⟦ source and ⟧<br>⟦ clip keys; source frame=⟧<br>⟦no vertex geometry⟧<br>⟦clip frame=⟧<br>⟦, constant world influence shares=⟧<br>⟦Required matching explicit affine/depth-yaw/pitch transforms in compatible DeformGeometry frames, ⟧<br>⟦with constant shares for world-space transfer. Fix the named frame/keys or author corresponding clip motion.⟧ |
12612
+
12613
+ ## 15. Posing in place for a player — the runtime poser (stage 1)
12495
12614
 
12496
12615
  rigc poses a model document twice over, on purpose (issue #1591). The **oracle**
12497
12616
  (`src/core/`) is the reference: each pose is a pure function of the document,
@@ -12526,17 +12645,43 @@ exact resolved table identity `(skin, model.slots[attachment.slot].name, placeho
12526
12645
  The display `name` may be shared by different placeholders; it is never a table
12527
12646
  key to guess the source from. These load-time strings do not change the pose path.
12528
12647
 
12529
- | | what stage 0 poses |
12648
+ | | what stage 1 poses |
12530
12649
  | --- | --- |
12531
12650
  | bones | local and world transforms, all five inherit modes, skin-required bones under the view's skin (an inactive bone is zeros, as the oracle poses it) |
12532
12651
  | timelines | `rotate`, `translate(x/y)`, `scale(x/y)`, `shear(x/y)`; a slot's `attachment`, `rgba`, `rgb` and `alpha`; ik and transform timelines; linear, stepped and Bézier keys, each read from a per-pose key cursor |
12533
12652
  | constraints | one- and two-bone ik; world-space, absolute transform constraints; the document's order, each followed by the bones below what it moved posed again |
12534
12653
  | vertices | regions, meshes, weighted meshes and linked meshes, written into the pose's buffer |
12654
+ | skins and events | named/no-skin views with their skin-required bones and constraints; event delivery in key order over `(last, time]`, tail then head across a wrap |
12535
12655
  | time | one animation at full weight from the setup pose, at a time (`apply`) or stepped as a looping track (`step`, the track time wrapped by the duration as `openTrack` wraps it) |
12536
12656
 
12657
+ Skin switching is a load-time view change: compile `underSkin(document, name)`
12658
+ (or `underNoSkin(document)`), create the replacement `RuntimePose`, then call
12659
+ `replacement.restore(animationName, oldPose.trackTime, oldPose.time)`. Restore
12660
+ carries both clocks **and the event cursor**: its event list is empty, a key at
12661
+ or before that restored animation time is not fired again, and later keys and
12662
+ subsequent loops still deliver normally. Do not restore by setting trackTime and
12663
+ calling apply: setAnimation starts the event cursor at −1, so that sequence
12664
+ replays already-passed cues. `restore` refuses non-finite times by name.
12665
+ The old buffers belong to the old view; do not keep drawing them as the new
12666
+ skin. An ambiguous merged `all` view still refuses `skin-conflict`; that refusal
12667
+ does not reject either named view.
12668
+
12669
+ Read `pose.events` immediately after `apply`/`step`. It is a reused borrowed list
12670
+ of immutable load-time records: `name`, float32 key `time`, `int`, `float`,
12671
+ `string`, `volume`, `balance`. Duplicate-time keys keep their authored order.
12672
+ The interval and wrap rules are the oracle's `openTrack` rules; a new animation
12673
+ starts at previous event time −1, so an apply at 0 includes its zero-time keys.
12674
+ The list is replaced on the next pose; retain copies only when needed. Delivery
12675
+ itself reuses records and list storage. The legacy five-column oracle event row
12676
+ is unchanged. Public `EventTimeline` observations also hold the audio details:
12677
+ silent keys keep volume/balance at 0; on a voiced key an omitted balance falls
12678
+ back to the definition's **volume**, matching the measured runtime rather than
12679
+ inventing a correction. An explicit `ignore: ['events']` diagnostic still mutes
12680
+ the list; normal compilation never does.
12681
+
12537
12682
  **Everything else is refused at load, by name.** `RuntimeRefusal.missing` lists
12538
12683
  every construct met, each `{ feature, detail, animation }`, and the message
12539
- reads `not posed by the runtime poser (stage 0), N construct(s) missing:
12684
+ reads `not posed by the runtime poser (stage 1), N construct(s) missing:
12540
12685
  <feature>: <detail>; …`. A model-level feature refuses the whole model; an
12541
12686
  animation-level one refuses that animation alone (`model.refused`;
12542
12687
  `model.animation(name)` and `setAnimation(name)` throw its refusal) and every
@@ -12545,7 +12690,6 @@ refused: nothing is posed approximately.
12545
12690
 
12546
12691
  | feature | refuses | what it is |
12547
12692
  | --- | --- | --- |
12548
- | `skin` | the model | a skin other than `default` |
12549
12693
  | `path-constraint`, `physics-constraint`, `slider-constraint` | the model | a constraint of that kind |
12550
12694
  | `local-transform`, `additive-transform` | the model | a transform constraint with `localSource` or `localTarget`, or `additive` |
12551
12695
  | `inactive-constrained-bone` | the model | an applied ik or transform constraint naming a bone the view leaves inactive (what the oracle writes there depends on the pass before) |
@@ -12554,7 +12698,6 @@ refused: nothing is posed approximately.
12554
12698
  | `skin-conflict` | as above | a placeholder the skins fill differently |
12555
12699
  | `oracle-absent` | the model | a pose the oracle itself leaves out (`constraintsAbsentWhy`) |
12556
12700
  | `inherit-timeline`, `deform-timeline`, `sequence-timeline`, `draw-order-timeline`, `dark-colour-timeline` | the animation | a timeline of that kind (`dark-colour` is `rgba2` / `rgb2`) |
12557
- | `events` | the animation, unless `ignore: ['events']` | event keys. Events move no pose, so a consumer that reads none may ask for them to be ignored and is handed a poser that fires none; without the request a dropped cue would be silent |
12558
12701
 
12559
12702
  A call it cannot answer throws `RuntimeInputError`, naming the call:
12560
12703
  `apply: no animation is set (setAnimation first)`, `animation "x" is not one of
@@ -12577,3 +12720,21 @@ side here. The selftest's `runtime-poser` suite holds the gate to its plants (a
12577
12720
  refused feature smuggled through — each caught by name), a seeded population of
12578
12721
  every construct above, the looping step against `openTrack`, and a steady-state
12579
12722
  frame measured at 0 bytes with the collector off.
12723
+
12724
+ ## 16. Compare sequential mesh candidates against one motion reference
12725
+
12726
+ Programmatic callers that gate candidates one at a time can import
12727
+ `prepareMotionReference` and `comparePreparedMotion` from `rig-c/meshcompare`.
12728
+ Pass the one-shot `MotionComparisonInput` without `candidates` to preparation,
12729
+ then pass each complete input with its candidate list to comparison. The handle
12730
+ reuses the reference's parsed build, art samples, carriers and poses only while
12731
+ the reference model text, schedule, attachments (including mask alpha bytes),
12732
+ art-fit bounds and motion options match exactly. A changed bound field, even
12733
+ `+0` to `-0`, throws `MeshReductionError` with
12734
+ `COMPARE_PREPARED_MISMATCH` naming the field; a forged handle throws
12735
+ `COMPARE_PREPARED_HANDLE`. Prepare a new handle for a different selection or
12736
+ held-out schedule. Returned reports are detached from the prepared state, and
12737
+ the one-shot `compareMeshesInMotion` retains its report and refusal contract.
12738
+ The full comparison contract and its input types are in the repository's
12739
+ [MESH_REDUCTION.md](https://github.com/rig-c/core/blob/main/docs/MESH_REDUCTION.md#the-comparison-as-implemented-stage-c1),
12740
+ which is not shipped in the package.
package/docs/FACE.md CHANGED
@@ -1946,3 +1946,22 @@ a different document and a different measurement. Everything here is one head
1946
1946
  turning, blinking and looking — and the one general lesson that might carry into
1947
1947
  that work is §7's: **allocate the channels before the first key, because Spine
1948
1948
  blends and does not add.**
1949
+
1950
+ ### A measured alpha aperture
1951
+
1952
+ A named socket plate can supply an `alphaClip` clipping attachment (AUTHORING,
1953
+ **Alpha-derived clipping**). This also accepts a generated grid/contour source
1954
+ with a depth sheet: the clip is carried by the source's UV/setup bindings and
1955
+ sampled depths, independent of the clip slot's own bone. Give source and clip
1956
+ explicit matching existing depth-yaw transform keys; a weight does not inherit
1957
+ another attachment's deformation. Source-triangle crossings become clip vertices
1958
+ and count against the declared budget. Do not replace this turn with a cosine-only
1959
+ stand-in, fill a source hole, or infer threshold/tolerance from an appearance.
1960
+
1961
+ `tools/alpha_clip_proof.ts --out <evidence>` builds a controlled painted ellipse
1962
+ and measures four gazes at yaw ±15°, with separate threshold/soft-edge error and
1963
+ source-boundary checks beside core/SkeletonClipping pixel parity. Fixed framing
1964
+ prevents a shifted clip from being hidden by automatic crop changes. These are
1965
+ controlled CPU-render/geometry observations when actually run; they do not certify
1966
+ production art, editor operation or browser/GPU playback. The actual editor trip
1967
+ must preserve the clip's end, bindings and nonempty per-state render figures too.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rig-c/core",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "AI-authored 2D rigging and animation, verified before it is written. rigc compiles a rig spec into skeleton data in the Spine 4.3 format, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Ships as an agent skill.",
5
5
  "type": "module",
6
6
  "bin": {