@heroiclands/package-build 22.19.0 → 22.21.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.
@@ -244,6 +244,24 @@ export const DECLARED_TAGS: Readonly<{
244
244
  tags: readonly string[];
245
245
  }>;
246
246
  }>;
247
+ /** The case-sensitive archetypes and their author-facing meanings. */
248
+ export const BEING_ARCHETYPES: Readonly<{
249
+ warrior: "Can hold a line and win a fight.";
250
+ skirmisher: "Fights light — ambush, missile, mobility.";
251
+ infiltrator: "Gets in unseen — locks, stealth, disguise.";
252
+ mage: "Commands arcane practice.";
253
+ cleric: "Commands religious practice and standing.";
254
+ healer: "Treats wounds and illness.";
255
+ scholar: "Reads, researches, and knows things.";
256
+ courtier: "Navigates rank, negotiation, and intrigue.";
257
+ woodsman: "Travels and survives wild country.";
258
+ mariner: "Handles boats and blue water.";
259
+ artisan: "Builds, repairs, and appraises craft work.";
260
+ trader: "Moves goods, values them, and knows markets.";
261
+ commoner: "Fits no more specific archetype; handles ordinary work and daily life.";
262
+ entertainer: "Performs for an audience through acting, music, comedy, or similar arts.";
263
+ guildsperson: "Has professional training, standing, or connections that open doors.";
264
+ }>;
247
265
  /**
248
266
  * Every note type this toolchain compiles, and the closed vocabulary it
249
267
  * declares.
@@ -278,15 +296,29 @@ export type DataFieldSpec = {
278
296
  * value — which is the honest answer wherever the specification's stated
279
297
  * shape and the shape notes are authored in today disagree.
280
298
  */
281
- kind?: "string" | "number" | "boolean" | "map" | "address" | "list" | "shortcode" | "scalar-or-map" | undefined;
299
+ kind?: "string" | "number" | "boolean" | "map" | "address" | "list" | "shortcode" | "list-or-map" | "scalar-or-map" | undefined;
282
300
  /**
283
301
  * - Type of each list or pack-map value.
284
302
  */
285
303
  entryKind?: "address" | "shortcode" | undefined;
286
304
  /**
287
- * - Type of each map key.
305
+ * - Type of each map key. On a
306
+ * `list-or-map` field it describes the map form alone, and `entryKind` the
307
+ * list form alone: reading both of one value would check a map's entries as
308
+ * if they were its keys.
288
309
  */
289
310
  keyKind?: "address" | "shortcode" | undefined;
311
+ /**
312
+ * - The map's entries are standings held in the
313
+ * body each is keyed by, so a row names the two together — see
314
+ * {@link module :engine/standings}.
315
+ */
316
+ standings?: boolean | undefined;
317
+ /**
318
+ * - The map names posts, each with a description,
319
+ * so each takes a row labelled and anchored by the post's own name.
320
+ */
321
+ roster?: boolean | undefined;
290
322
  /**
291
323
  * - An alternative `subType:<skill-subtype>` key.
292
324
  */
