@heroiclands/package-build 23.5.1 → 23.6.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 (43) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/bin/content-commands.mjs +23 -5
  3. package/docs/api.md +9 -0
  4. package/docs/authoring/links-and-markup.md +77 -12
  5. package/docs/content-format.md +16 -0
  6. package/docs/guides/reference-pages.md +89 -0
  7. package/docs/reference/format-details.md +533 -84
  8. package/docs/reference/note-types.md +191 -149
  9. package/engine/base-compiler.mjs +21 -0
  10. package/engine/content-format.yaml +9 -0
  11. package/engine/content-index.mjs +19 -5
  12. package/engine/culture-reach.mjs +330 -0
  13. package/engine/dictionary-layout.mjs +340 -0
  14. package/engine/dictionary-rows.mjs +608 -0
  15. package/engine/glossary-entries.mjs +182 -0
  16. package/engine/glossary-language.mjs +99 -0
  17. package/engine/index.mjs +1 -0
  18. package/engine/infobox.mjs +8 -0
  19. package/engine/language-words.mjs +360 -0
  20. package/engine/literature-notes.mjs +10 -8
  21. package/engine/note-addresses.mjs +25 -0
  22. package/engine/note-vocabulary.mjs +161 -1
  23. package/engine/path-charset.mjs +120 -0
  24. package/engine/sql-tables.mjs +311 -21
  25. package/engine/word-classes.mjs +167 -0
  26. package/engine/word-rows.mjs +233 -0
  27. package/engine/word-terms.mjs +306 -0
  28. package/package.json +1 -1
  29. package/types/engine/content-index.d.mts +15 -5
  30. package/types/engine/culture-reach.d.mts +66 -0
  31. package/types/engine/dictionary-layout.d.mts +69 -0
  32. package/types/engine/dictionary-rows.d.mts +39 -0
  33. package/types/engine/glossary-entries.d.mts +12 -0
  34. package/types/engine/glossary-language.d.mts +42 -0
  35. package/types/engine/index.d.mts +1 -0
  36. package/types/engine/language-words.d.mts +33 -0
  37. package/types/engine/literature-notes.d.mts +2 -2
  38. package/types/engine/note-addresses.d.mts +17 -0
  39. package/types/engine/path-charset.d.mts +38 -0
  40. package/types/engine/sql-tables.d.mts +7 -2
  41. package/types/engine/word-classes.d.mts +87 -0
  42. package/types/engine/word-rows.d.mts +64 -0
  43. package/types/engine/word-terms.d.mts +68 -0
@@ -118,78 +118,86 @@ These fields are accepted by every note type. A field's value can still be irrel
118
118
 
119
119
  **Subtypes:** `guild`, `order`, `polity`, `faithtradition`, `arcanetradition`, `spirittradition`, `lineage`, `venture`, `criminal`, `governmental`, `fellowship`.
120
120
 
