@heroiclands/package-build 22.20.0 → 22.22.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-content-build.md +0 -8
- package/CHANGELOG.md +204 -0
- package/assets/styles/content-base.css +162 -0
- package/bin/content-commands.mjs +26 -3
- package/bin/package-build.mjs +15 -2
- package/ci/content-format-reference.mjs +68 -1
- package/config.mjs +26 -0
- package/content-config.mjs +73 -4
- package/docs/api.md +46 -28
- package/docs/authoring/dates-and-calendars.md +4 -2
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +237 -28
- package/docs/configuration.md +27 -1
- package/docs/content-format.md +3 -1
- package/docs/guides/reference-pages.md +15 -0
- package/docs/reference/format-details.md +265 -42
- package/docs/reference/note-types.md +45 -28
- package/engine/actor-compiler.mjs +23 -0
- package/engine/anchored-sections.mjs +35 -4
- package/engine/anchors.mjs +10 -20
- package/engine/base-compiler.mjs +86 -21
- package/engine/base-styles.mjs +149 -0
- package/engine/bundles.mjs +0 -1
- package/engine/compendiums.mjs +8 -1
- package/engine/content-blocks.mjs +410 -0
- package/engine/content-captions.mjs +57 -8
- package/engine/content-footnotes.mjs +189 -0
- package/engine/content-format-check.mjs +15 -12
- package/engine/content-index.mjs +2 -2
- package/engine/content-links.mjs +14 -6
- package/engine/content-tables.mjs +45 -4
- package/engine/content-tasklists.mjs +189 -0
- package/engine/docs-checks.mjs +4 -4
- package/engine/extension-attributes.mjs +48 -2
- package/engine/frontmatter-lint.mjs +62 -66
- package/engine/frontmatter.mjs +3 -3
- package/engine/heading-attributes.mjs +308 -0
- package/engine/helpers.mjs +19 -13
- package/engine/homepage.mjs +8 -8
- package/engine/index.mjs +3 -0
- package/engine/infobox-render.mjs +21 -4
- package/engine/infobox.mjs +167 -7
- package/engine/journals.mjs +36 -17
- package/engine/macros.mjs +1 -1
- package/engine/markdown-expressions.mjs +178 -53
- package/engine/note-addresses.mjs +17 -8
- package/engine/note-dates.mjs +49 -0
- package/engine/note-frontmatter.mjs +7 -16
- package/engine/note-vocabulary.mjs +216 -42
- package/engine/page-lists.mjs +358 -0
- package/engine/pdf-build.mjs +76 -42
- package/engine/pdf-render.mjs +422 -49
- package/engine/rank-ladder.mjs +198 -0
- package/engine/reckoning-markers.mjs +1 -1
- package/engine/reference-targets.mjs +13 -1
- package/engine/retired-fields.mjs +8 -13
- package/engine/scenes.mjs +10 -3
- package/engine/site-build.mjs +41 -12
- package/engine/site-config.mjs +10 -1
- package/engine/site-index.mjs +39 -4
- package/engine/site-preview.mjs +18 -7
- package/engine/social-ties.mjs +52 -14
- package/engine/sql-tables.mjs +66 -9
- package/engine/standing-terms.mjs +19 -0
- package/engine/standings.mjs +538 -0
- package/engine/svg-theme.mjs +15 -4
- package/engine/system-block.mjs +12 -14
- package/engine/web-wikilinks.mjs +48 -1
- package/engine/wikilinks.mjs +3 -1
- package/hm3/actors.mjs +6 -3
- package/manifest.mjs +13 -0
- package/package.json +1 -1
- package/sohl/actors.mjs +82 -103
- package/sohl/item-fields.mjs +4 -5
- package/sohl/items.mjs +1 -1
- package/sohl/note-schemas.mjs +11 -21
- package/types/config.d.mts +9 -0
- package/types/engine/actor-compiler.d.mts +11 -0
- package/types/engine/anchored-sections.d.mts +24 -0
- package/types/engine/base-compiler.d.mts +19 -0
- package/types/engine/base-styles.d.mts +93 -0
- package/types/engine/content-blocks.d.mts +87 -0
- package/types/engine/content-captions.d.mts +14 -1
- package/types/engine/content-footnotes.d.mts +73 -0
- package/types/engine/content-format-check.d.mts +8 -6
- package/types/engine/content-tables.d.mts +12 -4
- package/types/engine/content-tasklists.d.mts +72 -0
- package/types/engine/extension-attributes.d.mts +22 -1
- package/types/engine/frontmatter.d.mts +1 -1
- package/types/engine/heading-attributes.d.mts +174 -0
- package/types/engine/helpers.d.mts +7 -4
- package/types/engine/homepage.d.mts +7 -7
- package/types/engine/index.d.mts +1 -0
- package/types/engine/infobox-render.d.mts +6 -1
- package/types/engine/journals.d.mts +14 -7
- package/types/engine/markdown-expressions.d.mts +23 -0
- package/types/engine/note-frontmatter.d.mts +1 -4
- package/types/engine/note-vocabulary.d.mts +98 -8
- package/types/engine/page-lists.d.mts +126 -0
- package/types/engine/pdf-render.d.mts +72 -2
- package/types/engine/rank-ladder.d.mts +32 -0
- package/types/engine/retired-fields.d.mts +4 -6
- package/types/engine/site-build.d.mts +13 -1
- package/types/engine/site-config.d.mts +9 -0
- package/types/engine/site-index.d.mts +13 -1
- package/types/engine/site-preview.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +31 -7
- package/types/engine/standing-terms.d.mts +16 -0
- package/types/engine/standings.d.mts +147 -0
- package/types/engine/system-block.d.mts +11 -12
- package/types/engine/web-wikilinks.d.mts +3 -1
- package/types/engine/wikilinks.d.mts +2 -1
- package/types/hm3/actors.d.mts +3 -3
- package/types/sohl/actors.d.mts +25 -7
- package/engine/content-admonitions.mjs +0 -114
- package/engine/content-secrets.mjs +0 -112
- package/types/engine/content-admonitions.d.mts +0 -24
- package/types/engine/content-secrets.d.mts +0 -17
|
@@ -12,30 +12,32 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* The **closed**
|
|
16
|
-
* `subType` each note type declares.
|
|
17
|
-
*
|
|
18
|
-
* A note's frontmatter has three regions
|
|
19
|
-
* **top level** describes the note as a published artefact
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* blocks** describe the subject as one system's documents
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Closed is the whole point
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* **`subType`
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
15
|
+
* The **closed vocabularies** of a note's frontmatter: the top-level keys, the
|
|
16
|
+
* `data:` container, and the `subType` each note type declares.
|
|
17
|
+
*
|
|
18
|
+
* A note's frontmatter has three regions and every one of them is closed. The
|
|
19
|
+
* **top level** describes the note as a published artefact and is a fixed list
|
|
20
|
+
* — {@link NOTE_TOP_LEVEL_FIELDS} — so a key it does not name is a finding at
|
|
21
|
+
* its own line rather than a value copied into the generated page. The
|
|
22
|
+
* **system blocks** describe the subject as one system's documents, and are
|
|
23
|
+
* checked against the published schema. Between them sits `data:` — the
|
|
24
|
+
* type-specific facts about the subject itself, system-agnostic.
|
|
25
|
+
*
|
|
26
|
+
* Closed is the whole point, and the failure it answers is a misspelling. A
|
|
27
|
+
* region that passes an unrecognised key through cannot tell `wieght` from a
|
|
28
|
+
* weapon that weighs nothing: the note says one thing, the build does another,
|
|
29
|
+
* and nothing says so. Under a closed vocabulary the same misspelling is an
|
|
30
|
+
* error naming the note and the key it was probably meant to be.
|
|
31
|
+
*
|
|
32
|
+
* **`subType` is a top-level key with a per-type vocabulary**, which is why it
|
|
33
|
+
* is declared twice over: once in {@link NOTE_TOP_LEVEL_FIELDS} as a key the
|
|
34
|
+
* region accepts, and once per type below as the closed set of values that type
|
|
35
|
+
* takes. It is not a `data:` key — a note's `(type, subType)` is what each
|
|
36
|
+
* system's map reads to derive a document type, so it describes the note rather
|
|
37
|
+
* than the subject — but its values are enumerated in the same `### type:`
|
|
38
|
+
* section of the specification that enumerates the `data:` keys, and keeping
|
|
39
|
+
* the two together means one entry per type rather than two registries free to
|
|
40
|
+
* disagree about which types exist.
|
|
39
41
|
*
|
|
40
42
|
* **This is note-format knowledge, so it lives in `engine/`.** `data:` holds
|
|
41
43
|
* what is true of the *thing* — a weapon's weight, an affliction's
|
|
@@ -79,6 +81,9 @@
|
|
|
79
81
|
// is how a disagreement between the three arises.
|
|
80
82
|
import { ART_SLOTS } from "./art-slots.mjs";
|
|
81
83
|
import { ADDRESS_SEGMENT_PATTERN, isAddressSegment } from "./address-charset.mjs";
|
|
84
|
+
// The system registry, so the top-level block keys below are the systems this
|
|
85
|
+
// toolchain recognises rather than a second list of them.
|
|
86
|
+
import { SYSTEM_IDS } from "./systems.mjs";
|
|
82
87
|
// The retirement window for a renamed type, read rather than restated: a
|
|
83
88
|
// vocabulary that answered only to the current spelling would report every key
|
|
84
89
|
// of an unswept note as unknown.
|
|
@@ -97,7 +102,10 @@ import {
|
|
|
97
102
|
// question, asked the way `checkPlace` and `checkCalendarNote` are.
|
|
98
103
|
import { checkBeingAge } from "./being-age.mjs";
|
|
99
104
|
import { checkSocialTies } from "./social-ties.mjs";
|
|
105
|
+
import { checkStandings } from "./standings.mjs";
|
|
106
|
+
import { STANDING_BODY_TYPES } from "./standing-terms.mjs";
|
|
100
107
|
import { checkDatedOffices } from "./office-holders.mjs";
|
|
108
|
+
import { checkRankLadder } from "./rank-ladder.mjs";
|
|
101
109
|
import { checkCalendarChoice } from "./calendar-choice.mjs";
|
|
102
110
|
import { checkCultureChoice } from "./culture-choice.mjs";
|
|
103
111
|
import { SOCIAL_TIES, SOCIAL_TIE_TARGET_TYPES } from "./social-tie-terms.mjs";
|
|
@@ -128,12 +136,20 @@ import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
|
128
136
|
* @typedef {object} DataFieldSpec
|
|
129
137
|
* @property {string} name - The key under `data:`, dotted for a nested one
|
|
130
138
|
* (`charges.value`).
|
|
131
|
-
* @property {"string"|"number"|"boolean"|"list"|"map"|"scalar-or-map"|"address"|"shortcode"} [kind] -
|
|
139
|
+
* @property {"string"|"number"|"boolean"|"list"|"map"|"list-or-map"|"scalar-or-map"|"address"|"shortcode"} [kind] -
|
|
132
140
|
* The value's shape, for the lint. Absent means no claim is made about the
|
|
133
141
|
* value — which is the honest answer wherever the specification's stated
|
|
134
142
|
* shape and the shape notes are authored in today disagree.
|
|
135
143
|
* @property {"address"|"shortcode"} [entryKind] - Type of each list or pack-map value.
|
|
136
|
-
* @property {"address"|"shortcode"} [keyKind] - Type of each map key.
|
|
144
|
+
* @property {"address"|"shortcode"} [keyKind] - Type of each map key. On a
|
|
145
|
+
* `list-or-map` field it describes the map form alone, and `entryKind` the
|
|
146
|
+
* list form alone: reading both of one value would check a map's entries as
|
|
147
|
+
* if they were its keys.
|
|
148
|
+
* @property {boolean} [standings] - The map's entries are standings held in the
|
|
149
|
+
* body each is keyed by, so a row names the two together — see
|
|
150
|
+
* {@link module:engine/standings}.
|
|
151
|
+
* @property {boolean} [roster] - The map names posts, each with a description,
|
|
152
|
+
* so each takes a row labelled and anchored by the post's own name.
|
|
137
153
|
* @property {"subType"} [keySelector] - An alternative `subType:<skill-subtype>` key.
|
|
138
154
|
* @property {readonly string[]} [accepts] - Allowed Address types, separate from `ref`.
|
|
139
155
|
* @property {string} [shape] - Human-readable shape, for a finding and for
|
|
@@ -208,12 +224,45 @@ const TEXT = Object.freeze({ shape: "string", kind: "string" });
|
|
|
208
224
|
/** A list. */
|
|
209
225
|
const LIST = Object.freeze({ shape: "list", kind: "list" });
|
|
210
226
|
|
|
227
|
+
/**
|
|
228
|
+
* One value, or several.
|
|
229
|
+
*
|
|
230
|
+
* The two are the same fact at different lengths rather than two different
|
|
231
|
+
* facts, so a single value means a list of one and a consumer reads one shape.
|
|
232
|
+
* That is what separates this from `list-or-map`, whose halves say different
|
|
233
|
+
* things.
|
|
234
|
+
*/
|
|
235
|
+
const TEXT_OR_LIST = Object.freeze({ shape: "string or list", kind: "string-or-list" });
|
|
236
|
+
|
|
211
237
|
/** A single Address. */
|
|
212
238
|
const LINK = Object.freeze({ shape: "an Address", kind: "address" });
|
|
213
239
|
|
|
214
240
|
/** A list of Addresses. */
|
|
215
241
|
const LINKS = Object.freeze({ shape: "list of Addresses", kind: "list", entryKind: "address" });
|
|
216
242
|
|
|
243
|
+
/**
|
|
244
|
+
* A being's memberships, and the standing it holds in each.
|
|
245
|
+
*
|
|
246
|
+
* A map keyed by the body's Address, whose entry is that membership's `rank`
|
|
247
|
+
* and `office` — the pair a flat list of bodies cannot state, because it has
|
|
248
|
+
* nowhere to say which body a standing is held in.
|
|
249
|
+
*
|
|
250
|
+
* **`list-or-map` is the sweep's shape**, and it says which half each
|
|
251
|
+
* declaration describes: `entryKind` the list's entries, `keyKind` the map's
|
|
252
|
+
* keys. A tree still writing the list form keeps the check it had, and the map
|
|
253
|
+
* form earns the two the body can answer — see {@link module:engine/standings}.
|
|
254
|
+
*/
|
|
255
|
+
const STANDINGS = Object.freeze({
|
|
256
|
+
shape: "a standing map keyed by Address, or a list of Addresses",
|
|
257
|
+
kind: "list-or-map",
|
|
258
|
+
entryKind: "address",
|
|
259
|
+
keyKind: "address",
|
|
260
|
+
standings: true,
|
|
261
|
+
ref: "affiliation",
|
|
262
|
+
accepts: STANDING_BODY_TYPES,
|
|
263
|
+
check: checkStandings,
|
|
264
|
+
});
|
|
265
|
+
|
|
217
266
|
/**
|
|
218
267
|
* A single Address, or one per pack.
|
|
219
268
|
*
|
|
@@ -270,6 +319,101 @@ const TOKEN_ICON = Object.freeze({
|
|
|
270
319
|
describe: "What a token on the canvas wears — an `icon` address; unset, it follows `icon`.",
|
|
271
320
|
});
|
|
272
321
|
|
|
322
|
+
/**
|
|
323
|
+
* One top-level frontmatter key.
|
|
324
|
+
*
|
|
325
|
+
* Narrower than a {@link DataFieldSpec}: the top level is a fixed list rather
|
|
326
|
+
* than a per-type vocabulary, nothing there is keyed by pack or resolved as an
|
|
327
|
+
* Address, and the value's shape is checked by whatever owns the region the key
|
|
328
|
+
* opens — `name`, `tags` and each system block each have their own check.
|
|
329
|
+
*
|
|
330
|
+
* @typedef {object} TopLevelFieldSpec
|
|
331
|
+
* @property {string} name - The key, as a note writes it.
|
|
332
|
+
* @property {boolean} [system] - The key opens a game system's block, so it is
|
|
333
|
+
* one of {@link module:engine/systems.SYSTEM_IDS} rather than a fixed key.
|
|
334
|
+
* @property {string} describe - One line, for the author-facing reference.
|
|
335
|
+
*/
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* **The top-level vocabulary**, in the order a formatted note writes it.
|
|
339
|
+
*
|
|
340
|
+
* The region describes the note as a published artefact: what it is called,
|
|
341
|
+
* what kind of thing it is about, how it is classified, and which of the other
|
|
342
|
+
* two regions it opens. Everything else a note states is a fact about the
|
|
343
|
+
* subject, which belongs under `data:`, or a fact about one system's document,
|
|
344
|
+
* which belongs inside that system's block.
|
|
345
|
+
*
|
|
346
|
+
* **Closed, like the other two regions.** A key this list does not name is a
|
|
347
|
+
* finding at its own line, because a region that passed one through would turn
|
|
348
|
+
* a misspelling into a value nothing reads — `title` beside `name`, or `pack`
|
|
349
|
+
* where `data.pack` was meant.
|
|
350
|
+
*
|
|
351
|
+
* **The system blocks are derived from the system registry**, so recognising a
|
|
352
|
+
* further system is a change to {@link module:engine/systems.SYSTEM_IDS} and to
|
|
353
|
+
* nothing else. They sort by name after the fixed keys: a block is the largest
|
|
354
|
+
* thing a note writes, every one of them belongs at the end, and a rule a
|
|
355
|
+
* reader can restate in one sentence is worth more than a hand-kept order.
|
|
356
|
+
*
|
|
357
|
+
* Five readers derive from this one list — the frontmatter lint's accepted set,
|
|
358
|
+
* the message it prints, the formatter's key order, the content-format check,
|
|
359
|
+
* and the author-facing reference — so none of them can fall behind the others.
|
|
360
|
+
*
|
|
361
|
+
* @type {readonly TopLevelFieldSpec[]}
|
|
362
|
+
*/
|
|
363
|
+
export const NOTE_TOP_LEVEL_FIELDS = Object.freeze([
|
|
364
|
+
Object.freeze({
|
|
365
|
+
name: "shortcode",
|
|
366
|
+
describe: "The note's own address segment, unique within its type.",
|
|
367
|
+
}),
|
|
368
|
+
Object.freeze({
|
|
369
|
+
name: "name",
|
|
370
|
+
describe: "The display names — a required `full`, and any `aliases`.",
|
|
371
|
+
}),
|
|
372
|
+
Object.freeze({
|
|
373
|
+
name: "type",
|
|
374
|
+
describe: "What the note is about, which decides its vocabulary and its document.",
|
|
375
|
+
}),
|
|
376
|
+
Object.freeze({
|
|
377
|
+
name: "subType",
|
|
378
|
+
describe: "The type's own genre, where it declares one.",
|
|
379
|
+
}),
|
|
380
|
+
Object.freeze({
|
|
381
|
+
name: "description",
|
|
382
|
+
describe: "The short page summary.",
|
|
383
|
+
}),
|
|
384
|
+
Object.freeze({
|
|
385
|
+
name: "tags",
|
|
386
|
+
describe: "Draft state, GM routing, and the descriptive labels a page list reads.",
|
|
387
|
+
}),
|
|
388
|
+
Object.freeze({
|
|
389
|
+
name: "data",
|
|
390
|
+
describe: "The facts about the subject itself, shared by every system.",
|
|
391
|
+
}),
|
|
392
|
+
...[...SYSTEM_IDS].sort().map((system) =>
|
|
393
|
+
Object.freeze({
|
|
394
|
+
name: system,
|
|
395
|
+
system: true,
|
|
396
|
+
describe:
|
|
397
|
+
`What the \`${system}\` system makes of the subject — its ` +
|
|
398
|
+
`document's mechanics, routing and art.`,
|
|
399
|
+
}),
|
|
400
|
+
),
|
|
401
|
+
]);
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The top-level keys, in formatted order.
|
|
405
|
+
*
|
|
406
|
+
* @type {readonly string[]}
|
|
407
|
+
*/
|
|
408
|
+
export const NOTE_TOP_LEVEL_KEYS = Object.freeze(NOTE_TOP_LEVEL_FIELDS.map((field) => field.name));
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Membership test for the top-level vocabulary.
|
|
412
|
+
*
|
|
413
|
+
* @type {ReadonlySet<string>}
|
|
414
|
+
*/
|
|
415
|
+
export const NOTE_TOP_LEVEL_KEY_SET = Object.freeze(new Set(NOTE_TOP_LEVEL_KEYS));
|
|
416
|
+
|
|
273
417
|
/**
|
|
274
418
|
* The `data:` keys **every** note type accepts, whatever it is.
|
|
275
419
|
*
|
|
@@ -415,7 +559,7 @@ function checkPlacePurpose(note) {
|
|
|
415
559
|
},
|
|
416
560
|
];
|
|
417
561
|
if (hasTag(note.fm, value)) return [];
|
|
418
|
-
const tags = note.fm?.tags
|
|
562
|
+
const tags = note.fm?.tags;
|
|
419
563
|
return [
|
|
420
564
|
{
|
|
421
565
|
...at(),
|
|
@@ -449,15 +593,17 @@ export const GM_TAG = "gm";
|
|
|
449
593
|
/**
|
|
450
594
|
* The tags that **classify** a note, grouped by what they classify.
|
|
451
595
|
*
|
|
452
|
-
* `tags:`
|
|
453
|
-
* region, a working state is the author's own and this build has no
|
|
454
|
-
* about it. A classifying tag is different, because something queries
|
|
596
|
+
* `tags:` holds an open vocabulary of *values*, and most tags belong there: a
|
|
597
|
+
* theme, a region, a working state is the author's own and this build has no
|
|
598
|
+
* opinion about it. A classifying tag is different, because something queries
|
|
599
|
+
* it — a
|
|
455
600
|
* settlement tagged `village` appears in the list of villages and an untagged
|
|
456
601
|
* one does not, so `vilage` does not merely look wrong, it removes the note from
|
|
457
602
|
* an index while the index still renders a table that looks complete.
|
|
458
603
|
*
|
|
459
604
|
* **This list is not a closed set.** An unrecognised tag is legal, because the
|
|
460
|
-
*
|
|
605
|
+
* values are the author's; what is reported is a **near miss** — a tag close
|
|
606
|
+
* enough to a
|
|
461
607
|
* declared one to be a typo of it.
|
|
462
608
|
*
|
|
463
609
|
* **Each group names the types it applies to**, and that scope is what makes the
|
|
@@ -629,14 +775,12 @@ export function exclusiveTagGroups(type, groups = DECLARED_TAGS) {
|
|
|
629
775
|
* The spelling of the tag *itself* still is — a near miss is a near miss, and
|
|
630
776
|
* the frontmatter lint is what reports it; nothing here guesses.
|
|
631
777
|
*
|
|
632
|
-
* Reads `tags` and the singular `tag` spelling.
|
|
633
|
-
*
|
|
634
778
|
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
635
779
|
* @param {string} tag - The tag to look for, in its declared spelling.
|
|
636
780
|
* @returns {boolean} Whether the note carries it.
|
|
637
781
|
*/
|
|
638
782
|
export function hasTag(fm, tag) {
|
|
639
|
-
const raw = fm?.tags
|
|
783
|
+
const raw = fm?.tags;
|
|
640
784
|
if (raw == null) return false;
|
|
641
785
|
const wanted = String(tag).toLowerCase();
|
|
642
786
|
for (const entry of Array.isArray(raw) ? raw : [raw]) {
|
|
@@ -725,7 +869,8 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
725
869
|
ref: "lore",
|
|
726
870
|
accepts: ["lore"],
|
|
727
871
|
describe:
|
|
728
|
-
"Lore concerning this being
|
|
872
|
+
"Lore concerning this being — the law it lives under, the customs it is " +
|
|
873
|
+
"subject to, the traditions it was raised in.",
|
|
729
874
|
},
|
|
730
875
|
{
|
|
731
876
|
name: "culture",
|
|
@@ -744,10 +889,11 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
744
889
|
},
|
|
745
890
|
{
|
|
746
891
|
name: "affiliations",
|
|
747
|
-
...
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
892
|
+
...STANDINGS,
|
|
893
|
+
describe:
|
|
894
|
+
"Bodies the being belongs to, keyed by Address, each entry holding the " +
|
|
895
|
+
"standing it holds there — `rank`, required, a level on that body's own " +
|
|
896
|
+
"ladder, and `office`, optional, a post that body names.",
|
|
751
897
|
},
|
|
752
898
|
{
|
|
753
899
|
name: "socialTies",
|
|
@@ -759,7 +905,14 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
759
905
|
check: checkSocialTies,
|
|
760
906
|
describe: `Defining ties directed from this being to others: ${SOCIAL_TIES.map(({ term, meaning }) => `\`${term}\` (${meaning})`).join("; ")}`,
|
|
761
907
|
},
|
|
762
|
-
{
|
|
908
|
+
{
|
|
909
|
+
name: "gender",
|
|
910
|
+
...TEXT,
|
|
911
|
+
describe:
|
|
912
|
+
"One of `male`, `female`, `nonbinary`, `none` or `other`. " +
|
|
913
|
+
"`none` says the being has no gender; an absent field says its " +
|
|
914
|
+
"gender is unrecorded.",
|
|
915
|
+
},
|
|
763
916
|
{
|
|
764
917
|
name: "species",
|
|
765
918
|
...LINK,
|
|
@@ -827,12 +980,19 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
827
980
|
{
|
|
828
981
|
name: "frame",
|
|
829
982
|
...TEXT,
|
|
830
|
-
describe:
|
|
983
|
+
describe:
|
|
984
|
+
"Relative frame — one of `scant`, `light`, `medium`, `heavy` or `massive`.",
|
|
831
985
|
},
|
|
832
986
|
{ name: "appearance.eye_color", ...TEXT, describe: "Eye colour." },
|
|
833
987
|
{ name: "appearance.hair_color", ...TEXT, describe: "Hair colour." },
|
|
834
988
|
{ name: "appearance.skin_color", ...TEXT, describe: "Skin colour." },
|
|
835
|
-
{
|
|
989
|
+
{
|
|
990
|
+
name: "appearance.complexion",
|
|
991
|
+
...TEXT_OR_LIST,
|
|
992
|
+
describe:
|
|
993
|
+
"The skin's condition — one value, or several, because a face " +
|
|
994
|
+
"carries more than one at once. A single value means a list of one.",
|
|
995
|
+
},
|
|
836
996
|
{
|
|
837
997
|
name: "appearance.extra_features",
|
|
838
998
|
...LIST,
|
|
@@ -896,11 +1056,25 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
896
1056
|
{
|
|
897
1057
|
name: "governance.ranks",
|
|
898
1058
|
...LIST,
|
|
1059
|
+
// A rung is `{level, title, description}` with an optional
|
|
1060
|
+
// `lore`. The shape check sees a list and stops there, so the
|
|
1061
|
+
// rungs inside it are checked here — a ladder is what a being's
|
|
1062
|
+
// `rank` indexes into, and an incomplete rung resolves to a
|
|
1063
|
+
// standing with no name.
|
|
1064
|
+
check: checkRankLadder,
|
|
1065
|
+
// Each rung takes a row of its own, labelled and anchored by
|
|
1066
|
+
// its title, ordered by level — the ladder's parallel to
|
|
1067
|
+
// `governance.offices`' `roster`.
|
|
1068
|
+
ranks: true,
|
|
899
1069
|
describe: "The ladder of ranks the body confers — level, title, description.",
|
|
900
1070
|
},
|
|
901
1071
|
{
|
|
902
1072
|
name: "governance.offices",
|
|
903
1073
|
...ANY,
|
|
1074
|
+
// A map of named posts: each takes a row of its own, labelled
|
|
1075
|
+
// by the post and anchored by it, so the description a body
|
|
1076
|
+
// declares is a destination a being's `office` can link to.
|
|
1077
|
+
roster: true,
|
|
904
1078
|
check: checkDatedOffices,
|
|
905
1079
|
describe: "Named offices, each with a description and optional dated holders.",
|
|
906
1080
|
},
|