@heroiclands/package-build 15.0.0 → 17.0.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.
@@ -395,7 +395,13 @@ export const NOTE_VOCABULARY = Object.freeze({
395
395
  { name: "archetypes", ...LIST, describe: "Archetypal behaviours the being fits." },
396
396
  { name: "occupation", ...TEXT, describe: "What the being does for a living." },
397
397
  { name: "stations", ...LINKS, describe: "Stations the being holds." },
398
- { name: "peoples", ...LINKS, describe: "Peoples the being belongs to." },
398
+ {
399
+ name: "lore",
400
+ ...LINKS,
401
+ describe:
402
+ "Lore concerning this being — the people it is of, the standing it " +
403
+ "holds, the law it lives under.",
404
+ },
399
405
  { name: "homes", ...LINKS, describe: "Places the being calls home." },
400
406
  {
401
407
  name: "affiliations",
@@ -425,6 +431,14 @@ export const NOTE_VOCABULARY = Object.freeze({
425
431
  ]),
426
432
  }),
427
433
 
434
+ vehicle: Object.freeze({
435
+ subTypes: null,
436
+ data: Object.freeze([
437
+ { name: "portrait", ...TEXT, describe: "Path to the portrait image." },
438
+ TEMPLATE_PRIORITY,
439
+ ]),
440
+ }),
441
+
428
442
  /* ----- items ---------------------------------------------------- */
429
443
 
430
444
  affiliation: Object.freeze({
@@ -448,6 +462,18 @@ export const NOTE_VOCABULARY = Object.freeze({
448
462
  ...TEXT,
449
463
  describe: "What one member is called — a Vylarian.",
450
464
  },
465
+ {
466
+ name: "epithet",
467
+ ...TEXT,
468
+ describe: "The by-name it is known by — a god's, an order's, a company's.",
469
+ },
470
+ {
471
+ name: "symbol",
472
+ ...TEXT,
473
+ describe:
474
+ "Its emblem in words: a feather atop a golden scale, a chisel carving " +
475
+ "a star.",
476
+ },
451
477
  {
452
478
  name: "governance.model",
453
479
  ...TEXT,
@@ -481,7 +507,13 @@ export const NOTE_VOCABULARY = Object.freeze({
481
507
  ...LINKS,
482
508
  describe: "What its economic life runs on — currencies, banking bodies, goods.",
483
509
  },
484
- { name: "peoples", ...LINKS, describe: "Peoples associated with it." },
510
+ {
511
+ name: "lore",
512
+ ...LINKS,
513
+ describe:
514
+ "Lore concerning it — the peoples it draws on, the god a faith " +
515
+ "venerates, its law, its calendar.",
516
+ },
485
517
  { name: "parents", ...LINKS, describe: "Affiliations it is subordinate to." },
486
518
  {
487
519
  name: "relations",
@@ -532,6 +564,11 @@ export const NOTE_VOCABULARY = Object.freeze({
532
564
  data: Object.freeze([TEMPLATE_PRIORITY, ...GEAR]),
533
565
  }),
534
566
 
567
+ armorlocation: Object.freeze({
568
+ subTypes: null,
569
+ data: Object.freeze([TEMPLATE_PRIORITY]),
570
+ }),
571
+
535
572
  attribute: Object.freeze({
536
573
  data: Object.freeze([TEMPLATE_PRIORITY]),
537
574
  }),
@@ -674,6 +711,86 @@ export const NOTE_VOCABULARY = Object.freeze({
674
711
 
675
712
  macro: Object.freeze({ data: Object.freeze([]) }),
676
713
 
714
+ lore: Object.freeze({
715
+ subTypes: Object.freeze([
716
+ "cosmology",
717
+ "deity",
718
+ "theology",
719
+ "arcana",
720
+ "spirit",
721
+ "economy",
722
+ "law",
723
+ "calendar",
724
+ "history",
725
+ "material",
726
+ "folk",
727
+ "culture",
728
+ "bestiary",
729
+ ]),
730
+ // Nothing of its own: a lore note is prose, and what it *is* about is
731
+ // its subType. The specification declares an empty table for it, and
732
+ // the authored corpus writes no `data:` key on any of the 180.
733
+ data: Object.freeze([]),
734
+ }),
735
+
736
+ place: Object.freeze({
737
+ subTypes: Object.freeze(["world", "region", "settlement", "site", "structure", "feature"]),
738
+ data: Object.freeze([
739
+ {
740
+ name: "demonym",
741
+ ...TEXT,
742
+ describe: "What a person from this place is called — a Vylarian.",
743
+ },
744
+ {
745
+ name: "lore",
746
+ ...LINKS,
747
+ describe:
748
+ "Lore concerning this place — its peoples, its law, its calendar, " +
749
+ "its history.",
750
+ },
751
+ {
752
+ name: "parents",
753
+ ...LINKS,
754
+ describe: "Enclosing places this one sits within.",
755
+ },
756
+ {
757
+ name: "population",
758
+ ...NUM,
759
+ describe: "Approximate population, to two significant digits.",
760
+ },
761
+ ]),
762
+ }),
763
+
764
+ scenario: Object.freeze({
765
+ subTypes: Object.freeze(["campaign", "adventure", "encounter"]),
766
+ data: Object.freeze([
767
+ { name: "parents", ...LINKS, describe: "Scenarios this one sits within." },
768
+ { name: "locations", ...LINKS, describe: "Places the scenario takes place in." },
769
+ { name: "cast", ...LINKS, describe: "Beings who appear in it." },
770
+ { name: "factions", ...LINKS, describe: "Affiliations with a stake in it." },
771
+ {
772
+ name: "follows",
773
+ ...LINKS,
774
+ describe: "Scenarios that should be played before this one.",
775
+ },
776
+ {
777
+ name: "status",
778
+ ...TEXT,
779
+ describe: "`draft`, `playtested` or `published`.",
780
+ },
781
+ {
782
+ name: "party.size",
783
+ ...TEXT,
784
+ describe: "`solo`, `small`, `standard`, `large` or `host`.",
785
+ },
786
+ {
787
+ name: "party.archetypes",
788
+ ...LIST,
789
+ describe: "Archetypes the scenario is written for.",
790
+ },
791
+ ]),
792
+ }),
793
+
677
794
  homepage: Object.freeze({ data: Object.freeze([]) }),
678
795
 
679
796
  map: Object.freeze({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "15.0.0",
3
+ "version": "17.0.0",
4
4
  "description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",
@@ -87,6 +87,22 @@ const MACRO_FIELDS = Object.freeze([
87
87
  *
88
88
  * @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
89
89
  */
90
+ /**
91
+ * A `vehicle` note — a conveyance that carries goods and people.
92
+ *
93
+ * Empty, and the emptiness is the declaration: the specification gives a
94
+ * vehicle two `data` properties and no `sohl:` field of its own, and the
95
+ * closed `data:` container is where `engine/note-vocabulary.mjs` holds them.
96
+ * Declaring the type with no fields distinguishes a type with no vocabulary
97
+ * from a type that is unknown, which are different findings (#231).
98
+ *
99
+ * It is here rather than in the engine because a vehicle is a SoHL actor —
100
+ * the specification maps it to `sohl` and marks it NA for hm3.
101
+ *
102
+ * @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
103
+ */
104
+ const VEHICLE_FIELDS = Object.freeze([]);
105
+
90
106
  const BEING_FIELDS = Object.freeze([
91
107
  {
92
108
  name: "body",
@@ -330,5 +346,6 @@ export const NOTE_SCHEMAS = Object.freeze({
330
346
  doc: DOC_FIELDS,
331
347
  macro: MACRO_FIELDS,
332
348
  being: Object.freeze([...BEING_FIELDS, ...PRESENTATION_FIELDS.being]),
349
+ vehicle: VEHICLE_FIELDS,
333
350
  map: MAP_FIELDS,
334
351
  });
@@ -41,6 +41,7 @@ export namespace DEFAULT_PATHS {
41
41
  let content: "assets/content";
42
42
  let manifests: "assets/manifests";
43
43
  let manifestOut: "build/manifests";
44
+ let contentIndex: "build/content-index";
44
45
  let packJson: "build/packs-json";
45
46
  let stage: "build/stage/packs";
46
47
  let unpack: "build/tmp/packs";
@@ -296,6 +297,13 @@ export type PathsInput = {
296
297
  * `manifests` directory.
297
298
  */
298
299
  manifestOut?: string | undefined;
300
+ /**
301
+ * Where `content-index` writes this
302
+ * package's note index. Outbound, and a
303
+ * derived artifact — never a source, and
304
+ * never inside `stage`.
305
+ */
306
+ contentIndex?: string | undefined;
299
307
  /**
300
308
  * Build-only per-entry JSON intermediate.
301
309
  */
@@ -316,6 +324,7 @@ export type ResolvedPaths = {
316
324
  content: string;
317
325
  manifests: string;
318
326
  manifestOut: string;
327
+ contentIndex: string;
319
328
  packJson: string;
320
329
  stage: string;
321
330
  unpack: string;
@@ -21,44 +21,32 @@
21
21
  */
22
22
  export function addressSlug(fm: object): string;
23
23
  /**
24
- * A note's address below the knowledgebase mount, e.g. `affliction-aconite/`.
24
+ * A note's address: `<type>-<shortcode>/`, e.g. `affliction-aconite/`.
25
25
  *
26
- * Every note, without exception. A `README.md` used to be its section's landing
27
- * page and to address the section instead of itself; a section is a Hugo
28
- * directory concept the note format no longer carries (#204), so a file's name
29
- * decides nothing about where it publishes.
30
- *
31
- * @param {object} fm - Parsed frontmatter.
32
- * @returns {string} The mount-relative address, with a trailing slash.
33
- * @throws {Error} When the note has no address.
34
- */
35
- export function contentAddress(fm: object): string;
36
- /**
37
- * A note's address relative to its **package**, e.g. `affliction-aconite/`.
38
- *
39
- * This is the form the link manifest records and the site build emits pages at,
40
- * and it is one function because those two must agree — a manifest asserting an
26
+ * This is the one form a note is addressed by. It is what the link manifest
27
+ * records as an entry's `path` and what the site build emits the page at, and
28
+ * it is one function because those two must agree a manifest asserting an
41
29
  * address the site does not publish resolves at build time and 404s for the
42
30
  * reader, which is the failure this module exists to prevent.
43
31
  *
44
- * **It is a pure function of the frontmatter.** Nothing about the file the note
45
- * was read from reaches it: the `README.md` convention that made one note
46
- * address a whole section is retired with the section itself (#204), so there
47
- * is one rule and no branch.
48
- *
49
- * **The prefix does not apply to a page's own address.** `prefix` says where the
50
- * content tree *mounts inside the package* the Hugo directory its pages are
51
- * written under and an address is `(type, shortcode)`, a package-wide identity
52
- * that takes no mount: `sohl` publishes `/sohl/affliction-aconite/` from a file
53
- * written under `kb/`. The `type-` half is what keeps that flat namespace clear
54
- * of the package's fixed mounts — `/<package>/` for the landing,
55
- * `/<package>/api/` for generated API docs, neither of which contains a hyphen
56
- * or names a type.
57
- *
58
- * **It takes no address scheme.** It took one until #215, to validate the
59
- * `landing` rule it then discarded; with that key retired, `prefix` was the
60
- * only thing left in the scheme and the paragraph above is the reason it never
61
- * applied. A parameter read by nothing is the defect this deletion is about.
32
+ * **The address is relative to the package**, and to nothing finer. A consumer
33
+ * composing a URL prepends where the package is served (`/<package>/`); a
34
+ * consumer composing a manifest entry measures against that same base. Nothing
35
+ * else is prepended: `prefix` says where the content tree *mounts inside the
36
+ * package* — the Hugo directory its pages are written under — and an address is
37
+ * `(type, shortcode)`, a package-wide identity that takes no mount, so `sohl`
38
+ * publishes `/sohl/affliction-aconite/` from a file written under `kb/`. The
39
+ * `type-` half is what keeps that flat namespace clear of the package's fixed
40
+ * mounts `/<package>/` for the landing, `/<package>/api/` for generated API
41
+ * docs, neither of which contains a hyphen or names a type.
42
+ *
43
+ * **It is a pure function of the frontmatter**, and takes no options. Nothing
44
+ * about the file the note was read from reaches it: the `README.md` convention
45
+ * that made one note address a whole section is retired with the section itself
46
+ * (#204), so every note is addressed alike and there is one rule and no branch.
47
+ * It took an address scheme until #215, to validate a `landing` rule it then
48
+ * discarded; with that key retired, `prefix` was the only thing left in the
49
+ * scheme and the paragraph above is the reason it never applied.
62
50
  *
63
51
  * @param {object} fm - Parsed frontmatter.
64
52
  * @returns {string} The package-relative address, with a trailing slash and no
@@ -0,0 +1,194 @@
1
+ /**
2
+ * The `{#slug}` anchors a note's body declares, with where each one sits.
3
+ *
4
+ * Only headings carrying an explicit anchor are collected. A bare `#` heading
5
+ * also starts a journal page, but it declares no slug, so nothing can address
6
+ * it with `#…` — listing it would offer a link that cannot be written.
7
+ *
8
+ * @param {string} body - The note's markdown body, frontmatter already removed.
9
+ * @param {number} [bodyLine] - The 1-based file line the body starts on, from
10
+ * `parseMarkdownFile`. Anchors are reported at their position in the **file**,
11
+ * so an editor can jump straight to one; passing nothing numbers from the body.
12
+ * @returns {Array<{slug: string, name: string, level: number, line: number}>}
13
+ * In document order.
14
+ */
15
+ export function collectAnchors(body: string, bodyLine?: number): Array<{
16
+ slug: string;
17
+ name: string;
18
+ level: number;
19
+ line: number;
20
+ }>;
21
+ /**
22
+ * The address a wikilink writes to reach a note, or `null` when it has none.
23
+ *
24
+ * A wikilink target is an address: `being-aurochs` locally, or
25
+ * `sohl-being-aurochs` from another package (`readQualifier` also accepts
26
+ * `being/aurochs`, the same two fields with a different separator). Both forms
27
+ * are already derivable from `type` and `shortcode`, which every record
28
+ * carries — so this field adds no information. What it adds is the *rule*:
29
+ * the lowercasing and the hyphen join live in one place, and a consumer that
30
+ * reimplements them slightly differently gets a lookup that matches nothing and
31
+ * says nothing about why. That is a real failure, not a hypothetical one — it
32
+ * is precisely how a resolver keyed on a bare `type/shortcode` silently misses
33
+ * every canonical `pkg-type-shortcode` entry.
34
+ *
35
+ * Derived by the same functions the link manifest and the site build use, so an
36
+ * index cannot disagree with either about where a note lives.
37
+ *
38
+ * @param {Record<string, any>} frontmatter - The note's parsed frontmatter.
39
+ * @param {string} contentPackage - The package the tree compiles as.
40
+ * @returns {{slug: string, canonical: string}|null} `slug` is what goes inside
41
+ * `[[…]]` within this package; `canonical` is the package-qualified key the
42
+ * manifest files the note under. `null` for a note with no type or no
43
+ * shortcode, which has no address at all and is stated as such rather than
44
+ * left for every reader to rediscover.
45
+ */
46
+ export function noteAddress(frontmatter: Record<string, any>, contentPackage: string): {
47
+ slug: string;
48
+ canonical: string;
49
+ } | null;
50
+ /**
51
+ * Recursively sort an object's keys, so serialization is order-independent.
52
+ *
53
+ * Arrays keep their order — it is authored — but every object inside one is
54
+ * sorted too. Anything that is not a plain object is returned as it is.
55
+ *
56
+ * @param {unknown} value - The value to normalize.
57
+ * @returns {unknown} The value with every plain object's keys in sorted order.
58
+ */
59
+ export function sortKeysDeep(value: unknown): unknown;
60
+ /**
61
+ * A note's display name reduced to printable 7-bit ASCII.
62
+ *
63
+ * Content names carry the setting's orthography — `Kûrbúl Helm`, `Hârn`,
64
+ * `Kèthîra` — and nobody types them. A reader searching the index, or an editor
65
+ * completing a wikilink, needs a form that matches what a keyboard produces, so
66
+ * the record states one rather than leaving every consumer to invent it (and to
67
+ * invent a *different* one, which is how two searches over the same data come
68
+ * to disagree).
69
+ *
70
+ * **Transliterated, not stripped.** `unidecode` — the same table
71
+ * {@link slugify} already runs, so an ASCII name and a slug can never disagree
72
+ * about a character — carries a letter across rather than deleting it:
73
+ * diacritics fold (`â`→`a`, `è`→`e`), ligatures expand (`æ`→`ae`, `Œ`→`OE`,
74
+ * `ß`→`ss`), the runic letters spell out (`þ`→`th`, `Þ`→`Th`, `ð`→`d`), and
75
+ * even a vulgar fraction becomes readable (`¾`→`3/4`). Deleting them instead
76
+ * would collapse `Kûrbúl` to `Krbl`, which is worse than the original.
77
+ *
78
+ * Anything still outside printable ASCII after that becomes a space, and runs
79
+ * of whitespace collapse — a space rather than nothing, so a character that
80
+ * transliterates away cannot silently weld two words together.
81
+ *
82
+ * The value is emitted even when it equals the name, so a consumer matching on
83
+ * it never has to branch on whether the name happened to be ASCII already.
84
+ *
85
+ * @param {unknown} name - The note's `name.full`.
86
+ * @returns {string|null} The ASCII form, or `null` when there is no name, or
87
+ * nothing printable survives.
88
+ */
89
+ export function asciiName(name: unknown): string | null;
90
+ /**
91
+ * A note's `name.aliases` reduced to printable 7-bit ASCII, in order.
92
+ *
93
+ * An alias is the name a reader is at least as likely to reach for as the
94
+ * canonical one — `Killer Whale` for an orca, `Ice Bear` for a polar bear,
95
+ * `Ix'balam` for a jaguar — so anything searching or completing over the index
96
+ * has to match them too, and needs the same keyboard-typeable form
97
+ * {@link asciiName} gives the primary name.
98
+ *
99
+ * Order is the authored order, so a caller can pair an entry with the alias it
100
+ * came from. An alias that is not a non-empty string, or that leaves nothing
101
+ * printable behind, is dropped rather than left as a hole — the array is a set
102
+ * of names to match, and a null in it is not one.
103
+ *
104
+ * @param {unknown} aliases - The note's `name.aliases`; may be absent or null.
105
+ * @returns {Array<string>} Possibly empty, never null: a note with no aliases
106
+ * has an empty set of them, which is a fact rather than a missing value, and
107
+ * a consumer iterating it should not have to check first.
108
+ */
109
+ export function asciiAliases(aliases: unknown): Array<string>;
110
+ /**
111
+ * Build one index record from a note's frontmatter and its place in the tree.
112
+ *
113
+ * @param {object} options - Options.
114
+ * @param {Record<string, any>} options.frontmatter - The note's parsed frontmatter.
115
+ * @param {string} options.relPath - Its path below the content root, POSIX-separated.
116
+ * @param {string} options.contentPackage - The package the tree compiles as.
117
+ * @param {string} [options.body] - The note's markdown body, for its anchors.
118
+ * @param {number} [options.bodyLine] - The 1-based file line the body starts on.
119
+ * @returns {Record<string, any>} The record, keys sorted at every depth.
120
+ * @throws {Error} When the note carries a key this module derives, which would
121
+ * otherwise be overwritten without a word.
122
+ */
123
+ export function buildIndexRecord({ frontmatter, relPath, contentPackage, body, bodyLine }: {
124
+ frontmatter: Record<string, any>;
125
+ relPath: string;
126
+ contentPackage: string;
127
+ body?: string | undefined;
128
+ bodyLine?: number | undefined;
129
+ }): Record<string, any>;
130
+ /**
131
+ * Read a content tree into index records, in the order they will be written.
132
+ *
133
+ * @param {string} contentBase - The content tree to walk.
134
+ * @param {object} options - Options.
135
+ * @param {string} options.contentPackage - The package the tree compiles as.
136
+ * @param {Array<string>} [options.skipDirectories] - Directory names to skip.
137
+ * @returns {Array<Record<string, any>>} The records, in a total order that does
138
+ * not depend on directory-read order.
139
+ */
140
+ export function collectContentIndex(contentBase: string, { contentPackage, skipDirectories }: {
141
+ contentPackage: string;
142
+ skipDirectories?: string[] | undefined;
143
+ }): Array<Record<string, any>>;
144
+ /**
145
+ * Serialize records as JSON Lines.
146
+ *
147
+ * @param {Array<Record<string, any>>} records - From {@link collectContentIndex}.
148
+ * @returns {string} One compact JSON object per line, newline-terminated. An
149
+ * empty set serializes to the empty string rather than to a lone newline, so
150
+ * the file is exactly the lines it holds.
151
+ */
152
+ export function serializeContentIndex(records: Array<Record<string, any>>): string;
153
+ /**
154
+ * Emit this package's content index.
155
+ *
156
+ * @param {object} [options] - Options.
157
+ * @param {string} [options.contentBase] - The content tree; defaults to the
158
+ * configured `paths.content`.
159
+ * @param {string} [options.outDir] - Where to write; defaults to the configured
160
+ * `paths.contentIndex`.
161
+ * @param {object} [options.config] - A resolved configuration; loaded when omitted.
162
+ * @returns {{file: string, notes: number, bytes: number}} Where it was written,
163
+ * how many notes it holds, and its size.
164
+ * @throws {Error} When the content tree is absent, or when it yields no note at
165
+ * all — an empty index is indistinguishable from a mis-pointed tree, and a
166
+ * reader would take it as the authoritative statement that this package has
167
+ * no content.
168
+ */
169
+ export function emitContentIndex({ contentBase, outDir, config }?: {
170
+ contentBase?: string | undefined;
171
+ outDir?: string | undefined;
172
+ config?: object | undefined;
173
+ }): {
174
+ file: string;
175
+ notes: number;
176
+ bytes: number;
177
+ };
178
+ /**
179
+ * The keys this module adds to a record, which a note therefore may not carry
180
+ * itself.
181
+ *
182
+ * `package` is the note's distribution unit — the configured `contentPackage`,
183
+ * since a note declaring its own is a hard error (package-build#56) — and it
184
+ * matches what the content-table expander puts on the same field, so a query
185
+ * reads the same value from either. `file` namespaces the note's place in the
186
+ * tree, again matching the expander's `file.*`.
187
+ *
188
+ * Both are checked rather than assumed: `folder` is real frontmatter on most
189
+ * notes, so the neighbouring names are close enough to a real key that a silent
190
+ * overwrite is a plausible future rather than a hypothetical one.
191
+ *
192
+ * @type {ReadonlyArray<string>}
193
+ */
194
+ export const DERIVED_KEYS: ReadonlyArray<string>;
@@ -17,6 +17,7 @@ export * as contentAddress from "./content-address.mjs";
17
17
  export * as foreignManifests from "./foreign-manifests.mjs";
18
18
  export * as kbManifest from "./kb-manifest.mjs";
19
19
  export * as manifestEmit from "./manifest-emit.mjs";
20
+ export * as contentIndex from "./content-index.mjs";
20
21
  export * as siteBuild from "./site-build.mjs";
21
22
  export * as contentLint from "./content-lint.mjs";
22
23
  export * as contentLinks from "./content-links.mjs";
@@ -187,6 +187,10 @@ export const NOTE_VOCABULARY: Readonly<{
187
187
  subTypes: null;
188
188
  data: readonly DataFieldSpec[];
189
189
  }>;
190
+ vehicle: Readonly<{
191
+ subTypes: null;
192
+ data: readonly DataFieldSpec[];
193
+ }>;
190
194
  affiliation: Readonly<{
191
195
  subTypes: readonly string[];
192
196
  data: readonly DataFieldSpec[];
@@ -198,6 +202,10 @@ export const NOTE_VOCABULARY: Readonly<{
198
202
  armorgear: Readonly<{
199
203
  data: readonly DataFieldSpec[];
200
204
  }>;
205
+ armorlocation: Readonly<{
206
+ subTypes: null;
207
+ data: readonly DataFieldSpec[];
208
+ }>;
201
209
  attribute: Readonly<{
202
210
  data: readonly DataFieldSpec[];
203
211
  }>;
@@ -256,6 +264,48 @@ export const NOTE_VOCABULARY: Readonly<{
256
264
  macro: Readonly<{
257
265
  data: readonly never[];
258
266
  }>;
267
+ lore: Readonly<{
268
+ subTypes: readonly string[];
269
+ data: readonly never[];
270
+ }>;
271
+ place: Readonly<{
272
+ subTypes: readonly string[];
273
+ data: readonly ({
274
+ describe: string;
275
+ shape: "string";
276
+ kind: "string";
277
+ name: string;
278
+ } | {
279
+ describe: string;
280
+ shape: "list of wikilinks";
281
+ kind: "list";
282
+ name: string;
283
+ } | {
284
+ describe: string;
285
+ shape: "number";
286
+ kind: "number";
287
+ name: string;
288
+ })[];
289
+ }>;
290
+ scenario: Readonly<{
291
+ subTypes: readonly string[];
292
+ data: readonly ({
293
+ describe: string;
294
+ shape: "list of wikilinks";
295
+ kind: "list";
296
+ name: string;
297
+ } | {
298
+ describe: string;
299
+ shape: "string";
300
+ kind: "string";
301
+ name: string;
302
+ } | {
303
+ describe: string;
304
+ shape: "list";
305
+ kind: "list";
306
+ name: string;
307
+ })[];
308
+ }>;
259
309
  homepage: Readonly<{
260
310
  data: readonly never[];
261
311
  }>;