121
- | Field | Shape | Meaning |
122
- | ------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
123
- | `data.icon` | an Address | The document's profile art — an `icon` address, resolved into `img`. |
124
- | `data.templatePriority` | number | Template priority; unset means the note is not a template. |
125
- | `data.events` | list of event entries | What happened to or at this subject — each entry one dated, attributed event: a founding, a war, a fall, or an occasion that recurs. |
126
- | `data.demonym` | string | What one member is called — a Vylarian. |
127
- | `data.epithet` | string | The by-name it is known by — a god's, an order's, a company's. |
128
- | `data.symbol` | string | Its emblem in words: a feather atop a golden scale, a chisel carving a star. |
129
- | `data.governance.model` | string | How the affiliation is governed, where it is. |
130
- | `data.governance.summary` | string | A sentence on how the governance actually works. |
131
- | `data.governance.ranks` | list of `{ level, title, description, lore? }` | The ladder of standings the body confers: level 0 for those it excludes and level 1 for its members, both required, and higher rungs only where the body has them. |
132
- | `data.governance.offices` | a map of named offices | Named offices, each with a description and optional dated holders. |
133
- | `data.seat` | an Address | Where the affiliation's authority sits. |
134
- | `data.population` | number | How many people it counts. |
135
- | `data.economy` | list of Addresses | What its economic life runs on — currencies, banking bodies, goods. |
136
- | `data.lore` | list of Addresses | Lore concerning it — the peoples it draws on, the god a faith venerates, its law, its calendar. |
137
- | `data.parents` | list of Addresses | Affiliations it is subordinate to. |
138
- | `data.culture` | an Address | The people this affiliation belongs to, as a culture lore note. |
139
- | `data.relations` | a map keyed by Address | Standing with other affiliations — aligned, unaligned, rival, nemesis. |
121
+ | Field | Shape | Meaning |
122
+ | ------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
123
+ | `data.icon` | an Address | The document's profile art — an `icon` address, resolved into `img`. |
124
+ | `data.templatePriority` | number | Template priority; unset means the note is not a template. |
125
+ | `data.events` | list of event entries | What happened to or at this subject — each entry one dated, attributed event: a founding, a war, a fall, or an occasion that recurs. |
126
+ | `data.demonym` | string | What one member is called — a Vylarian. |
127
+ | `data.epithet` | string | The by-name it is known by — a god's, an order's, a company's. |
128
+ | `data.symbol` | string | Its emblem in words: a feather atop a golden scale, a chisel carving a star. |
129
+ | `data.governance.model` | string | How the affiliation is governed, where it is. |
130
+ | `data.governance.summary` | string | A sentence on how the governance actually works. |
131
+ | `data.governance.ranks` | list of `{ level, title, description, lore? }` | The ladder of standings the body confers: level 0 for those it excludes and level 1 for its members, both required, and higher rungs only where the body has them. |
132
+ | `data.governance.offices` | a map of named offices | Named offices, each with a description and optional dated holders. |
133
+ | `data.seat` | an Address | Where the affiliation's authority sits. |
134
+ | `data.population` | number | How many people it counts. |
135
+ | `data.economy` | list of Addresses | What its economic life runs on — currencies, banking bodies, goods. |
136
+ | `data.lore` | list of Addresses | Lore concerning it — the peoples it draws on, the god a faith venerates, its law, its calendar. |
137
+ | `data.parents` | list of Addresses | Affiliations it is subordinate to. |
138
+ | `data.culture` | an Address | The people this affiliation belongs to, as a culture lore note. |
139
+ | `data.language` | an Address | The trade, temple or order tongue the affiliation keeps, as a language skill note. It reaches the affiliation's culture, and its words enter that culture's glossary. A glossary entry that states no `language` is in it. |
140
+ | `data.glossary` | list of `{ name, name_sort?, common?, definition?, pronunciation?, language? }` | Its own glossary terms: euphemisms, idioms, terms of art and titles that are not ranks. An entry with a `definition` creates a term or replaces its senses; one without marks a term the note already has. Every entry resolves to a language. Empty when unstated. See [glossary terms](format-details.md#glossary-terms). |
141
+ | `data.relations` | a map keyed by Address | Standing with other affiliations — aligned, unaligned, rival, nemesis. |
140
142
 
141
143
  **Inner keys** — a key a field's value does not declare is an error at its own line and column.
142
144
 
143
- | Key | Shape | Required | Meaning |
144
- | ---------------------------------------------------- | ------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
145
- | `data.events[].id` | an address segment | no | The event's name within its note — lowercase letters and digits. |
146
- | `data.events[].kind` | one `eventKind` value | no | What sort of thing happened. |
147
- | `data.events[].depth` | one `depth` value | no | How far the event's history reaches. |
148
- | `data.events[].when` | a date, or `"0.<day>"` | yes | When it happened, or the anchor and first instance of an event that recurs. |
149
- | `data.events[].until` | a date | no | When a continuous event ended, or where a recurring series stopped. |
150
- | `data.events[].recurs` | `{ every }` or `{ on }` | no | How further occurrences are found; absent for an event that happened once. |
151
- | `data.events[].recurs.every` | a whole number of years, 1 or more | no | A period counted on the canonical axis from `when`. |
152
- | `data.events[].recurs.on` | a list of dates, strictly increasing, each later than `when` | no | Recorded occurrences beyond the first, in place of a period. |
153
- | `data.events[].summary` | a string | yes | What happened, stated plainly. |
154
- | `data.events[].standing` | one `standing` value | no | What the world's evidence supports. |
155
- | `data.events[].names` | `{ name, by, gloss? }[]` | no | The event's names in the world, each with who uses it. |
156
- | `data.events[].names[].name` | a string | yes | One name the event goes by in the world. |
157
- | `data.events[].names[].by` | an Address naming an `affiliation`, `lore`, `place`, `being` or `skill` note | yes | Who uses that name — a people, a polity, a faith, a place, a tongue. |
158
- | `data.events[].names[].gloss` | a string | no | What the name means, or how it is used. |
159
- | `data.events[].where` | `{ locus?, reach? }` | no | Where it happened, and where it was felt. |
160
- | `data.events[].where.locus` | a list of Addresses, each defaulting to `place` | no | Where the event physically happened; each resolves to a place. |
161
- | `data.events[].where.reach` | `{ place, how, knowledge, attributedTo? }[]` | no | Where the event was felt, and whether each place knows why. |
162
- | `data.events[].where.reach[].place` | an Address, defaulting to `place` | yes | A place where the event was felt. |
163
- | `data.events[].where.reach[].how` | a string | yes | One clause: a consequence someone in that place could notice. |
164
- | `data.events[].where.reach[].knowledge` | one `knowledge` value | yes | Whether that place connects what it felt to this event. |
165
- | `data.events[].where.reach[].attributedTo` | an Address, defaulting to `lore`, naming an event or a `lore` note | no | Only beside `knowledge: misattributed` — the cause that place names instead. |
166
- | `data.events[].who` | `{ ref, role }[]` | no | Who took part, and as what. |
167
- | `data.events[].who[].ref` | an Address naming a `being`, `affiliation` or `lore` note | yes | A participant — a being, a people, an affiliation. |
168
- | `data.events[].who[].role` | one `role` value | yes | What the participant was to the event. |
169
- | `data.events[].follows` | `{ event, how, note? }[]` | no | The earlier events this one follows from. |
170
- | `data.events[].follows[].event` | an Address, defaulting to `lore`, anchored to an `event` where its note holds several | yes | The earlier event; it resolves to exactly one event. |
171
- | `data.events[].follows[].how` | one `followsHow` value | yes | How this event follows from it. |
172
- | `data.events[].follows[].note` | a string | no | One clause saying what connects the two. |
173
- | `data.events[].accounts` | `{ by, says, agrees, withholds? }[]` | no | What each people, polity, faith or place says about it. |
174
- | `data.events[].accounts[].by` | an Address naming an `affiliation`, `lore`, `place` or `being` note | yes | Who holds the account. |
175
- | `data.events[].accounts[].says` | a string | yes | What they say happened, in their terms. |
176
- | `data.events[].accounts[].agrees` | one `agrees` value | yes | How far their account agrees with `summary`. |
177
- | `data.events[].accounts[].withholds` | a string | no | What they decline to say. |
178
- | `data.events[].unresolved` | a list of strings | no | What the world itself has not settled. |
179
- | `data.events[].sources` | a list of Addresses | no | The notes that state or support the event; each resolves. |
180
- | `data.events[].stated` | `{ calendar, text }` | no | The date as one tradition writes it in its own reckoning. |
181
- | `data.events[].stated.calendar` | an Address, or a lore shortcode | yes | The calendar that tradition reckons in — a `lore` note with `subType: calendar`. |
182
- | `data.events[].stated.text` | a string | yes | The date as that tradition writes it. |
183
- | `data.governance.ranks[].level` | a whole number — the rung's position on this body's own ladder | yes | The rung's position on the ladder — `0` the excluded, `1` the ordinary member; a member's `rank` names it. |
184
- | `data.governance.ranks[].title` | what the standing is called | yes | What the standing is called. |
185
- | `data.governance.ranks[].description` | what the standing is | yes | What the standing is. |
186
- | `data.governance.ranks[].lore` | a lore Address | no | Lore saying more about the standing than a description holds. |
187
- | `data.governance.offices.<name>.description` | a string | yes | What the office is. |
188
- | `data.governance.offices.<name>.holders` | a list of `{ being, start?, end?, contested? }` | yes | Who has held it, each with the dates of the term. |
189
- | `data.governance.offices.<name>.holders[].being` | a being Address | yes | Who held the office. |
190
- | `data.governance.offices.<name>.holders[].start` | a date | no | When the term began; unstated, it began before anything recorded. |
191
- | `data.governance.offices.<name>.holders[].end` | a date | no | When the term ended; unstated, the being holds the office now. |
192
- | `data.governance.offices.<name>.holders[].contested` | `true` or `false` | no | The term may overlap another holder's. |
145
+ | Key | Shape | Required | Meaning |
146
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
147
+ | `data.events[].id` | an address segment | no | The event's name within its note — lowercase letters and digits. |
148
+ | `data.events[].kind` | one `eventKind` value | no | What sort of thing happened. |
149
+ | `data.events[].depth` | one `depth` value | no | How far the event's history reaches. |
150
+ | `data.events[].when` | a date, or `"0.<day>"` | yes | When it happened, or the anchor and first instance of an event that recurs. |
151
+ | `data.events[].until` | a date | no | When a continuous event ended, or where a recurring series stopped. |
152
+ | `data.events[].recurs` | `{ every }` or `{ on }` | no | How further occurrences are found; absent for an event that happened once. |
153
+ | `data.events[].recurs.every` | a whole number of years, 1 or more | no | A period counted on the canonical axis from `when`. |
154
+ | `data.events[].recurs.on` | a list of dates, strictly increasing, each later than `when` | no | Recorded occurrences beyond the first, in place of a period. |
155
+ | `data.events[].summary` | a string | yes | What happened, stated plainly. |
156
+ | `data.events[].standing` | one `standing` value | no | What the world's evidence supports. |
157
+ | `data.events[].names` | `{ name, by, gloss? }[]` | no | The event's names in the world, each with who uses it. |
158
+ | `data.events[].names[].name` | a string | yes | One name the event goes by in the world. |
159
+ | `data.events[].names[].by` | an Address naming an `affiliation`, `lore`, `place`, `being` or `skill` note | yes | Who uses that name — a people, a polity, a faith, a place, a tongue. |
160
+ | `data.events[].names[].gloss` | a string | no | What the name means, or how it is used. |
161
+ | `data.events[].where` | `{ locus?, reach? }` | no | Where it happened, and where it was felt. |
162
+ | `data.events[].where.locus` | a list of Addresses, each defaulting to `place` | no | Where the event physically happened; each resolves to a place. |
163
+ | `data.events[].where.reach` | `{ place, how, knowledge, attributedTo? }[]` | no | Where the event was felt, and whether each place knows why. |
164
+ | `data.events[].where.reach[].place` | an Address, defaulting to `place` | yes | A place where the event was felt. |
165
+ | `data.events[].where.reach[].how` | a string | yes | One clause: a consequence someone in that place could notice. |
166
+ | `data.events[].where.reach[].knowledge` | one `knowledge` value | yes | Whether that place connects what it felt to this event. |
167
+ | `data.events[].where.reach[].attributedTo` | an Address, defaulting to `lore`, naming an event or a `lore` note | no | Only beside `knowledge: misattributed` — the cause that place names instead. |
168
+ | `data.events[].who` | `{ ref, role }[]` | no | Who took part, and as what. |
169
+ | `data.events[].who[].ref` | an Address naming a `being`, `affiliation` or `lore` note | yes | A participant — a being, a people, an affiliation. |
170
+ | `data.events[].who[].role` | one `role` value | yes | What the participant was to the event. |
171
+ | `data.events[].follows` | `{ event, how, note? }[]` | no | The earlier events this one follows from. |
172
+ | `data.events[].follows[].event` | an Address, defaulting to `lore`, anchored to an `event` where its note holds several | yes | The earlier event; it resolves to exactly one event. |
173
+ | `data.events[].follows[].how` | one `followsHow` value | yes | How this event follows from it. |
174
+ | `data.events[].follows[].note` | a string | no | One clause saying what connects the two. |
175
+ | `data.events[].accounts` | `{ by, says, agrees, withholds? }[]` | no | What each people, polity, faith or place says about it. |
176
+ | `data.events[].accounts[].by` | an Address naming an `affiliation`, `lore`, `place` or `being` note | yes | Who holds the account. |
177
+ | `data.events[].accounts[].says` | a string | yes | What they say happened, in their terms. |
178
+ | `data.events[].accounts[].agrees` | one `agrees` value | yes | How far their account agrees with `summary`. |
179
+ | `data.events[].accounts[].withholds` | a string | no | What they decline to say. |
180
+ | `data.events[].unresolved` | a list of strings | no | What the world itself has not settled. |
181
+ | `data.events[].sources` | a list of Addresses | no | The notes that state or support the event; each resolves. |
182
+ | `data.events[].stated` | `{ calendar, text }` | no | The date as one tradition writes it in its own reckoning. |
183
+ | `data.events[].stated.calendar` | an Address, or a lore shortcode | yes | The calendar that tradition reckons in — a `lore` note with `subType: calendar`. |
184
+ | `data.events[].stated.text` | a string | yes | The date as that tradition writes it. |
185
+ | `data.governance.ranks[].level` | a whole number — the rung's position on this body's own ladder | yes | The rung's position on the ladder — `0` the excluded, `1` the ordinary member; a member's `rank` names it. |
186
+ | `data.governance.ranks[].title` | what the standing is called | yes | What the standing is called. |
187
+ | `data.governance.ranks[].description` | what the standing is | yes | What the standing is. |
188
+ | `data.governance.ranks[].lore` | a lore Address | no | Lore saying more about the standing than a description holds. |
189
+ | `data.governance.offices.<name>.description` | a string | yes | What the office is. |
190
+ | `data.governance.offices.<name>.holders` | a list of `{ being, start?, end?, contested? }` | yes | Who has held it, each with the dates of the term. |
191
+ | `data.governance.offices.<name>.holders[].being` | a being Address | yes | Who held the office. |
192
+ | `data.governance.offices.<name>.holders[].start` | a date | no | When the term began; unstated, it began before anything recorded. |
193
+ | `data.governance.offices.<name>.holders[].end` | a date | no | When the term ended; unstated, the being holds the office now. |
194
+ | `data.governance.offices.<name>.holders[].contested` | `true` or `false` | no | The term may overlap another holder's. |
195
+ | `data.glossary[].name` | a string | yes | The headword. |
196
+ | `data.glossary[].name_sort` | a string | no | Its sort key; the name when unstated. |
197
+ | `data.glossary[].common` | `true` or `false` | no | A word a player is expected to know by heart; `false` when unstated. |
198
+ | `data.glossary[].definition` | a string | no | The sense text. Stated, it creates the entry or replaces every sense the entry's other sources give it. |
199
+ | `data.glossary[].pronunciation` | a respelling such as `sol-MEH-reh` | no | A respelling, held to the same rule as a word's. |
200
+ | `data.glossary[].language` | a language skill Address | no | The language the term is in. Unstated, it is the affiliation's language, its parents', or its culture's, or the culture's own. |
193
201
 
194
202
  **System mappings**
195
203
 
@@ -443,20 +451,47 @@ These fields are accepted by every note type. A field's value can still be irrel
443
451
 
444
452
  **Subtypes:** `social`, `nature`, `craft`, `lore`, `language`, `script`, `mystical`, `physical`, `combat`, `combattechnique`.
445
453
 
446
- | Field | Shape | Meaning |
447
- | ----------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
448
- | `data.icon` | an Address | The document's profile art — an `icon` address, resolved into `img`. |
449
- | `data.templatePriority` | number | Template priority; unset means the note is not a template. |
450
- | `data.masteryLevel` | number | Mastery before any modifier. |
451
- | `data.parentSkill` | an Address | The skill this one specialises. |
452
- | `data.names` | a language's name lists | How this language's speakers are named, for `package-build npc new`: `pattern` builds a name from placeholders such as "{given} {clan}" and literal text, `given` maps a gender to a list of given names, `female` and `male` required, and every other key is a list of names a placeholder of the same name draws from. A language skill note only. See [name lists](format-details.md#name-lists). |
454
+ | Field | Shape | Meaning |
455
+ | ----------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
456
+ | `data.icon` | an Address | The document's profile art — an `icon` address, resolved into `img`. |
457
+ | `data.templatePriority` | number | Template priority; unset means the note is not a template. |
458
+ | `data.masteryLevel` | number | Mastery before any modifier. |
459
+ | `data.parentSkill` | an Address | The skill this one specialises. |
460
+ | `data.names` | a language's name lists | How this language's speakers are named, for `package-build npc new`: `pattern` builds a name from placeholders such as "{given} {clan}" and literal text, `given` maps a gender to a list of given names, `female` and `male` required, and every other key is a list of names a placeholder of the same name draws from. A language skill note only. See [name lists](format-details.md#name-lists). |
461
+ | `data.family` | an Address | The parent language, as a language skill note: this language reads the parent's words, and its parents' in turn, as its own. A language skill note only. See [a language's words](format-details.md#a-languages-words). |
462
+ | `data.abbreviation` | a string, such as `Prov.` | The tag a dictionary prints for the language, beside an entry's class and in an etymology, as `Prov.`; the language's full name where unstated. A language skill note only. See [a language's words](format-details.md#a-languages-words). |
463
+ | `data.classModifiers` | list of word class modifiers | The class modifiers the language uses, from the master word classes. A word's class may carry only these. Empty when unstated. A language skill note only. |
464
+ | `data.fields` | list of `{ name, holds }` | The fields the language's words are grouped under. A language skill note only. |
465
+ | `data.words` | list of `{ form, class, field, gloss, … }` | The language's words, one map each, in the order they print. A language skill note only. See [a language's words](format-details.md#a-languages-words). |
466
+ | `data.affixes` | list of `{ form, gloss, attaches?, examples? }` | The affixes a word's `built` pieces may name, each with its gloss. A language skill note only. |
453
467
 
454
468
  **Inner keys** — a key a field's value does not declare is an error at its own line and column.
455
469
 
456
- | Key | Shape | Required | Meaning |
457
- | -------------------- | --------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
458
- | `data.names.pattern` | a pattern such as "{given} {clan}", a list of them, or a map of genders to either | yes | How a name is built: each `{list}` placeholder is filled from the list of that name, `{given.<gender>}` from that gender's given names, and the text around them is written as it stands. A list states every form a name may take; a map keyed by gender states each gender's forms, and a gender with none takes every pattern stated. |
459
- | `data.names.given` | a map of genders to lists of names | yes | Given names by gender word. A gender with no list draws from every given list together. |
470
+ | Key | Shape | Required | Meaning |
471
+ | ------------------------------ | --------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
472
+ | `data.names.pattern` | a pattern such as "{given} {clan}", a list of them, or a map of genders to either | yes | How a name is built: each `{list}` placeholder is filled from the list of that name, `{given.<gender>}` from that gender's given names, and the text around them is written as it stands. A list states every form a name may take; a map keyed by gender states each gender's forms, and a gender with none takes every pattern stated. |
473
+ | `data.names.given` | a map of genders to lists of names | yes | Given names by gender word. A gender with no list draws from every given list together. |
474
+ | `data.fields[].name` | a string | yes | The field's name, which a word's `field` names. |
475
+ | `data.fields[].holds` | a string | yes | What the field holds. |
476
+ | `data.words[].form` | the word as cited, bare | yes | The word as cited, with no backticks. |
477
+ | `data.words[].name_sort` | a string | no | The sort key; the form when unstated. |
478
+ | `data.words[].class` | a word class, base first, such as `n I f` | yes | A class from the master word classes: a base, then the modifiers the language declares in `classModifiers`. |
479
+ | `data.words[].field` | one of the language's `fields` names | yes | The field the word is grouped under, named in the language's `fields`. |
480
+ | `data.words[].gloss` | a string | yes | The meaning, in brief. |
481
+ | `data.words[].source` | a map — `{ kind, language?, form? }` | no | Where the word comes from. |
482
+ | `data.words[].source.kind` | one of inherited, borrowed, kept, worn, coined, substrate, island | yes | How the word came into the tongue. |
483
+ | `data.words[].source.language` | a language skill Address | no | The tongue the word was borrowed from; required for `borrowed`. |
484
+ | `data.words[].source.form` | a string | no | The form the word had in its source, bare. |
485
+ | `data.words[].built` | a list of pieces | no | The pieces the word is built from, in order, as the language's words and affixes write them; absent for a root word. |
486
+ | `data.words[].attested` | a list of Addresses | no | The notes the word is cited from. |
487
+ | `data.words[].pronunciation` | a respelling such as `sol-MEH-reh` | no | A respelling in hyphen-separated syllables, the stressed one in capitals: letters, apostrophes and hyphens only, and at most one capitalised syllable. |
488
+ | `data.words[].tones` | a string | no | A tone pattern, such as `LHL`. |
489
+ | `data.words[].root` | a string | no | A consonantal skeleton, such as `d-l-k`. |
490
+ | `data.words[].frame` | a string | no | The frame a root is set in. |
491
+ | `data.affixes[].form` | the affix as written, such as `-rith` | yes | The affix as a word's `built` names it. |
492
+ | `data.affixes[].gloss` | a string | yes | What the affix means. |
493
+ | `data.affixes[].attaches` | a string | no | How it attaches, such as `replaces a closing vowel`. |
494
+ | `data.affixes[].examples` | a list of strings | no | The names the affix is read from. |
460
495
 
461
496
  **System mappings**
462
497
 
@@ -537,80 +572,87 @@ Lore records in-world knowledge. A `culture` describes a people; a `custom` desc
537
572
  | `data.culture` | an Address | The people this lore belongs to — for a work of literature, the people whose work it is — as a culture lore note. A culture note is its own culture, and states the field only as itself. |
538
573
  | `data.form` | string | The kind of work in its people's own terms — an epic, a saga, a praise-song, an elegy. Free text. |
539
574
  | `data.subjects` | list of Addresses | The beings, places, gods and other notes the work concerns, and each event it concerns, named as `place-ironfells#sack`. |
540
- | `data.language` | an Address | The tongue the work is composed in, or on a culture note the tongue its people speak, as a language skill note. `package-build npc new` draws a culture's names from its language's `data.names`. A `literature` or `culture` note only. |
575
+ | `data.language` | an Address | The tongue the note is in, as a language skill note: the tongue a work is composed in, a culture's people speak, or an affiliation keeps as its trade, temple or order tongue. `package-build npc new` draws a culture's names from its language's `data.names`. A `literature` or `culture` lore note, or an affiliation, only. |
541
576
  | `data.events` | list of event entries | What happened to or at this subject — each entry one dated, attributed event: a founding, a war, a fall, or an occasion that recurs. |
542
577
  | `data.appearanceProfile` | an appearance profile | What this culture's people look like, for `package-build npc new`: `frame`, `eye_color`, `hair_color`, `skin_color` and `complexion` each map words of that field's vocabulary to positive weights, and `height` maps a gender to a `{min, max}` range, `female` and `male` required. A culture note only. See [appearance profiles](format-details.md#appearance-profiles). |
578
+ | `data.glossary` | list of `{ name, name_sort?, common?, definition?, pronunciation?, language? }` | `culture` only: Its own glossary terms: euphemisms, idioms, terms of art and titles that are not ranks. An entry with a `definition` creates a term or replaces its senses; one without marks a term the note already has. Every entry resolves to a language. Empty when unstated. See [glossary terms](format-details.md#glossary-terms). |
543
579
 
544
580
  **Inner keys** — a key a field's value does not declare is an error at its own line and column.
545
581
 
546
- | Key | Shape | Required | Meaning |
547
- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------ |
548
- | `data.months[].name` | a string | yes | What it is called. |
549
- | `data.months[].abbreviation` | a string | no | Its short form, for a compact date. |
550
- | `data.months[].days` | a whole number of days | yes | How many days the month holds. |
551
- | `data.weekdays[].name` | a string | yes | What it is called. |
552
- | `data.weekdays[].abbreviation` | a string | no | Its short form, for a compact date. |
553
- | `data.seasons[].name` | a string | yes | What it is called. |
554
- | `data.seasons[].abbreviation` | a string | no | Its short form, for a compact date. |
555
- | `data.seasons[].start` | a one-based day of the year, as a whole number | yes | The day of the year the season begins. |
556
- | `data.namedDays[].name` | a string | yes | What it is called. |
557
- | `data.namedDays[].abbreviation` | a string | no | Its short form, for a compact date. |
558
- | `data.namedDays[].day` | a one-based day of the year, as a whole number | yes | The day of the year it names. |
559
- | `data.eras[].shortcode` | the era's own shortcode, unique within the calendar — an era is addressed `<calendar shortcode>.<era shortcode>` | yes | The era's own segment of `<calendar>.<era>`. |
560
- | `data.eras[].name` | a string | yes | What it is called. |
561
- | `data.eras[].marker` | uppercase letters and digits, beginning with a letter | no | The marker an authored date names the era by, unique across the corpus. |
562
- | `data.eras[].abbreviation` | a string | no | Its short form, for a compact date. |
563
- | `data.eras[].proclaimedBy` | an Address | no | The body that began the reckoning. |
564
- | `data.eras[].start` | `null`, or the calendar year the era begins, as a whole number | yes | The calendar year the era begins; `null` for the era before year 1. |
565
- | `data.eras[].label` | a string, or an `{ after, before }` map | no | How a date in the era reads, around one `{date}` slot. |
566
- | `data.eras[].label.after` | a string with one `{date}` slot | no | How a date in the era reads. |
567
- | `data.eras[].label.before` | a string with one `{date}` slot | no | How a date before the era's start reads. |
568
- | `data.events[].id` | an address segment | no | The event's name within its note — lowercase letters and digits. |
569
- | `data.events[].kind` | one `eventKind` value | no | What sort of thing happened. |
570
- | `data.events[].depth` | one `depth` value | no | How far the event's history reaches. |
571
- | `data.events[].when` | a date, or `"0.<day>"` | yes | When it happened, or the anchor and first instance of an event that recurs. |
572
- | `data.events[].until` | a date | no | When a continuous event ended, or where a recurring series stopped. |
573
- | `data.events[].recurs` | `{ every }` or `{ on }` | no | How further occurrences are found; absent for an event that happened once. |
574
- | `data.events[].recurs.every` | a whole number of years, 1 or more | no | A period counted on the canonical axis from `when`. |
575
- | `data.events[].recurs.on` | a list of dates, strictly increasing, each later than `when` | no | Recorded occurrences beyond the first, in place of a period. |
576
- | `data.events[].summary` | a string | yes | What happened, stated plainly. |
577
- | `data.events[].standing` | one `standing` value | no | What the world's evidence supports. |
578
- | `data.events[].names` | `{ name, by, gloss? }[]` | no | The event's names in the world, each with who uses it. |
579
- | `data.events[].names[].name` | a string | yes | One name the event goes by in the world. |
580
- | `data.events[].names[].by` | an Address naming an `affiliation`, `lore`, `place`, `being` or `skill` note | yes | Who uses that name — a people, a polity, a faith, a place, a tongue. |
581
- | `data.events[].names[].gloss` | a string | no | What the name means, or how it is used. |
582
- | `data.events[].where` | `{ locus?, reach? }` | no | Where it happened, and where it was felt. |
583
- | `data.events[].where.locus` | a list of Addresses, each defaulting to `place` | no | Where the event physically happened; each resolves to a place. |
584
- | `data.events[].where.reach` | `{ place, how, knowledge, attributedTo? }[]` | no | Where the event was felt, and whether each place knows why. |
585
- | `data.events[].where.reach[].place` | an Address, defaulting to `place` | yes | A place where the event was felt. |
586
- | `data.events[].where.reach[].how` | a string | yes | One clause: a consequence someone in that place could notice. |
587
- | `data.events[].where.reach[].knowledge` | one `knowledge` value | yes | Whether that place connects what it felt to this event. |
588
- | `data.events[].where.reach[].attributedTo` | an Address, defaulting to `lore`, naming an event or a `lore` note | no | Only beside `knowledge: misattributed` — the cause that place names instead. |
589
- | `data.events[].who` | `{ ref, role }[]` | no | Who took part, and as what. |
590
- | `data.events[].who[].ref` | an Address naming a `being`, `affiliation` or `lore` note | yes | A participant — a being, a people, an affiliation. |
591
- | `data.events[].who[].role` | one `role` value | yes | What the participant was to the event. |
592
- | `data.events[].follows` | `{ event, how, note? }[]` | no | The earlier events this one follows from. |
593
- | `data.events[].follows[].event` | an Address, defaulting to `lore`, anchored to an `event` where its note holds several | yes | The earlier event; it resolves to exactly one event. |
594
- | `data.events[].follows[].how` | one `followsHow` value | yes | How this event follows from it. |
595
- | `data.events[].follows[].note` | a string | no | One clause saying what connects the two. |
596
- | `data.events[].accounts` | `{ by, says, agrees, withholds? }[]` | no | What each people, polity, faith or place says about it. |
597
- | `data.events[].accounts[].by` | an Address naming an `affiliation`, `lore`, `place` or `being` note | yes | Who holds the account. |
598
- | `data.events[].accounts[].says` | a string | yes | What they say happened, in their terms. |
599
- | `data.events[].accounts[].agrees` | one `agrees` value | yes | How far their account agrees with `summary`. |
600
- | `data.events[].accounts[].withholds` | a string | no | What they decline to say. |
601
- | `data.events[].unresolved` | a list of strings | no | What the world itself has not settled. |
602
- | `data.events[].sources` | a list of Addresses | no | The notes that state or support the event; each resolves. |
603
- | `data.events[].stated` | `{ calendar, text }` | no | The date as one tradition writes it in its own reckoning. |
604
- | `data.events[].stated.calendar` | an Address, or a lore shortcode | yes | The calendar that tradition reckons in — a `lore` note with `subType: calendar`. |
605
- | `data.events[].stated.text` | a string | yes | The date as that tradition writes it. |
606
- | `data.appearanceProfile.frame` | a map of words to weights | yes | Weighted `frame` words; a word left out is never drawn. |
607
- | `data.appearanceProfile.eye_color` | a map of words to weights | yes | Weighted `eye_color` words; a word left out is never drawn. |
608
- | `data.appearanceProfile.hair_color` | a map of words to weights | yes | Weighted `hair_color` words; a word left out is never drawn. |
609
- | `data.appearanceProfile.skin_color` | a map of words to weights | yes | Weighted `skin_color` words; a word left out is never drawn. |
610
- | `data.appearanceProfile.complexion` | a map of words to weights | yes | Weighted `complexion` words; a word left out is never drawn. |
611
- | `data.appearanceProfile.height` | a map of genders to height ranges | yes | Height ranges keyed by gender; a gender with no range draws from the span of every range stated. |
612
- | `data.appearanceProfile.height.<name>.min` | a height in metres or feet and inches | yes | The shortest height drawn. |
613
- | `data.appearanceProfile.height.<name>.max` | a height in metres or feet and inches | yes | The tallest height drawn. |
582
+ | Key | Shape | Required | Meaning |
583
+ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
584
+ | `data.months[].name` | a string | yes | What it is called. |
585
+ | `data.months[].abbreviation` | a string | no | Its short form, for a compact date. |
586
+ | `data.months[].days` | a whole number of days | yes | How many days the month holds. |
587
+ | `data.weekdays[].name` | a string | yes | What it is called. |
588
+ | `data.weekdays[].abbreviation` | a string | no | Its short form, for a compact date. |
589
+ | `data.seasons[].name` | a string | yes | What it is called. |
590
+ | `data.seasons[].abbreviation` | a string | no | Its short form, for a compact date. |
591
+ | `data.seasons[].start` | a one-based day of the year, as a whole number | yes | The day of the year the season begins. |
592
+ | `data.namedDays[].name` | a string | yes | What it is called. |
593
+ | `data.namedDays[].abbreviation` | a string | no | Its short form, for a compact date. |
594
+ | `data.namedDays[].day` | a one-based day of the year, as a whole number | yes | The day of the year it names. |
595
+ | `data.eras[].shortcode` | the era's own shortcode, unique within the calendar — an era is addressed `<calendar shortcode>.<era shortcode>` | yes | The era's own segment of `<calendar>.<era>`. |
596
+ | `data.eras[].name` | a string | yes | What it is called. |
597
+ | `data.eras[].marker` | uppercase letters and digits, beginning with a letter | no | The marker an authored date names the era by, unique across the corpus. |
598
+ | `data.eras[].abbreviation` | a string | no | Its short form, for a compact date. |
599
+ | `data.eras[].proclaimedBy` | an Address | no | The body that began the reckoning. |
600
+ | `data.eras[].start` | `null`, or the calendar year the era begins, as a whole number | yes | The calendar year the era begins; `null` for the era before year 1. |
601
+ | `data.eras[].label` | a string, or an `{ after, before }` map | no | How a date in the era reads, around one `{date}` slot. |
602
+ | `data.eras[].label.after` | a string with one `{date}` slot | no | How a date in the era reads. |
603
+ | `data.eras[].label.before` | a string with one `{date}` slot | no | How a date before the era's start reads. |
604
+ | `data.events[].id` | an address segment | no | The event's name within its note — lowercase letters and digits. |
605
+ | `data.events[].kind` | one `eventKind` value | no | What sort of thing happened. |
606
+ | `data.events[].depth` | one `depth` value | no | How far the event's history reaches. |
607
+ | `data.events[].when` | a date, or `"0.<day>"` | yes | When it happened, or the anchor and first instance of an event that recurs. |
608
+ | `data.events[].until` | a date | no | When a continuous event ended, or where a recurring series stopped. |
609
+ | `data.events[].recurs` | `{ every }` or `{ on }` | no | How further occurrences are found; absent for an event that happened once. |
610
+ | `data.events[].recurs.every` | a whole number of years, 1 or more | no | A period counted on the canonical axis from `when`. |
611
+ | `data.events[].recurs.on` | a list of dates, strictly increasing, each later than `when` | no | Recorded occurrences beyond the first, in place of a period. |
612
+ | `data.events[].summary` | a string | yes | What happened, stated plainly. |
613
+ | `data.events[].standing` | one `standing` value | no | What the world's evidence supports. |
614
+ | `data.events[].names` | `{ name, by, gloss? }[]` | no | The event's names in the world, each with who uses it. |
615
+ | `data.events[].names[].name` | a string | yes | One name the event goes by in the world. |
616
+ | `data.events[].names[].by` | an Address naming an `affiliation`, `lore`, `place`, `being` or `skill` note | yes | Who uses that name — a people, a polity, a faith, a place, a tongue. |
617
+ | `data.events[].names[].gloss` | a string | no | What the name means, or how it is used. |
618
+ | `data.events[].where` | `{ locus?, reach? }` | no | Where it happened, and where it was felt. |
619
+ | `data.events[].where.locus` | a list of Addresses, each defaulting to `place` | no | Where the event physically happened; each resolves to a place. |
620
+ | `data.events[].where.reach` | `{ place, how, knowledge, attributedTo? }[]` | no | Where the event was felt, and whether each place knows why. |
621
+ | `data.events[].where.reach[].place` | an Address, defaulting to `place` | yes | A place where the event was felt. |
622
+ | `data.events[].where.reach[].how` | a string | yes | One clause: a consequence someone in that place could notice. |
623
+ | `data.events[].where.reach[].knowledge` | one `knowledge` value | yes | Whether that place connects what it felt to this event. |
624
+ | `data.events[].where.reach[].attributedTo` | an Address, defaulting to `lore`, naming an event or a `lore` note | no | Only beside `knowledge: misattributed` — the cause that place names instead. |
625
+ | `data.events[].who` | `{ ref, role }[]` | no | Who took part, and as what. |
626
+ | `data.events[].who[].ref` | an Address naming a `being`, `affiliation` or `lore` note | yes | A participant — a being, a people, an affiliation. |
627
+ | `data.events[].who[].role` | one `role` value | yes | What the participant was to the event. |
628
+ | `data.events[].follows` | `{ event, how, note? }[]` | no | The earlier events this one follows from. |
629
+ | `data.events[].follows[].event` | an Address, defaulting to `lore`, anchored to an `event` where its note holds several | yes | The earlier event; it resolves to exactly one event. |
630
+ | `data.events[].follows[].how` | one `followsHow` value | yes | How this event follows from it. |
631
+ | `data.events[].follows[].note` | a string | no | One clause saying what connects the two. |
632
+ | `data.events[].accounts` | `{ by, says, agrees, withholds? }[]` | no | What each people, polity, faith or place says about it. |
633
+ | `data.events[].accounts[].by` | an Address naming an `affiliation`, `lore`, `place` or `being` note | yes | Who holds the account. |
634
+ | `data.events[].accounts[].says` | a string | yes | What they say happened, in their terms. |
635
+ | `data.events[].accounts[].agrees` | one `agrees` value | yes | How far their account agrees with `summary`. |
636
+ | `data.events[].accounts[].withholds` | a string | no | What they decline to say. |
637
+ | `data.events[].unresolved` | a list of strings | no | What the world itself has not settled. |
638
+ | `data.events[].sources` | a list of Addresses | no | The notes that state or support the event; each resolves. |
639
+ | `data.events[].stated` | `{ calendar, text }` | no | The date as one tradition writes it in its own reckoning. |
640
+ | `data.events[].stated.calendar` | an Address, or a lore shortcode | yes | The calendar that tradition reckons in — a `lore` note with `subType: calendar`. |
641
+ | `data.events[].stated.text` | a string | yes | The date as that tradition writes it. |
642
+ | `data.appearanceProfile.frame` | a map of words to weights | yes | Weighted `frame` words; a word left out is never drawn. |
643
+ | `data.appearanceProfile.eye_color` | a map of words to weights | yes | Weighted `eye_color` words; a word left out is never drawn. |
644
+ | `data.appearanceProfile.hair_color` | a map of words to weights | yes | Weighted `hair_color` words; a word left out is never drawn. |
645
+ | `data.appearanceProfile.skin_color` | a map of words to weights | yes | Weighted `skin_color` words; a word left out is never drawn. |
646
+ | `data.appearanceProfile.complexion` | a map of words to weights | yes | Weighted `complexion` words; a word left out is never drawn. |
647
+ | `data.appearanceProfile.height` | a map of genders to height ranges | yes | Height ranges keyed by gender; a gender with no range draws from the span of every range stated. |
648
+ | `data.appearanceProfile.height.<name>.min` | a height in metres or feet and inches | yes | The shortest height drawn. |
649
+ | `data.appearanceProfile.height.<name>.max` | a height in metres or feet and inches | yes | The tallest height drawn. |
650
+ | `data.glossary[].name` | a string | yes | The headword. |
651
+ | `data.glossary[].name_sort` | a string | no | Its sort key; the name when unstated. |
652
+ | `data.glossary[].common` | `true` or `false` | no | A word a player is expected to know by heart; `false` when unstated. |
653
+ | `data.glossary[].definition` | a string | no | The sense text. Stated, it creates the entry or replaces every sense the entry's other sources give it. |
654
+ | `data.glossary[].pronunciation` | a respelling such as `sol-MEH-reh` | no | A respelling, held to the same rule as a word's. |
655
+ | `data.glossary[].language` | a language skill Address | no | The language the term is in. Unstated, it is the affiliation's language, its parents', or its culture's, or the culture's own. |
614
656
 
615
657
  ### map
616
658
 
@@ -1261,6 +1261,8 @@ export class BasePackCompiler {
1261
1261
  // to have the body anyway. So the read is one this pass makes anyway;
1262
1262
  // the index decides which files it covers.
1263
1263
  const dates = reckoningContext({ notes: this.corpus.records });
1264
+ /** The note first filed under each document id in this pass. @type {Map<string, string>} */
1265
+ const idOwners = new Map();
1264
1266
  for (const record of this.corpus.records) {
1265
1267
  if (!isNoteRecord(record)) continue;
1266
1268
  const absPath = noteFile(this.contentBase, record);
@@ -1360,6 +1362,25 @@ export class BasePackCompiler {
1360
1362
  continue;
1361
1363
  }
1362
1364
 
1365
+ // Two notes filed under one id are one document. Reported here, at
1366
+ // the second, because everything derived from the id downstream (an
1367
+ // actor's embedded items, a pack's file names) then collides with
1368
+ // itself and names neither note.
1369
+ const owner = idOwners.get(fm.id);
1370
+ if (owner !== undefined) {
1371
+ stats.declined++;
1372
+ this.errorCount++;
1373
+ this.noteError(
1374
+ `${this.noteLabel(fm)}: document id "${fm.id}" is already the id of ` +
1375
+ `${path.relative(process.cwd(), owner)}. Give one of the two notes a ` +
1376
+ `different \`id\` under \`data:\`, or remove it to take the id derived ` +
1377
+ `from its address`,
1378
+ locateFrontmatterKey(absPath, "id"),
1379
+ );
1380
+ continue;
1381
+ }
1382
+ idOwners.set(fm.id, absPath);
1383
+
1363
1384
  log.debug(`Processing ${this.noteLabel(fm)}: ${resolveName(fm)} (${absPath})`);
1364
1385
  try {
1365
1386
  const doc = this.compileNote(fm, this.convertBody(fm, body));