@heroiclands/package-build 22.28.0 → 23.1.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.
Files changed (156) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +1 -1
  3. package/bin/content-commands.mjs +1 -1
  4. package/ci/content-format-reference.mjs +46 -0
  5. package/docs/api.md +125 -142
  6. package/docs/authoring/assets.md +1 -1
  7. package/docs/authoring/dates-and-calendars.md +12 -6
  8. package/docs/authoring/first-note.md +1 -1
  9. package/docs/authoring/frontmatter.md +2 -2
  10. package/docs/authoring/links-and-markup.md +145 -29
  11. package/docs/commands.md +8 -10
  12. package/docs/configuration.md +13 -11
  13. package/docs/content-format.md +3 -3
  14. package/docs/guides/book.md +59 -4
  15. package/docs/guides/icons.md +4 -5
  16. package/docs/guides/packs.md +6 -0
  17. package/docs/guides/reference-pages.md +21 -1
  18. package/docs/guides/release-and-deploy.md +17 -1
  19. package/docs/guides/site.md +17 -18
  20. package/docs/reference/format-details.md +904 -275
  21. package/docs/reference/note-types.md +282 -91
  22. package/docs/reference/theme-pages.md +58 -66
  23. package/engine/address-values.mjs +124 -4
  24. package/engine/address.mjs +44 -3
  25. package/engine/anchors.mjs +103 -3
  26. package/engine/art-slots.mjs +2 -9
  27. package/engine/base-compiler.mjs +25 -6
  28. package/engine/calendar-artifacts.mjs +4 -1
  29. package/engine/calendar-notes.mjs +166 -102
  30. package/engine/content-format-check.mjs +2 -3
  31. package/engine/content-format.mjs +44 -1
  32. package/engine/content-format.yaml +84 -16
  33. package/engine/content-images.mjs +10 -1
  34. package/engine/content-index.mjs +37 -9
  35. package/engine/content-links.mjs +24 -5
  36. package/engine/content-tables.mjs +24 -3
  37. package/engine/data-keys.mjs +347 -0
  38. package/engine/derived-sections.mjs +322 -0
  39. package/engine/diagnostics.mjs +28 -0
  40. package/engine/event-fields.mjs +187 -0
  41. package/engine/event-rows.mjs +126 -0
  42. package/engine/event-views.mjs +305 -0
  43. package/engine/exported-scene.mjs +29 -53
  44. package/engine/frontmatter-lint.mjs +103 -77
  45. package/engine/frontmatter.mjs +7 -2
  46. package/engine/generate.mjs +53 -0
  47. package/engine/generated-sections.mjs +223 -0
  48. package/engine/heading-attributes.mjs +4 -2
  49. package/engine/helpers.mjs +12 -1
  50. package/engine/holdings.mjs +12 -33
  51. package/engine/ids.mjs +17 -11
  52. package/engine/index.mjs +4 -4
  53. package/engine/infobox-render.mjs +21 -6
  54. package/engine/infobox.mjs +217 -99
  55. package/engine/journals.mjs +104 -47
  56. package/engine/literature-notes.mjs +136 -0
  57. package/engine/literature-works.mjs +161 -0
  58. package/engine/map-graphviz.mjs +4 -18
  59. package/engine/markdown-expressions.mjs +116 -11
  60. package/engine/metadata-index.mjs +34 -0
  61. package/engine/near-miss.mjs +90 -0
  62. package/engine/note-addresses.mjs +75 -19
  63. package/engine/note-dates.mjs +5 -3
  64. package/engine/note-event-terms.mjs +364 -0
  65. package/engine/note-events.mjs +823 -0
  66. package/engine/note-vocabulary.mjs +195 -92
  67. package/engine/office-holders.mjs +20 -54
  68. package/engine/packagebuild-index.mjs +16 -1
  69. package/engine/pdf-build.mjs +97 -60
  70. package/engine/pdf-index.mjs +263 -0
  71. package/engine/pdf-render.mjs +47 -5
  72. package/engine/place-relation-terms.mjs +174 -0
  73. package/engine/place-relations.mjs +72 -160
  74. package/engine/rank-ladder.mjs +14 -41
  75. package/engine/reckoning-markers.mjs +2 -0
  76. package/engine/region-events.mjs +6 -12
  77. package/engine/scenes.mjs +73 -468
  78. package/engine/site-build.mjs +74 -85
  79. package/engine/site-index.mjs +3 -1
  80. package/engine/site-maps.mjs +43 -29
  81. package/engine/site-preview.mjs +40 -31
  82. package/engine/social-ties.mjs +2 -1
  83. package/engine/sql-tables.mjs +212 -9
  84. package/engine/standing-terms.mjs +145 -6
  85. package/engine/standings.mjs +37 -49
  86. package/engine/stub-lint.mjs +6 -0
  87. package/engine/web-wikilinks.mjs +21 -1
  88. package/engine/wikilink-syntax.mjs +34 -4
  89. package/engine/wikilinks.mjs +27 -2
  90. package/hugo-theme/layouts/partials/infobox/entry.html +4 -1
  91. package/hugo-theme/layouts/partials/page-body.html +0 -2
  92. package/hugo-theme/static/css/style.css +25 -55
  93. package/package.json +1 -2
  94. package/packagebuild-metadata.jsonl +13 -13
  95. package/sohl/default-item-art.mjs +6 -6
  96. package/sohl/note-schemas.mjs +8 -73
  97. package/types/engine/address-values.d.mts +50 -3
  98. package/types/engine/address.d.mts +21 -0
  99. package/types/engine/anchors.d.mts +49 -2
  100. package/types/engine/art-slots.d.mts +1 -1
  101. package/types/engine/base-compiler.d.mts +13 -4
  102. package/types/engine/calendar-notes.d.mts +7 -0
  103. package/types/engine/content-format.d.mts +52 -0
  104. package/types/engine/content-images.d.mts +5 -1
  105. package/types/engine/content-tables.d.mts +9 -1
  106. package/types/engine/data-keys.d.mts +145 -0
  107. package/types/engine/derived-sections.d.mts +73 -0
  108. package/types/engine/diagnostics.d.mts +15 -0
  109. package/types/engine/event-fields.d.mts +99 -0
  110. package/types/engine/event-rows.d.mts +12 -0
  111. package/types/engine/event-views.d.mts +67 -0
  112. package/types/engine/exported-scene.d.mts +5 -2
  113. package/types/engine/frontmatter-lint.d.mts +3 -3
  114. package/types/engine/generated-sections.d.mts +143 -0
  115. package/types/engine/holdings.d.mts +6 -20
  116. package/types/engine/ids.d.mts +15 -12
  117. package/types/engine/index.d.mts +1 -1
  118. package/types/engine/infobox.d.mts +13 -3
  119. package/types/engine/journals.d.mts +24 -11
  120. package/types/engine/literature-notes.d.mts +29 -0
  121. package/types/engine/literature-works.d.mts +67 -0
  122. package/types/engine/map-graphviz.d.mts +2 -10
  123. package/types/engine/markdown-expressions.d.mts +12 -3
  124. package/types/engine/near-miss.d.mts +42 -0
  125. package/types/engine/note-addresses.d.mts +7 -1
  126. package/types/engine/note-event-terms.d.mts +68 -0
  127. package/types/engine/note-events.d.mts +71 -0
  128. package/types/engine/note-vocabulary.d.mts +37 -8
  129. package/types/engine/office-holders.d.mts +3 -0
  130. package/types/engine/pdf-index.d.mts +35 -0
  131. package/types/engine/pdf-render.d.mts +8 -2
  132. package/types/engine/place-relation-terms.d.mts +83 -0
  133. package/types/engine/place-relations.d.mts +11 -66
  134. package/types/engine/rank-ladder.d.mts +2 -0
  135. package/types/engine/region-events.d.mts +6 -12
  136. package/types/engine/scenes.d.mts +6 -32
  137. package/types/engine/site-build.d.mts +8 -22
  138. package/types/engine/site-maps.d.mts +15 -7
  139. package/types/engine/sql-tables.d.mts +77 -2
  140. package/types/engine/standing-terms.d.mts +37 -4
  141. package/types/engine/standings.d.mts +14 -9
  142. package/types/engine/wikilink-syntax.d.mts +20 -0
  143. package/types/engine/wikilinks.d.mts +3 -2
  144. package/docs/guides/government-migration.md +0 -52
  145. package/engine/itinerary-scenes.mjs +0 -166
  146. package/engine/lore-events.mjs +0 -197
  147. package/engine/map-notes.mjs +0 -1309
  148. package/engine/map-raster.mjs +0 -51
  149. package/hugo-theme/layouts/partials/holdings/entry.html +0 -21
  150. package/hugo-theme/layouts/partials/holdings/kind-groups.html +0 -71
  151. package/hugo-theme/layouts/partials/holdings.html +0 -61
  152. package/hugo-theme/layouts/partials/place-map.html +0 -9
  153. package/types/engine/itinerary-scenes.d.mts +0 -29
  154. package/types/engine/lore-events.d.mts +0 -14
  155. package/types/engine/map-notes.d.mts +0 -297
  156. package/types/engine/map-raster.d.mts +0 -8
