@heroiclands/package-build 22.9.2 → 22.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/CONTENT.md +54 -0
- package/bin/content-build.mjs +3 -1
- package/docs/api.md +11 -6
- package/docs/commands.md +1 -1
- package/docs/content-format.md +411 -201
- package/engine/actor-compiler.mjs +20 -7
- package/engine/address-fields.mjs +426 -0
- package/engine/address-values.mjs +80 -0
- package/engine/address.mjs +769 -0
- package/engine/art-fields.mjs +112 -97
- package/engine/art-slots.mjs +81 -0
- package/engine/base-compiler.mjs +61 -7
- package/engine/bundle-notes.mjs +5 -2
- package/engine/bundles.mjs +17 -10
- package/engine/content-address.mjs +23 -186
- package/engine/content-embeds.mjs +18 -4
- package/engine/content-format-check.mjs +50 -22
- package/engine/content-index.mjs +85 -43
- package/engine/content-links.mjs +105 -104
- package/engine/content-lint.mjs +11 -1
- package/engine/content-tables.mjs +9 -1
- package/engine/field-spec.mjs +21 -4
- package/engine/folder-notes.mjs +44 -15
- package/engine/foundry-entries.mjs +3 -0
- package/engine/frontmatter-lint.mjs +150 -47
- package/engine/frontmatter.mjs +55 -10
- package/engine/generate.mjs +7 -1
- package/engine/helpers.mjs +37 -9
- package/engine/holdings.mjs +41 -12
- package/engine/index-records.mjs +2 -0
- package/engine/infobox-render.mjs +2 -1
- package/engine/infobox.mjs +26 -6
- package/engine/item-compiler.mjs +5 -0
- package/engine/item-docs.mjs +7 -44
- package/engine/map-dot.mjs +2 -0
- package/engine/map-notes.mjs +7 -2
- package/engine/map-places.mjs +42 -10
- package/engine/metadata-index.mjs +60 -5
- package/engine/note-addresses.mjs +325 -0
- package/engine/note-vocabulary.mjs +140 -28
- package/engine/pdf-build.mjs +1 -1
- package/engine/pdf-toc.mjs +17 -2
- package/engine/place-relations.mjs +160 -50
- package/engine/populations.mjs +17 -9
- package/engine/reference-targets.mjs +54 -0
- package/engine/scenes.mjs +5 -2
- package/engine/shortcode-references.mjs +68 -0
- package/engine/site-build.mjs +39 -15
- package/engine/site-index.mjs +57 -75
- package/engine/sql-tables.mjs +43 -9
- package/engine/system-block.mjs +21 -0
- package/engine/system-vocabulary.mjs +46 -0
- package/engine/web-wikilinks.mjs +11 -9
- package/engine/wikilink-syntax.mjs +21 -0
- package/engine/wikilinks.mjs +92 -306
- package/hm3/actors.mjs +8 -24
- package/hm3/item-fields.mjs +15 -5
- package/package.json +1 -1
- package/sohl/infobox.mjs +111 -17
- package/sohl/item-fields.mjs +131 -41
- package/types/engine/address-fields.d.mts +95 -0
- package/types/engine/address-values.d.mts +34 -0
- package/types/engine/address.d.mts +452 -0
- package/types/engine/art-fields.d.mts +59 -53
- package/types/engine/art-slots.d.mts +40 -0
- package/types/engine/base-compiler.d.mts +24 -2
- package/types/engine/bundle-notes.d.mts +2 -2
- package/types/engine/content-address.d.mts +3 -139
- package/types/engine/content-embeds.d.mts +1 -1
- package/types/engine/content-format-check.d.mts +25 -0
- package/types/engine/content-index.d.mts +2 -2
- package/types/engine/content-links.d.mts +6 -5
- package/types/engine/field-spec.d.mts +46 -9
- package/types/engine/folder-notes.d.mts +11 -10
- package/types/engine/frontmatter-lint.d.mts +7 -97
- package/types/engine/frontmatter.d.mts +16 -1
- package/types/engine/helpers.d.mts +2 -2
- package/types/engine/infobox.d.mts +6 -0
- package/types/engine/item-docs.d.mts +1 -35
- package/types/engine/map-notes.d.mts +2 -2
- package/types/engine/note-addresses.d.mts +41 -0
- package/types/engine/note-vocabulary.d.mts +20 -3
- package/types/engine/place-relations.d.mts +12 -1
- package/types/engine/populations.d.mts +17 -2
- package/types/engine/reference-targets.d.mts +9 -0
- package/types/engine/shortcode-references.d.mts +14 -0
- package/types/engine/site-build.d.mts +3 -3
- package/types/engine/site-index.d.mts +9 -30
- package/types/engine/sql-tables.d.mts +2 -2
- package/types/engine/system-block.d.mts +17 -0
- package/types/engine/system-vocabulary.d.mts +16 -0
- package/types/engine/wikilink-syntax.d.mts +7 -0
- package/types/engine/wikilinks.d.mts +6 -109
- package/types/hm3/item-fields.d.mts +5 -0
- package/types/sohl/infobox.d.mts +6 -5
- package/types/sohl/item-fields.d.mts +8 -0
package/sohl/infobox.mjs
CHANGED
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
* @module
|
|
52
52
|
*/
|
|
53
53
|
|
|
54
|
+
import { parseAddress, isAddressTuple, completeAddress } from "../engine/address.mjs";
|
|
55
|
+
import { contentPackage } from "../engine/content-package.mjs";
|
|
54
56
|
import {
|
|
55
57
|
DURATION_LABELS,
|
|
56
58
|
GEAR_UNITS,
|
|
@@ -63,6 +65,8 @@ import {
|
|
|
63
65
|
import { currentType } from "../engine/ids.mjs";
|
|
64
66
|
import { subTypes } from "../engine/note-vocabulary.mjs";
|
|
65
67
|
import { systemBlock, systemData } from "../engine/system-block.mjs";
|
|
68
|
+
import { readCanonicalKey } from "../engine/content-address.mjs";
|
|
69
|
+
import { readQualifier } from "../engine/address.mjs";
|
|
66
70
|
import { NOTE_SCHEMAS } from "./note-schemas.mjs";
|
|
67
71
|
import { GEAR_TYPE_TO_KEY } from "./being-info.mjs";
|
|
68
72
|
|
|
@@ -174,15 +178,45 @@ function isMapping(value) {
|
|
|
174
178
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
175
179
|
}
|
|
176
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Every content type `model:` may name — the note vocabulary this system
|
|
183
|
+
* declares, engine types included.
|
|
184
|
+
*
|
|
185
|
+
* @type {ReadonlySet<string>}
|
|
186
|
+
*/
|
|
187
|
+
const MODEL_TYPES = new Set(Object.keys(NOTE_SCHEMAS));
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The `(type, shortcode)` an entry's `model:` names, or `undefined`.
|
|
191
|
+
*
|
|
192
|
+
* `model` is an Address, read by the same grammar every wikilink is —
|
|
193
|
+
* `type-shortcode` within this package, `package-system-type-shortcode` to
|
|
194
|
+
* name a dependency's catalogue. This decode answers neither of the two
|
|
195
|
+
* questions {@link module:engine/address} asks about the package: a runtime
|
|
196
|
+
* lookup among one actor's embedded items resolves by `(type, shortcode)`
|
|
197
|
+
* alone, where packages do not exist, so a fully qualified `model` is read for
|
|
198
|
+
* its last two segments exactly as a bare one is.
|
|
199
|
+
*
|
|
200
|
+
* @param {unknown} model - The authored value.
|
|
201
|
+
* @returns {{type: string, shortcode: string}|undefined} What it names.
|
|
202
|
+
*/
|
|
203
|
+
function modelAddress(model) {
|
|
204
|
+
const target = parseAddress(
|
|
205
|
+
model,
|
|
206
|
+
{ package: contentPackage(), system: "sohl", types: MODEL_TYPES },
|
|
207
|
+
{ declared: true, legacyShortcodeCase: true },
|
|
208
|
+
);
|
|
209
|
+
return target.reason ? undefined : target;
|
|
210
|
+
}
|
|
211
|
+
|
|
177
212
|
/**
|
|
178
213
|
* What one entry of `sohl.items` names.
|
|
179
214
|
*
|
|
180
215
|
* An entry addresses its item three ways and every tree uses all three: a
|
|
181
|
-
* `model` address
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* which form a note happened to use.
|
|
216
|
+
* `model` address, explicit `type` / `shortcode` keys, or a locally authored
|
|
217
|
+
* item stating its type with the shortcode inside its own `system` block. One
|
|
218
|
+
* decode covers all of them, so nothing downstream has to know which form a
|
|
219
|
+
* note happened to use.
|
|
186
220
|
*
|
|
187
221
|
* @param {object} entry - One `sohl.items` entry.
|
|
188
222
|
* @returns {{type: string, shortcode: string, name: string, system: object}|undefined}
|
|
@@ -190,19 +224,21 @@ function isMapping(value) {
|
|
|
190
224
|
*/
|
|
191
225
|
export function decodeItem(entry) {
|
|
192
226
|
if (!isMapping(entry)) return undefined;
|
|
227
|
+
const address = entry.model ? modelAddress(entry.model) : undefined;
|
|
193
228
|
let type = typeof entry.type === "string" ? entry.type : "";
|
|
194
229
|
let shortcode = typeof entry.shortcode === "string" ? entry.shortcode : "";
|
|
195
|
-
if (!type
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
198
|
-
type =
|
|
199
|
-
if (!shortcode) shortcode =
|
|
230
|
+
if (!type) {
|
|
231
|
+
const named = address;
|
|
232
|
+
if (named) {
|
|
233
|
+
type = named.type;
|
|
234
|
+
if (!shortcode) shortcode = named.shortcode;
|
|
200
235
|
}
|
|
201
236
|
}
|
|
202
237
|
const system = isMapping(entry.system) ? entry.system : {};
|
|
203
238
|
if (!shortcode && typeof system.shortcode === "string") shortcode = system.shortcode;
|
|
204
239
|
if (!type) return undefined;
|
|
205
240
|
return {
|
|
241
|
+
...(address ? { address } : {}),
|
|
206
242
|
type: currentType(type),
|
|
207
243
|
shortcode,
|
|
208
244
|
name: typeof entry.name === "string" ? entry.name : "",
|
|
@@ -222,6 +258,32 @@ function itemsOf(fm, block) {
|
|
|
222
258
|
return (Array.isArray(declared) ? declared : []).map(decodeItem).filter(Boolean);
|
|
223
259
|
}
|
|
224
260
|
|
|
261
|
+
/**
|
|
262
|
+
* A model's documentation presentation, with its native name as plain text
|
|
263
|
+
* when no documentation is published. The model keeps its native identity.
|
|
264
|
+
* @param {object} item - Decoded native item.
|
|
265
|
+
* @param {Function} resolve - The medium's reference resolver.
|
|
266
|
+
* @param {string} block - The native system.
|
|
267
|
+
* @returns {object|undefined} Documentation links and display metadata.
|
|
268
|
+
*/
|
|
269
|
+
function itemPresentation(item, resolve, block) {
|
|
270
|
+
if (!item.shortcode) return undefined;
|
|
271
|
+
if (!item.address)
|
|
272
|
+
return resolve?.(item.shortcode, { kind: "shortcode", type: item.type, system: block });
|
|
273
|
+
const documentation = completeAddress({ ...item.address, system: "none" });
|
|
274
|
+
const found = resolve?.(documentation, { type: documentation.type });
|
|
275
|
+
const native =
|
|
276
|
+
!found?.name || !found?.subType ? resolve?.(item.address, { type: item.type }) : undefined;
|
|
277
|
+
if (!found && !native) return undefined;
|
|
278
|
+
return {
|
|
279
|
+
name: found?.name ?? native?.name,
|
|
280
|
+
subType: found?.subType ?? native?.subType,
|
|
281
|
+
address: documentation,
|
|
282
|
+
...(found?.url ? { url: found.url } : {}),
|
|
283
|
+
...(found?.uuid ? { uuid: found.uuid } : {}),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
225
287
|
/**
|
|
226
288
|
* One item as a value a renderer can draw: its name, and a link where the
|
|
227
289
|
* index reached it.
|
|
@@ -229,10 +291,11 @@ function itemsOf(fm, block) {
|
|
|
229
291
|
* @param {object} item - A decoded item.
|
|
230
292
|
* @param {(ref: unknown, hint?: object) => object|undefined} resolve - The
|
|
231
293
|
* medium's resolver.
|
|
232
|
-
* @
|
|
294
|
+
* @param {string} block - The native system.
|
|
295
|
+
* @returns {{text: string, url?: string, uuid?: string, address?: import("../engine/address.mjs").AddressTuple}} The value.
|
|
233
296
|
*/
|
|
234
|
-
function itemValue(item, resolve) {
|
|
235
|
-
const found = item
|
|
297
|
+
function itemValue(item, resolve, block) {
|
|
298
|
+
const found = itemPresentation(item, resolve, block);
|
|
236
299
|
const value = { text: item.name || found?.name || humanizeValue(item.shortcode) };
|
|
237
300
|
if (found?.url) value.url = found.url;
|
|
238
301
|
if (found?.uuid) value.uuid = found.uuid;
|
|
@@ -277,9 +340,9 @@ export function beingSections(fm, { block, resolve }) {
|
|
|
277
340
|
if (item.type !== "skill") continue;
|
|
278
341
|
const mastery = item.system.masteryLevelBase;
|
|
279
342
|
if (!hasValue(mastery)) continue;
|
|
280
|
-
const found = item
|
|
343
|
+
const found = itemPresentation(item, resolve, block);
|
|
281
344
|
const family = item.system.subType ?? found?.subType;
|
|
282
|
-
const value = itemValue(item, resolve);
|
|
345
|
+
const value = itemValue(item, resolve, block);
|
|
283
346
|
const entry = { ...value, text: `${value.text} ${mastery}` };
|
|
284
347
|
if (family && families.has(family)) families.get(family).push(entry);
|
|
285
348
|
else unplaced.push(entry);
|
|
@@ -298,7 +361,7 @@ export function beingSections(fm, { block, resolve }) {
|
|
|
298
361
|
const mystical = [];
|
|
299
362
|
for (const item of items) {
|
|
300
363
|
if (item.type !== "mysticalability") continue;
|
|
301
|
-
mystical.push(itemValue(item, resolve));
|
|
364
|
+
mystical.push(itemValue(item, resolve, block));
|
|
302
365
|
}
|
|
303
366
|
if (mystical.length) {
|
|
304
367
|
sections.push({
|
|
@@ -313,7 +376,7 @@ export function beingSections(fm, { block, resolve }) {
|
|
|
313
376
|
for (const item of items) {
|
|
314
377
|
const key = GEAR_TYPE_TO_KEY[item.type];
|
|
315
378
|
if (!key) continue;
|
|
316
|
-
gear.get(key).push(itemValue(item, resolve));
|
|
379
|
+
gear.get(key).push(itemValue(item, resolve, block));
|
|
317
380
|
}
|
|
318
381
|
const gearGroups = [];
|
|
319
382
|
for (const [key, entries] of gear) {
|
|
@@ -480,6 +543,36 @@ function genericSections(fm, type, ctx) {
|
|
|
480
543
|
return systemRowsSection(fm, NOTE_SCHEMAS[type], ctx);
|
|
481
544
|
}
|
|
482
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
|
+
|
|
483
576
|
/**
|
|
484
577
|
* SoHL's infobox declaration.
|
|
485
578
|
*
|
|
@@ -492,6 +585,7 @@ export const SOHL_INFOBOX = defineInfobox({
|
|
|
492
585
|
presentation: SOHL_FIELD_PRESENTATION,
|
|
493
586
|
sections: {
|
|
494
587
|
being: beingSections,
|
|
588
|
+
affiliation: affiliationSections,
|
|
495
589
|
armorgear: armorSections,
|
|
496
590
|
weapongear: weaponSections,
|
|
497
591
|
projectilegear: projectileSections,
|
package/sohl/item-fields.mjs
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
* @module
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
+
import { isAddressTuple } from "../engine/address.mjs";
|
|
38
39
|
import {
|
|
39
40
|
AS_AUTHORED,
|
|
40
41
|
BLANK_IS_DEFAULT,
|
|
@@ -143,14 +144,56 @@ const SKILL_APTITUDES = Object.freeze({
|
|
|
143
144
|
read: (_raw, { fm }) => resolveSkillAptitudes(fm, noteContext(fm, "mystery")),
|
|
144
145
|
});
|
|
145
146
|
|
|
147
|
+
/* --------------------------------------------------------------------- */
|
|
148
|
+
/* The Address positions */
|
|
149
|
+
/* --------------------------------------------------------------------- */
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
* An affiliation names other notes in four of its fields, and each of those
|
|
153
|
+
* values is an **Address** — written at whatever length says what it means —
|
|
154
|
+
* while the compiled Item holds the **Shortcode** the runtime resolves among
|
|
155
|
+
* one actor's items. `engine/address-fields.mjs` is the step between them, and
|
|
156
|
+
* these are the positions it reads.
|
|
157
|
+
*
|
|
158
|
+
* Each states two things, and they are not the same statement: `type` is the
|
|
159
|
+
* default an omitted `<type>` segment takes, and `accepts` is the set of types
|
|
160
|
+
* the field takes at all. They coincide at all four, which is the common case
|
|
161
|
+
* rather than the rule — an art slot defaults to `icon` and accepts `icon` or
|
|
162
|
+
* `image` — so each is written out rather than derived from the other.
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
/** One place: an affiliation's seat. */
|
|
166
|
+
const PLACE_VALUE = Object.freeze({ type: "place", accepts: Object.freeze(["place"]) });
|
|
167
|
+
|
|
168
|
+
/** A list of places: the ones an affiliation holds sway over. */
|
|
169
|
+
const PLACE_ITEMS = Object.freeze({ ...PLACE_VALUE, holds: "items" });
|
|
170
|
+
|
|
171
|
+
/** A list of affiliations: the ones this affiliation answers to. */
|
|
172
|
+
const AFFILIATION_ITEMS = Object.freeze({
|
|
173
|
+
type: "affiliation",
|
|
174
|
+
accepts: Object.freeze(["affiliation"]),
|
|
175
|
+
holds: "items",
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
/** A map keyed by affiliation: this one's standing toward each. */
|
|
179
|
+
const AFFILIATION_KEYS = Object.freeze({ ...AFFILIATION_ITEMS, holds: "keys" });
|
|
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
|
+
|
|
146
189
|
/** Standings toward other affiliations, validated against the closed list. */
|
|
147
190
|
const RELATION = Object.freeze({
|
|
148
|
-
shape: "map of affiliation
|
|
191
|
+
shape: "map of affiliation address → standing",
|
|
149
192
|
read: (_raw, { fm }) => resolveRelation(fm, noteContext(fm, "affiliation")),
|
|
150
193
|
});
|
|
151
194
|
|
|
152
195
|
/**
|
|
153
|
-
* A list of references, each
|
|
196
|
+
* A list of references, each an address.
|
|
154
197
|
*
|
|
155
198
|
* Blank entries are dropped rather than emitted: a list that has been edited
|
|
156
199
|
* down in a property editor keeps its empty rows, and an empty string is not a
|
|
@@ -158,15 +201,15 @@ const RELATION = Object.freeze({
|
|
|
158
201
|
* value here — a sovereign polity is subordinate to nobody — rather than an
|
|
159
202
|
* unset one.
|
|
160
203
|
*/
|
|
161
|
-
const
|
|
162
|
-
shape: "list of
|
|
204
|
+
const ADDRESS_LIST = Object.freeze({
|
|
205
|
+
shape: "list of addresses",
|
|
163
206
|
kind: "list",
|
|
164
207
|
read: (raw) =>
|
|
165
208
|
(Array.isArray(raw) ? raw
|
|
166
209
|
: raw == null || raw === "" ? []
|
|
167
210
|
: [raw]
|
|
168
211
|
)
|
|
169
|
-
.map((entry) => String(entry ?? "").trim())
|
|
212
|
+
.map((entry) => (isAddressTuple(entry) ? entry : String(entry ?? "").trim()))
|
|
170
213
|
.filter((entry) => entry !== ""),
|
|
171
214
|
});
|
|
172
215
|
|
|
@@ -237,7 +280,8 @@ const GEAR_COMMON = Object.freeze([
|
|
|
237
280
|
describe: "How many the stack holds. A compendium article ships one.",
|
|
238
281
|
},
|
|
239
282
|
{
|
|
240
|
-
name: "weight",
|
|
283
|
+
name: "data.weight",
|
|
284
|
+
legacyKey: "weight",
|
|
241
285
|
to: "weightBase",
|
|
242
286
|
...AS_AUTHORED,
|
|
243
287
|
kind: "number",
|
|
@@ -245,7 +289,8 @@ const GEAR_COMMON = Object.freeze([
|
|
|
245
289
|
describe: "Weight of one, in pounds.",
|
|
246
290
|
},
|
|
247
291
|
{
|
|
248
|
-
name: "value",
|
|
292
|
+
name: "data.value",
|
|
293
|
+
legacyKey: "value",
|
|
249
294
|
to: "valueBase",
|
|
250
295
|
...AS_AUTHORED,
|
|
251
296
|
kind: "number",
|
|
@@ -253,7 +298,8 @@ const GEAR_COMMON = Object.freeze([
|
|
|
253
298
|
describe: "Worth of one, in pence.",
|
|
254
299
|
},
|
|
255
300
|
{
|
|
256
|
-
name: "quality",
|
|
301
|
+
name: "data.quality",
|
|
302
|
+
legacyKey: "quality",
|
|
257
303
|
to: "qualityBase",
|
|
258
304
|
...AS_AUTHORED,
|
|
259
305
|
kind: "number",
|
|
@@ -261,7 +307,8 @@ const GEAR_COMMON = Object.freeze([
|
|
|
261
307
|
describe: "Craftsmanship, as a modifier to what the article does.",
|
|
262
308
|
},
|
|
263
309
|
{
|
|
264
|
-
name: "durability",
|
|
310
|
+
name: "data.durability",
|
|
311
|
+
legacyKey: "durability",
|
|
265
312
|
to: "durabilityBase",
|
|
266
313
|
...AS_AUTHORED,
|
|
267
314
|
kind: "number",
|
|
@@ -293,6 +340,14 @@ const GEAR_COMMON = Object.freeze([
|
|
|
293
340
|
* Every item type's frontmatter vocabulary, in the order the `system` block
|
|
294
341
|
* emits it.
|
|
295
342
|
*
|
|
343
|
+
* **A field whose shared source is a path into `data:` names both positions.**
|
|
344
|
+
* `name` is the source the content format's mapping tables state — `data.seat`,
|
|
345
|
+
* `data.weight` — and `legacyKey` is the key the system block still carries, which
|
|
346
|
+
* wins while a tree writes it. Naming only the source would move the in-block
|
|
347
|
+
* read to a key spelled `data.seat`, which nothing authors; naming only the key
|
|
348
|
+
* leaves the container the specification points at unread, and the field then
|
|
349
|
+
* compiles its default over an authored value with nothing said.
|
|
350
|
+
*
|
|
296
351
|
* @type {Readonly<Record<string, readonly import("../engine/field-spec.mjs").FieldSpec[]>>}
|
|
297
352
|
*/
|
|
298
353
|
export const ITEM_FIELDS = Object.freeze({
|
|
@@ -338,12 +393,23 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
338
393
|
default: 0,
|
|
339
394
|
describe: "Standing within the society.",
|
|
340
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
|
+
},
|
|
341
405
|
{
|
|
342
406
|
// Plural because the field holds a map of many standings, one per
|
|
343
407
|
// affiliation — the singular reads as a misnomer.
|
|
344
|
-
name: "relations",
|
|
408
|
+
name: "data.relations",
|
|
409
|
+
legacyKey: "relations",
|
|
345
410
|
to: "relations",
|
|
346
411
|
...RELATION,
|
|
412
|
+
address: AFFILIATION_KEYS,
|
|
347
413
|
default: {},
|
|
348
414
|
describe: "How this society regards others: aligned, unaligned, rival or nemesis.",
|
|
349
415
|
},
|
|
@@ -351,21 +417,25 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
351
417
|
// The organisational relation: which bodies this one answers to.
|
|
352
418
|
// A list, because an affiliation may sit under more than one at
|
|
353
419
|
// once — an arcane tradition within an order, say.
|
|
354
|
-
name: "parents",
|
|
420
|
+
name: "data.parents",
|
|
421
|
+
legacyKey: "parents",
|
|
355
422
|
to: "parents",
|
|
356
|
-
...
|
|
423
|
+
...ADDRESS_LIST,
|
|
424
|
+
address: AFFILIATION_ITEMS,
|
|
357
425
|
default: [],
|
|
358
|
-
describe: "Affiliations this one is subordinate to, by
|
|
426
|
+
describe: "Affiliations this one is subordinate to, by address.",
|
|
359
427
|
},
|
|
360
428
|
{
|
|
361
429
|
// Not `capital` or `headquarters`: each fits about half the eleven
|
|
362
430
|
// subTypes, while a seat covers a polity, a guild, an order and a
|
|
363
431
|
// faith alike.
|
|
364
|
-
name: "seat",
|
|
432
|
+
name: "data.seat",
|
|
433
|
+
legacyKey: "seat",
|
|
365
434
|
to: "seat",
|
|
366
435
|
...BLANK_IS_NULL,
|
|
436
|
+
address: PLACE_VALUE,
|
|
367
437
|
default: null,
|
|
368
|
-
describe: "Where the affiliation's authority sits, by place
|
|
438
|
+
describe: "Where the affiliation's authority sits, by place address.",
|
|
369
439
|
},
|
|
370
440
|
{
|
|
371
441
|
// The geographic relation, kept apart from the organisational one
|
|
@@ -375,11 +445,13 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
375
445
|
// mapping row states (`data.domains` → `system.domain`). The two
|
|
376
446
|
// spellings are deliberate rather than a slip, so the declaration
|
|
377
447
|
// carries both rather than either side guessing.
|
|
378
|
-
name: "domains",
|
|
448
|
+
name: "data.domains",
|
|
449
|
+
legacyKey: "domains",
|
|
379
450
|
to: "domain",
|
|
380
|
-
...
|
|
451
|
+
...ADDRESS_LIST,
|
|
452
|
+
address: PLACE_ITEMS,
|
|
381
453
|
default: [],
|
|
382
|
-
describe: "Places this affiliation holds sway over, by
|
|
454
|
+
describe: "Places this affiliation holds sway over, by address.",
|
|
383
455
|
},
|
|
384
456
|
]),
|
|
385
457
|
|
|
@@ -418,14 +490,16 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
418
490
|
describe: "How readily the host throws it off.",
|
|
419
491
|
},
|
|
420
492
|
{
|
|
421
|
-
name: "contagionIndex",
|
|
493
|
+
name: "data.contagionIndex",
|
|
494
|
+
legacyKey: "contagionIndex",
|
|
422
495
|
to: "contagionIndexBase",
|
|
423
496
|
...NUMBER,
|
|
424
497
|
default: 0,
|
|
425
498
|
describe: "How readily it passes to someone else.",
|
|
426
499
|
},
|
|
427
500
|
{
|
|
428
|
-
name: "transmission",
|
|
501
|
+
name: "data.transmission",
|
|
502
|
+
legacyKey: "transmission",
|
|
429
503
|
to: "transmission",
|
|
430
504
|
...AS_AUTHORED,
|
|
431
505
|
default: "none",
|
|
@@ -440,7 +514,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
440
514
|
"Days from contracting to onset, rolled by the receiving actor. Unset means no incubation.",
|
|
441
515
|
},
|
|
442
516
|
{
|
|
443
|
-
name: "outcome",
|
|
517
|
+
name: "data.outcome",
|
|
518
|
+
legacyKey: "outcome",
|
|
444
519
|
to: "outcome",
|
|
445
520
|
...BLANK_IS_DEFAULT,
|
|
446
521
|
default: "cured",
|
|
@@ -464,7 +539,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
464
539
|
// cannot be grepped, and each phase's prose differs anyway — which is
|
|
465
540
|
// the whole content of the declaration.
|
|
466
541
|
{
|
|
467
|
-
name: "onsetDurationFormula",
|
|
542
|
+
name: "data.onsetDurationFormula",
|
|
543
|
+
legacyKey: "onsetDurationFormula",
|
|
468
544
|
to: "onsetDurationFormula",
|
|
469
545
|
...DURATION_FORMULA,
|
|
470
546
|
omitWhenAbsent: true,
|
|
@@ -472,7 +548,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
472
548
|
"Interval from contracting the affliction to the start of onset. Omitted when unset, leaving no incubation.",
|
|
473
549
|
},
|
|
474
550
|
{
|
|
475
|
-
name: "onsetDurationBase",
|
|
551
|
+
name: "data.onsetDurationBase",
|
|
552
|
+
legacyKey: "onsetDurationBase",
|
|
476
553
|
to: "onsetDurationBase",
|
|
477
554
|
...DURATION_BASE,
|
|
478
555
|
omitWhenAbsent: true,
|
|
@@ -480,7 +557,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
480
557
|
"The onset interval in seconds, standing in for a roll of the formula. Omitted when unset.",
|
|
481
558
|
},
|
|
482
559
|
{
|
|
483
|
-
name: "healingCheckDurationFormula",
|
|
560
|
+
name: "data.healingCheckDurationFormula",
|
|
561
|
+
legacyKey: "healingCheckDurationFormula",
|
|
484
562
|
to: "healingCheckDurationFormula",
|
|
485
563
|
...DURATION_FORMULA,
|
|
486
564
|
omitWhenAbsent: true,
|
|
@@ -488,7 +566,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
488
566
|
"Interval between healing checks, once the affliction is symptomatic. Omitted when unset.",
|
|
489
567
|
},
|
|
490
568
|
{
|
|
491
|
-
name: "healingCheckDurationBase",
|
|
569
|
+
name: "data.healingCheckDurationBase",
|
|
570
|
+
legacyKey: "healingCheckDurationBase",
|
|
492
571
|
to: "healingCheckDurationBase",
|
|
493
572
|
...DURATION_BASE,
|
|
494
573
|
omitWhenAbsent: true,
|
|
@@ -496,7 +575,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
496
575
|
"The healing-check interval in seconds, standing in for a roll of the formula. Omitted when unset.",
|
|
497
576
|
},
|
|
498
577
|
{
|
|
499
|
-
name: "resolutionDurationFormula",
|
|
578
|
+
name: "data.resolutionDurationFormula",
|
|
579
|
+
legacyKey: "resolutionDurationFormula",
|
|
500
580
|
to: "resolutionDurationFormula",
|
|
501
581
|
...DURATION_FORMULA,
|
|
502
582
|
omitWhenAbsent: true,
|
|
@@ -504,7 +584,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
504
584
|
"Interval from onset to the affliction running its course. Omitted when unset.",
|
|
505
585
|
},
|
|
506
586
|
{
|
|
507
|
-
name: "resolutionDurationBase",
|
|
587
|
+
name: "data.resolutionDurationBase",
|
|
588
|
+
legacyKey: "resolutionDurationBase",
|
|
508
589
|
to: "resolutionDurationBase",
|
|
509
590
|
...DURATION_BASE,
|
|
510
591
|
omitWhenAbsent: true,
|
|
@@ -711,7 +792,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
711
792
|
{
|
|
712
793
|
name: "assocSkillCode",
|
|
713
794
|
to: "assocSkillCode",
|
|
714
|
-
|
|
795
|
+
code: "skill",
|
|
715
796
|
...BLANK_IS_NULL,
|
|
716
797
|
default: null,
|
|
717
798
|
describe: "Shortcode of the skill the mystery is tested against.",
|
|
@@ -719,7 +800,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
719
800
|
{
|
|
720
801
|
name: "assocAffiliationCode",
|
|
721
802
|
to: "assocAffiliationCode",
|
|
722
|
-
|
|
803
|
+
code: "affiliation",
|
|
723
804
|
...BLANK_IS_NULL,
|
|
724
805
|
default: null,
|
|
725
806
|
describe:
|
|
@@ -733,14 +814,16 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
733
814
|
describe: "The mystery's level before any modifier.",
|
|
734
815
|
},
|
|
735
816
|
{
|
|
736
|
-
name: "skillAptitudes",
|
|
817
|
+
name: "data.skillAptitudes",
|
|
818
|
+
legacyKey: "skillAptitudes",
|
|
737
819
|
to: "skillAptitudes",
|
|
738
820
|
...SKILL_APTITUDES,
|
|
739
821
|
default: {},
|
|
740
822
|
describe: "Aptitude the mystery grants, per skill selector.",
|
|
741
823
|
},
|
|
742
824
|
{
|
|
743
|
-
name: "charges",
|
|
825
|
+
name: "data.charges",
|
|
826
|
+
legacyKey: "charges",
|
|
744
827
|
to: "charges",
|
|
745
828
|
...CHARGES,
|
|
746
829
|
default: { value: null, max: null },
|
|
@@ -760,7 +843,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
760
843
|
{
|
|
761
844
|
name: "assocSkillCode",
|
|
762
845
|
to: "assocSkillCode",
|
|
763
|
-
|
|
846
|
+
code: "skill",
|
|
764
847
|
...AS_AUTHORED,
|
|
765
848
|
default: "",
|
|
766
849
|
describe: "Shortcode of the skill the ability is tested against.",
|
|
@@ -768,7 +851,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
768
851
|
{
|
|
769
852
|
name: "assocAffiliationCode",
|
|
770
853
|
to: "assocAffiliationCode",
|
|
771
|
-
|
|
854
|
+
code: "affiliation",
|
|
772
855
|
...BLANK_IS_NULL,
|
|
773
856
|
default: null,
|
|
774
857
|
describe:
|
|
@@ -796,7 +879,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
796
879
|
describe: "The ability's level before any modifier.",
|
|
797
880
|
},
|
|
798
881
|
{
|
|
799
|
-
name: "charges",
|
|
882
|
+
name: "data.charges",
|
|
883
|
+
legacyKey: "charges",
|
|
800
884
|
to: "charges",
|
|
801
885
|
...CHARGES,
|
|
802
886
|
default: { value: null, max: null },
|
|
@@ -884,7 +968,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
884
968
|
{
|
|
885
969
|
name: "parentSkillCode",
|
|
886
970
|
to: "parentSkillCode",
|
|
887
|
-
|
|
971
|
+
code: "skill",
|
|
888
972
|
...AS_AUTHORED,
|
|
889
973
|
default: "",
|
|
890
974
|
describe: "Shortcode of the skill this one specialises, for a specialisation.",
|
|
@@ -959,7 +1043,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
959
1043
|
// `bloodLossAdvance*` is the one an author reaches for most: a trauma
|
|
960
1044
|
// that sets it bleeds, and one that leaves it unset does not.
|
|
961
1045
|
{
|
|
962
|
-
name: "healingCheckDurationFormula",
|
|
1046
|
+
name: "data.healingCheckDurationFormula",
|
|
1047
|
+
legacyKey: "healingCheckDurationFormula",
|
|
963
1048
|
to: "healingCheckDurationFormula",
|
|
964
1049
|
...DURATION_FORMULA,
|
|
965
1050
|
omitWhenAbsent: true,
|
|
@@ -967,7 +1052,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
967
1052
|
"Interval between healing checks. Omitted when unset, leaving the world's configured interval to apply.",
|
|
968
1053
|
},
|
|
969
1054
|
{
|
|
970
|
-
name: "healingCheckDurationBase",
|
|
1055
|
+
name: "data.healingCheckDurationBase",
|
|
1056
|
+
legacyKey: "healingCheckDurationBase",
|
|
971
1057
|
to: "healingCheckDurationBase",
|
|
972
1058
|
...DURATION_BASE,
|
|
973
1059
|
omitWhenAbsent: true,
|
|
@@ -975,7 +1061,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
975
1061
|
"The healing-check interval in seconds, standing in for a roll of the formula. Omitted when unset.",
|
|
976
1062
|
},
|
|
977
1063
|
{
|
|
978
|
-
name: "bloodLossAdvanceDurationFormula",
|
|
1064
|
+
name: "data.bloodLossAdvanceDurationFormula",
|
|
1065
|
+
legacyKey: "bloodLossAdvanceDurationFormula",
|
|
979
1066
|
to: "bloodLossAdvanceDurationFormula",
|
|
980
1067
|
...DURATION_FORMULA,
|
|
981
1068
|
omitWhenAbsent: true,
|
|
@@ -983,7 +1070,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
983
1070
|
"Interval between blood-loss advances. Omitted when unset, leaving the world's configured interval to apply.",
|
|
984
1071
|
},
|
|
985
1072
|
{
|
|
986
|
-
name: "bloodLossAdvanceDurationBase",
|
|
1073
|
+
name: "data.bloodLossAdvanceDurationBase",
|
|
1074
|
+
legacyKey: "bloodLossAdvanceDurationBase",
|
|
987
1075
|
to: "bloodLossAdvanceDurationBase",
|
|
988
1076
|
...DURATION_BASE,
|
|
989
1077
|
omitWhenAbsent: true,
|
|
@@ -991,7 +1079,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
991
1079
|
"The blood-loss interval in seconds. Setting it is what makes the wound bleed; omitted when unset, and the wound does not.",
|
|
992
1080
|
},
|
|
993
1081
|
{
|
|
994
|
-
name: "courseDurationFormula",
|
|
1082
|
+
name: "data.courseDurationFormula",
|
|
1083
|
+
legacyKey: "courseDurationFormula",
|
|
995
1084
|
to: "courseDurationFormula",
|
|
996
1085
|
...DURATION_FORMULA,
|
|
997
1086
|
omitWhenAbsent: true,
|
|
@@ -999,7 +1088,8 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
999
1088
|
"Interval between course tests, for a condition that runs one — shock, coma, infection. Omitted when unset.",
|
|
1000
1089
|
},
|
|
1001
1090
|
{
|
|
1002
|
-
name: "courseDurationBase",
|
|
1091
|
+
name: "data.courseDurationBase",
|
|
1092
|
+
legacyKey: "courseDurationBase",
|
|
1003
1093
|
to: "courseDurationBase",
|
|
1004
1094
|
...DURATION_BASE,
|
|
1005
1095
|
omitWhenAbsent: true,
|