@heroiclands/package-build 22.11.0 → 22.12.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/CHANGELOG.md +6 -0
- package/CONTENT.md +6 -0
- package/docs/content-format.md +50 -28
- package/engine/frontmatter-lint.mjs +15 -7
- package/engine/holdings.mjs +7 -9
- package/engine/map-places.mjs +8 -7
- package/engine/populations.mjs +5 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 22.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **References:** An Address in note data that names no local or declared dependency target now reports an error; missing artwork keeps its fallback warning.
|
|
8
|
+
|
|
3
9
|
## 22.11.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/CONTENT.md
CHANGED
|
@@ -1249,6 +1249,12 @@ somewhere other than where they were made, or not at all:
|
|
|
1249
1249
|
reference answered by a vendored manifest lands exactly as it would in a
|
|
1250
1250
|
wikilink. `--no-references` turns this one off for a tree whose cross-package
|
|
1251
1251
|
references it cannot see.
|
|
1252
|
+
- **Dead Address reference** — an Address-valued `data:` field naming no local
|
|
1253
|
+
note, asset or entry in a declared dependency index. The finding names the
|
|
1254
|
+
field and the complete Address it names. A short form means this package;
|
|
1255
|
+
another package requires the full Address. `--no-references` also turns off
|
|
1256
|
+
this target check. Art slots report unresolved art through their own fallback
|
|
1257
|
+
warning.
|
|
1252
1258
|
|
|
1253
1259
|
**A schema says what a note may _write_, not what the compiler emits.** Those
|
|
1254
1260
|
are different, and the difference is the whole calibration of the check: a note
|
package/docs/content-format.md
CHANGED
|
@@ -1315,6 +1315,12 @@ Address-keyed relation maps hold typed targets and standing values. Only generat
|
|
|
1315
1315
|
output serializes these properties to Address strings. Runtime Shortcodes,
|
|
1316
1316
|
Foundry UUIDs, file paths and URLs keep their own representations.
|
|
1317
1317
|
|
|
1318
|
+
An Address-valued `data:` field must name a target in this package or a declared
|
|
1319
|
+
dependency index. `content-build lint` reports a missing target as an error at
|
|
1320
|
+
the value or map key that names it. A stub counts as a target for data even
|
|
1321
|
+
though it publishes no page. Art slots use their own missing-art warning and
|
|
1322
|
+
document fallback.
|
|
1323
|
+
|
|
1318
1324
|
#### An omitted segment defaults from where the link is written
|
|
1319
1325
|
|
|
1320
1326
|
It is **not** a wildcard and resolution is not a search. Every short form expands
|
|
@@ -3015,9 +3021,7 @@ If `sohl` is present, this becomes an `affliction` item.
|
|
|
3015
3021
|
| `subType` | `system.subType` | NA |
|
|
3016
3022
|
| `data.transmission` | `system.transmission` | NA |
|
|
3017
3023
|
| `data.outcome` | `system.outcome` | NA |
|
|
3018
|
-
| `data.healingRate` | `system.healingRateBase` | NA |
|
|
3019
3024
|
| `data.contagionIndex` | `system.contagionIndexBase` | NA |
|
|
3020
|
-
| `data.outcomeTraumas` | `system.outcomeTraumas` | NA |
|
|
3021
3025
|
| `data.onsetDurationFormula` | `system.onsetDurationFormula` | NA |
|
|
3022
3026
|
| `data.onsetDurationBase` | `system.onsetDurationBase` | NA |
|
|
3023
3027
|
| `data.healingCheckDurationFormula` | `system.healingCheckDurationFormula` | NA |
|
|
@@ -3055,6 +3059,10 @@ that writes one fails the build. They are world times, and `0` is a valid one,
|
|
|
3055
3059
|
so there is no blank a note could write either; leave them out and the data
|
|
3056
3060
|
model's `null` stands.
|
|
3057
3061
|
|
|
3062
|
+
`data.healingRate` and `data.outcomeTraumas` are page data. SoHL reads an
|
|
3063
|
+
affliction’s base healing rate from `sohl.healingRateBase`. Its item declaration
|
|
3064
|
+
does not read `data.outcomeTraumas`.
|
|
3065
|
+
|
|
3058
3066
|
### type: armorgear
|
|
3059
3067
|
|
|
3060
3068
|
Note: `data.quantity` may not be specified. Quantity is always 1.
|
|
@@ -3129,7 +3137,9 @@ if a `sohl` property is present, a SoHL item of type "concoctiongear" will be cr
|
|
|
3129
3137
|
| `data.value` | `system.valueBase` | NA |
|
|
3130
3138
|
| `data.quality` | `system.qualityBase` | NA |
|
|
3131
3139
|
| `data.durability` | `system.durabilityBase` | NA |
|
|
3132
|
-
|
|
3140
|
+
|
|
3141
|
+
`data.quantity` is page data for a concoction. SoHL sets `system.quantity` to
|
|
3142
|
+
`1` for each compendium item.
|
|
3133
3143
|
|
|
3134
3144
|
### type: containergear
|
|
3135
3145
|
|
|
@@ -3142,19 +3152,21 @@ Note: `data.quantity` may not be specified; quantity is always set to 1.
|
|
|
3142
3152
|
| `value` | `number` | Gear value |
|
|
3143
3153
|
| `quality` | `number` | Gear quality |
|
|
3144
3154
|
| `durability` | `number` | Gear durability |
|
|
3145
|
-
| `capacity` | `number` |
|
|
3155
|
+
| `capacity` | `number` | HM3 container capacity (in lbs) |
|
|
3146
3156
|
|
|
3147
3157
|
if a `sohl` property is present, a SoHL item of type "containergear" will be created.
|
|
3148
3158
|
|
|
3149
3159
|
if a `hm3` property is present, an HM3 item of type "containergear" will be created.
|
|
3150
3160
|
|
|
3151
|
-
| shared source | → sohl
|
|
3152
|
-
| ----------------- |
|
|
3153
|
-
| `data.weight` | `system.weightBase`
|
|
3154
|
-
| `data.value` | `system.valueBase`
|
|
3155
|
-
| `data.quality` | `system.qualityBase`
|
|
3156
|
-
| `data.durability` | `system.durabilityBase`
|
|
3157
|
-
| `data.capacity` |
|
|
3161
|
+
| shared source | → sohl | → hm3 |
|
|
3162
|
+
| ----------------- | ----------------------- | --------------------- |
|
|
3163
|
+
| `data.weight` | `system.weightBase` | `system.weight` |
|
|
3164
|
+
| `data.value` | `system.valueBase` | `system.value` |
|
|
3165
|
+
| `data.quality` | `system.qualityBase` | NA |
|
|
3166
|
+
| `data.durability` | `system.durabilityBase` | NA |
|
|
3167
|
+
| `data.capacity` | NA | `system.capacity.max` |
|
|
3168
|
+
|
|
3169
|
+
SoHL reads a container’s capacity in pounds from `sohl.maxCapacity`.
|
|
3158
3170
|
|
|
3159
3171
|
### type: miscgear
|
|
3160
3172
|
|
|
@@ -3165,7 +3177,7 @@ if a `hm3` property is present, an HM3 item of type "containergear" will be crea
|
|
|
3165
3177
|
| `value` | `number` | Gear value |
|
|
3166
3178
|
| `quality` | `number` | Gear quality |
|
|
3167
3179
|
| `durability` | `number` | Gear durability |
|
|
3168
|
-
| `quantity` | `number` |
|
|
3180
|
+
| `quantity` | `number` | HM3 gear quantity (default: 1) |
|
|
3169
3181
|
|
|
3170
3182
|
if a `sohl` property is present, a SoHL item of type "miscgear" will be created.
|
|
3171
3183
|
|
|
@@ -3177,7 +3189,9 @@ if a `hm3` property is present, an HM3 item of type "miscgear" will be created.
|
|
|
3177
3189
|
| `data.value` | `system.valueBase` | `system.value` |
|
|
3178
3190
|
| `data.quality` | `system.qualityBase` | NA |
|
|
3179
3191
|
| `data.durability` | `system.durabilityBase` | NA |
|
|
3180
|
-
| `data.quantity` |
|
|
3192
|
+
| `data.quantity` | NA | `system.quantity` |
|
|
3193
|
+
|
|
3194
|
+
SoHL sets `system.quantity` to `1` for each compendium item.
|
|
3181
3195
|
|
|
3182
3196
|
### type: mystery
|
|
3183
3197
|
|
|
@@ -3213,10 +3227,12 @@ if a `sohl` property is present, a SoHL item of type "mystery" will be created.
|
|
|
3213
3227
|
| --------------------- | ----------------------- | ----- |
|
|
3214
3228
|
| `subType` | `system.subType` | NA |
|
|
3215
3229
|
| `data.skillAptitudes` | `system.skillAptitudes` | NA |
|
|
3216
|
-
| `data.level` | `system.levelBase` | NA |
|
|
3217
3230
|
| `data.charges.value` | `system.charges.value` | NA |
|
|
3218
3231
|
| `data.charges.max` | `system.charges.max` | NA |
|
|
3219
3232
|
|
|
3233
|
+
`data.level` is page data for a mystery. SoHL reads the item’s level from
|
|
3234
|
+
`sohl.levelBase`.
|
|
3235
|
+
|
|
3220
3236
|
### type: mysticalability
|
|
3221
3237
|
|
|
3222
3238
|
**subType**:
|
|
@@ -3250,13 +3266,15 @@ If an `hm3` property is present, an HM3 item is created, and `hm3.type` states w
|
|
|
3250
3266
|
`sohl.system` as shortcodes and have no shared source; `data.assocSkill` and
|
|
3251
3267
|
`data.assocAffiliation` name the same two notes as Addresses, for the page.
|
|
3252
3268
|
|
|
3253
|
-
| shared source | → sohl
|
|
3254
|
-
| -------------------- |
|
|
3255
|
-
| `subType` | `system.subType`
|
|
3256
|
-
| `data.
|
|
3257
|
-
| `data.
|
|
3258
|
-
|
|
3259
|
-
|
|
3269
|
+
| shared source | → sohl | → hm3 |
|
|
3270
|
+
| -------------------- | ---------------------- | ------------- |
|
|
3271
|
+
| `subType` | `system.subType` | **see above** |
|
|
3272
|
+
| `data.charges.value` | `system.charges.value` | NA |
|
|
3273
|
+
| `data.charges.max` | `system.charges.max` | NA |
|
|
3274
|
+
|
|
3275
|
+
`data.masteryLevel` and `data.level` are page data for an ability. SoHL reads
|
|
3276
|
+
the item’s base mastery and level from `sohl.masteryLevelBase` and
|
|
3277
|
+
`sohl.levelBase`.
|
|
3260
3278
|
|
|
3261
3279
|
### type: projectilegear
|
|
3262
3280
|
|
|
@@ -3276,7 +3294,7 @@ If an `hm3` property is present, an HM3 item is created, and `hm3.type` states w
|
|
|
3276
3294
|
| `value` | `number` | Gear value |
|
|
3277
3295
|
| `quality` | `number` | Gear quality |
|
|
3278
3296
|
| `durability` | `number` | Gear durability |
|
|
3279
|
-
| `quantity` | `number` |
|
|
3297
|
+
| `quantity` | `number` | HM3 gear quantity (default: 1) |
|
|
3280
3298
|
|
|
3281
3299
|
if a `sohl` property is present, a SoHL item of type "projectilegear" will be created.
|
|
3282
3300
|
|
|
@@ -3293,7 +3311,9 @@ HM3 side while remaining distinct on the SoHL side.
|
|
|
3293
3311
|
| `data.value` | `system.valueBase` | `system.value` |
|
|
3294
3312
|
| `data.quality` | `system.qualityBase` | NA |
|
|
3295
3313
|
| `data.durability` | `system.durabilityBase` | NA |
|
|
3296
|
-
| `data.quantity` |
|
|
3314
|
+
| `data.quantity` | NA | `system.quantity` |
|
|
3315
|
+
|
|
3316
|
+
SoHL sets `system.quantity` to `1` for each compendium item.
|
|
3297
3317
|
|
|
3298
3318
|
### type: skill
|
|
3299
3319
|
|
|
@@ -3313,7 +3333,7 @@ HM3 side while remaining distinct on the SoHL side.
|
|
|
3313
3333
|
| `data` property | Values | Description |
|
|
3314
3334
|
| ------------------ | --------- | ------------------------------------------ |
|
|
3315
3335
|
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3316
|
-
| `masteryLevel` | `number` |
|
|
3336
|
+
| `masteryLevel` | `number` | HM3 mastery level |
|
|
3317
3337
|
| `parentSkill` | `Address` | Parent skill this skill specializes |
|
|
3318
3338
|
|
|
3319
3339
|
if a `sohl` property is present, a SoHL item of type "skill" will be created.
|
|
@@ -3326,10 +3346,12 @@ Note: `hm3.system.type` (skill types) use the values "Craft", "Physical", "Commu
|
|
|
3326
3346
|
skill this one specializes, and has no shared source; `data.parentSkill` names that
|
|
3327
3347
|
skill as an Address, for the page.
|
|
3328
3348
|
|
|
3329
|
-
| shared source | → sohl
|
|
3330
|
-
| ------------------- |
|
|
3331
|
-
| `subType` | `system.subType`
|
|
3332
|
-
| `data.masteryLevel` |
|
|
3349
|
+
| shared source | → sohl | → hm3 |
|
|
3350
|
+
| ------------------- | ---------------- | --------------------- |
|
|
3351
|
+
| `subType` | `system.subType` | See notes above |
|
|
3352
|
+
| `data.masteryLevel` | NA | `system.masteryLevel` |
|
|
3353
|
+
|
|
3354
|
+
SoHL reads a skill’s base mastery from `sohl.masteryLevelBase`.
|
|
3333
3355
|
|
|
3334
3356
|
### type: trauma
|
|
3335
3357
|
|
|
@@ -68,7 +68,7 @@ import { positionInFrontmatter, positionOfFrontmatterPath } from "./diagnostics.
|
|
|
68
68
|
import { pathnameProblem } from "./pathnames.mjs";
|
|
69
69
|
import { checkHomepageAddressFields } from "./homepage.mjs";
|
|
70
70
|
import { RETIRED_TYPES, RENAMED_TYPES, currentType, renamedTypeMessage } from "./ids.mjs";
|
|
71
|
-
import { parseAddress, acceptsType } from "./address.mjs";
|
|
71
|
+
import { parseAddress, acceptsType, renderAddress } from "./address.mjs";
|
|
72
72
|
import { ASSET_TYPE_NAMES } from "./asset-types.mjs";
|
|
73
73
|
import { isAddressSegment } from "./address-charset.mjs";
|
|
74
74
|
// The one place the "every pack not named" key is spelled. Imported rather
|
|
@@ -421,7 +421,7 @@ function dataBlock(fm) {
|
|
|
421
421
|
* was loaded to recognise it would be worse than not checking.
|
|
422
422
|
* @returns {object[]} Findings.
|
|
423
423
|
*/
|
|
424
|
-
function checkDataContainer(note, { type, fields, packs, addressContext }) {
|
|
424
|
+
function checkDataContainer(note, { type, fields, packs, addressContext, index }) {
|
|
425
425
|
const findings = [];
|
|
426
426
|
const { present, entries, malformed } = dataBlock(note.fm ?? {});
|
|
427
427
|
if (!present) return findings;
|
|
@@ -504,7 +504,7 @@ function checkDataContainer(note, { type, fields, packs, addressContext }) {
|
|
|
504
504
|
});
|
|
505
505
|
continue;
|
|
506
506
|
}
|
|
507
|
-
findings.push(...checkDataReferences(note, field, value, segments, addressContext));
|
|
507
|
+
findings.push(...checkDataReferences(note, field, value, segments, addressContext, index));
|
|
508
508
|
// A `scalar-or-map` written in its map form is checked entry by entry,
|
|
509
509
|
// because that is the correction an author has to make: one key's
|
|
510
510
|
// value, not the whole map. Quoting the map back would name every
|
|
@@ -521,15 +521,16 @@ function checkDataContainer(note, { type, fields, packs, addressContext }) {
|
|
|
521
521
|
}
|
|
522
522
|
|
|
523
523
|
/**
|
|
524
|
-
* Validate typed entries
|
|
524
|
+
* Validate typed entries and resolve declared non-art Address targets.
|
|
525
525
|
* @param {object} note - The authored note.
|
|
526
526
|
* @param {object} field - Its data field declaration.
|
|
527
527
|
* @param {unknown} value - The field value.
|
|
528
528
|
* @param {string[]} segments - The field's path under `data`.
|
|
529
529
|
* @param {object} context - The builder's Address context.
|
|
530
|
+
* @param {object} [index] - The local and declared dependency Address index.
|
|
530
531
|
* @returns {object[]} Located findings.
|
|
531
532
|
*/
|
|
532
|
-
function checkDataReferences(note, field, value, segments, context) {
|
|
533
|
+
function checkDataReferences(note, field, value, segments, context, index) {
|
|
533
534
|
const checks = [];
|
|
534
535
|
if (field.kind === "address") checks.push({ value, path: [], kind: "address" });
|
|
535
536
|
if (field.entryKind) {
|
|
@@ -573,10 +574,16 @@ function checkDataReferences(note, field, value, segments, context) {
|
|
|
573
574
|
let reason;
|
|
574
575
|
if (!matchesKind(check.value, check.kind, defaults))
|
|
575
576
|
reason = `should be ${check.kind === "address" ? "an Address" : "a Shortcode"}`;
|
|
576
|
-
else if (check.kind === "address"
|
|
577
|
+
else if (check.kind === "address") {
|
|
577
578
|
const tuple = parseAddress(check.value, defaults, { declared: true });
|
|
578
|
-
if (!acceptsType(tuple, accepted))
|
|
579
|
+
if (accepted && !acceptsType(tuple, accepted))
|
|
579
580
|
reason = `accepts ${accepted.join(" or ")}, not ${tuple.type}`;
|
|
581
|
+
else if (
|
|
582
|
+
index?.addressHit &&
|
|
583
|
+
!ART_SLOTS.some((slot) => slot.key === field.name) &&
|
|
584
|
+
!index.addressHit(renderAddress(tuple))
|
|
585
|
+
)
|
|
586
|
+
reason = `names ${renderAddress(tuple)}, which does not resolve in this package or its declared dependencies`;
|
|
580
587
|
}
|
|
581
588
|
if (!reason) continue;
|
|
582
589
|
const path = ["data", ...segments, ...check.path];
|
|
@@ -1519,6 +1526,7 @@ export function lintNote(
|
|
|
1519
1526
|
fields,
|
|
1520
1527
|
packs,
|
|
1521
1528
|
addressContext: referenceContext,
|
|
1529
|
+
index,
|
|
1522
1530
|
}),
|
|
1523
1531
|
);
|
|
1524
1532
|
findings.push(...checkSubType(note, { type, entry }));
|
package/engine/holdings.mjs
CHANGED
|
@@ -124,15 +124,12 @@ export const HELD_SUBTYPES = Object.freeze(["settlement", "site", "structure"]);
|
|
|
124
124
|
* system, only the shortcode {@link module:engine/address} would resolve it
|
|
125
125
|
* to anyway.
|
|
126
126
|
*
|
|
127
|
-
* **The
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* ask an entry's package to agree with the citing note's, and a fetched
|
|
134
|
-
* entry's own `parents` or `domains` is written in *its* package while the
|
|
135
|
-
* blend it takes part in is *this* one's — see
|
|
127
|
+
* **The holdings graph has package-blind Shortcode identity.** `places` and
|
|
128
|
+
* `affiliations` merge local and fetched notes into one shortcode space; the
|
|
129
|
+
* first declaration wins. A fetched place attaches to a local parent with
|
|
130
|
+
* the same shortcode, and a fetched affiliation can name both local and
|
|
131
|
+
* fetched domains. The authored entries remain Addresses; this graph uses
|
|
132
|
+
* their shortcode projection for containment and tenure. See
|
|
136
133
|
* `tests/site-holdings.test.ts`'s `empire.domains: ["mill", "abroad"]`, where
|
|
137
134
|
* `mill` is local and `abroad` is the dependency's own, both bare, both
|
|
138
135
|
* correct only because neither package is asked.
|
|
@@ -321,6 +318,7 @@ export function holdingsPages(nodes) {
|
|
|
321
318
|
// Read through the one reader, so a caller handing in what a note
|
|
322
319
|
// wrote — an address, a wikilink, a repeat — is read as `holdingsNode`
|
|
323
320
|
// reads it.
|
|
321
|
+
// Shortcode identity: the first node of each type owns this graph key.
|
|
324
322
|
by.set(shortcode, {
|
|
325
323
|
...node,
|
|
326
324
|
shortcode,
|
package/engine/map-places.mjs
CHANGED
|
@@ -108,15 +108,14 @@ const PLACE_TYPES = Object.freeze(new Set(["place"]));
|
|
|
108
108
|
* since this position asks neither its package nor its system, only the
|
|
109
109
|
* shortcode {@link module:engine/address} would resolve it to anyway.
|
|
110
110
|
*
|
|
111
|
-
* **The
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
111
|
+
* **The map graph has package-blind Shortcode identity.** `world.places`
|
|
112
|
+
* merges local and fetched places by shortcode, with local declarations
|
|
113
|
+
* taking precedence. A fetched place attaches to the local containment tree
|
|
114
|
+
* by its parent's shortcode. The authored parent remains an Address; the map
|
|
115
|
+
* uses its shortcode projection for containment, layout, and DOT nodes. See
|
|
115
116
|
* `tests/map.test.ts`'s _loads a dependency's places beside the package's
|
|
116
117
|
* own_, where a fetched place's bare `parents: ["world"]` reaches this
|
|
117
|
-
* package's own root.
|
|
118
|
-
* dependency's own package to agree with this one's, which the feature is
|
|
119
|
-
* built to cross.
|
|
118
|
+
* package's own root.
|
|
120
119
|
*
|
|
121
120
|
* @param {unknown} parent - One `parents` entry.
|
|
122
121
|
* @returns {string} Its shortcode, or `""` for an entry that names nothing.
|
|
@@ -193,6 +192,7 @@ export function placesFromRecords(records, { contentBase, base }) {
|
|
|
193
192
|
// lint's finding.
|
|
194
193
|
if (places.has(shortcode)) continue;
|
|
195
194
|
const data = record.data && typeof record.data === "object" ? record.data : {};
|
|
195
|
+
// Shortcode identity: local places claim the map node first.
|
|
196
196
|
places.set(shortcode, {
|
|
197
197
|
shortcode,
|
|
198
198
|
name,
|
|
@@ -243,6 +243,7 @@ function addForeignPlaces(world, foreignIndex) {
|
|
|
243
243
|
if (entry?.type !== "place") continue;
|
|
244
244
|
const shortcode = readCanonicalKey(canonical)?.shortcode?.toLowerCase() ?? "";
|
|
245
245
|
if (!shortcode || world.places.has(shortcode)) continue;
|
|
246
|
+
// Shortcode identity: fetched places fill only unclaimed map nodes.
|
|
246
247
|
world.places.set(shortcode, {
|
|
247
248
|
shortcode,
|
|
248
249
|
name: String(entry.name ?? shortcode),
|
package/engine/populations.mjs
CHANGED
|
@@ -242,8 +242,10 @@ export function foreignNode(canonical, entry) {
|
|
|
242
242
|
/**
|
|
243
243
|
* The corpus a link index describes.
|
|
244
244
|
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
245
|
+
* Population containment and tenure use the same package-blind Shortcode
|
|
246
|
+
* identity as holdings and maps. Each type has one node per shortcode, with
|
|
247
|
+
* local notes taking precedence over fetched entries. A fetched place can
|
|
248
|
+
* attach to local geography, while its authored `parents` remains an Address.
|
|
247
249
|
*
|
|
248
250
|
* @param {object} index - The link index.
|
|
249
251
|
* @returns {Corpus} The corpus.
|
|
@@ -263,6 +265,7 @@ function corpusOf(index) {
|
|
|
263
265
|
if (!node) return;
|
|
264
266
|
const by = node.type === "place" ? places : affiliations;
|
|
265
267
|
if (by.has(node.shortcode)) return;
|
|
268
|
+
// Shortcode identity: the first node of each type owns this graph key.
|
|
266
269
|
by.set(node.shortcode, node);
|
|
267
270
|
// Not a canonical Address: package-blind by design, so a local place
|
|
268
271
|
// shadows a foreign one of the same type and shortcode. `/` keeps that
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.12.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|