spine-rigc 0.22.1 → 0.22.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.
- package/docs/AUTHORING.md +64 -10
- package/docs/SPEC_COVERAGE.md +13 -7
- package/package.json +1 -1
- package/src/compile.ts +116 -5
- package/src/types.ts +14 -0
package/docs/AUTHORING.md
CHANGED
|
@@ -642,11 +642,17 @@ against `mike2`). Both of those *build* as animation names and are refused as sk
|
|
|
642
642
|
names, and the two refusals say which is which.
|
|
643
643
|
|
|
644
644
|
**R12 — A placeholder that more than one skin fills gets a per-skin attachment
|
|
645
|
-
`name
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
is
|
|
645
|
+
`name`, and the `default` skin may not be one of those skins.** rigc writes
|
|
646
|
+
`"name": "<skin>/<placeholder>"` on each of those entries and restates `path`
|
|
647
|
+
beside it so the texture still resolves where it did; you do not author that and
|
|
648
|
+
there is nothing to do about it, but it is visible in the emitted file, so §3.4.2
|
|
649
|
+
says what it is and why. What you *do* author is where the shared art lives: a
|
|
650
|
+
placeholder the `default` skin shares with a named skin is a **compile error**,
|
|
651
|
+
because the Spine editor has no representation for it in either spelling (#567,
|
|
652
|
+
measured on 4.3.26 — named, the export re-keys it and the default skin draws
|
|
653
|
+
nothing; unnamed, the import is refused). Put the shared entry in a named skin —
|
|
654
|
+
`base` — and every filler is a named skin. A placeholder only one skin fills is
|
|
655
|
+
emitted exactly as before, in the default skin or anywhere else.
|
|
650
656
|
|
|
651
657
|
---
|
|
652
658
|
|
|
@@ -1430,18 +1436,62 @@ names you already gave it:
|
|
|
1430
1436
|
|
|
1431
1437
|
```json
|
|
1432
1438
|
"skins": {
|
|
1433
|
-
"default": { "
|
|
1439
|
+
"default": { "block": { "block": { "image": "block.png" } } },
|
|
1440
|
+
"base": { "patch": { "patch": { "image": "patch_a.png" } } },
|
|
1434
1441
|
"zulu": { "patch": { "patch": { "image": "patch_a.png", "x": 4 } } }
|
|
1435
1442
|
}
|
|
1436
1443
|
```
|
|
1437
1444
|
|
|
1438
|
-
emits
|
|
1445
|
+
emits, for slot `patch`
|
|
1439
1446
|
|
|
1440
1447
|
```json
|
|
1441
|
-
{ "name": "
|
|
1442
|
-
{ "name": "zulu/patch",
|
|
1448
|
+
{ "name": "base/patch", "path": "patch", "width": 64, "height": 64 }
|
|
1449
|
+
{ "name": "zulu/patch", "path": "patch", "width": 64, "height": 64, "x": 4 }
|
|
1443
1450
|
```
|
|
1444
1451
|
|
|
1452
|
+
🚨 **Every skin that shares a placeholder has to be a named one — the default
|
|
1453
|
+
skin may not be among them, and rigc refuses the rig if it is.** That is not a
|
|
1454
|
+
style rule; it is the editor's model, and two round trips through Spine
|
|
1455
|
+
**4.3.26** established it by ruling out both of the only two spellings there are
|
|
1456
|
+
([#567](https://github.com/firejune/rigc/issues/567)):
|
|
1457
|
+
|
|
1458
|
+
- **Give the default skin's entry a name of its own** (`"name": "default/patch"`)
|
|
1459
|
+
and the import succeeds — then the export comes back with that name as the
|
|
1460
|
+
JSON **key** (`"default/patch": { … }`, the `name` field gone), because the
|
|
1461
|
+
editor's default skin holds no skin placeholders: an attachment there hangs on
|
|
1462
|
+
the slot and is known by its name alone. The slot's setup `attachment: "patch"`
|
|
1463
|
+
now names a key the default skin does not have, so the default skin **draws
|
|
1464
|
+
nothing** — `diff` read `attachments.names 3/5`, `check` read 98.52 mean MAE
|
|
1465
|
+
with `drewSlots: 0` on that chain, and `validate --profile spine` stayed green
|
|
1466
|
+
throughout.
|
|
1467
|
+
- **Leave it as its placeholder** (no `name`, which is the obvious repair) and
|
|
1468
|
+
the editor **refuses the import**:
|
|
1469
|
+
|
|
1470
|
+
```
|
|
1471
|
+
ERROR: Unable to import skeleton.
|
|
1472
|
+
Cause: [error] Error reading attachment: mike/patch (nSX)
|
|
1473
|
+
Cause: [error] Multiple attachments have the same name:
|
|
1474
|
+
patch
|
|
1475
|
+
patch
|
|
1476
|
+
```
|
|
1477
|
+
|
|
1478
|
+
In one slot, a default-skin attachment name and a named skin's placeholder name
|
|
1479
|
+
are the same namespace, and both are `patch`.
|
|
1480
|
+
|
|
1481
|
+
⇒ **The rule: move the shared art into a named skin.** Call it `base`. Every
|
|
1482
|
+
filler of that placeholder is then a named skin, rigc composes all of them, and
|
|
1483
|
+
the names are unique within the slot — which is all
|
|
1484
|
+
[#541](https://github.com/firejune/rigc/issues/541) needed: `base/patch`,
|
|
1485
|
+
`zulu/patch` and `mike/patch` are three names. That shape is the one the editor
|
|
1486
|
+
does hold: the same three fillers in named skins imported, exported and measured
|
|
1487
|
+
**0.00 mean MAE** with names and paths intact.
|
|
1488
|
+
|
|
1489
|
+
📎 **The earlier reading, kept because it was reasonable and wrong.** Between the
|
|
1490
|
+
two trips this guide said *compose off the default skin only* — keep the default
|
|
1491
|
+
skin's entry as its placeholder and name the others. Trip 7 supported it and trip
|
|
1492
|
+
8 refuted it: that is the spelling the editor refuses at the door. There is no
|
|
1493
|
+
third spelling, which is why this is a refusal rather than a naming scheme.
|
|
1494
|
+
|
|
1445
1495
|
Three things to know about it and nothing to author:
|
|
1446
1496
|
|
|
1447
1497
|
- **`path` is restated, and it has to be.** `path` defaults to the attachment's
|
|
@@ -1454,6 +1504,9 @@ Three things to know about it and nothing to author:
|
|
|
1454
1504
|
- **A composed name that collides is a compile error, not a surprise.** If some
|
|
1455
1505
|
other placeholder in the same slot is literally called `zulu/patch`, rigc refuses
|
|
1456
1506
|
and names both sites rather than emitting two attachments with one name again.
|
|
1507
|
+
The walk covers every *uncontested* entry's plain name too, the default skin's
|
|
1508
|
+
included — a name that composed nothing can still be the one another skin
|
|
1509
|
+
composes.
|
|
1457
1510
|
(`/` is the separator because it appears in **0** of the 160 placeholder names and
|
|
1458
1511
|
159 atlas region names in `examples/` and `gallery/`, where `-` appears in 85 and
|
|
1459
1512
|
`_` in 37.)
|
|
@@ -3529,7 +3582,8 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
3529
3582
|
| `animation "A": "position" is a path constraint timeline, and this track names no constraint` | §4.12 — put the name in `"path"` |
|
|
3530
3583
|
| `N pair(s) of animation names have no one order: … "turn" / "Turn" (case) — they are one name in two cases, and which of them the editor puts first is not measured; rename one of them so they differ by more than letter case` | **R10** — rename until no pair is left. The kind in brackets says which of the editor comparator's four UNMEASURED choices decides the pair: `case` (a pure case tie), `number` (one number written two ways, or a run of digits against a word) or `separator` (make the first character that differs a letter or a digit). rigc keys `animations` in the editor's own comparator — natural and case-insensitive ([#539](https://github.com/firejune/rigc/issues/539), [#543](https://github.com/firejune/rigc/issues/543)) — so a pair that comparator settles is emitted rather than refused, and only the four choices nobody has measured are a compile error; on those, the editor's re-key repoints every slider whose animation moves index ([#535](https://github.com/firejune/rigc/issues/535)) |
|
|
3531
3584
|
| `N pair(s) of skin names have no one order: … "Zulu" / "mike" (case) — folded to one case "Zulu" and "mike" order the other way round, so whether the editor folds SKIN names decides this pair` | **R11** — rename until no pair is left. The same shape as the row above with a **wider** family: #539 measured the editor's comparator for animation names and thereby ruled codepoint out, and nothing has ruled anything out for skin names, so a pair the candidates could disagree about is refused even where the animation rule would emit it. `Zulu`/`mike` and `mike10`/`mike2` build as animation names and are refused as skin names ([#541](https://github.com/firejune/rigc/issues/541)) |
|
|
3532
|
-
| `
|
|
3585
|
+
| `slot "patch": placeholder "patch" is filled by the "default" skin AND by skins "zulu", "mike", and the Spine editor has no way to hold that … Move the default skin's entry for this slot into a named skin — call it "base"` | **R12** — do what it says: move that entry out of `default` into a named skin. The editor has no representation for a placeholder the default skin shares with a named one, in either spelling, and §3.4.2 has both measurements. Renaming the placeholder does not help; the shape is what is refused |
|
|
3586
|
+
| `N attachment name collision(s): a placeholder that more than one skin fills is emitted with the name "<skin>/<placeholder>" … slot "patch": skin "base" placeholder "zulu/patch" and skin "zulu" placeholder "patch" would both be named "zulu/patch"` | **R12** — rename the placeholder or the skin. rigc composes an attachment name for every placeholder more than one skin fills (§3.4.2), and this fires when a composed name is one another entry in the same slot already answers to — including a plain name in the default skin, which composed nothing. Both sites are named; either rename ends it |
|
|
3533
3587
|
|
|
3534
3588
|
### 5.2 Assertions — the gate
|
|
3535
3589
|
|
package/docs/SPEC_COVERAGE.md
CHANGED
|
@@ -603,13 +603,19 @@ with the member's own `skin: true` — either half alone is refused, because `Sk
|
|
|
603
603
|
| `sequence` block | ❌ | |
|
|
604
604
|
|
|
605
605
|
Across all five types, rigc emits the attachment's own **`name`** in exactly one case: a placeholder
|
|
606
|
-
that more than one skin fills (`compile.ts`'s `nameSkinAttachment`).
|
|
607
|
-
matters — `name` defaults to the placeholder and `path` defaults to
|
|
608
|
-
one placeholder are several attachments with one name, which
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
606
|
+
that more than one skin fills (`compile.ts`'s `composeSkinAttachmentName` and `nameSkinAttachment`).
|
|
607
|
+
Part 1-5 above states why it matters — `name` defaults to the placeholder and `path` defaults to
|
|
608
|
+
`name` — so several skins under one placeholder are several attachments with one name, which
|
|
609
|
+
spine-core accepts and the Spine editor refuses on import
|
|
610
|
+
([#541](https://github.com/firejune/rigc/issues/541)). The composed name is `<skin>/<placeholder>`,
|
|
611
|
+
`path` is restated beside it so the region still resolves, and a placeholder one skin fills is
|
|
612
|
+
emitted with neither. ⚠️ The **`default` skin may not be one of those skins**, and that is a
|
|
613
|
+
`CompileError` rather than an emission rule: the editor holds no placeholder the default skin shares
|
|
614
|
+
with a named one in either spelling — named, the export re-keys the attachment by its name and the
|
|
615
|
+
slot's setup attachment stops resolving; unnamed, the import is refused with `Multiple attachments
|
|
616
|
+
have the same name` ([#567](https://github.com/firejune/rigc/issues/567), Spine 4.3.26, round trips
|
|
617
|
+
7 and 8). Nothing else in the tree carries a `name`: of the twelve editor exports in `examples/`,
|
|
618
|
+
**0** attachments do, because all twelve declare one skin.
|
|
613
619
|
|
|
614
620
|
Mesh geometry is generated by exactly three procedural generators (`mesh.ts`): `buildRingMesh`
|
|
615
621
|
(three concentric rings + hub, outer two pinned), `buildRibbonMesh` (a two-wide strip along a bone
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.2",
|
|
4
4
|
"description": "Rig compiler for Spine — declarative rig specs in, Spine 4.3 skeleton data out, verified by a spine-core round-trip. Built so AI agents can author rigs and check their own work; the output imports into the Spine editor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/compile.ts
CHANGED
|
@@ -1900,9 +1900,12 @@ export function compile(opts: CompileOptions): CompileResult {
|
|
|
1900
1900
|
// The name is put on AFTER the builder rather than inside it: five
|
|
1901
1901
|
// builders write five shapes, the rule is one rule, and a rule that has
|
|
1902
1902
|
// to be remembered in five places is a rule that will be kept in four.
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1903
|
+
// `null` is an entry that carries no `name` field, which is every
|
|
1904
|
+
// uncontested placeholder. A contested one the DEFAULT skin fills never
|
|
1905
|
+
// reaches here: it is refused above (issue #567), because the editor
|
|
1906
|
+
// holds no such shape in either spelling.
|
|
1907
|
+
const composed = composeSkinAttachmentName(skinName, placeholder, shared?.has(placeholder) === true);
|
|
1908
|
+
perSlot[placeholder] = composed === null ? built : nameSkinAttachment(built, composed, placeholder);
|
|
1906
1909
|
}
|
|
1907
1910
|
tableFor(skinName)[rigSlot.name] = perSlot;
|
|
1908
1911
|
}
|
|
@@ -2528,6 +2531,92 @@ function skinAttachmentName(skinName: string, placeholder: string): string {
|
|
|
2528
2531
|
return `${skinName}${SKIN_ATTACHMENT_SEPARATOR}${placeholder}`;
|
|
2529
2532
|
}
|
|
2530
2533
|
|
|
2534
|
+
/**
|
|
2535
|
+
* The `name` one skin's entry for a placeholder is emitted with, or `null` for
|
|
2536
|
+
* the entries that carry no `name` field at all.
|
|
2537
|
+
*
|
|
2538
|
+
* Stated once, and called by both the emit and `contestedPlaceholders`'
|
|
2539
|
+
* collision walk, because two readings of one rule is how issue #567 happened.
|
|
2540
|
+
* By the time either caller runs, a contested placeholder the **default** skin
|
|
2541
|
+
* fills has already been refused — see `refuseDefaultSkinContest` — so every
|
|
2542
|
+
* entry this composes for is a named skin's.
|
|
2543
|
+
*/
|
|
2544
|
+
function composeSkinAttachmentName(skinName: string, placeholder: string, contested: boolean): string | null {
|
|
2545
|
+
return contested ? skinAttachmentName(skinName, placeholder) : null;
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
/**
|
|
2549
|
+
* Refuse a placeholder that the **default** skin and a named skin both fill.
|
|
2550
|
+
*
|
|
2551
|
+
* 🚨 This is issue #567 and it is not a naming problem, which took two editor
|
|
2552
|
+
* round trips to establish because each of them looked like one.
|
|
2553
|
+
*
|
|
2554
|
+
* ## What the editor's model is, bracketed by two trips
|
|
2555
|
+
*
|
|
2556
|
+
* Both on Spine **4.3.26**, on a rig whose three skins fill one placeholder
|
|
2557
|
+
* `patch` from three PNGs of three sizes, with a second slot `block` that one
|
|
2558
|
+
* skin fills as the fixed point.
|
|
2559
|
+
*
|
|
2560
|
+
* **Trip 7 — the default skin's attachment given a name of its own**
|
|
2561
|
+
* (`"name": "default/patch"`, as issue #552 emitted it). The editor IMPORTS it,
|
|
2562
|
+
* and exports the default skin's entry re-keyed by that name:
|
|
2563
|
+
*
|
|
2564
|
+
* built "default": { "patch": { "patch": { "name": "default/patch", … } } }
|
|
2565
|
+
* exported "default": { "patch": { "default/patch": { … } } }
|
|
2566
|
+
*
|
|
2567
|
+
* The editor's default skin holds no skin placeholders — an attachment there
|
|
2568
|
+
* hangs on the slot and is known by its name alone — so the name becomes the
|
|
2569
|
+
* key. The slot's setup `attachment: "patch"` then resolves in no default-skin
|
|
2570
|
+
* key and the default skin draws NOTHING: `check` read mean MAE 98.52 with
|
|
2571
|
+
* `drewSlots: 0` on the `patch` chain while `validate --profile spine` stayed
|
|
2572
|
+
* green, because the file is well-formed and only the editor's model says what
|
|
2573
|
+
* a key means.
|
|
2574
|
+
*
|
|
2575
|
+
* **Trip 8 — the default skin's attachment left as its placeholder** (no `name`,
|
|
2576
|
+
* the obvious repair). The editor REFUSES the import:
|
|
2577
|
+
*
|
|
2578
|
+
* ERROR: Unable to import skeleton.
|
|
2579
|
+
* Cause: [error] Error reading attachment: mike/patch (nSX)
|
|
2580
|
+
* Cause: [error] Multiple attachments have the same name:
|
|
2581
|
+
* patch
|
|
2582
|
+
* patch
|
|
2583
|
+
*
|
|
2584
|
+
* The default skin's attachment `patch` hangs on the slot; the named skins'
|
|
2585
|
+
* placeholder `patch` is that slot's other child. **In one slot, a default-skin
|
|
2586
|
+
* attachment name and a named skin's placeholder name are the same namespace.**
|
|
2587
|
+
*
|
|
2588
|
+
* ⇒ The two trips close the case: name it and the setup attachment resolves
|
|
2589
|
+
* nowhere, do not name it and the import is refused. **The editor has no
|
|
2590
|
+
* representation for a placeholder the default skin and a named skin both
|
|
2591
|
+
* fill** — its own convention is shared art in the default skin, per-skin art in
|
|
2592
|
+
* placeholders, and a slot's one setup `attachment` string naming one or the
|
|
2593
|
+
* other. There is no third spelling to find, so this is a `CompileError` and not
|
|
2594
|
+
* a scheme, in the shape issue #543 used: refuse by name and say what to do.
|
|
2595
|
+
*
|
|
2596
|
+
* ⚠️ What this does NOT touch, and the trips measured that half too: a
|
|
2597
|
+
* placeholder that two or more NAMED skins fill keeps #552's composition
|
|
2598
|
+
* exactly. Trip 8's second rig — two named skins filling `patch`, the default
|
|
2599
|
+
* skin holding `block` only — imported, exported and measured **0.0000 mean
|
|
2600
|
+
* MAE**, names and paths intact. The remedy this refusal states is that rig:
|
|
2601
|
+
* move the default skin's entry into a named skin.
|
|
2602
|
+
*/
|
|
2603
|
+
function refuseDefaultSkinContest(slotName: string, placeholder: string, skins: readonly string[]): never {
|
|
2604
|
+
const named = skins.filter((skin) => skin !== DEFAULT_SKIN);
|
|
2605
|
+
throw new CompileError(
|
|
2606
|
+
`slot "${slotName}": placeholder "${placeholder}" is filled by the "${DEFAULT_SKIN}" skin AND by ` +
|
|
2607
|
+
`${named.length === 1 ? 'skin' : 'skins'} ${named.map((skin) => `"${skin}"`).join(', ')}, and the Spine ` +
|
|
2608
|
+
'editor has no way to hold that. Measured on 4.3.26 in both spellings: give the default skin\'s attachment a ' +
|
|
2609
|
+
`name of its own ("${DEFAULT_SKIN}${SKIN_ATTACHMENT_SEPARATOR}${placeholder}") and the editor re-keys it by ` +
|
|
2610
|
+
`that name on export, so the slot's setup attachment "${placeholder}" resolves in no default-skin key and the ` +
|
|
2611
|
+
'default skin draws nothing; leave it as the placeholder and the import is refused outright with ' +
|
|
2612
|
+
`"Multiple attachments have the same name: ${placeholder} ${placeholder}", because a default-skin attachment ` +
|
|
2613
|
+
"hangs on the slot beside the named skins' placeholder of that name. Move the default skin's entry for this " +
|
|
2614
|
+
`slot into a named skin — call it "base" — so every skin filling "${placeholder}" is a named one. Two or more ` +
|
|
2615
|
+
'named skins sharing a placeholder is the shape the editor does hold, and rigc composes their names for them ' +
|
|
2616
|
+
'(#541, #552).',
|
|
2617
|
+
);
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2531
2620
|
/**
|
|
2532
2621
|
* Which `(slot, placeholder)` pairs more than one skin fills — and, on the way,
|
|
2533
2622
|
* the refusal that keeps the composed names from colliding with authored ones.
|
|
@@ -2551,7 +2640,7 @@ function skinAttachmentName(skinName: string, placeholder: string): string {
|
|
|
2551
2640
|
* entry given its own `name`** IMPORTS — all four skins. So it is neither the
|
|
2552
2641
|
* skin count nor the timelines; it is one name over several attachments.
|
|
2553
2642
|
*
|
|
2554
|
-
* ## Only the contested pairs are named, and
|
|
2643
|
+
* ## Only the contested pairs are named, and the default skin may not contest
|
|
2555
2644
|
*
|
|
2556
2645
|
* A placeholder one skin fills keeps the emitted shape it has always had: no
|
|
2557
2646
|
* `name`, no `path` it did not already carry. Every rig in this tree declares
|
|
@@ -2559,6 +2648,14 @@ function skinAttachmentName(skinName: string, placeholder: string): string {
|
|
|
2559
2648
|
* multi-skin rig whose skins use distinct placeholders does not move either,
|
|
2560
2649
|
* because nothing there is ambiguous to begin with.
|
|
2561
2650
|
*
|
|
2651
|
+
* ⚠️ A contested placeholder the **default** skin fills is refused before any
|
|
2652
|
+
* of this runs — `refuseDefaultSkinContest`, issue #567 — because two editor
|
|
2653
|
+
* round trips showed the editor holds no such shape in either spelling. So
|
|
2654
|
+
* every entry the walk below composes for belongs to a named skin, and the
|
|
2655
|
+
* emitted name comes off `composeSkinAttachmentName`, which this function calls
|
|
2656
|
+
* rather than restates: the emit and the refusal disagreeing about one name is
|
|
2657
|
+
* the defect both of them exist to prevent.
|
|
2658
|
+
*
|
|
2562
2659
|
* ⚠️ The scope of the editor's uniqueness rule is **not** skeleton-wide, and the
|
|
2563
2660
|
* corpus proves it rather than a hypothesis doing so: `spineboy-pro.json`, which
|
|
2564
2661
|
* the editor wrote, gives the name `head` to a region in slot `head` and to a
|
|
@@ -2596,12 +2693,26 @@ function contestedPlaceholders(
|
|
|
2596
2693
|
const collisions: string[] = [];
|
|
2597
2694
|
for (const [slotName, perSlot] of fillers) {
|
|
2598
2695
|
const shared = new Set([...perSlot].filter(([, skins]) => skins.length > 1).map(([placeholder]) => placeholder));
|
|
2696
|
+
// 🚨 Before anything is composed: a contested placeholder the DEFAULT skin
|
|
2697
|
+
// fills has no representation in the editor at all, in either spelling
|
|
2698
|
+
// (issue #567, round trips 7 and 8). It is refused here rather than emitted,
|
|
2699
|
+
// and the refusal comes first because renaming cannot repair it — the
|
|
2700
|
+
// remedy is a different rig, not a different string.
|
|
2701
|
+
for (const placeholder of shared) {
|
|
2702
|
+
const skins = perSlot.get(placeholder)!;
|
|
2703
|
+
if (skins.includes(DEFAULT_SKIN)) refuseDefaultSkinContest(slotName, placeholder, skins);
|
|
2704
|
+
}
|
|
2599
2705
|
if (shared.size) contested.set(slotName, shared);
|
|
2600
2706
|
/** Emitted attachment name -> the first entry that claimed it. */
|
|
2601
2707
|
const claimed = new Map<string, string>();
|
|
2602
2708
|
for (const [placeholder, skins] of perSlot) {
|
|
2603
2709
|
for (const skinName of skins) {
|
|
2604
|
-
|
|
2710
|
+
// The emitted name, read off the one function that decides it — so the
|
|
2711
|
+
// refusal and the emit cannot drift into two readings. An UNCONTESTED
|
|
2712
|
+
// entry is claimed under its bare placeholder, the default skin's
|
|
2713
|
+
// included: a named skin whose composed name equals it is a collision,
|
|
2714
|
+
// and one this walk sees for the same reason it sees every other.
|
|
2715
|
+
const name = composeSkinAttachmentName(skinName, placeholder, shared.has(placeholder)) ?? placeholder;
|
|
2605
2716
|
const site = `skin "${skinName}" placeholder "${placeholder}"`;
|
|
2606
2717
|
const taken = claimed.get(name);
|
|
2607
2718
|
if (taken === undefined) claimed.set(name, site);
|
package/src/types.ts
CHANGED
|
@@ -717,6 +717,20 @@ export interface SpineSlot {
|
|
|
717
717
|
* (`:529`, `:559`), so an attachment given a name and no path resolves its region
|
|
718
718
|
* at the new name and the atlas lookup misses. `nameSkinAttachment` in
|
|
719
719
|
* `compile.ts` is the one place that writes either, and it always writes both.
|
|
720
|
+
*
|
|
721
|
+
* ⚠️ And the **`default` skin may never be one of the skins sharing that
|
|
722
|
+
* placeholder** — a fact about the editor rather than about the format (issue
|
|
723
|
+
* #567, Spine 4.3.26, round trips 7 and 8), and a `CompileError` rather than a
|
|
724
|
+
* spelling. The editor's named skins hold *skin placeholders*, a key holding a
|
|
725
|
+
* named attachment, and come back untouched. Its default skin holds no
|
|
726
|
+
* placeholders: an attachment there hangs on the slot and is known by its name
|
|
727
|
+
* alone. So writing a name there gets it re-keyed by that name on export and
|
|
728
|
+
* the slot's setup `attachment` stops resolving (trip 7), and NOT writing one
|
|
729
|
+
* makes that attachment's name collide with the named skins' placeholder of the
|
|
730
|
+
* same name, which the editor refuses at import (trip 8). Both spellings are
|
|
731
|
+
* measured, so there is no third; `refuseDefaultSkinContest` in `compile.ts` is
|
|
732
|
+
* where that lives, and `composeSkinAttachmentName` beside it decides the name
|
|
733
|
+
* for the skins that are left.
|
|
720
734
|
*/
|
|
721
735
|
export interface SpineRegionAttachment {
|
|
722
736
|
name?: string;
|