@heroiclands/package-build 22.10.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 +12 -0
- package/CONTENT.md +27 -0
- package/docs/api.md +1 -0
- package/docs/commands.md +1 -1
- package/docs/content-format.md +78 -30
- package/engine/address-fields.mjs +31 -9
- package/engine/base-compiler.mjs +2 -0
- package/engine/field-spec.mjs +3 -2
- package/engine/frontmatter-lint.mjs +21 -11
- package/engine/holdings.mjs +7 -9
- package/engine/map-places.mjs +8 -7
- package/engine/note-addresses.mjs +17 -2
- package/engine/note-vocabulary.mjs +0 -7
- package/engine/populations.mjs +5 -2
- package/package.json +1 -1
- package/sohl/infobox.mjs +31 -0
- package/sohl/item-fields.mjs +17 -0
- package/types/engine/address-fields.d.mts +5 -0
- package/types/engine/field-spec.d.mts +6 -4
- package/types/sohl/infobox.d.mts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
9
|
+
## 22.11.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- **Affiliations:** SoHL common skills link to their exact native Items; authors declare them in the SoHL block, and shared declarations receive an error.
|
|
14
|
+
|
|
3
15
|
## 22.10.0
|
|
4
16
|
|
|
5
17
|
### 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
|
|
@@ -1444,6 +1450,27 @@ segment, and returns a complete tuple or a reason. This mode leaves package
|
|
|
1444
1450
|
membership to resolution. The lint receives the builder's package separately
|
|
1445
1451
|
from target lookup, including when reference resolution is disabled.
|
|
1446
1452
|
|
|
1453
|
+
An affiliation authors native SoHL skill references at
|
|
1454
|
+
`sohl.system.commonSkills`:
|
|
1455
|
+
|
|
1456
|
+
```yaml
|
|
1457
|
+
sohl:
|
|
1458
|
+
system:
|
|
1459
|
+
commonSkills:
|
|
1460
|
+
- herb
|
|
1461
|
+
- sohl-sohl-skill-law
|
|
1462
|
+
```
|
|
1463
|
+
|
|
1464
|
+
The first Address names this content package's SoHL skill; the second names a
|
|
1465
|
+
skill in the declared SoHL dependency. The content index retains their complete
|
|
1466
|
+
Addresses. Foundry compilation resolves each exact native skill Item in the
|
|
1467
|
+
local or declared dependency index and writes its published Item UUID to the
|
|
1468
|
+
affiliation's `system.commonSkills` array. The producer's module ID and pack
|
|
1469
|
+
come from that index. An unresolved target or a target without a native Item
|
|
1470
|
+
UUID is a located error. The shared `data:` container has no `commonSkills`
|
|
1471
|
+
property. Infoboxes use a skill's published documentation journal when it has
|
|
1472
|
+
one and show its known name as plain text otherwise.
|
|
1473
|
+
|
|
1447
1474
|
**There is deliberately no third rule** requiring every note to repeat its own
|
|
1448
1475
|
`type-shortcode` address in `aliases:`. It would serve one reader — Obsidian, so
|
|
1449
1476
|
`[[type-shortcode]]` resolves in the editor — and no build reads it: both
|
package/docs/api.md
CHANGED
|
@@ -1369,6 +1369,7 @@ Which of SoHL's facts a note's summary panel carries, and how they group. Read o
|
|
|
1369
1369
|
| `SOHL_FIELD_PRESENTATION` | `const SOHL_FIELD_PRESENTATION` | — | looking up what one of SoHL's fields is called, or why it carries no row |
|
|
1370
1370
|
| `PROTECTION_FIELDS` | `const PROTECTION_FIELDS` | — | reading the declarations of the aspects armour is rated against, in the order shown |
|
|
1371
1371
|
| `UNSTATED` | `const UNSTATED` | — | naming what a strike mode shows where a value was not stated |
|
|
1372
|
+
| `affiliationSections` | `affiliationSections(fm, ctx)` | `object[]` | building an affiliation's box with skill documentation links |
|
|
1372
1373
|
| `armorSections` | `armorSections(fm, ctx)` | `object[]` | building armour's box, protection included |
|
|
1373
1374
|
| `beingSections` | `beingSections(fm, ctx)` | `object[]` | building a being's attributes, skills, mystical abilities and equipment |
|
|
1374
1375
|
| `decodeItem` | `decodeItem(entry)` | `object \| undefined` | reading what one `sohl.items` entry names, whichever form it was written in |
|
package/docs/commands.md
CHANGED
|
@@ -1445,7 +1445,7 @@ declaration that compiles it. Otherwise 0.
|
|
|
1445
1445
|
$ content-build content-format fields --fields sohl --coverage
|
|
1446
1446
|
[…] 13 type(s) compared against sohl's declarations (56 field pair(s)).
|
|
1447
1447
|
[…] 12 type(s) the format declares are out of reach — no `itemBuilders` entry covers them: being, homepage, vehicle, armorlocation, lore, map, place, scenario, doc, macro, bundle, folder.
|
|
1448
|
-
[…] affiliation: format only [
|
|
1448
|
+
[…] affiliation: format only [demonym, economy, epithet, governance, lore, population, symbol, templatePriority], declaration only [commonSkills, level, office, society, subType, title]
|
|
1449
1449
|
```
|
|
1450
1450
|
|
|
1451
1451
|
**SEE ALSO**
|
package/docs/content-format.md
CHANGED
|
@@ -495,6 +495,12 @@ reduction happens at build time, and two things are errors naming the note: an
|
|
|
495
495
|
Address whose type the field does not accept, and two Addresses that reduce to one
|
|
496
496
|
shortcode, which the field cannot hold both of.
|
|
497
497
|
|
|
498
|
+
**A SoHL skill Address becomes a native Item UUID.** An affiliation's
|
|
499
|
+
`sohl.system.commonSkills` names skills in its own package or a declared
|
|
500
|
+
dependency. Its `system.commonSkills` contains the exact published Item UUIDs,
|
|
501
|
+
including each foreign module's own pack name. Native skill lookup uses those
|
|
502
|
+
UUIDs; the journal, website and book use documentation links for presentation.
|
|
503
|
+
|
|
498
504
|
**A `Code` suffix marks a different thing.** `system.assocSkillCode`,
|
|
499
505
|
`system.assocAffiliationCode` and `system.parentSkillCode` hold a `Shortcode` at
|
|
500
506
|
both ends — each is authored as one, under `<system>.system`, and emitted verbatim
|
|
@@ -1309,6 +1315,12 @@ Address-keyed relation maps hold typed targets and standing values. Only generat
|
|
|
1309
1315
|
output serializes these properties to Address strings. Runtime Shortcodes,
|
|
1310
1316
|
Foundry UUIDs, file paths and URLs keep their own representations.
|
|
1311
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
|
+
|
|
1312
1324
|
#### An omitted segment defaults from where the link is written
|
|
1313
1325
|
|
|
1314
1326
|
It is **not** a wildcard and resolution is not a search. Every short form expands
|
|
@@ -2844,7 +2856,6 @@ rank names the standing, and the standing says.
|
|
|
2844
2856
|
| `governance.summary` | `string` | summary of the governance situation |
|
|
2845
2857
|
| `governance.ranks` | `Rank[]` | The ranks available to members of the affiliation |
|
|
2846
2858
|
| `governance.offices` | `Map<name, description>` | Official offices in the affiliation |
|
|
2847
|
-
| `commonSkills` | `Address[]` | Common skills among members (languages, etc.) |
|
|
2848
2859
|
| `seat` | `Address` | Where the affiliation's authority sits |
|
|
2849
2860
|
| `domains` | `Address[]` | Places over which this affiliation holds sway |
|
|
2850
2861
|
| `population` | `number` | Number of people in the affiliation (precision 2 significant digits). |
|
|
@@ -2926,6 +2937,27 @@ If `sohl` is present, this becomes an `affiliation` item.
|
|
|
2926
2937
|
| `data.parents` | `system.parents` | NA |
|
|
2927
2938
|
| `data.relations` | `system.relations` | NA |
|
|
2928
2939
|
|
|
2940
|
+
An affiliation writes SoHL common skills under `sohl.system.commonSkills` as a
|
|
2941
|
+
list of skill Addresses. An abbreviated value names a skill in the authoring
|
|
2942
|
+
package; a skill in the SoHL system package uses its complete Address:
|
|
2943
|
+
|
|
2944
|
+
```yaml
|
|
2945
|
+
sohl:
|
|
2946
|
+
system:
|
|
2947
|
+
commonSkills:
|
|
2948
|
+
- herb
|
|
2949
|
+
- sohl-sohl-skill-law
|
|
2950
|
+
```
|
|
2951
|
+
|
|
2952
|
+
The compiled SoHL Item stores the exact published Item UUIDs in
|
|
2953
|
+
`system.commonSkills`. An empty or absent list emits `[]`. A foreign target must
|
|
2954
|
+
come from a declared `relationships.systems` or `relationships.requires` content
|
|
2955
|
+
index. Each UUID uses the module and pack named by that target's published index;
|
|
2956
|
+
a missing skill Item or Item UUID is an error at the authored Address. The
|
|
2957
|
+
content index retains complete Addresses. Journal, web and book infoboxes link
|
|
2958
|
+
to a skill's published documentation journal when one exists and otherwise show
|
|
2959
|
+
its name as plain text.
|
|
2960
|
+
|
|
2929
2961
|
`governance` reaches no system field. Ranks and offices are the note's and the
|
|
2930
2962
|
web page's — SoHL's affiliation item has nowhere to put them, and inventing a
|
|
2931
2963
|
mapping for a field no schema declares is the drift these tables exist to catch.
|
|
@@ -2989,9 +3021,7 @@ If `sohl` is present, this becomes an `affliction` item.
|
|
|
2989
3021
|
| `subType` | `system.subType` | NA |
|
|
2990
3022
|
| `data.transmission` | `system.transmission` | NA |
|
|
2991
3023
|
| `data.outcome` | `system.outcome` | NA |
|
|
2992
|
-
| `data.healingRate` | `system.healingRateBase` | NA |
|
|
2993
3024
|
| `data.contagionIndex` | `system.contagionIndexBase` | NA |
|
|
2994
|
-
| `data.outcomeTraumas` | `system.outcomeTraumas` | NA |
|
|
2995
3025
|
| `data.onsetDurationFormula` | `system.onsetDurationFormula` | NA |
|
|
2996
3026
|
| `data.onsetDurationBase` | `system.onsetDurationBase` | NA |
|
|
2997
3027
|
| `data.healingCheckDurationFormula` | `system.healingCheckDurationFormula` | NA |
|
|
@@ -3029,6 +3059,10 @@ that writes one fails the build. They are world times, and `0` is a valid one,
|
|
|
3029
3059
|
so there is no blank a note could write either; leave them out and the data
|
|
3030
3060
|
model's `null` stands.
|
|
3031
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
|
+
|
|
3032
3066
|
### type: armorgear
|
|
3033
3067
|
|
|
3034
3068
|
Note: `data.quantity` may not be specified. Quantity is always 1.
|
|
@@ -3103,7 +3137,9 @@ if a `sohl` property is present, a SoHL item of type "concoctiongear" will be cr
|
|
|
3103
3137
|
| `data.value` | `system.valueBase` | NA |
|
|
3104
3138
|
| `data.quality` | `system.qualityBase` | NA |
|
|
3105
3139
|
| `data.durability` | `system.durabilityBase` | NA |
|
|
3106
|
-
|
|
3140
|
+
|
|
3141
|
+
`data.quantity` is page data for a concoction. SoHL sets `system.quantity` to
|
|
3142
|
+
`1` for each compendium item.
|
|
3107
3143
|
|
|
3108
3144
|
### type: containergear
|
|
3109
3145
|
|
|
@@ -3116,19 +3152,21 @@ Note: `data.quantity` may not be specified; quantity is always set to 1.
|
|
|
3116
3152
|
| `value` | `number` | Gear value |
|
|
3117
3153
|
| `quality` | `number` | Gear quality |
|
|
3118
3154
|
| `durability` | `number` | Gear durability |
|
|
3119
|
-
| `capacity` | `number` |
|
|
3155
|
+
| `capacity` | `number` | HM3 container capacity (in lbs) |
|
|
3120
3156
|
|
|
3121
3157
|
if a `sohl` property is present, a SoHL item of type "containergear" will be created.
|
|
3122
3158
|
|
|
3123
3159
|
if a `hm3` property is present, an HM3 item of type "containergear" will be created.
|
|
3124
3160
|
|
|
3125
|
-
| shared source | → sohl
|
|
3126
|
-
| ----------------- |
|
|
3127
|
-
| `data.weight` | `system.weightBase`
|
|
3128
|
-
| `data.value` | `system.valueBase`
|
|
3129
|
-
| `data.quality` | `system.qualityBase`
|
|
3130
|
-
| `data.durability` | `system.durabilityBase`
|
|
3131
|
-
| `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`.
|
|
3132
3170
|
|
|
3133
3171
|
### type: miscgear
|
|
3134
3172
|
|
|
@@ -3139,7 +3177,7 @@ if a `hm3` property is present, an HM3 item of type "containergear" will be crea
|
|
|
3139
3177
|
| `value` | `number` | Gear value |
|
|
3140
3178
|
| `quality` | `number` | Gear quality |
|
|
3141
3179
|
| `durability` | `number` | Gear durability |
|
|
3142
|
-
| `quantity` | `number` |
|
|
3180
|
+
| `quantity` | `number` | HM3 gear quantity (default: 1) |
|
|
3143
3181
|
|
|
3144
3182
|
if a `sohl` property is present, a SoHL item of type "miscgear" will be created.
|
|
3145
3183
|
|
|
@@ -3151,7 +3189,9 @@ if a `hm3` property is present, an HM3 item of type "miscgear" will be created.
|
|
|
3151
3189
|
| `data.value` | `system.valueBase` | `system.value` |
|
|
3152
3190
|
| `data.quality` | `system.qualityBase` | NA |
|
|
3153
3191
|
| `data.durability` | `system.durabilityBase` | NA |
|
|
3154
|
-
| `data.quantity` |
|
|
3192
|
+
| `data.quantity` | NA | `system.quantity` |
|
|
3193
|
+
|
|
3194
|
+
SoHL sets `system.quantity` to `1` for each compendium item.
|
|
3155
3195
|
|
|
3156
3196
|
### type: mystery
|
|
3157
3197
|
|
|
@@ -3187,10 +3227,12 @@ if a `sohl` property is present, a SoHL item of type "mystery" will be created.
|
|
|
3187
3227
|
| --------------------- | ----------------------- | ----- |
|
|
3188
3228
|
| `subType` | `system.subType` | NA |
|
|
3189
3229
|
| `data.skillAptitudes` | `system.skillAptitudes` | NA |
|
|
3190
|
-
| `data.level` | `system.levelBase` | NA |
|
|
3191
3230
|
| `data.charges.value` | `system.charges.value` | NA |
|
|
3192
3231
|
| `data.charges.max` | `system.charges.max` | NA |
|
|
3193
3232
|
|
|
3233
|
+
`data.level` is page data for a mystery. SoHL reads the item’s level from
|
|
3234
|
+
`sohl.levelBase`.
|
|
3235
|
+
|
|
3194
3236
|
### type: mysticalability
|
|
3195
3237
|
|
|
3196
3238
|
**subType**:
|
|
@@ -3224,13 +3266,15 @@ If an `hm3` property is present, an HM3 item is created, and `hm3.type` states w
|
|
|
3224
3266
|
`sohl.system` as shortcodes and have no shared source; `data.assocSkill` and
|
|
3225
3267
|
`data.assocAffiliation` name the same two notes as Addresses, for the page.
|
|
3226
3268
|
|
|
3227
|
-
| shared source | → sohl
|
|
3228
|
-
| -------------------- |
|
|
3229
|
-
| `subType` | `system.subType`
|
|
3230
|
-
| `data.
|
|
3231
|
-
| `data.
|
|
3232
|
-
|
|
3233
|
-
|
|
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`.
|
|
3234
3278
|
|
|
3235
3279
|
### type: projectilegear
|
|
3236
3280
|
|
|
@@ -3250,7 +3294,7 @@ If an `hm3` property is present, an HM3 item is created, and `hm3.type` states w
|
|
|
3250
3294
|
| `value` | `number` | Gear value |
|
|
3251
3295
|
| `quality` | `number` | Gear quality |
|
|
3252
3296
|
| `durability` | `number` | Gear durability |
|
|
3253
|
-
| `quantity` | `number` |
|
|
3297
|
+
| `quantity` | `number` | HM3 gear quantity (default: 1) |
|
|
3254
3298
|
|
|
3255
3299
|
if a `sohl` property is present, a SoHL item of type "projectilegear" will be created.
|
|
3256
3300
|
|
|
@@ -3267,7 +3311,9 @@ HM3 side while remaining distinct on the SoHL side.
|
|
|
3267
3311
|
| `data.value` | `system.valueBase` | `system.value` |
|
|
3268
3312
|
| `data.quality` | `system.qualityBase` | NA |
|
|
3269
3313
|
| `data.durability` | `system.durabilityBase` | NA |
|
|
3270
|
-
| `data.quantity` |
|
|
3314
|
+
| `data.quantity` | NA | `system.quantity` |
|
|
3315
|
+
|
|
3316
|
+
SoHL sets `system.quantity` to `1` for each compendium item.
|
|
3271
3317
|
|
|
3272
3318
|
### type: skill
|
|
3273
3319
|
|
|
@@ -3287,7 +3333,7 @@ HM3 side while remaining distinct on the SoHL side.
|
|
|
3287
3333
|
| `data` property | Values | Description |
|
|
3288
3334
|
| ------------------ | --------- | ------------------------------------------ |
|
|
3289
3335
|
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3290
|
-
| `masteryLevel` | `number` |
|
|
3336
|
+
| `masteryLevel` | `number` | HM3 mastery level |
|
|
3291
3337
|
| `parentSkill` | `Address` | Parent skill this skill specializes |
|
|
3292
3338
|
|
|
3293
3339
|
if a `sohl` property is present, a SoHL item of type "skill" will be created.
|
|
@@ -3300,10 +3346,12 @@ Note: `hm3.system.type` (skill types) use the values "Craft", "Physical", "Commu
|
|
|
3300
3346
|
skill this one specializes, and has no shared source; `data.parentSkill` names that
|
|
3301
3347
|
skill as an Address, for the page.
|
|
3302
3348
|
|
|
3303
|
-
| shared source | → sohl
|
|
3304
|
-
| ------------------- |
|
|
3305
|
-
| `subType` | `system.subType`
|
|
3306
|
-
| `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`.
|
|
3307
3355
|
|
|
3308
3356
|
### type: trauma
|
|
3309
3357
|
|
|
@@ -3903,7 +3951,7 @@ because they were true of something else, and each removal has a home to go to.
|
|
|
3903
3951
|
|
|
3904
3952
|
**`languages` is a fact about a polity.** A place's languages change when its
|
|
3905
3953
|
ruler changes, which is what makes them the ruler's property — and
|
|
3906
|
-
`
|
|
3954
|
+
`sohl.system.commonSkills` holds them. The authored corpus agrees: of 206
|
|
3907
3955
|
places carrying `languages`, 190 were settlements and 16 were regions, and not one
|
|
3908
3956
|
was a site, a structure or a feature. A ruin has no language.
|
|
3909
3957
|
|
|
@@ -12,16 +12,18 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* **An authored Address, an emitted
|
|
15
|
+
* **An authored Address, an emitted native reference.**
|
|
16
16
|
*
|
|
17
17
|
* A declared field may hold an Address where the document it compiles into
|
|
18
|
-
* holds a **Shortcode
|
|
18
|
+
* holds a **Shortcode** or a published Item UUID. An affiliation's `seat` is a Shortcode case: the note
|
|
19
19
|
* names a place at whatever length says what it means — `tashal`,
|
|
20
20
|
* `place-tashal`, `none-place-tashal`, `kethira-none-place-tashal` — and the
|
|
21
21
|
* compiled Item carries `tashal`, which is what the runtime resolves among the
|
|
22
22
|
* items one actor holds, where packages do not exist.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
24
|
+
* The `emit: "uuid"` declaration resolves an exact native target from the
|
|
25
|
+
* local or declared dependency index. The target supplies its published UUID.
|
|
26
|
+
* This module performs both conversions at the Foundry output boundary.
|
|
25
27
|
* Storing the written form instead would store a value plus a context that does
|
|
26
28
|
* not travel with it: a four-segment string reaches a runtime that looks up one
|
|
27
29
|
* segment and returns nothing, silently.
|
|
@@ -70,10 +72,8 @@
|
|
|
70
72
|
*
|
|
71
73
|
* ## What this module does not do
|
|
72
74
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* and nothing else. Whether the target exists is a separate question, asked
|
|
76
|
-
* where the index is.
|
|
75
|
+
* Shortcode output needs only the Address grammar. UUID output requires the
|
|
76
|
+
* exact target in the published index and accepts only Item UUIDs.
|
|
77
77
|
*
|
|
78
78
|
* A failure is a **finding**, never a diagnostic: the caller owns the file and
|
|
79
79
|
* the severity, and locates the finding at the value inside the note.
|
|
@@ -107,6 +107,7 @@ export const ADDRESS_FIELD_SHAPES = Object.freeze(["value", "items", "keys"]);
|
|
|
107
107
|
* @property {readonly string[]} accepts - Every type the position accepts.
|
|
108
108
|
* @property {"value"|"items"|"keys"} [holds] - Where the Addresses are.
|
|
109
109
|
* Defaults to `value`.
|
|
110
|
+
* @property {"uuid"} [emit] - Resolve each exact target to its published Item UUID.
|
|
110
111
|
*/
|
|
111
112
|
|
|
112
113
|
/**
|
|
@@ -171,7 +172,7 @@ function reduceOne(value, field, vocabulary, findings) {
|
|
|
171
172
|
// Two keys, or two list entries, reducing to one emitted shortcode: the
|
|
172
173
|
// Address each of them named, by the shortcode all of them land on.
|
|
173
174
|
const seen = new Map();
|
|
174
|
-
const read = (written) => readOne(written, field, defaults, seen, findings, shape);
|
|
175
|
+
const read = (written) => readOne(written, field, defaults, seen, findings, shape, vocabulary);
|
|
175
176
|
|
|
176
177
|
if (shape === "keys") {
|
|
177
178
|
if (!isMapping(value)) return undefined;
|
|
@@ -212,8 +213,15 @@ function reduceOne(value, field, vocabulary, findings) {
|
|
|
212
213
|
* @returns {string|undefined} The shortcode, or `undefined` when nothing is
|
|
213
214
|
* emitted for this value.
|
|
214
215
|
*/
|
|
215
|
-
function readOne(written, field, defaults, seen, findings, shape) {
|
|
216
|
+
function readOne(written, field, defaults, seen, findings, shape, vocabulary) {
|
|
216
217
|
const key = findingKey(field);
|
|
218
|
+
// An embedded Item may inherit an already compiled reference from its model.
|
|
219
|
+
if (
|
|
220
|
+
field.address.emit === "uuid" &&
|
|
221
|
+
typeof written === "string" &&
|
|
222
|
+
/^Compendium\.[^.]+\.[^.]+\.Item\.[^.]+$/.test(written)
|
|
223
|
+
)
|
|
224
|
+
return written;
|
|
217
225
|
const value =
|
|
218
226
|
isAddressTuple(written) ? renderAddress(written)
|
|
219
227
|
: typeof written === "string" ? written
|
|
@@ -231,6 +239,20 @@ function readOne(written, field, defaults, seen, findings, shape) {
|
|
|
231
239
|
}
|
|
232
240
|
|
|
233
241
|
const address = renderAddress(tuple);
|
|
242
|
+
if (field.address.emit === "uuid") {
|
|
243
|
+
const target =
|
|
244
|
+
tuple.package === vocabulary.package ?
|
|
245
|
+
vocabulary.referenceTargets?.get(address)
|
|
246
|
+
: vocabulary.foreignReferences?.get(address);
|
|
247
|
+
const uuid = target?.uuid;
|
|
248
|
+
if (!uuid || !/^Compendium\.[^.]+\.[^.]+\.Item\.[^.]+$/.test(uuid)) {
|
|
249
|
+
report(
|
|
250
|
+
`\`${key}\` names \`${address}\`, but its exact native Item has no published Item UUID`,
|
|
251
|
+
);
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
return uuid;
|
|
255
|
+
}
|
|
234
256
|
const code = tuple.shortcode;
|
|
235
257
|
const first = seen.get(code);
|
|
236
258
|
if (first && first.address !== address) {
|
package/engine/base-compiler.mjs
CHANGED
|
@@ -717,6 +717,8 @@ export class BasePackCompiler {
|
|
|
717
717
|
types: this.linkIndex?.types,
|
|
718
718
|
packages: this.linkIndex?.packages,
|
|
719
719
|
noIndexPackages: this.linkIndex?.noIndexPackages,
|
|
720
|
+
referenceTargets: this.linkIndex?.referenceTargets,
|
|
721
|
+
foreignReferences: this.linkIndex?.foreignReferences,
|
|
720
722
|
});
|
|
721
723
|
for (const finding of findings) {
|
|
722
724
|
this.errorCount++;
|
package/engine/field-spec.mjs
CHANGED
|
@@ -148,8 +148,9 @@ export { legacyKeyOf, retiredTopLevelKey, setPath };
|
|
|
148
148
|
* being's own body structure, not a note, so it declares none. Distinct
|
|
149
149
|
* from a `DataFieldSpec`'s `ref` ({@link module:engine/note-vocabulary}),
|
|
150
150
|
* which names an **Address** instead.
|
|
151
|
-
* @property {import("./address-fields.mjs").AddressPosition} [address] -
|
|
152
|
-
* authored value is an Address
|
|
151
|
+
* @property {import("./address-fields.mjs").AddressPosition} [address] - The
|
|
152
|
+
* authored value is an Address. The emitted value is its Shortcode, or the
|
|
153
|
+
* exact published Item UUID when `emit: "uuid"` is declared. The
|
|
153
154
|
* statement carries the default type an omitted `<type>` segment takes, the
|
|
154
155
|
* set of types the position accepts — two separate things, which coincide at
|
|
155
156
|
* most positions — and where the Addresses sit: the value itself, every
|
|
@@ -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;
|
|
@@ -476,10 +476,12 @@ function checkDataContainer(note, { type, fields, packs, addressContext }) {
|
|
|
476
476
|
...positionOfFrontmatterPath(raw, ["data", key], { key: true }),
|
|
477
477
|
severity: "error",
|
|
478
478
|
message:
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
`not
|
|
482
|
-
|
|
479
|
+
type === "affiliation" && key === "commonSkills" ?
|
|
480
|
+
"`data.commonSkills` is not an affiliation field; write skill Addresses at `sohl.system.commonSkills`"
|
|
481
|
+
: `"${key}" is not a \`data:\` property declared by ${type}; ` +
|
|
482
|
+
`the container is closed, so unlike a top-level key it is ` +
|
|
483
|
+
`not passed through to the page` +
|
|
484
|
+
(guess ? `. Did you mean "${guess}"?` : ""),
|
|
483
485
|
});
|
|
484
486
|
}
|
|
485
487
|
|
|
@@ -502,7 +504,7 @@ function checkDataContainer(note, { type, fields, packs, addressContext }) {
|
|
|
502
504
|
});
|
|
503
505
|
continue;
|
|
504
506
|
}
|
|
505
|
-
findings.push(...checkDataReferences(note, field, value, segments, addressContext));
|
|
507
|
+
findings.push(...checkDataReferences(note, field, value, segments, addressContext, index));
|
|
506
508
|
// A `scalar-or-map` written in its map form is checked entry by entry,
|
|
507
509
|
// because that is the correction an author has to make: one key's
|
|
508
510
|
// value, not the whole map. Quoting the map back would name every
|
|
@@ -519,15 +521,16 @@ function checkDataContainer(note, { type, fields, packs, addressContext }) {
|
|
|
519
521
|
}
|
|
520
522
|
|
|
521
523
|
/**
|
|
522
|
-
* Validate typed entries
|
|
524
|
+
* Validate typed entries and resolve declared non-art Address targets.
|
|
523
525
|
* @param {object} note - The authored note.
|
|
524
526
|
* @param {object} field - Its data field declaration.
|
|
525
527
|
* @param {unknown} value - The field value.
|
|
526
528
|
* @param {string[]} segments - The field's path under `data`.
|
|
527
529
|
* @param {object} context - The builder's Address context.
|
|
530
|
+
* @param {object} [index] - The local and declared dependency Address index.
|
|
528
531
|
* @returns {object[]} Located findings.
|
|
529
532
|
*/
|
|
530
|
-
function checkDataReferences(note, field, value, segments, context) {
|
|
533
|
+
function checkDataReferences(note, field, value, segments, context, index) {
|
|
531
534
|
const checks = [];
|
|
532
535
|
if (field.kind === "address") checks.push({ value, path: [], kind: "address" });
|
|
533
536
|
if (field.entryKind) {
|
|
@@ -571,10 +574,16 @@ function checkDataReferences(note, field, value, segments, context) {
|
|
|
571
574
|
let reason;
|
|
572
575
|
if (!matchesKind(check.value, check.kind, defaults))
|
|
573
576
|
reason = `should be ${check.kind === "address" ? "an Address" : "a Shortcode"}`;
|
|
574
|
-
else if (check.kind === "address"
|
|
577
|
+
else if (check.kind === "address") {
|
|
575
578
|
const tuple = parseAddress(check.value, defaults, { declared: true });
|
|
576
|
-
if (!acceptsType(tuple, accepted))
|
|
579
|
+
if (accepted && !acceptsType(tuple, accepted))
|
|
577
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`;
|
|
578
587
|
}
|
|
579
588
|
if (!reason) continue;
|
|
580
589
|
const path = ["data", ...segments, ...check.path];
|
|
@@ -1517,6 +1526,7 @@ export function lintNote(
|
|
|
1517
1526
|
fields,
|
|
1518
1527
|
packs,
|
|
1519
1528
|
addressContext: referenceContext,
|
|
1529
|
+
index,
|
|
1520
1530
|
}),
|
|
1521
1531
|
);
|
|
1522
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),
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { systemBlocksFor } from "./system-vocabulary.mjs";
|
|
8
|
+
import { declaredSystems, systemBlocksFor } from "./system-vocabulary.mjs";
|
|
9
9
|
import { RETIRED_FIELD_ALIASES } from "./retired-fields.mjs";
|
|
10
10
|
import {
|
|
11
11
|
isAddressTuple,
|
|
@@ -260,10 +260,25 @@ export function decodeNoteAddresses(fm, context) {
|
|
|
260
260
|
* @returns {object} Builder defaults and system declarations.
|
|
261
261
|
*/
|
|
262
262
|
export function noteAddressContext(config) {
|
|
263
|
+
const systemBlocks = { ...systemBlocksFor(config) };
|
|
264
|
+
// A module with one declared system uses its flat Item registry for that
|
|
265
|
+
// system's Address positions, including fields stored in its content index.
|
|
266
|
+
const systems = new Set([
|
|
267
|
+
...declaredSystems(config),
|
|
268
|
+
...(config.relationships?.systems ?? []).map((relationship) => relationship.id),
|
|
269
|
+
]);
|
|
270
|
+
if (
|
|
271
|
+
systems.size === 1 &&
|
|
272
|
+
config.itemFields &&
|
|
273
|
+
Object.keys(config.itemFieldsBySystem ?? {}).length === 0
|
|
274
|
+
) {
|
|
275
|
+
const [system] = systems;
|
|
276
|
+
systemBlocks[system] = { ...systemBlocks[system], fields: config.itemFields };
|
|
277
|
+
}
|
|
263
278
|
return {
|
|
264
279
|
package: config.contentPackage,
|
|
265
280
|
system: "none",
|
|
266
|
-
systemBlocks
|
|
281
|
+
systemBlocks,
|
|
267
282
|
};
|
|
268
283
|
}
|
|
269
284
|
|
|
@@ -761,13 +761,6 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
761
761
|
...ANY,
|
|
762
762
|
describe: "The named offices it appoints, each with what the office does.",
|
|
763
763
|
},
|
|
764
|
-
{
|
|
765
|
-
name: "commonSkills",
|
|
766
|
-
...LINKS,
|
|
767
|
-
ref: "skill",
|
|
768
|
-
accepts: ["skill"],
|
|
769
|
-
describe: "Skills common among members — languages first among them.",
|
|
770
|
-
},
|
|
771
764
|
{
|
|
772
765
|
name: "seat",
|
|
773
766
|
...LINK,
|
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",
|
package/sohl/infobox.mjs
CHANGED
|
@@ -543,6 +543,36 @@ function genericSections(fm, type, ctx) {
|
|
|
543
543
|
return systemRowsSection(fm, NOTE_SCHEMAS[type], ctx);
|
|
544
544
|
}
|
|
545
545
|
|
|
546
|
+
/** An affiliation presents skill documentation while retaining native Item identity. */
|
|
547
|
+
export function affiliationSections(fm, ctx) {
|
|
548
|
+
const field = NOTE_SCHEMAS.affiliation.find((entry) => entry.to === "commonSkills");
|
|
549
|
+
const sections = systemRowsSection(
|
|
550
|
+
fm,
|
|
551
|
+
NOTE_SCHEMAS.affiliation.filter((entry) => entry !== field),
|
|
552
|
+
ctx,
|
|
553
|
+
);
|
|
554
|
+
const { value } = ctx.resolveField(field, fm, { block: ctx.block });
|
|
555
|
+
if (!Array.isArray(value) || value.length === 0) return sections;
|
|
556
|
+
const entries = value.map((skill) => {
|
|
557
|
+
const native = ctx.resolve?.(skill, { type: "skill", system: "sohl" });
|
|
558
|
+
const docAddress =
|
|
559
|
+
isAddressTuple(skill) ? completeAddress({ ...skill, system: "none" }) : undefined;
|
|
560
|
+
const doc = docAddress ? ctx.resolve?.(docAddress, { type: docAddress.type }) : undefined;
|
|
561
|
+
return {
|
|
562
|
+
text: doc?.name || native?.name || humanizeValue(skill),
|
|
563
|
+
...(doc?.url ? { url: doc.url } : {}),
|
|
564
|
+
...(doc?.uuid ? { uuid: doc.uuid } : {}),
|
|
565
|
+
...(docAddress && (doc?.url || doc?.uuid) ? { address: docAddress } : {}),
|
|
566
|
+
};
|
|
567
|
+
});
|
|
568
|
+
sections.push({
|
|
569
|
+
id: "commonskills",
|
|
570
|
+
layout: "rows",
|
|
571
|
+
rows: [{ label: "Common skills", kind: "links", value: entries }],
|
|
572
|
+
});
|
|
573
|
+
return sections;
|
|
574
|
+
}
|
|
575
|
+
|
|
546
576
|
/**
|
|
547
577
|
* SoHL's infobox declaration.
|
|
548
578
|
*
|
|
@@ -555,6 +585,7 @@ export const SOHL_INFOBOX = defineInfobox({
|
|
|
555
585
|
presentation: SOHL_FIELD_PRESENTATION,
|
|
556
586
|
sections: {
|
|
557
587
|
being: beingSections,
|
|
588
|
+
affiliation: affiliationSections,
|
|
558
589
|
armorgear: armorSections,
|
|
559
590
|
weapongear: weaponSections,
|
|
560
591
|
projectilegear: projectileSections,
|
package/sohl/item-fields.mjs
CHANGED
|
@@ -178,6 +178,14 @@ const AFFILIATION_ITEMS = Object.freeze({
|
|
|
178
178
|
/** A map keyed by affiliation: this one's standing toward each. */
|
|
179
179
|
const AFFILIATION_KEYS = Object.freeze({ ...AFFILIATION_ITEMS, holds: "keys" });
|
|
180
180
|
|
|
181
|
+
/** Native skill references retain the published Item UUID. */
|
|
182
|
+
const SKILL_UUID_ITEMS = Object.freeze({
|
|
183
|
+
type: "skill",
|
|
184
|
+
accepts: Object.freeze(["skill"]),
|
|
185
|
+
holds: "items",
|
|
186
|
+
emit: "uuid",
|
|
187
|
+
});
|
|
188
|
+
|
|
181
189
|
/** Standings toward other affiliations, validated against the closed list. */
|
|
182
190
|
const RELATION = Object.freeze({
|
|
183
191
|
shape: "map of affiliation address → standing",
|
|
@@ -385,6 +393,15 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
385
393
|
default: 0,
|
|
386
394
|
describe: "Standing within the society.",
|
|
387
395
|
},
|
|
396
|
+
{
|
|
397
|
+
name: "commonSkills",
|
|
398
|
+
to: "commonSkills",
|
|
399
|
+
...ADDRESS_LIST,
|
|
400
|
+
address: SKILL_UUID_ITEMS,
|
|
401
|
+
default: [],
|
|
402
|
+
topLevelMeans: "Common skills belong to the SoHL affiliation item.",
|
|
403
|
+
describe: "Native SoHL skill Items common among members, by address.",
|
|
404
|
+
},
|
|
388
405
|
{
|
|
389
406
|
// Plural because the field holds a map of many standings, one per
|
|
390
407
|
// affiliation — the singular reads as a misnomer.
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* @property {readonly string[]} accepts - Every type the position accepts.
|
|
7
7
|
* @property {"value"|"items"|"keys"} [holds] - Where the Addresses are.
|
|
8
8
|
* Defaults to `value`.
|
|
9
|
+
* @property {"uuid"} [emit] - Resolve each exact target to its published Item UUID.
|
|
9
10
|
*/
|
|
10
11
|
/**
|
|
11
12
|
* What a reduction could not do, for a caller to report.
|
|
@@ -69,6 +70,10 @@ export type AddressPosition = {
|
|
|
69
70
|
* Defaults to `value`.
|
|
70
71
|
*/
|
|
71
72
|
holds?: "keys" | "value" | "items" | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* - Resolve each exact target to its published Item UUID.
|
|
75
|
+
*/
|
|
76
|
+
emit?: "uuid" | undefined;
|
|
72
77
|
};
|
|
73
78
|
/**
|
|
74
79
|
* What a reduction could not do, for a caller to report.
|
|
@@ -277,8 +277,9 @@ export function runtimeOnlyFields(fields: readonly FieldSpec[]): FieldSpec[];
|
|
|
277
277
|
* being's own body structure, not a note, so it declares none. Distinct
|
|
278
278
|
* from a `DataFieldSpec`'s `ref` ({@link module:engine/note-vocabulary}),
|
|
279
279
|
* which names an **Address** instead.
|
|
280
|
-
* @property {import("./address-fields.mjs").AddressPosition} [address] -
|
|
281
|
-
* authored value is an Address
|
|
280
|
+
* @property {import("./address-fields.mjs").AddressPosition} [address] - The
|
|
281
|
+
* authored value is an Address. The emitted value is its Shortcode, or the
|
|
282
|
+
* exact published Item UUID when `emit: "uuid"` is declared. The
|
|
282
283
|
* statement carries the default type an omitted `<type>` segment takes, the
|
|
283
284
|
* set of types the position accepts — two separate things, which coincide at
|
|
284
285
|
* most positions — and where the Addresses sit: the value itself, every
|
|
@@ -553,8 +554,9 @@ export type FieldSpec = {
|
|
|
553
554
|
*/
|
|
554
555
|
code?: string | undefined;
|
|
555
556
|
/**
|
|
556
|
-
* -
|
|
557
|
-
* authored value is an Address
|
|
557
|
+
* - The
|
|
558
|
+
* authored value is an Address. The emitted value is its Shortcode, or the
|
|
559
|
+
* exact published Item UUID when `emit: "uuid"` is declared. The
|
|
558
560
|
* statement carries the default type an omitted `<type>` segment takes, the
|
|
559
561
|
* set of types the position accepts — two separate things, which coincide at
|
|
560
562
|
* most positions — and where the Addresses sit: the value itself, every
|
package/types/sohl/infobox.d.mts
CHANGED
|
@@ -82,6 +82,8 @@ export function projectileSections(fm: object, ctx: object): object[];
|
|
|
82
82
|
* @returns {[string, object][]} Mode name → the mode.
|
|
83
83
|
*/
|
|
84
84
|
export function strikeModes(declared: unknown): [string, object][];
|
|
85
|
+
/** An affiliation presents skill documentation while retaining native Item identity. */
|
|
86
|
+
export function affiliationSections(fm: any, ctx: any): object[];
|
|
85
87
|
/** What this system's box is called. @type {string} */
|
|
86
88
|
export const SOHL_INFOBOX_TITLE: string;
|
|
87
89
|
/**
|