@@ -49,10 +49,8 @@ export function assertNoDraftField(fm: object | null | undefined, { file, absPat
49
49
  * unambiguous folded in every note's `name.full` and so dictated what a note
50
50
  * could be named. The form is retired, so the list has no reader.
51
51
  *
52
- * **`name.aliases` is a different field and is not retired.** It fed the same
53
- * index, but unlike the top-level list it is being kept — reserved, unread,
54
- * and deliberately unmentioned by this message, which would otherwise tell an
55
- * author to delete a field they are allowed to write. See
52
+ * **`name.aliases` is a different field.** It lists alternate names in the
53
+ * infobox and is deliberately unmentioned by this message. See
56
54
  * {@link assertNoAliasesField}.
57
55
  *
58
56
  * @param {string} [file] - The note's path, named in the message. Omit it where
@@ -68,13 +66,9 @@ export function aliasesRetiredMessage(file?: string): string;
68
66
  * it reads as "this note claims no other names", a statement about a namespace
69
67
  * that no longer exists.
70
68
  *
71
- * **The nested `name.aliases` is deliberately not refused.** Both spellings fed
72
- * the retired alias index, and both lost their reader with it, but only the
73
- * top-level one is retired: `name.aliases` is **reserved**, held for a use that
74
- * does not exist yet. So it is neither refused nor read — no index consults it,
75
- * no rule validates its contents, nothing derives from it, and nothing emits
76
- * it. It rides in the note as inert data, and a note carrying one compiles,
77
- * resolves and emits exactly as if it were absent.
69
+ * **The nested `name.aliases` is deliberately not refused.** It is the note's
70
+ * ordered alternate names and appears in the note infobox. It does not create
71
+ * addresses or change how authored links resolve.
78
72
  *
79
73
  * That distinction is the reason this checks `Object.hasOwn(fm, "aliases")`
80
74
  * rather than resolving a dotted key: the top-level field is the whole subject,
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The note type a standing is held in.
3
+ *
4
+ * A standing is always held in a body: the entry is keyed by that body's
5
+ * Address, which is what lets the rung and the post be checked against what
6
+ * the body itself declares.
7
+ */
8
+ export const STANDING_BODY_TYPES: readonly string[];
9
+ /**
10
+ * The two keys one standing carries.
11
+ *
12
+ * `rank` is the level on the body's own `governance.ranks` ladder, as a number.
13
+ * `office` is a key of its `governance.offices` map, as a string. Both are
14
+ * optional — a membership with neither is the ordinary shape.
15
+ */
16
+ export const STANDING_KEYS: readonly string[];
@@ -0,0 +1,129 @@
1
+ /**
2
+ * The standings a being authored, in one shape whichever shape it wrote.
3
+ *
4
+ * The list form names bodies and no standings; the map form names both. Three
5
+ * spellings reach here and every reader downstream takes the same entries, so
6
+ * none of them asks which it was handed:
7
+ *
8
+ * - an **{@link AddressEntries}** — the map after the note boundary decoded its
9
+ * keys into typed Addresses, which is the shape every compile sees;
10
+ * - a **plain map**, which is the shape a checker reading raw YAML sees;
11
+ * - a **list**, which names bodies and says nothing about standing in them.
12
+ *
13
+ * An emptied map arrives from the property editor as `[]` and means what `{}`
14
+ * means — this being authors no membership — so both read as no entries.
15
+ *
16
+ * Each entry carries `body` for a message and `sourceKey` for a position: the
17
+ * authored key is what a reader has to find in the file, and the decoded
18
+ * Address is what a finding should name.
19
+ *
20
+ * @param {unknown} value - The authored `data.affiliations`.
21
+ * @returns {{form: "absent"|"list"|"map"|"malformed",
22
+ * entries: Array<{body: unknown, sourceKey: string|number, standing: unknown}>}}
23
+ * The form it was written in, and its entries.
24
+ */
25
+ export function readStandings(value: unknown): {
26
+ form: "absent" | "list" | "map" | "malformed";
27
+ entries: Array<{
28
+ body: unknown;
29
+ sourceKey: string | number;
30
+ standing: unknown;
31
+ }>;
32
+ };
33
+ /**
34
+ * The rungs an affiliation's ladder declares, as level → title.
35
+ *
36
+ * A rung states its own `level`, so the ladder is read by that number rather
37
+ * than by its position in the list: a body may write its rungs in any order,
38
+ * and several start at zero.
39
+ *
40
+ * @param {unknown} ranks - An affiliation's `data.governance.ranks`.
41
+ * @returns {Map<number, string>} Level → the title that level is called.
42
+ */
43
+ export function rankLadder(ranks: unknown): Map<number, string>;
44
+ /**
45
+ * The offices an affiliation names, as key → description.
46
+ *
47
+ * An office is authored either as a bare description or as a `{description,
48
+ * holders}` map, and a reader of the post's name wants the same string from
49
+ * both.
50
+ *
51
+ * @param {unknown} offices - An affiliation's `data.governance.offices`.
52
+ * @returns {Map<string, string>} Office key → its description.
53
+ */
54
+ export function officeRoster(offices: unknown): Map<string, string>;
55
+ /**
56
+ * The anchor an office's row is addressed by.
57
+ *
58
+ * Declared once, because the row that carries the anchor and the link that
59
+ * reaches it are drawn by different passes: two spellings of the same
60
+ * derivation is a link into a page that offers no such destination.
61
+ *
62
+ * An office whose name reduces to nothing URL-safe has no anchor, and the
63
+ * caller is the one that can say what to do without one.
64
+ *
65
+ * @param {string} office - The office's key, as the body wrote it.
66
+ * @returns {string} The anchor, or `""` where the name carries nothing.
67
+ */
68
+ export function officeAnchor(office: string): string;
69
+ /**
70
+ * What an affiliation lends to a reference to it.
71
+ *
72
+ * A reference's value answers what was found as well as where it is — the same
73
+ * reason a target's `subType` travels with it. A standing is read on the
74
+ * *being*, and what it is called is declared on the *body*, so the ladder and
75
+ * the office roster ride along to the one place that needs them.
76
+ *
77
+ * Nothing rides for a note that declares neither.
78
+ *
79
+ * @param {object} fm - The affiliation note's frontmatter.
80
+ * @returns {{ranks?: Record<string, string>, offices?: Record<string, string>}|undefined}
81
+ * What a reference carries, or nothing.
82
+ */
83
+ export function standingsDigest(fm: object): {
84
+ ranks?: Record<string, string>;
85
+ offices?: Record<string, string>;
86
+ } | undefined;
87
+ /**
88
+ * How a standing reads beside the body that confers it.
89
+ *
90
+ * The office leads, because it is the more particular of the two: a reader
91
+ * meeting "War Chief" has been told the rung as well. The body closes the
92
+ * phrase, which is the whole point of keying the entry by it — a standing with
93
+ * no body named is the lossy form.
94
+ *
95
+ * No article is supplied. One body's name reads with a "the" in front of it and
96
+ * the next does not, and inventing one is this build editing the corpus.
97
+ *
98
+ * @param {object} standing - `{rank, office}`, either or both absent.
99
+ * @param {string} body - What the body is called.
100
+ * @param {{ranks?: Record<string, string>}} [digest] - The body's ladder.
101
+ * @returns {string} The phrase, or the body's name where no standing is held.
102
+ */
103
+ export function standingPhrase(standing: object, body: string, digest?: {
104
+ ranks?: Record<string, string>;
105
+ }): string;
106
+ /**
107
+ * Validate a being's memberships and the standing it holds in each.
108
+ *
109
+ * Four questions, and the last two are what keying the entry by its body buys:
110
+ *
111
+ * 1. the value is a map keyed by Address (or the list this window still takes);
112
+ * 2. every key names an affiliation that resolves, once;
113
+ * 3. a `rank` is a level the named body's `governance.ranks` declares;
114
+ * 4. an `office` is a key of that body's `governance.offices`.
115
+ *
116
+ * Both of the last two are read from the named body's own frontmatter as the
117
+ * lint runs, so a renamed rung or a renamed post fails on every being pointing
118
+ * at it rather than on a list somebody has to remember to edit.
119
+ *
120
+ * @param {object} note - The note.
121
+ * @param {{index?: object}} [options] - The lint's options.
122
+ * @returns {object[]} Located findings.
123
+ */
124
+ export function checkStandings(note: object, { index }?: {
125
+ index?: object;
126
+ }): object[];
127
+ import { STANDING_BODY_TYPES } from "./standing-terms.mjs";
128
+ import { STANDING_KEYS } from "./standing-terms.mjs";
129
+ export { STANDING_BODY_TYPES, STANDING_KEYS };
@@ -133,7 +133,7 @@ export function convertWikilinks(markdown: string, { type, id, pack, docPack, in
133
133
  * @param {object} index - From {@link buildWikilinkIndex}.
134
134
  * @param {unknown} ref - The reference, as authored.
135
135
  * @param {object} [hint] - `{type}`, where the caller knows what it expects.
136
- * @returns {{name?: string, uuid?: string, address?: import("./address.mjs").AddressTuple, subType?: string}|undefined}
136
+ * @returns {{name?: string, uuid?: string, address?: import("./address.mjs").AddressTuple, subType?: string, standings?: object}|undefined}
137
137
  * The target, or `undefined` where nothing answers.
138
138
  */
139
139
  export function resolveReference(index: object, ref: unknown, hint?: object): {
@@ -141,6 +141,7 @@ export function resolveReference(index: object, ref: unknown, hint?: object): {
141
141
  uuid?: string;
142
142
  address?: import("./address.mjs").AddressTuple;
143
143
  subType?: string;
144
+ standings?: object;
144
145
  } | undefined;
145
146
  import { ITEM_PACK } from "./ids.mjs";
146
147
  import { PACK_BY_TYPE } from "./ids.mjs";
@@ -21,6 +21,25 @@ export class Actors extends SystemActorCompiler {
21
21
  * entry plus one per `sohl.items` entry. `sohl.skills` is ignored.
22
22
  */
23
23
  buildEmbeddedItems(itemsMap: any, actorId: any, fm: any, ctx: any): object[];
24
+ /**
25
+ * One embedded affiliation item per body the being belongs to.
26
+ *
27
+ * The body's own compiled item is the model, so a membership carries
28
+ * whatever that body declares — its subtype, its common skills, its
29
+ * standings toward others — and the being adds only what is true of *this*
30
+ * membership: the rung it stands on and the post it holds.
31
+ *
32
+ * A being naming a body that compiles to no item is a finding rather than a
33
+ * silently dropped membership: the note says the being belongs somewhere,
34
+ * and a sheet that shows nothing says it does not.
35
+ *
36
+ * @param {Map<string, object>} itemsMap - The predefined-item catalogue.
37
+ * @param {string} actorId - The owning actor's id.
38
+ * @param {object} fm - The note's frontmatter.
39
+ * @param {string} ctx - Diagnostic context.
40
+ * @returns {object[]} The embedded items, in the order the note names them.
41
+ */
42
+ buildMemberships(itemsMap: Map<string, object>, actorId: string, fm: object, ctx: string): object[];
24
43
  /**
25
44
  * Bake each unopened skill's opening mastery level into the document.
26
45
  *