@@ -0,0 +1,364 @@
1
+ /*
2
+ * This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
3
+ * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
4
+ *
5
+ * This work is licensed under the GNU General Public License v3.0 (GPLv3).
6
+ * You may copy, modify, and distribute it under the terms of that license.
7
+ *
8
+ * For full terms, see the LICENSE.md file in the project root or visit:
9
+ * https://www.gnu.org/licenses/gpl-3.0.html
10
+ *
11
+ * SPDX-License-Identifier: GPL-3.0-or-later
12
+ */
13
+
14
+ /**
15
+ * The **terms of an event**: the closed value lists an event entry draws on,
16
+ * and the declaration of every key an entry and each map inside it take.
17
+ *
18
+ * A leaf module, importing nothing. The note vocabulary reads
19
+ * {@link EVENT_ENTRY} as it loads, so the frontmatter lint's inner-key check
20
+ * closes every key of an entry the way it closes every other `data:` field's;
21
+ * {@link module:engine/note-events} walks the same declaration for what the
22
+ * values mean — closed lists, Addresses, identity and order — and reaches the
23
+ * vocabulary through the Address reader.
24
+ *
25
+ * An entry is declared in the inner-key vocabulary of
26
+ * {@link module:engine/data-keys} — `fields`, `entries`, `kind`, `shape`,
27
+ * `required`, `describe` — with four properties of its own that the event
28
+ * check reads:
29
+ *
30
+ * - `oneOf` — `{name, values}`: the value is one of `values`, a closed list the
31
+ * format reference states as the vocabulary table `name`;
32
+ * - `ref`, `accepts`, `anchors`, `single`, `eventOrLore`, `calendar` on an
33
+ * Address: its default type, its accepted types, the anchor kinds it may
34
+ * name, and whether it must name exactly one event, an event or a `lore`
35
+ * note, or a calendar note;
36
+ * - `own` — the key's value is judged by the event check's own code (`id`,
37
+ * `when`, `until`, `recurs`) rather than by the walker.
38
+ *
39
+ * @module
40
+ */
41
+
42
+ /** What an event is, grouped for reading and closed for validation. */
43
+ export const EVENT_KINDS = Object.freeze([
44
+ // peoples
45
+ "arrival",
46
+ "departure",
47
+ "migration",
48
+ "contact",
49
+ "displacement",
50
+ // polities
51
+ "founding",
52
+ "charter",
53
+ "accession",
54
+ "secession",
55
+ "conquest",
56
+ "treaty",
57
+ "dissolution",
58
+ // conflict
59
+ "battle",
60
+ "war",
61
+ "siege",
62
+ "revolt",
63
+ // ruin
64
+ "fall",
65
+ "catastrophe",
66
+ "plague",
67
+ "famine",
68
+ // works
69
+ "raising",
70
+ "ruin",
71
+ "making",
72
+ "loss",
73
+ "discovery",
74
+ // institutions and belief
75
+ "schism",
76
+ "law",
77
+ "council",
78
+ // persons
79
+ "birth",
80
+ "death",
81
+ ]);
82
+
83
+ /** How far an event's history reaches. Set by hand. */
84
+ export const EVENT_DEPTHS = Object.freeze(["world", "region", "local"]);
85
+
86
+ /** What the world's evidence supports about an event. */
87
+ export const EVENT_STANDINGS = Object.freeze([
88
+ "attested",
89
+ "single-source",
90
+ "reconstructed",
91
+ "disputed",
92
+ "legendary",
93
+ ]);
94
+
95
+ /** Whether a place that felt an event knows what caused it. */
96
+ export const REACH_KNOWLEDGE = Object.freeze(["named", "misattributed", "unlinked"]);
97
+
98
+ /** What a participant was to an event. */
99
+ export const PARTICIPANT_ROLES = Object.freeze([
100
+ "actor",
101
+ "victim",
102
+ "instrument",
103
+ "witness",
104
+ "founder",
105
+ "ruler",
106
+ "author",
107
+ "signatory",
108
+ ]);
109
+
110
+ /** How a later event follows from an earlier one. */
111
+ export const FOLLOWS_HOW = Object.freeze(["caused", "enabled", "ended", "answered"]);
112
+
113
+ /** How far an account agrees with the event's summary. */
114
+ export const ACCOUNT_AGREES = Object.freeze(["full", "partly", "disputes", "denies", "silent"]);
115
+
116
+ /**
117
+ * Every closed list, by the name of the vocabulary table the format reference
118
+ * and `engine/content-format.yaml` state it under. `eventKind` is not `kind`
119
+ * because the infobox's row kinds already hold that name.
120
+ */
121
+ export const EVENT_VOCABULARIES = Object.freeze({
122
+ eventKind: EVENT_KINDS,
123
+ depth: EVENT_DEPTHS,
124
+ standing: EVENT_STANDINGS,
125
+ knowledge: REACH_KNOWLEDGE,
126
+ role: PARTICIPANT_ROLES,
127
+ followsHow: FOLLOWS_HOW,
128
+ agrees: ACCOUNT_AGREES,
129
+ });
130
+
131
+ /** A key holding text, stated with something in it. */
132
+ const text = (name, describe, required = false) =>
133
+ Object.freeze({ name, kind: "string", shape: "a string", required, describe });
134
+
135
+ /** A key holding one value of a closed list. */
136
+ const oneOf = (name, vocabulary, describe, required = false) =>
137
+ Object.freeze({
138
+ name,
139
+ kind: "string",
140
+ shape: `one \`${vocabulary}\` value`,
141
+ oneOf: Object.freeze({ name: vocabulary, values: EVENT_VOCABULARIES[vocabulary] }),
142
+ required,
143
+ describe,
144
+ });
145
+
146
+ /** A key holding one Address, with what it may name. */
147
+ const address = (name, shape, describe, extra = {}, required = false) =>
148
+ Object.freeze({ name, kind: "address", shape, required, describe, ...extra });
149
+
150
+ /** A key holding a list of maps, each closed to `fields`. */
151
+ const listOf = (name, shape, describe, fields) =>
152
+ Object.freeze({
153
+ name,
154
+ kind: "list",
155
+ shape,
156
+ entries: Object.freeze({
157
+ kind: "map",
158
+ shape: `a map — ${shape.replace(/\[\]$/, "")}`,
159
+ fields,
160
+ }),
161
+ describe,
162
+ });
163
+
164
+ /** An Address naming a place; a bare shortcode is read as one. */
165
+ const PLACE = Object.freeze({ ref: "place", accepts: Object.freeze(["place"]) });
166
+
167
+ /**
168
+ * One event entry, as an inner-key declaration: every key it takes, and every
169
+ * key of each map nested in it.
170
+ *
171
+ * @type {Readonly<{kind: "map", shape: string, fields: readonly object[]}>}
172
+ */
173
+ export const EVENT_ENTRY = Object.freeze({
174
+ kind: "map",
175
+ shape: "an event — a map of the keys below",
176
+ fields: Object.freeze([
177
+ Object.freeze({
178
+ name: "id",
179
+ kind: "shortcode",
180
+ shape: "an address segment",
181
+ own: true,
182
+ describe: "The event's name within its note — lowercase letters and digits.",
183
+ }),
184
+ oneOf("kind", "eventKind", "What sort of thing happened."),
185
+ oneOf("depth", "depth", "How far the event's history reaches."),
186
+ Object.freeze({
187
+ name: "when",
188
+ kind: "date",
189
+ shape: 'a date, or `"0.<day>"`',
190
+ required: true,
191
+ own: true,
192
+ describe: "When it happened, or the anchor and first instance of an event that recurs.",
193
+ }),
194
+ Object.freeze({
195
+ name: "until",
196
+ kind: "date",
197
+ shape: "a date",
198
+ own: true,
199
+ describe: "When a continuous event ended, or where a recurring series stopped.",
200
+ }),
201
+ Object.freeze({
202
+ name: "recurs",
203
+ kind: "map",
204
+ shape: "`{ every }` or `{ on }`",
205
+ own: true,
206
+ fields: Object.freeze([
207
+ Object.freeze({
208
+ name: "every",
209
+ kind: "integer",
210
+ shape: "a whole number of years, 1 or more",
211
+ describe: "A period counted on the canonical axis from `when`.",
212
+ }),
213
+ Object.freeze({
214
+ name: "on",
215
+ kind: "list",
216
+ shape: "a list of dates, strictly increasing, each later than `when`",
217
+ entries: Object.freeze({ kind: "date", shape: "a date" }),
218
+ describe: "Recorded occurrences beyond the first, in place of a period.",
219
+ }),
220
+ ]),
221
+ describe: "How further occurrences are found; absent for an event that happened once.",
222
+ }),
223
+ text("summary", "What happened, stated plainly.", true),
224
+ oneOf("standing", "standing", "What the world's evidence supports."),
225
+ listOf(
226
+ "names",
227
+ "`{ name, by, gloss? }[]`",
228
+ "The event's names in the world, each with who uses it.",
229
+ [
230
+ text("name", "One name the event goes by in the world.", true),
231
+ address(
232
+ "by",
233
+ "an Address naming an `affiliation`, `lore`, `place`, `being` or `skill` note",
234
+ "Who uses that name — a people, a polity, a faith, a place, a tongue.",
235
+ { accepts: Object.freeze(["affiliation", "lore", "place", "being", "skill"]) },
236
+ true,
237
+ ),
238
+ text("gloss", "What the name means, or how it is used."),
239
+ ],
240
+ ),
241
+ Object.freeze({
242
+ name: "where",
243
+ kind: "map",
244
+ shape: "`{ locus?, reach? }`",
245
+ fields: Object.freeze([
246
+ Object.freeze({
247
+ name: "locus",
248
+ kind: "list",
249
+ shape: "a list of Addresses, each defaulting to `place`",
250
+ entries: Object.freeze({ kind: "address", shape: "a place Address", ...PLACE }),
251
+ describe: "Where the event physically happened; each resolves to a place.",
252
+ }),
253
+ listOf(
254
+ "reach",
255
+ "`{ place, how, knowledge, attributedTo? }[]`",
256
+ "Where the event was felt, and whether each place knows why.",
257
+ [
258
+ address(
259
+ "place",
260
+ "an Address, defaulting to `place`",
261
+ "A place where the event was felt.",
262
+ PLACE,
263
+ true,
264
+ ),
265
+ text(
266
+ "how",
267
+ "One clause: a consequence someone in that place could notice.",
268
+ true,
269
+ ),
270
+ oneOf(
271
+ "knowledge",
272
+ "knowledge",
273
+ "Whether that place connects what it felt to this event.",
274
+ true,
275
+ ),
276
+ address(
277
+ "attributedTo",
278
+ "an Address, defaulting to `lore`, naming an event or a `lore` note",
279
+ "Only beside `knowledge: misattributed` — the cause that place names instead.",
280
+ { ref: "lore", anchors: Object.freeze(["event"]), eventOrLore: true },
281
+ ),
282
+ ],
283
+ ),
284
+ ]),
285
+ describe: "Where it happened, and where it was felt.",
286
+ }),
287
+ listOf("who", "`{ ref, role }[]`", "Who took part, and as what.", [
288
+ address(
289
+ "ref",
290
+ "an Address naming a `being`, `affiliation` or `lore` note",
291
+ "A participant — a being, a people, an affiliation.",
292
+ { accepts: Object.freeze(["being", "affiliation", "lore"]) },
293
+ true,
294
+ ),
295
+ oneOf("role", "role", "What the participant was to the event.", true),
296
+ ]),
297
+ listOf(
298
+ "follows",
299
+ "`{ event, how, note? }[]`",
300
+ "The earlier events this one follows from.",
301
+ [
302
+ address(
303
+ "event",
304
+ "an Address, defaulting to `lore`, anchored to an `event` where its note holds several",
305
+ "The earlier event; it resolves to exactly one event.",
306
+ { ref: "lore", anchors: Object.freeze(["event"]), single: true },
307
+ true,
308
+ ),
309
+ oneOf("how", "followsHow", "How this event follows from it.", true),
310
+ text("note", "One clause saying what connects the two."),
311
+ ],
312
+ ),
313
+ listOf(
314
+ "accounts",
315
+ "`{ by, says, agrees, withholds? }[]`",
316
+ "What each people, polity, faith or place says about it.",
317
+ [
318
+ address(
319
+ "by",
320
+ "an Address naming an `affiliation`, `lore`, `place` or `being` note",
321
+ "Who holds the account.",
322
+ { accepts: Object.freeze(["affiliation", "lore", "place", "being"]) },
323
+ true,
324
+ ),
325
+ text("says", "What they say happened, in their terms.", true),
326
+ oneOf("agrees", "agrees", "How far their account agrees with `summary`.", true),
327
+ text("withholds", "What they decline to say."),
328
+ ],
329
+ ),
330
+ Object.freeze({
331
+ name: "unresolved",
332
+ kind: "list",
333
+ shape: "a list of strings",
334
+ entries: Object.freeze({ kind: "string", shape: "a string" }),
335
+ describe: "What the world itself has not settled.",
336
+ }),
337
+ Object.freeze({
338
+ name: "sources",
339
+ kind: "list",
340
+ shape: "a list of Addresses",
341
+ entries: Object.freeze({ kind: "address", shape: "an Address, written with its type" }),
342
+ describe: "The notes that state or support the event; each resolves.",
343
+ }),
344
+ Object.freeze({
345
+ name: "stated",
346
+ kind: "map",
347
+ shape: "`{ calendar, text }`",
348
+ fields: Object.freeze([
349
+ address(
350
+ "calendar",
351
+ "an Address, or a lore shortcode",
352
+ "The calendar that tradition reckons in — a `lore` note with `subType: calendar`.",
353
+ { ref: "lore", accepts: Object.freeze(["lore"]), calendar: true },
354
+ true,
355
+ ),
356
+ text("text", "The date as that tradition writes it.", true),
357
+ ]),
358
+ describe: "The date as one tradition writes it in its own reckoning.",
359
+ }),
360
+ ]),
361
+ });
362
+
363
+ /** Every key an event entry admits, in the order the reference documents them. */
364
+ export const EVENT_ENTRY_KEYS = Object.freeze(EVENT_ENTRY.fields.map((field) => field.name));