@heroiclands/package-build 22.15.1 → 22.16.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 (221) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/README.md +39 -678
  3. package/bin/{content-build.mjs → content-commands.mjs} +197 -118
  4. package/bin/package-build.mjs +303 -42
  5. package/bin/report.mjs +1 -1
  6. package/ci/ci-docker.mjs +6 -3
  7. package/ci/ci-steps.mjs +6 -4
  8. package/ci/content-format-reference.mjs +106 -0
  9. package/config.mjs +22 -2
  10. package/content-config.mjs +74 -134
  11. package/docs/api.md +294 -234
  12. package/docs/authoring/assets.md +31 -0
  13. package/docs/authoring/dates-and-calendars.md +33 -0
  14. package/docs/authoring/first-note.md +48 -0
  15. package/docs/authoring/frontmatter.md +40 -0
  16. package/docs/authoring/links-and-markup.md +137 -0
  17. package/docs/commands.md +631 -162
  18. package/docs/configuration.md +176 -140
  19. package/docs/content-format.md +50 -4229
  20. package/docs/design.md +32 -0
  21. package/docs/diagnostics.md +29 -22
  22. package/docs/getting-started.md +21 -877
  23. package/docs/guides/book.md +153 -0
  24. package/docs/guides/e2e.md +143 -0
  25. package/docs/guides/icons.md +126 -0
  26. package/docs/guides/localization.md +120 -0
  27. package/docs/guides/module-project.md +915 -0
  28. package/docs/guides/packs.md +167 -0
  29. package/docs/guides/reference-pages.md +148 -0
  30. package/docs/guides/release-and-deploy.md +114 -0
  31. package/docs/guides/site.md +152 -0
  32. package/docs/guides/system-project.md +45 -0
  33. package/docs/index.md +16 -0
  34. package/docs/project-setup.md +54 -35
  35. package/docs/reference/format-details.md +4420 -0
  36. package/docs/reference/note-types.md +531 -0
  37. package/engine/address-render.mjs +26 -0
  38. package/engine/address.mjs +3 -20
  39. package/engine/anchors.mjs +15 -6
  40. package/engine/asset-bindings.mjs +6 -2
  41. package/engine/base-compiler.mjs +47 -11
  42. package/engine/being-measurements.mjs +70 -0
  43. package/engine/calendar-artifacts.mjs +1 -1
  44. package/engine/calendar-choice.mjs +32 -0
  45. package/engine/calendar-human.mjs +71 -0
  46. package/engine/calendar-notes.mjs +41 -3
  47. package/engine/calendars.mjs +8 -1
  48. package/engine/changelog-lint.mjs +2 -2
  49. package/engine/code-fences.mjs +23 -97
  50. package/engine/compile-corpus.mjs +6 -7
  51. package/engine/content-admonitions.mjs +114 -0
  52. package/engine/content-captions.mjs +181 -0
  53. package/engine/content-embeds.mjs +2 -2
  54. package/engine/content-footnotes.mjs +34 -0
  55. package/engine/content-format-check.mjs +2 -2
  56. package/engine/content-format.mjs +127 -73
  57. package/engine/content-format.yaml +745 -0
  58. package/engine/content-html.mjs +1 -1
  59. package/engine/content-icons.mjs +45 -92
  60. package/engine/content-images.mjs +22 -74
  61. package/engine/content-index.mjs +12 -3
  62. package/engine/content-links.mjs +21 -26
  63. package/engine/content-lint.mjs +1 -5
  64. package/engine/content-secrets.mjs +3 -1
  65. package/engine/content-tables.mjs +47 -1257
  66. package/engine/culture-choice.mjs +32 -0
  67. package/engine/date-conversion.mjs +47 -0
  68. package/engine/declaration-check.mjs +87 -0
  69. package/engine/docs-checks.mjs +140 -0
  70. package/engine/documentation-links.mjs +31 -0
  71. package/engine/exported-scene.mjs +1 -1
  72. package/engine/extension-attributes.mjs +74 -0
  73. package/engine/field-reference.mjs +1 -1
  74. package/engine/field-spec.mjs +4 -0
  75. package/engine/foreign-catalog.mjs +5 -5
  76. package/engine/format-generated.mjs +15 -0
  77. package/engine/foundry-entries.mjs +3 -1
  78. package/engine/frontmatter-lint.mjs +5 -1
  79. package/engine/frontmatter.mjs +10 -0
  80. package/engine/generate.mjs +2 -2
  81. package/engine/helpers.mjs +71 -117
  82. package/engine/homepage.mjs +2 -3
  83. package/engine/index-records.mjs +1 -0
  84. package/engine/index.mjs +18 -3
  85. package/engine/infobox-registry.mjs +3 -1
  86. package/engine/infobox.mjs +38 -20
  87. package/engine/item-compiler.mjs +10 -8
  88. package/engine/item-docs.mjs +7 -6
  89. package/engine/journals.mjs +51 -10
  90. package/engine/macros.mjs +1 -1
  91. package/engine/map-build.mjs +1 -1
  92. package/engine/map-dot.mjs +1 -1
  93. package/engine/markdown-expressions.mjs +117 -0
  94. package/engine/metadata-index.mjs +7 -3
  95. package/engine/note-addresses.mjs +1 -0
  96. package/engine/note-dates.mjs +283 -61
  97. package/engine/note-package.mjs +5 -25
  98. package/engine/note-state.mjs +6 -2
  99. package/engine/note-vocabulary.mjs +74 -19
  100. package/engine/number-words.mjs +75 -0
  101. package/engine/office-holders.mjs +188 -0
  102. package/engine/pack-config.mjs +2 -15
  103. package/engine/pack-router.mjs +24 -5
  104. package/engine/packages.mjs +3 -2
  105. package/engine/pathnames.mjs +21 -24
  106. package/engine/pdf-build.mjs +217 -55
  107. package/engine/pdf-fonts.mjs +2 -2
  108. package/engine/pdf-images.mjs +82 -0
  109. package/engine/pdf-render.mjs +174 -22
  110. package/engine/populations.mjs +3 -2
  111. package/engine/project-init.mjs +499 -0
  112. package/engine/prose-config.mjs +1 -1
  113. package/engine/prose-lint.mjs +7 -6
  114. package/engine/readability-lint.mjs +86 -0
  115. package/engine/reckoning-markers.mjs +205 -93
  116. package/engine/scenes.mjs +5 -2
  117. package/engine/schema-check.mjs +3 -3
  118. package/engine/site-build.mjs +87 -37
  119. package/engine/site-config.mjs +8 -8
  120. package/engine/site-index.mjs +4 -0
  121. package/engine/site-maps.mjs +1 -1
  122. package/engine/site-preview.mjs +19 -8
  123. package/engine/social-ties.mjs +5 -1
  124. package/engine/sql-tables.mjs +92 -22
  125. package/engine/web-wikilinks.mjs +15 -31
  126. package/engine/wikilink-syntax.mjs +4 -1
  127. package/engine/wikilinks.mjs +12 -14
  128. package/hm3/item-fields.mjs +1 -1
  129. package/hm3/items.mjs +12 -45
  130. package/index.mjs +2 -14
  131. package/manifest.mjs +31 -4
  132. package/markdownlint-config.mjs +1 -1
  133. package/package.json +32 -11
  134. package/prettier-config.mjs +1 -1
  135. package/sohl/being-info.mjs +3 -145
  136. package/sohl/index.mjs +1 -1
  137. package/sohl/infobox.mjs +2 -1
  138. package/sohl/item-builders.mjs +3 -27
  139. package/sohl/item-fields.mjs +61 -22
  140. package/sohl/kb-passes.mjs +2 -2
  141. package/sohl/note-schemas.mjs +0 -8
  142. package/stage.mjs +19 -0
  143. package/types/config.d.mts +12 -3
  144. package/types/content-config.d.mts +15 -81
  145. package/types/engine/address-render.d.mts +13 -0
  146. package/types/engine/address.d.mts +2 -11
  147. package/types/engine/anchors.d.mts +3 -4
  148. package/types/engine/asset-bindings.d.mts +9 -2
  149. package/types/engine/base-compiler.d.mts +1 -2
  150. package/types/engine/being-measurements.d.mts +16 -0
  151. package/types/engine/calendar-choice.d.mts +14 -0
  152. package/types/engine/calendar-human.d.mts +4 -0
  153. package/types/engine/calendar-notes.d.mts +2 -0
  154. package/types/engine/calendars.d.mts +5 -3
  155. package/types/engine/code-fences.d.mts +6 -72
  156. package/types/engine/compile-corpus.d.mts +1 -3
  157. package/types/engine/content-admonitions.d.mts +24 -0
  158. package/types/engine/content-captions.d.mts +45 -0
  159. package/types/engine/content-footnotes.d.mts +10 -0
  160. package/types/engine/content-format-check.d.mts +17 -4
  161. package/types/engine/content-format.d.mts +24 -11
  162. package/types/engine/content-icons.d.mts +14 -23
  163. package/types/engine/content-images.d.mts +2 -2
  164. package/types/engine/content-index.d.mts +9 -2
  165. package/types/engine/content-links.d.mts +3 -5
  166. package/types/engine/content-tables.d.mts +11 -140
  167. package/types/engine/culture-choice.d.mts +14 -0
  168. package/types/engine/date-conversion.d.mts +4 -0
  169. package/types/engine/declaration-check.d.mts +24 -0
  170. package/types/engine/docs-checks.d.mts +28 -0
  171. package/types/engine/documentation-links.d.mts +7 -0
  172. package/types/engine/exported-scene.d.mts +6 -2
  173. package/types/engine/extension-attributes.d.mts +14 -0
  174. package/types/engine/field-spec.d.mts +9 -0
  175. package/types/engine/format-generated.d.mts +7 -0
  176. package/types/engine/frontmatter.d.mts +14 -4
  177. package/types/engine/generate.d.mts +1 -1
  178. package/types/engine/helpers.d.mts +37 -92
  179. package/types/engine/homepage.d.mts +2 -3
  180. package/types/engine/index.d.mts +5 -0
  181. package/types/engine/infobox-registry.d.mts +3 -1
  182. package/types/engine/infobox.d.mts +17 -6
  183. package/types/engine/item-compiler.d.mts +4 -4
  184. package/types/engine/item-docs.d.mts +4 -3
  185. package/types/engine/journals.d.mts +10 -7
  186. package/types/engine/map-graphviz.d.mts +1 -1
  187. package/types/engine/markdown-expressions.d.mts +17 -0
  188. package/types/engine/note-dates.d.mts +47 -1
  189. package/types/engine/note-package.d.mts +3 -19
  190. package/types/engine/note-state.d.mts +2 -1
  191. package/types/engine/note-vocabulary.d.mts +9 -0
  192. package/types/engine/number-words.d.mts +4 -0
  193. package/types/engine/office-holders.d.mts +14 -0
  194. package/types/engine/pack-config.d.mts +1 -1
  195. package/types/engine/packages.d.mts +3 -2
  196. package/types/engine/pdf-images.d.mts +38 -0
  197. package/types/engine/populations.d.mts +2 -2
  198. package/types/engine/project-init.d.mts +17 -0
  199. package/types/engine/prose-config.d.mts +1 -1
  200. package/types/engine/prose-lint.d.mts +3 -2
  201. package/types/engine/readability-lint.d.mts +22 -0
  202. package/types/engine/reckoning-markers.d.mts +4 -2
  203. package/types/engine/schema-check.d.mts +3 -3
  204. package/types/engine/site-build.d.mts +38 -28
  205. package/types/engine/site-config.d.mts +2 -2
  206. package/types/engine/site-index.d.mts +4 -0
  207. package/types/engine/site-preview.d.mts +1 -1
  208. package/types/engine/social-ties.d.mts +8 -2
  209. package/types/engine/sql-tables.d.mts +13 -2
  210. package/types/engine/web-wikilinks.d.mts +2 -1
  211. package/types/engine/wikilinks.d.mts +7 -1
  212. package/types/hm3/items.d.mts +5 -6
  213. package/types/labels.d.mts +40 -0
  214. package/types/manifest.d.mts +3 -3
  215. package/types/sohl/actors.d.mts +2 -2
  216. package/types/sohl/being-info.d.mts +3 -43
  217. package/types/sohl/index.d.mts +1 -1
  218. package/types/sohl/infobox.d.mts +5 -2
  219. package/types/sohl/item-fields.d.mts +4 -13
  220. package/types/stage.d.mts +9 -0
  221. package/CONTENT.md +0 -3239
@@ -0,0 +1,4420 @@
1
+ ---
2
+ shortcode: referenceformatdetails
3
+ name: { full: "Content format details" }
4
+ type: doc
5
+ subType: reference
6
+ ---
7
+
8
+ ## Content format: how a note becomes documents
9
+
10
+ ### Documentation
11
+
12
+ Markdown generates a JournalEntry: content before the first H1 becomes its "Introduction" page, and each H1 begins another page. The document Address is `<package>-note-<note_type>-<shortcode>`.
13
+
14
+ An **infobox** is prepended to the entry's first page, before the prose. It is
15
+ not a page of its own: a page is what a Foundry UUID addresses, and a summary a
16
+ reader has to navigate to is a summary they do not see. What the box holds, and
17
+ where it sits in every other medium, is [the infobox](#the-infobox) below.
18
+
19
+ **There are two kinds of infobox.**
20
+
21
+ A **note infobox** summarises the subject itself, from `data:` — a scenario's
22
+ length, party size and required archetypes; a place's subtype and its parent; a
23
+ weapon's weight and value. None of that is SoHL or HM3 information, and it
24
+ deserves a summary panel all the same. Any type that declares `data:` fields has
25
+ one.
26
+
27
+ A **system infobox** summarises what one system makes of the note, drawn from
28
+ that system's block. There is one per system, and they are not alike: the two
29
+ systems describe a weapon with different values for impact, heft, reach and draw,
30
+ so neither box can stand for the other.
31
+
32
+ **Which boxes appear is decided by the note type; what they say is decided by the
33
+ note.** A type that both systems support always shows both system boxes, and a
34
+ box whose system this particular note produces no document for reads
35
+ **"Not available"**.
36
+
37
+ | note type | system boxes shown |
38
+ | --------------------------------------------------------------------- | -------------------------------------------------- |
39
+ | `weapon`, `skill`, `being`, `armorgear`, `containergear`, `miscgear`… | SoHL **and** HM3 — either may read _Not available_ |
40
+ | `affiliation`, `affliction`, `attribute`, `concoctiongear`, `mystery` | SoHL only |
41
+ | `armorlocation` | HM3 only |
42
+
43
+ This is stated rather than inferred from an empty block, because an absence is a
44
+ poor signal: noticing that something is missing requires already knowing it
45
+ should have been there, and a reader meeting one page has no way to know. A box
46
+ that says _Not available_ tells them outright.
47
+
48
+ It also separates two facts that an absence would conflate. `Spear (thrown)` is
49
+ HM3-only while spears plainly exist in SoHL — _that_ note has no SoHL form, and
50
+ its SoHL box says so. HM3, by contrast, has no affiliations, mysteries or
51
+ attributes **at all**; on those pages a box reading "Not available" would suggest
52
+ a gap in the note when the truth is about the system's scope, so no HM3 box is
53
+ drawn.
54
+
55
+ The set of boxes needs no new declaration: the note-type → document-subtype map
56
+ already records which systems each type reaches. Rendering one box per mapped
57
+ system makes the page checkable — _every page carries exactly the boxes its type
58
+ maps to_ is an assertion the build can make, so a missing infobox is a failure
59
+ rather than something nobody notices.
60
+
61
+ For web pages, the full Markdown body renders as HTML with its infoboxes.
62
+
63
+ ### The infobox
64
+
65
+ **The infobox is generated content at a known position — prepended, before the
66
+ prose.** It is not a floating sidebar and it is not defined in a rendering
67
+ template. The toolchain settles what each box holds; each medium lays that out
68
+ its own way.
69
+
70
+ | medium | placement | collapsing |
71
+ | ------- | ------------------------------------------------------ | ------------------------- |
72
+ | book | flows in the column measure, breaking between sections | none, and none needed |
73
+ | website | side rail on wide screens, inline on narrow | `<details>`, default open |
74
+ | Foundry | inlined, the page being narrow | `<details>`, default open |
75
+
76
+ Two columns are print-only. A scrolling page has no fixed viewport, so columns
77
+ make a reader travel down and back up, and a narrow screen collapses to one
78
+ column regardless.
79
+
80
+ **Six rules hold in every medium.**
81
+
82
+ 1. **It is content in document order**, prepended before the prose. An image
83
+ authored before it appears before it.
84
+ 2. **It contains no image.** A picture is authored in the text with its own
85
+ directive, and its position governs.
86
+ 3. **A section is the unit that flows.** Sections are whole and unbreakable and
87
+ the panel breaks _between_ them, which is what lets a long box cross a
88
+ column or page boundary without splitting a stat grid down the middle.
89
+ 4. **An absent field is absent, not empty.** A row with no value is not
90
+ emitted, and neither is a section with no rows: a creature carrying no
91
+ equipment has no `EQUIPMENT` heading rather than an empty one. A placeholder
92
+ asserts a fact that is not there, and an empty heading asserts that something
93
+ was expected here and is missing. A corpus writes an absence three ways and
94
+ all three count: the key is omitted, it holds the field's own default, or it
95
+ holds a **sentinel** — `na`, `none`, `not applicable` — which is the same
96
+ absence written as a word. `Potency: Na` is not a fact about a potion.
97
+ 5. **A system box is never an empty panel; it says which silence it is.** Rule
98
+ 4 governs rows, and a box is not a row — a heading over nothing asserts that
99
+ something should have been there. See
100
+ [the three states of a system box](#the-three-states-of-a-system-box).
101
+ 6. **Order is the toolchain's.** A medium renders boxes, sections and rows in
102
+ the order given.
103
+
104
+ **Embedded models link to documentation in an infobox.** The model Address
105
+ continues to identify the native Item used for compilation. Its displayed link
106
+ uses that same package, type and Shortcode under `note`, which names the
107
+ corresponding documentation journal. A model with no published documentation
108
+ keeps its known name as plain text; the infobox does not substitute an Actor or
109
+ Item link.
110
+
111
+ #### What the note box holds
112
+
113
+ **The note box's fields are the type's own `data:` vocabulary, in its declared
114
+ order** — the very table each `### type:` section below states. There is no
115
+ second list, so a key added to a type appears in the book, on the website and
116
+ in a compendium journal with no further edit. A `Name` row stands first on
117
+ every note, because every note has one.
118
+
119
+ A few keys carry no row, and the reason in each case is one of exactly two —
120
+ the key is **machinery**, steering a build or an interface rather than
121
+ describing the subject, or it is an **image**, which rule 2 keeps out of the
122
+ box:
123
+
124
+ | key | why it carries no row |
125
+ | ------------------------ | ------------------------------------------------ |
126
+ | `templatePriority` | template machinery, not a fact about the subject |
127
+ | `color` | sidebar machinery, not a fact about the subject |
128
+ | the art slots, `overlay` | an image, which rule 2 keeps out of the box |
129
+
130
+ A field whose value is a **mapping** carries no row either — a governance
131
+ ladder or a wall layout has no summary shape — and neither does one the note
132
+ left empty.
133
+
134
+ A being's `age`, `height`, `weight` and `appearance.*` compose into a single
135
+ **Appearance** clause rather than taking six rows of their own: _Age 34, 6′ 1″,
136
+ 181 lbs, medium frame, brown eyes_.
137
+
138
+ #### What a system box holds
139
+
140
+ **A system box's fields are that system's own field declaration**, in its
141
+ order. There is no second list here either, so a field added to a type reaches
142
+ the box with no further edit.
143
+
144
+ Two things keep a fact off it:
145
+
146
+ - **A value the declaration would have supplied anyway.** A field answered by
147
+ its own default is a fact about the compiler, not about the note — and that is
148
+ true of the value, wherever it was written. A corpus writes its defaults out:
149
+ `improveFlag: false` and `combatCategory: none` are typed into hundreds of
150
+ notes that mean nothing by them.
151
+ - **A fact the note box already put on the page.** A gear item's weight is
152
+ system-agnostic and belongs to the note. What counts is what the note box
153
+ _shows_, not what its vocabulary declares — the same fact is written under
154
+ `data:` on one note and at its destination path on another, and a system box
155
+ that stood down on the declaration alone would leave it on no surface at all.
156
+
157
+ **A measured quantity carries its unit on its value** — `Price 160d`,
158
+ `Weight 1.1 lbs`. The unit belongs to the quantity rather than to the name of
159
+ the quantity, so it is not folded into the label: `Price (d)` beside `160` makes
160
+ a reader reassemble one fact from two cells, and reads worst in the book, whose
161
+ label column is a narrow small-caps rule. A medium cannot supply it either —
162
+ appending `d` to a price means knowing which row is the price, which is the one
163
+ thing a generic renderer must never know. A row carrying a unit is `text`,
164
+ because a number with a unit is text for presentation.
165
+
166
+ **What a field is called** is a **presentation overlay** on that same
167
+ declaration, per system. It carries two things a compiler's field list cannot,
168
+ because they are about a page rather than about a document: a reader's word
169
+ where the declaration's key is the compiler's — `assocSkillCode` is exactly
170
+ right in a DataModel and wrong in a panel somebody reads — and the handful of
171
+ keys that belong on no page at all, either because the box shows the same fact
172
+ whole somewhere else or because they steer a character sheet rather than
173
+ describe the subject. It is an overlay and not a list: a field it does not
174
+ mention still gets a row, under its own humanised name.
175
+
176
+ Four types are read differently, because their box is derived rather than read
177
+ field by field — a **being**, whose attributes, skills, mystical abilities and
178
+ carried gear are one flat `sohl.items` list; **armour**, whose protection is
179
+ shown for all four aspects with an unstated one rendered `0`; a **weapon**,
180
+ whose strike modes are shown one per line with an unstated value rendered `—`;
181
+ and a **projectile**, whose impact is three declared fields composing into the
182
+ one quantity a reader wants. Both of those placeholders are decided by the
183
+ toolchain and travel as data, so no renderer has to know which field it is
184
+ looking at.
185
+
186
+ A weapon's strike modes are authored either as a **list**, each mode carrying
187
+ its own `shortcode`, or as a **mapping** keyed by the mode's name. Both are
188
+ read, and both yield the same section.
189
+
190
+ #### The three states of a system box
191
+
192
+ A system box says something on every page that carries one. Which of the three
193
+ it says is decided here and travels as the box's `statement`, so a medium draws
194
+ one thing and decides none of it.
195
+
196
+ | state | when | what the box says |
197
+ | -------------- | ---------------------------------------------------------- | --------------------------------------- |
198
+ | content | the system holds something the note box has not shown | its sections, and no `statement` |
199
+ | not available | the system maps this note's type but produced no document | `statement: Not available` |
200
+ | nothing to add | the system produced a document holding nothing new to show | `statement: Nothing beyond the profile` |
201
+
202
+ The third state is not the second. _Not available_ is a statement about this
203
+ note — the system has no document for it, and a reader can act on that. A
204
+ system that _does_ compile the note and holds only facts the note box has
205
+ already given says so instead, because a reader told "not available" would go
206
+ looking for a document that exists, and an empty panel tells them nothing at
207
+ all.
208
+
209
+ A system that does not map the note's type gets **no box**. A box reading
210
+ _Not available_ on every affiliation page would suggest a gap in the note when
211
+ the truth is about the system's scope.
212
+
213
+ Whether a document was produced is the compile's own answer rather than a
214
+ reading of the frontmatter. Three things must hold: the note must carry that
215
+ system's block, its type must map to the system, and it must route to a pack of
216
+ the document class that map names. The block is the first of them because a
217
+ system block is what makes a game document — see
218
+ [A system block is what makes a game document](#a-system-block-is-what-makes-a-game-document).
219
+ A pack declaring **no** system is compiled by the fallback pass, which reads one
220
+ system's block and no other, so the question is the same one whether or not the
221
+ pack answered it.
222
+
223
+ #### Four section layouts
224
+
225
+ A section says how it is arranged, and a renderer switches on that and on a
226
+ value's `kind` — never on a field name, which is what keeps a field list from
227
+ leaking back into a template.
228
+
229
+ | `layout` value | shape | what carries it | where it is used |
230
+ | -------------- | ------------------------------- | --------------- | -------------------------- |
231
+ | `rows` | label/value pairs, one per line | `rows` | a profile |
232
+ | `grid` | short label/value cells | `cells` | attributes, armour aspects |
233
+ | `runin` | groups of comma-joined entries | `groups` | skills, equipment |
234
+ | `list` | one entry per line | `entries` | mystical abilities |
235
+
236
+ | `kind` value | the value is |
237
+ | ------------ | ------------------------------- |
238
+ | `text` | a string |
239
+ | `number` | a number |
240
+ | `link` | `{text, url?, uuid?, address?}` |
241
+ | `links` | a list of those |
242
+ | `list` | a list of strings |
243
+
244
+ A website renders a link by its `url`, a compendium journal by its `uuid`, and
245
+ the book by the `address`, which is the entry's own label in the volume. A
246
+ reference the index could not reach keeps its words rather than being dropped.
247
+
248
+ **Emitted shape**, as a page's front matter carries it:
249
+
250
+ ```yaml
251
+ infoboxes:
252
+ - id: note
253
+ kind: note
254
+ title: Profile
255
+ sections:
256
+ - id: profile
257
+ layout: rows
258
+ rows:
259
+ - label: Name
260
+ kind: text
261
+ value: Brànwâal Dôrgaar
262
+ - label: Affiliations
263
+ kind: links
264
+ value:
265
+ - text: The Silent Talon Company
266
+ url: /thalorna/affiliation-slntlncmpny/
267
+ - id: sohl
268
+ kind: system
269
+ system: sohl
270
+ title: SoHL
271
+ available: true
272
+ sections:
273
+ - id: attributes
274
+ label: Attributes
275
+ layout: grid
276
+ cells:
277
+ - label: STR
278
+ value: 14
279
+ - id: skills
280
+ label: Skills
281
+ layout: runin
282
+ groups:
283
+ - label: Combat
284
+ entries:
285
+ - text: Melee 75
286
+ - id: hm3
287
+ kind: system
288
+ system: hm3
289
+ title: HM3
290
+ available: false
291
+ sections: []
292
+ statement: Not available
293
+ ```
294
+
295
+ ### Frontmatter has three regions
296
+
297
+ A HeroicLands note has opening YAML frontmatter with nonempty `shortcode` and
298
+ `type` values. Other Markdown files are ordinary Markdown. A note's frontmatter
299
+ has three regions:
300
+
301
+ | Region | Describes | Unknown keys |
302
+ | -------------------- | ------------------------------------------------------- | ------------ |
303
+ | **Top level** | The note's identity, name, description, and publication | An error |
304
+ | **`data:`** | The subject itself, shared by systems | An error |
305
+ | **`hm3:` / `sohl:`** | The subject as one system's documents | An error |
306
+
307
+ The complete top-level allowlist, in its required order, is `shortcode`, `name`,
308
+ `type`, `subType`, `description`, `tags`, `data`, `hm3`, `sohl`. Omit fields that
309
+ do not apply. The first fields of a typical note look like this:
310
+
311
+ ```yaml
312
+ ---
313
+ shortcode: example
314
+ name: { full: Example }
315
+ type: lore
316
+ subType: concept
317
+ description: A short page description.
318
+ tags: [draft]
319
+ data: {}
320
+ sohl: {}
321
+ ---
322
+ ```
323
+
324
+ Any other top-level key is an error. To keep an author-only note beside the
325
+ content, comment out its YAML lines with `#`. Comments remain valid YAML text
326
+ and do not become data in the JSONL index, generated pages, or Foundry documents.
327
+ There is no custom site metadata. `description` supplies the page's short
328
+ description; `tags` carries publication and classification tags.
329
+
330
+ Arrays and objects use one-line YAML flow form when the complete line is under
331
+ 100 characters. At 100 characters or more, they use full block form. A multiline
332
+ flow collection is formatted into one of those forms; folded collections are
333
+ not used. `package-build format --write` applies this rule and the top-level
334
+ order without changing the Markdown body.
335
+
336
+ #### Dates and calendars
337
+
338
+ A calendar names and orders the days within a year. An **era** states where its
339
+ year count begins. Several eras may use one calendar; each has its own origin.
340
+ Every frontmatter date uses either a canonical day or a named calendar date:
341
+
342
+ ```yaml
343
+ data:
344
+ born: datefrom vrcal 23 Taranis 326 VR
345
+ died: ~326.114
346
+ ```
347
+
348
+ `datefrom` takes a calendar shortcode or Address, followed by a date in that
349
+ calendar. The date may name a day and month (`23 Taranis 326 VR`), a month and
350
+ year (`Taranis 326 VR`), or a year (`326 VR`). Its era label is required when
351
+ the calendar has several eras. Month names and their abbreviations come from
352
+ the calendar note; an era's `abbreviation`, `name`, marker, or shortcode names
353
+ it. The conversion retains year or month precision when no exact day is given.
354
+ A 24-hour time follows an exact day, such as `23 Taranis 326 VR 14:30:05`.
355
+
356
+ The calendar-neutral spelling is `<year>.<day>[:HHMMSS]`: `720.136` names day
357
+ 136 of canonical year 720, and `720.136:143005` adds 14:30:05. The dot means
358
+ **day of year**, not a month. Its day must fit the world's `data.year.days`,
359
+ and its time must fit a 24-hour day. A negative canonical year is valid.
360
+ Prefix either form with `~` to mark it approximate, as in `~326.114` or
361
+ `~datefrom vrcal 23 Taranis 326 VR`. `unknown` states that an occurrence is
362
+ known but its date is not recorded, in fields that permit it. No other
363
+ frontmatter date form is valid.
364
+
365
+ `package-build datefrom <calendar> <date>` prints the canonical day for a
366
+ calendar date. `package-build dateto <calendar> <canonical-date>` prints the
367
+ calendar date in the era covering that day. The calendar argument is its
368
+ shortcode or Address. `datefrom` reads the named calendar form; `dateto` prints
369
+ its named month and era. A gap between eras has no calendar date to print.
370
+ Both commands read the current note tree and print only the converted value
371
+ on success.
372
+
373
+ An era's `start` locates its first year on the canonical timeline. It may be
374
+ written canonically, such as `start: 1.1`, or in another declared era. The build
375
+ resolves these dependencies across calendar notes and reports cycles, unknown
376
+ eras, and dates outside an era's `end`. A year-precision start anchors the
377
+ reckoning at the first day of that canonical year for conversion; it does not
378
+ date the event that gave the era its name. A marker, when declared, is unique
379
+ across the corpus and contains uppercase letters or digits, beginning with a
380
+ letter. A
381
+ year-precision end includes the whole named year. Month and day values are
382
+ bounded by the named era's calendar months; canonical dates remain
383
+ calendar-neutral.
384
+
385
+ Eras in one calendar are listed by increasing `start`. The first era also
386
+ names years before its start with negative numbers: if its year 1 begins in
387
+ canonical year `-200`, its year `-100` corresponds to canonical year `-300`.
388
+ Later eras count forward from their own year 1; a negative year in a later era
389
+ is refused. The next era's start closes the preceding era on the day before it
390
+ begins. An explicit earlier `end` leaves a gap, whose dates have no era in
391
+ that calendar. The authored note dates and the neutral timeline follow this
392
+ rule; the compiled calendar definition retains the format required by its
393
+ Foundry consumers.
394
+
395
+ An era is addressed as `<calendar shortcode>.<era shortcode>` where a
396
+ qualified reference is needed. A full calendar Address works when calendar
397
+ shortcodes occur in more than one package. An ambiguous reference is an error.
398
+ The content index and generated page
399
+ frontmatter carry `resolvedDates` for a being's `born` and `died`, and for a
400
+ lore note's `data.event.when` and `until`. Each record retains the authored
401
+ `text`, precision, approximation, and, when known, its canonical year, day,
402
+ span, and sort value. An `unknown` date has no sortable year. A labelled era
403
+ also supplies `prose` for display; otherwise `prose` is null.
404
+
405
+ To print a date in another calendar, select the single era whose span contains
406
+ the date. Dates before the first era's start select that era's negative count.
407
+ A gap selects no era and keeps the authored date. Overlapping era declarations
408
+ are an error. The printable form keeps year, month, day, and `~`
409
+ at the precision the author supplied. Years on either side of an era's origin
410
+ are `-1` and `1`; there is no year zero. A page's infobox displays the date in
411
+ its authored era, using that era's prose label when one is declared.
412
+
413
+ A being or place may set `data.calendar` to the Address of a `lore` note with
414
+ `subType: calendar`. A being's infobox prints its dates in the era active in
415
+ that calendar; a place's infobox prints its `present` there. When no calendar
416
+ is named, it prints the date's authored era.
417
+ Clock time is preserved in either form.
418
+
419
+ Markdown prose accepts inline `{{...}}` expressions. A property path reads
420
+ the note's frontmatter, and `dateformat` converts a date to the reckoning used by a
421
+ specified calendar. The first argument names a calendar note by shortcode or
422
+ Address, or one of its eras by `<calendar>.<era>`. Use a full Address when a
423
+ short name is ambiguous. The second argument is a canonical date or a
424
+ frontmatter property holding a date. A gap in the calendar's eras prints the
425
+ canonical date. An explicit era that does not cover the date is an error.
426
+
427
+ ```markdown
428
+ {{name.full}} was born on {{dateformat "vrcal" data.born}}.
429
+ {{dateformat data.calendar data.born}}
430
+ The event fell on {{dateformat "vrcal" "300.25"}}.
431
+ {{and (gt 3 5) (lt 4 2)}}
432
+ {{sql "SELECT COUNT(*) FROM notes WHERE type = 'being'"}}
433
+ {{words (sql "SELECT COUNT(*) FROM notes WHERE type = 'being'")}}
434
+ {{digits (sql "SELECT COUNT(*) FROM entries")}}
435
+ {{gt (sql "SELECT COUNT(*) FROM notes WHERE type = 'being'") 10}}
436
+ ```
437
+
438
+ The comparison helpers `gt`, `gte`, `lt`, and `lte` take two numbers; `eq`
439
+ compares two values. `and`, `or`, and `not` combine Boolean results. Nested
440
+ helpers use Handlebars parentheses. These expressions are replaced on web,
441
+ Foundry, and book pages. Code spans and fences remain literal, as do Hugo
442
+ shortcodes such as `{{< glyph slug="bes" >}}`. Prefix `\{{` to show an
443
+ expression literally in prose. A missing property, unresolved
444
+ calendar, or invalid date produces a located build error.
445
+
446
+ `sql` runs a quoted SQL query against the same `notes` index used by SQL
447
+ tables. It returns one scalar value, so the query must produce exactly one
448
+ row and one column. It can be nested inside comparisons. A query returning
449
+ zero rows, several rows, several columns, or a null or empty value is an error
450
+ at the expression. `sql` preserves the value's numeric type for comparisons.
451
+ Wrap a whole number in `words` to write American cardinal prose, including
452
+ hyphenated compounds such as **forty-five**. Wrap a number in `digits` to
453
+ group thousands, such as **12,345**. A decimal can use `digits`; `words`
454
+ requires a whole number. An unwrapped `sql` result prints its ordinary value.
455
+ The query is evaluated once per unique SQL string in a note. The returned
456
+ value is the same in web, Foundry, and book output.
457
+ Dependency indexes are available through their attached package schemas,
458
+ just as they are in SQL tables.
459
+
460
+ **`data:` is closed.** It holds the type-specific facts about the
461
+ subject — a weapon's weight, an affliction's transmission, a being's species —
462
+ and every note type declares which keys it may carry. A misspelled key is a
463
+ finding in either region.
464
+
465
+ Every note type may put a pinned document id in `data.id`, a shared compendium
466
+ route in `data.pack`, a shared folder Address in `data.packFolder`, and source
467
+ details in `data.harnworld`. A system block's `pack` or `packFolder` overrides
468
+ the shared value for that system. A `being` may also describe its social profile
469
+ in `data.social`.
470
+
471
+ **Tags are open, except the ones that classify.** A tag naming a theme or a region — `underworld`,
472
+ `byzaria`, `riverlands` — is the author's own and this build has no opinion about it.
473
+ A tag that classifies the subject is different, because something queries it. A
474
+ settlement tagged `village` appears in the list of villages and an untagged one
475
+ does not, so `vilage` does not merely look wrong: it removes the note from an
476
+ index, silently, and the index still renders. That is the same failure a
477
+ misspelled `data:` key is, and it gets the same answer — the vocabulary
478
+ is declared, so a near miss is a finding that names what you probably meant.
479
+
480
+ | group | applies to | tags |
481
+ | ------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
482
+ | **place kind** | `place` / `settlement` | `city`, `city-state`, `town`, `village`, `settlement`, `port`, `fortress`, `citadel`, `castle`, `stronghold`, `garrison`, `camp`, `oasis`, `waypoint`, `post`, `precinct`, `district`, `necropolis`, `hall`, `capital` |
483
+ | **place character** | `place` | `fortified`, `temple`, `market`, `trading`, `merchant`, `mining`, `fishing`, `naval`, `military`, `imperial`, `provincial`, `coastal`, `river`, `lakeside`, `hill`, `mountain`, `valley`, `forest`, `woodland`, `inland`, `island`, `frontier`, `border`, `craft`, `caravan`, `pilgrimage`, `holy`, `sacred`, `free`, `ford`, `portage`, `pass`, `well` |
484
+ | **place scale** | `place` / `region` | `continent` |
485
+ | **being station** | `being` | `tradesfolk`, `common-folk`, `soldiery`, `administration`, `clergy`, `mages`, `underworld`, `dependents`, `guilded`, `unguilded` |
486
+ | **being kind** | `being` | `character`, `creature` |
487
+ | **state** | any | `draft` |
488
+ | **audience** | any | `gm` |
489
+
490
+ `gm` marks a note for GM access. It is excluded from public site pages, SQL
491
+ tables on the public site, and the book. Foundry compiles it only to private
492
+ compendiums. A regular note cannot link to it; a GM note can.
493
+
494
+ **`draft` marks unfinished content.** It does two things and nothing
495
+ else: a link _into_ a draft note renders marked, and the note itself states that
496
+ it is unfinished wherever a reader meets it.
497
+
498
+ A link into it renders as
499
+ `<span class="sohl-draft-link" title="Draft — not yet written">…</span>` in a
500
+ compiled journal and on the website alike, with the appearance supplied by the
501
+ consuming system's stylesheet or the site theme.
502
+
503
+ The note's own statement is the **draft notice**, and it reads the same on every
504
+ surface:
505
+
506
+ > **Draft.** This entry is unfinished. What it states may change, and nothing in
507
+ > it is settled.
508
+
509
+ It leads the entry — above the infobox and above the prose — because a reader
510
+ deciding whether to rely on an entry has to be told before they read it. Where
511
+ it lands:
512
+
513
+ | Surface | What carries it |
514
+ | ------------ | -------------------------------------------------------------------- |
515
+ | JournalEntry | a `blockquote.sohl-draft-notice` at the head of the first page |
516
+ | Actor | the dossier the sheet draws — SoHL's `dossier`, HM3's `biography` |
517
+ | The book | a drawn mark and a left rule between the entry's plate and its panel |
518
+ | The website | the theme's own notice, above the infobox rail and the prose |
519
+
520
+ **An Actor carries it once.** A being's prose reaches two fields, and a document
521
+ saying the same thing twice teaches a reader to skip it — a printed sheet draws
522
+ both in turn — so the dossier carries the notice and the appearance is
523
+ untouched. The two systems pair by the authored section each field reads rather
524
+ than by its name, so `dossier` and `biography` carry it while `appearance` and
525
+ `description` do not.
526
+
527
+ **The mark is a notice, not a hazard** — `fa-circle-exclamation`, which Foundry
528
+ bundles, so a journal page carries it with no module stylesheet loaded. The book
529
+ draws its own, because a consumer declaring no icon family is the ordinary case
530
+ and a glyph a face lacks is silent in Typst. Stripped of mark and rule the
531
+ sentence still states the whole thing, which is the floor every surface is built
532
+ to.
533
+
534
+ **Nothing else changes**: the note compiles, validates, publishes and resolves
535
+ exactly as any other, and it stays in the packs, in the link manifest and on the
536
+ site. The `draft` tag marks the content for readers without making its Address unresolvable.
537
+
538
+ **The group's scope is what makes the check work.** A group names the types it
539
+ applies to, and a place's kinds are only ever checked on a place. Without that
540
+ the rule is wrong on every note it touches: `azravan` on a faith, `barter` on an
541
+ economy note and `secret` on three lore notes each sit a typo's distance from
542
+ `caravan`, `border` and `sacred`, and none of them is a mistake. Scoped, the same
543
+ corpus reports nothing — while a settlement tagged `vilage` is still caught, even
544
+ beside a correct `town`.
545
+
546
+ **Kind and character are separate because one slot could not hold both.** A
547
+ fishing village is a `village` that is `fishing`, and the field this list
548
+ replaced had to spell that `Fishing Village` as a value of its own — which is why
549
+ a query for villages found two of the eleven that existed.
550
+
551
+ **A station is not a rank.** Which kind of body a person belongs to — the
552
+ clergy, the soldiery, the tradesfolk — is a different axis from where they stand
553
+ inside one, which is what `data.lore` carries by naming the rank. A tag holds the
554
+ first because a person may be several at once and because nothing ranks
555
+ `clergy` against `mages`.
556
+
557
+ **A continent is a region carrying a tag, not a subtype**, because structurally it
558
+ is a region: the same fields, the same parent chain, everything but scale.
559
+
560
+ #### A being's kind is the one tag group that is a slot
561
+
562
+ A person and a beast carry very different amounts of data, and a being note says
563
+ which it is with a tag rather than leaving it to be guessed from how much the
564
+ note holds.
565
+
566
+ | `beingKind` value | the subject is |
567
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
568
+ | `character` | a person — someone with a name, a station, a history, and the attributes, skills and carried gear that go with living a life |
569
+ | `creature` | a beast, a monster or a made thing — statted for what it does rather than for who it is, and usually carrying no equipment and no affiliation |
570
+
571
+ **The slot is filled once, or not at all.** The two are alternative answers to
572
+ one question, so a note carrying both has answered it twice and every reader of
573
+ the tag gets to pick; that is refused as an error naming the note. A being
574
+ carrying **neither** is not a finding. The kind is authored deliberately, and
575
+ nothing can tell a being nobody has classified from one whose author means to
576
+ leave it unclassified — a tree part-way through tagging is a tree with untagged
577
+ beings in it, and failing its build would be a rule about the schedule rather
578
+ than about the content.
579
+
580
+ **Nothing else fills the slot, and nothing else is refused for failing to.** A
581
+ being tagged `undead` or `beast-of-burden` is describing the subject in the
582
+ author's own words, in a region that is open, and this build has no standing to
583
+ take that back. What is refused is a near miss of one of the two — `charcter`
584
+ drops the note out of every query for characters while the list still renders —
585
+ and the two of them together. Those are the only two refusals a closed tag
586
+ vocabulary can make here, and both are errors, so a build carrying one fails.
587
+
588
+ **The same two words name two other things, and neither is this.** `hm3.type`
589
+ takes `character` or `creature` as the HM3 _document_ a being compiles to, in a
590
+ system block rather than in `tags:`. The note itself uses `type: being`; `type: character` is refused.
591
+ Three namespaces, one pair of words; the tag is the system-agnostic one, and it
592
+ is the only one that describes the subject rather than a document.
593
+
594
+ **Neither the book nor the website branches on the kind.** One flow serves both
595
+ — the authored image, then the infobox, then the prose — and a creature's box is
596
+ simply shorter, because it has less in it. A section with nothing to put in it is
597
+ not emitted at all, so a creature with no equipment has no `EQUIPMENT` heading
598
+ rather than an empty one, exactly as rule 4 of [the infobox](#the-infobox) drops
599
+ a row with no value. The tag classifies the subject, which is what makes a list
600
+ of the creatures in a setting a thing a query can ask for; it is not an
601
+ instruction to a renderer.
602
+
603
+ The mapping tables below describe the **document** destinations. A key that
604
+ appears in no table still reaches the web page; it simply reaches no Foundry
605
+ document.
606
+
607
+ **The shared source is a default; the system block overrides it.** Where a field
608
+ can be reached two ways — `data.weight` through the mapping table, or
609
+ `sohl.system.weightBase` written directly — the value in the system block wins.
610
+ `data:` says what is true of the thing in general; a system says what is true of
611
+ it _there_, and a system that disagrees is not in error. A weapon weighs what
612
+ `data.weight` says unless SoHL says otherwise, and then SoHL is right for SoHL.
613
+
614
+ This is the same rule as `hm3.type` overriding a derived document type, applied
615
+ to fields: derive from the shared source, and let the system state the exception.
616
+
617
+ **A shared source and an in-block key are separate declarations.**
618
+ `data.gender` is the shared source for HM3 gender; `hm3.gender` is its
619
+ in-block spelling, and `hm3.system.gender` is the native destination.
620
+ The native destination wins, followed by the in-block value and the shared
621
+ source. Field declarations name both source and destination explicitly.
622
+
623
+ **A field without a shared source reads its system declaration.** An
624
+ `affiliation` item's `system.title` is the style of address an office carries;
625
+ the note's heading reads `name.full`. The field reads
626
+ `<system>.system.<field>` and the in-block position, which describe the
627
+ document. Native species is independent of the shared lore Address in `data.species`.
628
+ `subType` has the same meaning at both levels.
629
+
630
+ **Some of a system's fields are runtime state, and a note may not write any of
631
+ them.** A data model declares everything a document stores, and part of that is
632
+ what _play_ writes: an affliction's `onsetDate` is the world time its onset
633
+ fired at. Writing `<system>.system.<field>` reaches such a field as directly as
634
+ any other — the block is a verbatim passthrough, and the field really is in the
635
+ schema — so a note left free to stamp one ships that world's play state to
636
+ every world that installs the compiled pack.
637
+
638
+ So a field the document writes for itself is **declared as such**, and the
639
+ declaration says both halves of the fact: authoring it is an error naming the
640
+ note, the key and what the field holds, and the compiled document leaves the key
641
+ out entirely so the data model's own initial value stands. This is a property of
642
+ the field rather than a list of names, so it covers every such field a system
643
+ adds later. The per-type tables below never list one — they are the vocabulary
644
+ an author writes — and each type that has any names them under its table.
645
+
646
+ **An `Address` becomes a `Shortcode` where the target field expects one.** SoHL
647
+ resolves a stored cross-reference among the items one actor holds, and packages do
648
+ not exist there, so the field holds a `Shortcode`. Four `affiliation` fields are
649
+ authored as Addresses and stored that way: `data.seat` becomes `system.seat`,
650
+ `data.parents` becomes `system.parents`, `data.domains` becomes `system.domain`,
651
+ and each authored key of `data.relations` becomes a key of `system.relations`. The
652
+ reduction happens at build time, and two things are errors naming the note: an
653
+ Address whose type the field does not accept, and two Addresses that reduce to one
654
+ shortcode, which the field cannot hold both of.
655
+
656
+ **A SoHL skill Address becomes a native Item UUID.** An affiliation's
657
+ `sohl.system.commonSkills` names skills in its own package or a declared
658
+ dependency. Its `system.commonSkills` contains the exact published Item UUIDs,
659
+ including each foreign module's own pack name. Native skill lookup uses those
660
+ UUIDs; the journal, website and book use documentation links for presentation.
661
+
662
+ **A `Code` suffix marks a different thing.** `system.assocSkillCode`,
663
+ `system.assocAffiliationCode` and `system.parentSkillCode` hold a `Shortcode` at
664
+ both ends — each is authored as one, under `<system>.system`, and emitted verbatim
665
+ — so no Address is reduced into them and none of them has a shared source. A value
666
+ carrying a separator is refused there by the shortcode charset rather than read as
667
+ an Address. `data.assocSkill`, `data.assocAffiliation` and `data.parentSkill` are
668
+ properties of the **page**: each is an Address naming the note an infobox row
669
+ links to, and none reaches a compiled document.
670
+
671
+ ### The note vocabulary, and how it maps
672
+
673
+ A note has its own `type` and `subType`. **This is a third vocabulary** — not
674
+ SoHL's and not HM3's. It says what the note _is_ and the scope it covers,
675
+ irrespective of whether it becomes an Actor, an Item, a JournalEntry or nothing
676
+ at all in any particular system.
677
+
678
+ Each system then declares a map from the note's `(type, subType)` onto its own
679
+ document type — and, for SoHL, its own `system.subType`. The map is **declared**,
680
+ never inferred from a coincidence of names: `skill`, `weapongear`, `armorgear`,
681
+ `containergear` and `miscgear` exist in both systems with _different_ data
682
+ models, so name-matching there would not fail, it would succeed wrongly.
683
+
684
+ Because the map derives the document type, `hm3.type` and `sohl.system.subType`
685
+ are **overrides**, not required declarations. A note states them only when the
686
+ map cannot decide, or decides wrongly.
687
+
688
+ **Four rows cannot decide, and there `hm3.type` is required.** HM3 splits four
689
+ of the note vocabulary's types across several documents — `mysticalability` into
690
+ a `psionic`, a `spell` or an `invocation`; `trauma` into an `injury` or a
691
+ `trait`; `weapongear` into a `weapongear` or a `missilegear`; `being` into a
692
+ `character` or a `creature`. Nothing in the note's own vocabulary partitions
693
+ cleanly onto any of those splits, so the note says which, in its own block, and
694
+ a note that says nothing is **an error naming the note and listing the permitted
695
+ values**. It is never defaulted: a default would pick one and be right about
696
+ half the time.
697
+
698
+ A consequence worth knowing before you author a being's embedded items: a
699
+ `(type, shortcode)` reference has no block of its own to read a discriminator
700
+ from, so it **cannot address a one-to-many type**. `[[weapongear-spear]]` names
701
+ no single HM3 document, and the reference is refused rather than resolved to
702
+ whichever came first.
703
+
704
+ A note whose `(type, subType)` has no mapping for a system it carries a block
705
+ for is an **error naming the note**, never a silent skip and never a guess at the
706
+ first matching value. Where a mapping is missing for a whole class of note, that
707
+ is a gap in the vocabulary rather than something to write into every note: an
708
+ override that thousands of notes need is a missing subType value.
709
+
710
+ **A type's `subType` values are stated in one shape**, so that they can be read
711
+ and compared to the vocabulary that enforces them. Under the type's
712
+ heading, write `**subType**:` on a line of its own, then one bullet per value —
713
+ `- <value>` or `- <value>: <definition>` — and nothing else between the marker
714
+ and the list. A type that has no `subType`, or whose values are not enumerated
715
+ yet, writes no marker at all. Any other spelling is a build error naming the
716
+ line: five were in use, and a reader that accepted them all would keep accepting
717
+ the sixth by reading a section as declaring nothing, which is exactly the drift
718
+ the comparison exists to catch.
719
+
720
+ ### Mappings every type shares
721
+
722
+ Eight rows were identical in all sixteen tables below, so they are stated once
723
+ here and omitted there. Each per-type table shows only what is particular to that
724
+ type.
725
+
726
+ | shared source | → sohl | → hm3 |
727
+ | ----------------------- | ------------------------- | ---------------------------- |
728
+ | `name.full` | `name` | `name` |
729
+ | `data.icon` | `img` | `img` |
730
+ | `data.id` | `_id` | `_id` |
731
+ | `data.packFolder` | `folder` | `folder` |
732
+ | `shortcode` | `system.shortcode` | NA |
733
+ | `data.templatePriority` | `system.templatePriority` | `flags.hm3.templatePriority` |
734
+ | `actionDefs` | `system.actionDefs` | NA |
735
+ | `notes` | `system.notes` | `system.notes` |
736
+
737
+ **A column reads NA wherever the type produces no document in that system.** An
738
+ `affiliation` has no HM3 form, so its whole HM3 column is NA; `armorlocation` has
739
+ no SoHL form, so its SoHL column is. Nothing else varies — which is why these
740
+ rows are worth stating once: repeated sixteen times they buried the differences
741
+ that matter.
742
+
743
+ **`data.icon` maps onto a document that has one, and not every type's does.**
744
+ The row is shared because the key is legal on every note whatever its type, not
745
+ because every document carries artwork: `doc`, `place`, `lore` and `scenario`
746
+ compile into a JournalEntry, which has no image of any kind, and a `folder`
747
+ compiles into a Foundry `Folder`, which has none either. A `homepage` compiles
748
+ into no compendium document at all. On any of those the mapping has no
749
+ destination, so the resolved value is simply dropped — the note validates, the
750
+ tree compiles, and the value goes nowhere.
751
+
752
+ **What a note writes is an address; what a document carries is a path.**
753
+ `data.icon` is an `Address` defaulting to type `icon`, so a being writes
754
+ `icon: anvil` and never a file name. The compiler resolves that address to the
755
+ file the owning package ships and writes the resulting path into `img`. The two
756
+ sides of this row are therefore in two different currencies, which is why the
757
+ row reads `data.icon → img` rather than naming one thing twice.
758
+
759
+ Since a dropped value looks exactly like a value never written, the frontmatter
760
+ lint **reports it**: an art key authored on a type whose passes emit none is a
761
+ warning naming the note and the key. A warning rather than an error, because the
762
+ note still compiles correctly and the key is not certainly unwanted — a note's
763
+ top level is the generated page's front matter as well, so a site template may
764
+ read there what no document carries. `null` is never reported: that is the
765
+ blessed way to say "this note names no art", and on a type with no art it is a
766
+ true and harmless thing to say.
767
+
768
+ Which types those are is **not a list**. It is asked of the passes: a type routes
769
+ to a document, a document to the pass that compiles it, and each pass declares
770
+ the art it writes (`emitsArt`). A second table of iconless types would be a table
771
+ free to drift from what is actually emitted, which is the defect rather than the
772
+ check.
773
+
774
+ #### The four art slots
775
+
776
+ A note declares the art its document needs, and every other image in it is
777
+ inline. All four slots are ordinary `Address` fields declaring a default type,
778
+ exactly as `seat` declares `place` — a bare shortcode takes its type from the
779
+ declaration, and qualification climbs the same short-form ladder every other
780
+ link uses.
781
+
782
+ | field | default type | accepts | resolves into | on |
783
+ | ----------- | ------------ | --------------- | ---------------------------- | ----------------------------------------- |
784
+ | `icon` | `icon` | `icon`, `image` | `document.img` | every Actor/Item type, and embedded items |
785
+ | `tokenIcon` | `icon` | `icon`, `image` | `prototypeToken.texture.src` | Actor types |
786
+ | `bgImage` | `image` | `icon`, `image` | `background.src` | map types |
787
+ | `banner` | `image` | `icon`, `image` | the site hero image | any note; not a Foundry field |
788
+
789
+ All four are authored under `data:`:
790
+
791
+ ```yaml
792
+ data:
793
+ icon: anvil
794
+ banner: packagebuild-none-image-skillbnr
795
+ ```
796
+
797
+ **Every slot accepts a second type beyond the one it defaults to**, and the two
798
+ defaults pair with the same accepted set: `icon` and `tokenIcon` default to
799
+ `icon`, `bgImage` and `banner` default to `image`, and all four accept `icon`
800
+ **and** `image` regardless of which one they default to. A bare shortcode still
801
+ takes the slot's default — `icon: anvil` above names an `icon`, not an `image`
802
+ — and it is a **qualified** value, one that states its own type, that the
803
+ accepted set governs. `sohl-kethira-basic` writes an `icon` slot this way
804
+ twenty times, once per faith tradition, because a deity's profile art is a full
805
+ illustration rather than a game icon:
806
+
807
+ ```yaml
808
+ data:
809
+ icon: image-kpagrik
810
+ ```
811
+
812
+ That value is accepted because `image` is in the slot's accepted set, not
813
+ because it matches the default — `icon` still defaults to `icon`. `audio` is
814
+ refused at all four slots: a sound is not art, and a value naming one — `icon:
815
+ audio-thunder`, say — is an error naming the set the slot accepts, never a
816
+ silent fall back to the note's default art.
817
+
818
+ **These properties are legal on _every_ note**, whatever its type. `data:` is a
819
+ closed container and the per-type vocabularies below are the only lists there
820
+ are, so a key legal everywhere is stated once here rather than repeated in
821
+ twenty-five tables where the one that was mistyped would be the one nobody
822
+ noticed:
823
+
824
+ | shared `data` property | Values | Description |
825
+ | ---------------------- | ---------------------- | -------------------------------------------------------------------- |
826
+ | `id` | `string` | A pinned document identity. |
827
+ | `pack` | `string` | The shared compendium route. |
828
+ | `packFolder` | `Address` or pack map | The shared compendium folder Address. |
829
+ | `harnworld` | `Map<string, unknown>` | HârnWorld source details. |
830
+ | `icon` | `Address` | The document's profile art — an `icon` address, resolved into `img`. |
831
+ | `banner` | `Address` | The page's hero image — an `image` address. |
832
+
833
+ `icon` is legal everywhere because most types compile into a document that
834
+ carries one; where a type's passes emit none, the lint says so and the value is
835
+ left alone, because a page template may still read it. `banner` reaches no
836
+ compiled document at all, and a page is what every note publishes.
837
+
838
+ The other two are narrower, and each type's own table names them: `tokenIcon`
839
+ on the Actor types, `bgImage` on a map.
840
+
841
+ **A portrait is not one of them.** A picture of the subject is a picture, so it
842
+ is authored in the prose that describes the subject, as an ordinary embedded
843
+ image — see [the lead image](#the-lead-image). A slot exists for art a
844
+ _document field_ needs; nothing else earns one.
845
+
846
+ **A reference carries no slashes and no extension.** That is what keeps it from
847
+ being read as a file name, and it is checked: a value shaped like a pathname is
848
+ an error naming the note and the key.
849
+
850
+ `tokenIcon` unset defaults to `icon`. A token has to read at grid scale and when
851
+ a map is zoomed out, so it is an `icon` rather than an `image`, and its fallback
852
+ has to be `icon`-typed too.
853
+
854
+ Actor types (`being`, `vehicle`) therefore add one row:
855
+
856
+ | shared source | → sohl | → hm3 |
857
+ | ---------------- | ---------------------------- | ---------------------------- |
858
+ | `data.tokenIcon` | `prototypeToken.texture.src` | `prototypeToken.texture.src` |
859
+
860
+ An actor's two pieces of art are two different questions. `data.icon` is the
861
+ profile art — what a directory listing shows beside the name and what the sheet
862
+ header carries. `data.tokenIcon` is what a token on the canvas wears. Both are
863
+ addresses and both resolve the same way; only the destination differs. An Item
864
+ has one piece of art, so this row applies to actor types alone.
865
+
866
+ **A `data:` source is still read at the top level, for now.** `data:` did
867
+ not invent the facts it holds — it gathered them out of the top level — so every
868
+ key it collected has a **pre-`data:` spelling** that is read after the declared
869
+ one and reported as retiring. This is the shared level's counterpart to the
870
+ in-block `<system>.<key>` retirement, and the two are separate: a note may have
871
+ moved one and not the other.
872
+
873
+ **No image key is one of those.** A top-level `img:` is not authored at all:
874
+ `data.icon` is a different key holding a different kind of value, and a note
875
+ writing `img:` is naming a file where an address belongs. `portrait:` is not authorable at any level; a portrait is an embed in the body.
876
+
877
+ **Two of the eight are Item-only in SoHL.** `actionDefs` and `notes` are declared
878
+ on every SoHL Item subtype and on no SoHL Actor, so on a `being` or a `vehicle`
879
+ the SoHL column of both reads NA.
880
+
881
+ `notes` is also the one row that is emitted rather than mapped: SoHL writes
882
+ `system.notes` empty on every Item, and no note-level key fills it yet. The row
883
+ states where such a key would land, which is what makes `armorlocation`'s
884
+ exception below sayable at all.
885
+
886
+ **A third SoHL Item mapping has no shared source, so it is not a row.** SoHL
887
+ writes `system.docHtml` on every Item from the note's own prose — the UUID of the
888
+ JournalEntry that prose compiled into, which is derived rather than authored.
889
+ HM3's data model has nowhere to put such a pointer, so the same prose reaches an
890
+ HM3 item only as its own journal.
891
+
892
+ **One exception.** HM3's `armorlocation` declares no `notes` — it is the one
893
+ subtype that extends the Foundry base directly with no templates — so `notes` is
894
+ NA on both sides for that type, and its table says so.
895
+
896
+ **And one divergence, tracked rather than specified away.** HM3 records a
897
+ template priority on an Actor and not on an Item: `hm3/actors.mjs` writes
898
+ `flags.hm3.templatePriority`, and HM3's Item pass writes no equivalent, so an HM3
899
+ item compiled from a template note loses the fact that it is one
900
+ The row states the mapping the format makes;
901
+ the gap is in the pass, not in the table.
902
+
903
+ #### A pathname names the package that owns the file
904
+
905
+ A note names a file once, and four surfaces have to serve it: a Foundry
906
+ install, this repository's own working tree, the website, and the book. Each
907
+ addresses the same file differently, so an authored pathname is a **statement
908
+ of ownership** and every surface derives its own address from it.
909
+
910
+ **The first segment names the package, when an `assets/` follows it.**
911
+ Everything after that `assets/` is the _suffix_ — the path inside the package's
912
+ shipped tree, and the one piece every derived form is built from. A pathname
913
+ that does not open `<package>/assets/` belongs to the package being compiled,
914
+ and the whole of it is the suffix.
915
+
916
+ ```markdown
917
+ ![A map of the Vale](images/maps/valeofthorns.webp) <!-- this package's -->
918
+ ![A shield](sohl/assets/icons/noun/shield.svg) <!-- the sohl package's -->
919
+ ```
920
+
921
+ The four forms, for a `harnensemble` note (the `harnensemble` content package
922
+ ships as the Foundry module `harn-ensemble`) writing `images/map.webp`:
923
+
924
+ | Surface | Address |
925
+ | ----------- | ---------------------------------------------------------- |
926
+ | **Foundry** | `modules/harn-ensemble/assets/images/map.webp` |
927
+ | **Local** | `assets/images/map.webp` |
928
+ | **Web** | `https://cdn.heroiclands.org/harnensemble/images/map.webp` |
929
+ | **Book** | `assets/images/map.webp`, staged beside the Typst source |
930
+
931
+ And for the same note writing `sohl/assets/icons/noun/shield.svg`, a file the
932
+ system ships and this repository does not hold:
933
+
934
+ | Surface | Address |
935
+ | ----------- | --------------------------------------------------------- |
936
+ | **Foundry** | `systems/sohl/assets/icons/noun/shield.svg` |
937
+ | **Local** | `assets/icons/noun/shield.svg`, in the `sohl` repository |
938
+ | **Web** | `https://cdn.heroiclands.org/sohl/icons/noun/shield.svg` |
939
+ | **Book** | not carried — a build stages only what this package ships |
940
+
941
+ **The content package name and Foundry id are separate values.**
942
+ `harnensemble` is what the content is called, what a note writes, and what the
943
+ website serves it under.
944
+ `harn-ensemble` is what Foundry installs the module as, and it appears in the
945
+ Foundry form alone. The two values happen to match for `thalorna`, `sohl`, and
946
+ `hm3`.
947
+
948
+ Which packages a build can resolve is derived from its configuration: its own
949
+ `contentPackage`, every game system it compiles content for (`systems:`,
950
+ `packs[].system`, `requiresSystem`, `relationships.systems`), and every package
951
+ it declares under `relationships`, whose `contentPackage` names the content
952
+ where that differs from the Foundry id. A pathname naming a package the build
953
+ does not know still resolves on the website and in the book — those need only
954
+ the name and the suffix — and has no Foundry address, which is refused rather
955
+ than guessed.
956
+
957
+ An art package that replaces another package's images can declare
958
+ `packageBuild.assets[].bindsTo` on the relevant asset copy. Each file's basename
959
+ then acts as a shortcode for the declared package and type. The related
960
+ `relationships.requires[].contentIndex` supplies the address index; with that
961
+ index enabled, an asset with no matching note is a located build error. See
962
+ [asset bindings](../configuration.md#packagebuildstagedir-and-packagebuildassets)
963
+ for the configuration and cache requirements.
964
+
965
+ **The host is configuration.** `site.assets` in `package-build.config.yaml` is
966
+ the root the web form is joined onto, and a package-owned image on a page with
967
+ none set is an error naming that key.
968
+
969
+ **A pathname naming no package at all passes through on every surface** — an
970
+ absolute URL, a `data:` URI, a protocol-relative `//host/…`, or a `/`-rooted
971
+ path, which Foundry serves from its data root. That is how a note addresses
972
+ core Foundry art (`/icons/svg/mystery-man.svg`) or a package this build knows
973
+ nothing of (`/systems/dnd5e/icons/spell.webp`).
974
+
975
+ **A `systems/…` or `modules/…` pathname is refused**, with a finding naming the
976
+ replacement. It is a Foundry address written where an ownership statement
977
+ belongs: it resolves for Foundry and for nothing else, because neither the
978
+ website nor the book has any such directory. `systems/sohl/assets/ui/logo.webp`
979
+ is written `sohl/assets/ui/logo.webp`, and `systems/hm3/images/svg/sword.svg` —
980
+ where the package serves its pictures from its own root — is written
981
+ `hm3/assets/images/svg/sword.svg`, because `assets/` is where a package's files
982
+ sit in every derived form.
983
+
984
+ `worlds/` is not a Foundry root for this purpose. A package may not ship files
985
+ out of a world, so a note writing one has made a different mistake and gets the
986
+ ordinary "this package owns it" reading — a plainly broken path rather than a
987
+ plausible one that 404s with nothing reporting it.
988
+
989
+ **The rule is about ownership, not a list of directories.** A package owns its
990
+ whole `assets/` tree and the directory names inside it are its own business:
991
+ `sohl-kethira-basic` keeps art under `assets/artwork/`, and `artwork/deity.webp`
992
+ is that package's by the same rule that claims `icons/…` and `images/…`.
993
+
994
+ **Where a pathname is still authored, it follows this rule.** That is the
995
+ address of an image in a note's body, and a map note's `overlay`. The art
996
+ fields do not: `icon`, `tokenIcon`, `bgImage` and `banner` name
997
+ addresses, and the path comes from the record the resolved address points at —
998
+ which already carries the owning package, so there is nothing for a first
999
+ segment to state.
1000
+
1001
+ #### `banner:` reaches no compiled document
1002
+
1003
+ `data.banner` is the page's hero image, and it is the one art slot with no
1004
+ Foundry destination: searching a built `packs-json` tree for it turns up
1005
+ nothing. It reaches the generated page and the book's section plates, and
1006
+ nothing else.
1007
+
1008
+ It is an `Address` all the same, defaulting to type `image`, so a section note
1009
+ writes `banner: skillbnr` and a note borrowing another package's plate writes
1010
+ `banner: packagebuild-none-image-skillbnr`. The resolved record carries the
1011
+ owning package, and each surface joins the suffix onto its own root — the site
1012
+ onto `site.assets`, the book onto the directory it stages banners into.
1013
+
1014
+ **`banner: null` is how a note says it wants no plate.** It is a true statement
1015
+ on any type, and the frontmatter lint never reports it.
1016
+
1017
+ #### The pack a note compiles into
1018
+
1019
+ `data.pack` names which configured compendium receives the note's document.
1020
+
1021
+ ```yaml
1022
+ data:
1023
+ pack: items-hm3
1024
+ ```
1025
+
1026
+ `contentPackage` in the repository configuration names the distribution that owns the note; `data.pack` names the compendium receiving its document.
1027
+
1028
+ **It names the pack for the note's _own_ document.** A document derived from it
1029
+ — an item's prose compiling into a `JournalEntry` of its own — is not what the
1030
+ author was addressing, and is routed by the pass that produces it.
1031
+
1032
+ **`<system>.pack` names the pack for one system's document.** Routing is per
1033
+ system: each block the note carries sends its own document where that block
1034
+ says, and a note carrying two blocks may place them in two packs.
1035
+
1036
+ ```yaml
1037
+ data:
1038
+ pack: items-sohl
1039
+ hm3:
1040
+ pack: items-hm3
1041
+ ```
1042
+
1043
+ **Unstated, a system's document goes to that system's default pack of its
1044
+ class** — the pack of the class declaring that `system:`, or, where none does,
1045
+ the pack of the class marked `default: true`. Where nothing answers, the build
1046
+ refuses rather than guessing, and names the candidates.
1047
+
1048
+ **`pack` and `packFolder` are different questions.** `pack` names _which
1049
+ compendium_ receives a document; `packFolder` names the folder note whose
1050
+ address the document is filed under _within_ whichever compendium that is. They
1051
+ compose, and both are read per system, so a note may place its two systems'
1052
+ documents in different packs and different folders:
1053
+
1054
+ ```yaml
1055
+ sohl:
1056
+ pack: characteristics
1057
+ packFolder: none-folder-miscgear
1058
+ ```
1059
+
1060
+ **`pack: none` compiles the note into no document.** The note is walked,
1061
+ published as a page, present in the content index with an address and no
1062
+ Foundry UUID, and addressable by wikilink like any other; every pack compiler
1063
+ passes over it without a finding, and the check that reports a note nothing
1064
+ compiles says nothing about it. Write it on a page that belongs on the website
1065
+ and in no compendium — a page of developer documentation, a page about the
1066
+ package itself:
1067
+
1068
+ ```yaml
1069
+ shortcode: architecture
1070
+ type: doc
1071
+ subType: concept
1072
+ data:
1073
+ pack: none
1074
+ ```
1075
+
1076
+ A link to such a note resolves everywhere the note is addressable. On the web
1077
+ it is an ordinary link to the page. In a compiled journal there is no document
1078
+ to open, so the reader gets the link's text as prose and no `@UUID` — the
1079
+ mirror of a link into a package that publishes documents and no pages.
1080
+
1081
+ It is accepted only on a type whose **sole document is the JournalEntry its
1082
+ prose becomes** — `doc`, `place`, `lore` and `scenario`. On a type that
1083
+ compiles an Item, an Actor, a Macro, a Scene or an Adventure it is refused by
1084
+ name, because there it would drop the document the type exists to produce;
1085
+ the message names that document. It is read per system like any other
1086
+ `pack:`, so `<system>.pack: none` withholds one system's document while the
1087
+ shared declaration names a pack for the rest, and a configured pack may not
1088
+ be called `none`.
1089
+
1090
+ Four declarations are refused, each with the reason:
1091
+
1092
+ | written | why it is refused |
1093
+ | ------------------------------------------- | -------------------------------------------------------------------------------------------- |
1094
+ | a **companion** pack | A companion is written by another pack's pass, so no note may be routed into one. |
1095
+ | a pack **nothing answers to** | The message lists the configured packs of that document type. |
1096
+ | a pack of **another document type** | A note's `pack:` names a pack of its own document type. |
1097
+ | **`none`** on a type with a second document | The Item, Actor, Macro, Scene or Adventure the type compiles into would be dropped, by name. |
1098
+
1099
+ #### Template priority: which template wins
1100
+
1101
+ A note can mark its document as a **starting template** the Create dialog offers
1102
+ to clone from, so a new being or item is born populated rather than blank. The
1103
+ value is a **priority**, and the priority is the whole mechanism — it decides
1104
+ which of several competing templates a player is actually offered.
1105
+
1106
+ The shared mapping table above names it `data.templatePriority`, targeting
1107
+ `system.templatePriority` in SoHL and `flags.hm3.templatePriority` in HM3.
1108
+
1109
+ > **`archetype` is the retiring spelling.** It is still read, in the `sohl:`
1110
+ > block and at the top level, so a tree sweeps on its own schedule
1111
+ > — but the frontmatter linter refuses it, and
1112
+ > what is compiled and emitted is `templatePriority` on all three sides.
1113
+ >
1114
+ > It is more than a rename, and the collision is **already live** rather than
1115
+ > pending: `archetypes` is specified above as the _sort_ a character is, and a
1116
+ > being's row declares it. So a number deciding which template wins and a list of
1117
+ > what sort of character this is would otherwise be distinguished **only by a
1118
+ > plural `s`**.
1119
+ >
1120
+ > A note carrying both spellings with **different** values is refused rather than
1121
+ > resolved quietly — `templatePriority: null` and `archetype: 0` say opposite
1122
+ > things, and picking either silently would decide it on the author's behalf.
1123
+
1124
+ ```yaml
1125
+ data:
1126
+ templatePriority: 0 # a template, at the priority SoHL's own ship at
1127
+ ```
1128
+
1129
+ ```yaml
1130
+ data:
1131
+ templatePriority: null # not a template
1132
+ ```
1133
+
1134
+ **Every note SoHL compiles into an Item or an Actor must state it.** Absent, the
1135
+ build refuses: "not a template" has to be _said_, not left out, or an omission
1136
+ and a decision look identical. The value is a number or `null`, and **`0` is a
1137
+ real priority** — the one SoHL's own templates ship at — not an absence.
1138
+
1139
+ **Where it lands differs by system, because HM3's data model has no field for
1140
+ it.** SoHL records it in `system`; HM3 keeps it under its own flag scope,
1141
+ `flags.hm3`, and a note that is not a template writes nothing there rather than a
1142
+ `null` nothing reads. Both HM3 Item and Actor passes write their own flag when
1143
+ the note declares a priority.
1144
+
1145
+ **How a winner is chosen.** Opening a Create dialog gathers every candidate
1146
+ across the world and every matching compendium, _including other modules'_. Those
1147
+ are filtered to the `(type, subType)` being created, deduped by **`shortcode`** —
1148
+ a template's stable identity, where the name is only presentation — and one
1149
+ winner is taken per shortcode:
1150
+
1151
+ 1. the highest priority;
1152
+ 2. then the nearest source — **world**, then **system**, then **module** — so a
1153
+ GM's own copy shadows a shipped one at equal priority;
1154
+ 3. then a stable UUID, so the answer never depends on load order.
1155
+
1156
+ **The reserved ranges make a collision predictable.** Two packages can easily
1157
+ ship a template under one shortcode, and the number says which yields:
1158
+
1159
+ | priority | reserved for |
1160
+ | ---------- | -------------------------- |
1161
+ | `0`–`98` | SoHL and HM3 themselves |
1162
+ | `99`–`999` | other HeroicLands packages |
1163
+ | `1000`+ | everyone else |
1164
+
1165
+ HeroicLands reserves everything below `1000`. Since the highest priority wins,
1166
+ **anyone else's template always beats content shipped from here** — which is the
1167
+ point: a module author can override a standard template without coordinating with
1168
+ anybody, and be certain it takes effect.
1169
+
1170
+ #### The document id
1171
+
1172
+ A note's Foundry `_id` is **derived from its canonical address**, and no note
1173
+ needs to write one:
1174
+
1175
+ ```yaml
1176
+ type: miscgear
1177
+ shortcode: bowlcer
1178
+ # no `id:` — the document is filed under makeId("document", "sohl-none-miscgear-bowlcer")
1179
+ ```
1180
+
1181
+ The derivation is exactly:
1182
+
1183
+ ```
1184
+ _id = makeId("document", "<package>-<system>-<type>-<shortcode>")
1185
+ ```
1186
+
1187
+ — the note's canonical address, hashed with SHA-1 and truncated to the 16 hex
1188
+ characters a Foundry id is. Nothing else feeds it.
1189
+
1190
+ **One type hashes under a different namespace, and a consumer must not assume
1191
+ otherwise.** A `Folder` is a document of its own class, and its id is
1192
+ `makeId("folder", <address>)` — a separate namespace so that a folder and an
1193
+ item sharing a shortcode cannot derive one id, which Foundry would not report
1194
+ because it keys folders and documents in separate collections. See
1195
+ [`type: folder`](#type-folder).
1196
+
1197
+ **So the id is transported, not recomputed.** The content index publishes an
1198
+ `id` on every entry it gives an identity to, beside the `uuid` that identity
1199
+ ends in, and both are computed by whatever owns that entry's derivation — the
1200
+ folder pass for a folder, the journals pass for a documentation journal, this
1201
+ rule for everything else. Re-deriving one from the `canonical` key alone reads
1202
+ correctly and is wrong for a folder, and wrong in a way nothing outside the
1203
+ build can detect: the result is a plausible 16-character id that resolves to
1204
+ nothing.
1205
+
1206
+ **The Address determines the default id.** `content-lint` refuses a duplicate
1207
+ Address across the packs of a document type, so the derived id shares that
1208
+ uniqueness scope. An author pins a document's identity with `data.id` when it
1209
+ must remain stable across an Address change.
1210
+
1211
+ **An authored `data.id` wins**, and that is how a document's identity is
1212
+ **pinned**:
1213
+
1214
+ ```yaml
1215
+ shortcode: bowlcer
1216
+ type: miscgear
1217
+ data:
1218
+ id: plaiQQm2T5zVK5mO # pinned: this document keeps this id
1219
+ ```
1220
+
1221
+ A blank `data.id:` is not a pin — it is a deleted value with the key left behind, and
1222
+ is treated as absent.
1223
+
1224
+ **A rename moves the id, and that is the trade.** The address carries the
1225
+ shortcode, so renaming a shortcode gives the document a new `_id`. Two things
1226
+ make that acceptable: a shortcode rename already breaks every wikilink to the
1227
+ note, so it is a breaking change either way; and a note that must keep its
1228
+ identity across one pins its `id`, which is what the pin is for. One diagnostic
1229
+ narrows — `package-build` tells a **rename** from a **withdrawal** by matching
1230
+ document ids across releases, and for an unpinned note both sides now move
1231
+ together. It never reports a _wrong_ successor, and it stays exact for a pinned
1232
+ note; for the rest, the diff reports the vacated address.
1233
+
1234
+ **A note with no address gets no id, and no document.** `type` and `shortcode`
1235
+ are what a note is addressed by, so a note missing either cannot be filed and
1236
+ the build refuses it by that name rather than by a missing `id:`.
1237
+
1238
+ #### Address changes
1239
+
1240
+ `(type, shortcode)` is a published interface. Other packages can link to it or
1241
+ use it as an Item model, so changing either part requires those references to
1242
+ change too. `package-build addresses diff` compares the package with a released
1243
+ baseline and reports addresses whose published documents are absent. A pinned
1244
+ `data.id` preserves document identity across a shortcode change; it does not
1245
+ keep the old address available for links.
1246
+
1247
+ #### The compendium folder
1248
+
1249
+ A note says which folder of its pack it lands in:
1250
+
1251
+ ```yaml
1252
+ data:
1253
+ packFolder: poisonsandtoxins # a folder note's address
1254
+ ```
1255
+
1256
+ **`packFolder` is a folder note's address** — an ordinary address, resolved the
1257
+ way every other reference is, and written in any form [the grammar
1258
+ admits](#shorter-forms). The field supplies the type, so a bare shortcode is a
1259
+ complete address here; `folder-poisonsandtoxins` and the fully qualified
1260
+ `sohl-none-folder-poisonsandtoxins` name the same folder. An address no folder
1261
+ note answers to is a build error naming the folders the package does declare.
1262
+
1263
+ Note this is the _pack_ folder, not the note's directory. The directory is
1264
+ `file.path` / `file.folder`, which a content table reads separately.
1265
+
1266
+ **Where a folder materialises is derived from what references it.** Every pack
1267
+ holding a document that names a folder gets that folder, and its ancestors with
1268
+ it — so a documentation journal is filed beside the item it describes without
1269
+ the journals pack having to declare anything. A folder nothing references
1270
+ materialises nowhere.
1271
+
1272
+ One folder note and one Address supply every pack that references it.
1273
+ `data.packFolder` names a folder by Address. A top-level `folder:` key is
1274
+ refused with a finding that names `data.packFolder` and the authored line.
1275
+
1276
+ #### The knowledgebase category
1277
+
1278
+ `kbcat` names the group a note is listed under on the knowledgebase and the
1279
+ website. It is written in the system block:
1280
+
1281
+ ```yaml
1282
+ sohl:
1283
+ kbcat: poisontoxin
1284
+ ```
1285
+
1286
+ **It compiles into no document.** No pack compiler reads it and no `system`
1287
+ field receives it. It reaches a published page because a note's frontmatter is
1288
+ copied onto that page, where a list layout groups by `sohl.kbcat` — so `kbcat`
1289
+ is the one key in this section that answers _where does this appear_ for the
1290
+ web rather than for Foundry. `pack` and `packFolder` place a document in a
1291
+ compendium; `kbcat` places a page in a list.
1292
+
1293
+ That is also why it is specified here rather than in a type's table. A type's
1294
+ fields say what the **builder** compiles, and `kbcat` is never compiled — but
1295
+ what a note **may write** is broader than what any one consumer reads, and a
1296
+ check that equated the two reported thousands of correctly authored properties
1297
+ as unknown.
1298
+
1299
+ **It is editorial, and deliberately independent of `subType`.** The two are not
1300
+ alternative spellings of one classification and neither is derived from the
1301
+ other. `kbcat` both _subdivides_ a subtype — `trauma`/`physcond` is listed as
1302
+ `physdisability`, `physfeature` or `physprivations` — and _renames_ one for
1303
+ display, as `trauma`/`fear` listed under `phobias`. Most notes that carry a
1304
+ `kbcat` declare no `subType` at all. So the two are stated separately where both
1305
+ apply, and a reviewer should not read a disagreement between them as an error.
1306
+
1307
+ **The value is free-form, and nothing validates it.** There is no configured
1308
+ list of categories. The frontmatter check knows `kbcat` is a key every type may
1309
+ write and says nothing whatever about its value. A layout supplies display
1310
+ titles and an explicit order for the values it knows about, and appends any
1311
+ other value as its own group, titled by humanizing it.
1312
+
1313
+ The consequence is worth stating plainly, because it is the failure mode this
1314
+ key has: **a misspelled category is not a build error and is not dropped — it
1315
+ silently becomes a group of one**, sorted in after the known ones.
1316
+
1317
+ **A note that writes none is dropped from the list entirely.** Grouping is by
1318
+ the key, so a page carrying no value falls in no group and is absent from the
1319
+ list page — not listed last, not listed under a fallback heading, absent, with
1320
+ nothing reported at either build. Every note of a listed type in SoHL's tree
1321
+ carries one today, and nothing in this package enforces that; the content index
1322
+ is where the question _which notes carry no `kbcat`?_ is answered.
1323
+
1324
+ **It is also what a content table sections on.** `sohl.kbcat AS _section` in a
1325
+ `sql` fence is the ordinary case of _Content tables_ below, and the same
1326
+ free-form value decides the headings there.
1327
+
1328
+ ### Addresses
1329
+
1330
+ Three types name one thing from another in a content tree, and they are not
1331
+ lengths of one another:
1332
+
1333
+ | type | written | where it is met |
1334
+ | ----------- | -------------------------------------------- | ----------------------------------------------------------- |
1335
+ | `Address` | `<package>-<system>-<note_type>-<shortcode>` | a frontmatter field, a `model:`, an art slot, a link target |
1336
+ | `Shortcode` | `<shortcode>` | a note's own identity, and a key a runtime resolves by |
1337
+ | `Wikilink` | `[[<Address>[#<anchor>]\|<text>]]` | body prose |
1338
+
1339
+ An **`Address`** is the tuple below, and it is what every field typed `Address`
1340
+ in the tables that follow holds. It is equally what a `model:` names, what an art
1341
+ slot points at and what a wikilink resolves through — so an address is the thing
1342
+ being named, and the brackets around one in prose are a separate matter. Three
1343
+ rules govern it, and the first three subsections below state them in turn: the
1344
+ tuple is read by position, any suffix of it may be written, and each omitted
1345
+ segment is supplied by a default the position declares.
1346
+
1347
+ A **`Shortcode`** is one segment, and nothing expands it — see _A shortcode is
1348
+ one segment, and nothing expands it_ below.
1349
+
1350
+ A **`Wikilink`** is the prose form, which contains an Address — see _Wikilinks_
1351
+ below.
1352
+
1353
+ #### The canonical address
1354
+
1355
+ ```text
1356
+ <package>-<system>-<note_type>-<shortcode>
1357
+ ```
1358
+
1359
+ The final segment is the shortcode, preceded by the note type, the system and
1360
+ the package. Each segment has one position. The system says which kind of target
1361
+ is named:
1362
+
1363
+ | system | target |
1364
+ | ------------- | --------------------------------------------------------------------------- |
1365
+ | `note` | Readable note content: a web page, a book entry, or a Foundry JournalEntry. |
1366
+ | `none` | A systemless target such as an asset, Folder, Macro or Scene. |
1367
+ | `sohl`, `hm3` | A game system's Actor or Item document. |
1368
+
1369
+ A note can publish more than one Address. A `being` note, for example, publishes
1370
+ `thalorna-note-being-bctrncml` for its readable content and
1371
+ `thalorna-sohl-being-bctrncml` for its SoHL Actor. An item note follows the same
1372
+ pattern for its page and Item. The type segment remains the authored note type
1373
+ in each Address.
1374
+
1375
+ #### Shorter forms
1376
+
1377
+ Authors may omit segments from the left. The complete Address states all four:
1378
+
1379
+ | form | expands to |
1380
+ | ------------------------------- | -------------------------------------------------------- |
1381
+ | `package-system-type-shortcode` | itself |
1382
+ | `system-type-shortcode` | `<this package>-system-type-shortcode` |
1383
+ | `type-shortcode` | `<this package>-<this position's system>-type-shortcode` |
1384
+ | `shortcode` | as above, with the type from the field's declaration |
1385
+
1386
+ There is no `package-type-shortcode` form. A reference to another package states
1387
+ its system as well as its package.
1388
+
1389
+ #### Authored references and generated references
1390
+
1391
+ Authored notes may use any permitted suffix. Builds leave source bytes unchanged.
1392
+ Generated content indexes, SQL records, site frontmatter and generated
1393
+ Wikilinks contain complete Addresses. For example, `data.parents: [north]` in
1394
+ `thalorna` emits `data.parents: [thalorna-note-place-north]`.
1395
+
1396
+ SQL queries compare Address-valued fields to complete Addresses. A note's
1397
+ `shortcode` is a distinct identity component, so it is not equal to an Address
1398
+ stored in `data.parents`, `data.domains` or a governance rank's `lore`. Use the
1399
+ target's complete identity for that comparison. A stub has no published page
1400
+ address; its note identity still contains the package, system, type and shortcode.
1401
+
1402
+ The toolchain holds parsed Addresses as complete tuples internally. Address-keyed
1403
+ relation maps hold typed targets and standing values. Only generated output
1404
+ serializes these properties to Address strings. Runtime Shortcodes, Foundry
1405
+ UUIDs, file paths and URLs keep their own representations.
1406
+
1407
+ An Address-valued `data:` field must name a target in this package or a declared
1408
+ dependency index. `package-build lint` reports a missing target at the value or
1409
+ map key that names it. A stub counts as a target for data even though it
1410
+ publishes no page. Art slots use their own missing-art warning and document
1411
+ fallback.
1412
+
1413
+ #### An omitted segment defaults from where the link is written
1414
+
1415
+ An omitted segment is a default, not a wildcard. Every short form expands to one
1416
+ complete Address before lookup.
1417
+
1418
+ - **Package:** the current package. A short address never falls through to a
1419
+ dependency.
1420
+ - **System in `[[link|text]]`:** `note`, for every note type. Use an explicit
1421
+ game system to name its Actor or Item, or `none` to name a systemless target.
1422
+ - **System in `![[link|text]]`:** `none`. An embed names an image or icon asset;
1423
+ it also supplies `image` as the type of a bare shortcode.
1424
+ - **System in frontmatter:** a system block such as `sohl:` or `hm3:` supplies
1425
+ its game system. Other Address fields default to `note`, except fields
1426
+ declared for images, icons or folders, which default to `none`.
1427
+
1428
+ For example, `[[macro-autoattack|Automated Attack]]` names the readable macro
1429
+ note, while `[[none-macro-autoattack|Automated Attack]]` names the Foundry Macro.
1430
+ `![[icon-anvil|Anvil]]` names the systemless icon asset.
1431
+
1432
+ **A complete Address is lowercase throughout.** Every segment — package,
1433
+ system, type and shortcode alike — contains only lowercase ASCII letters and
1434
+ digits (`^[a-z0-9]+$`). Declared Address fields reject uppercase segments.
1435
+ A model reference accepts mixed-case Shortcodes and resolves their lowercase
1436
+ Address identity; this does not change a native embedded item's Shortcode.
1437
+
1438
+ **Parsing is positional counting from the right, and nothing else.** Every
1439
+ segment is lowercase alphanumeric — shortcodes, **types** and **subTypes** are
1440
+ all `^[a-z0-9]+$`, systems come from a closed registry, and `contentPackage` is
1441
+ `^[a-z0-9]+$` too — so the hyphen is purely a separator. There is no longest-match
1442
+ against a roster and no vocabulary check before splitting.
1443
+
1444
+ **`type` and `subType` are held to that charset, not merely expected to meet
1445
+ it**. A type is the first segment of every address, so a hyphen in one is
1446
+ read back as a segment boundary that was never meant as one. A `subType` reaches no address, but it is a vocabulary term the toolchain keys on and follows the same charset rule. Both are checked
1447
+ against the same constant a shortcode is checked against, and a note carrying a
1448
+ hyphenated value is reported where it wrote it:
1449
+
1450
+ ```text
1451
+ Trauma/Blood_Loss.md:3:1: error: `subType` "blood-loss" is not a well-formed subType — a subType is lowercase letters and digits only (^[a-z0-9]+$), the same charset a type, a shortcode and a contentPackage are held to. It is a vocabulary term the whole toolchain keys on, and one closed set away from being an address segment again, so a charset that held for every term but this one would be a rule nobody could state in a sentence
1452
+ ```
1453
+
1454
+ The `doc` subType is **`userguide`**. A hyphenated `user-guide` is refused by
1455
+ the shared charset check. Shortcodes likewise contain only lowercase letters
1456
+ and digits, preserving one unambiguous Address grammar.
1457
+
1458
+ `type/shortcode` with a slash is accepted. A slash is _unconditionally_ an
1459
+ address separator — pipe or no pipe — so an unknown type before one is an error
1460
+ rather than something to guess at.
1461
+
1462
+ #### A shortcode is one segment, and nothing expands it
1463
+
1464
+ A **`Shortcode`** is a single segment of the same charset an address segment is
1465
+ held to, and it is a type of its own rather than the shortest `Address`. It
1466
+ names a thing's identity, or it is the key something resolves by where the other
1467
+ three segments have nothing to say:
1468
+
1469
+ | where a `Shortcode` is written | what it names |
1470
+ | -------------------------------------------- | ----------------------------------------------------------------------------------- |
1471
+ | a note's own `shortcode:` | the note's identity — the name every address of it is built from |
1472
+ | a custom embedded entry's `system.shortcode` | that entry's identity within the actor that holds it |
1473
+ | an emitted `system.relations` key | the other body this one has a standing towards |
1474
+ | a mystery's `skillAptitudes` keys | the skill an aptitude weighs, where the key is not a `subType:` selector |
1475
+ | a `Code`-suffixed system field | what the resolved address is stored as — see _The note vocabulary, and how it maps_ |
1476
+
1477
+ **The test is whether the position can take the full four-tuple.** An `Address`
1478
+ accepts it by definition, and the positions above cannot: a `system.relations`
1479
+ key is read at runtime as `relations[shortcode]`, among the items one actor
1480
+ carries, where packages do not exist. So nothing defaults a `Shortcode` out to
1481
+ four segments, and a value persisted there as an address names no standing at
1482
+ all.
1483
+
1484
+ **The two halves of `relations` are two types.** The key an author writes is an
1485
+ `Address` — a body in another package is named in full, and one in this package
1486
+ by its shortcode alone — and the key the compiled Item carries is the
1487
+ `Shortcode` that address names. The builder performs the reduction, exactly as
1488
+ it does for a `Code`-suffixed field, and two authored keys reducing to one
1489
+ emitted key is an error naming both addresses: a shortcode is unique within one
1490
+ package's address space, so two packages may each declare one, and the compiled
1491
+ field holds one of the two.
1492
+
1493
+ **A bare shortcode in an `Address` field is not a `Shortcode`.** It is an
1494
+ Address written at its shortest, with the package, the system and the type all
1495
+ supplied by the position — the same value as the full tuple, spelled shorter.
1496
+ Which of the two a field holds is the field's own declaration, stated in the
1497
+ `Values` column of the tables below and nowhere else.
1498
+
1499
+ #### Every address resolves, and every build says so the same way
1500
+
1501
+ An address that names no note **fails the build** — in the link checker,
1502
+ in the pack compilers and in the site build alike.
1503
+
1504
+ A `draft` note exists, resolves, compiles and publishes; a link to it renders
1505
+ visibly marked. An Address landing nowhere is a typo or an omission and earns
1506
+ an error.
1507
+
1508
+ There are six ways a link can fail, and each is one **error** with one message
1509
+ wherever it is met:
1510
+
1511
+ | finding | what it means | the fix |
1512
+ | ---------------- | --------------------------------------------- | ------------------------------------------------------ |
1513
+ | `unlabelled` | no `\|`, so the link addresses nothing | write `[[type-shortcode\|Text]]` |
1514
+ | `not-an-address` | labelled, but the target is not an address | write the address, not the name |
1515
+ | `not-lowercase` | any segment of the address is capitalised | lowercase it — every segment is lowercase |
1516
+ | `unknown-type` | qualified, but names no type this build knows | correct the type segment |
1517
+ | `unresolved` | parses as an address; nothing publishes it | fix the shortcode, or qualify to reach another package |
1518
+ | `ambiguous` | _unreachable; kept for the manifest_ | — |
1519
+ | `unknown-anchor` | the address resolves; the `#section` does not | correct the anchor |
1520
+
1521
+ `ambiguous` is a reserved reason in the result vocabulary. An omitted segment
1522
+ defaults rather than wildcarding, so each written target expands to one
1523
+ canonical Address and a lookup returns one entry or none.
1524
+
1525
+ The vocabulary and the messages live in one module (`engine/wikilink-syntax.mjs`)
1526
+ precisely because an author meets whichever build ran first. Three resolvers read
1527
+ one authored link; they must not describe the same mistake in three ways, and
1528
+ they must never disagree about whether it is a mistake at all.
1529
+
1530
+ #### In frontmatter, the address is written bare
1531
+
1532
+ An **`Address` field** takes the address with no brackets:
1533
+
1534
+ ```yaml
1535
+ data:
1536
+ parents:
1537
+ - hexhodai
1538
+ seat: tashal
1539
+ ```
1540
+
1541
+ not `[[hexhodai]]`. The field is declared as an `Address`, so the schema knows
1542
+ what the value is and reads it as one; brackets are a wikilink's punctuation,
1543
+ which the reader would have to strip before it could do anything.
1544
+
1545
+ A frontmatter value is parsed by the address grammar above, so a single-segment
1546
+ value such as `hexhodai` names the target by its shortcode alone, with every
1547
+ other segment supplied by the position. Frontmatter is structure rather than
1548
+ prose: a field value is a reference something else will compile against, and it
1549
+ should say exactly what it points at.
1550
+
1551
+ **A field can supply the type.** A field naming one kind of note declares its
1552
+ default — `seat` a `place`, `parents` an `affiliation`, `stations` a `lore` — so
1553
+ the type segment defaults from the declaration and a bare shortcode is the
1554
+ ordinary case, not an abbreviation of one. That is why the examples above read
1555
+ `tashal` rather than `place-tashal`: the shorter form carries the same
1556
+ information, because the field already said what kind of thing it points at.
1557
+
1558
+ The frontmatter lint checks scalar Addresses, entries in Address lists, and
1559
+ Address map keys at the entry's own location. Segments contain lowercase ASCII
1560
+ letters and digits; wikilink brackets, labels, anchors, and spaces do not belong
1561
+ in these fields. Optional null values and empty lists or maps express no
1562
+ reference. Grammar and accepted types are checked independently of whether a
1563
+ target exists.
1564
+
1565
+ An affiliation's `economy` accepts only `affiliation` and `lore` Addresses.
1566
+ Each entry states its type: `affiliation-merchants` or `lore-tanvrcrncy`.
1567
+ There is no default type; a bare shortcode is an error.
1568
+ Bundle `contents` likewise requires a type for each document.
1569
+
1570
+ **The declaration constrains the type; it does not shorten the address.** A field
1571
+ value may be written at any length, and every length is equally correct so long
1572
+ as the type it names is the one the field declares. All four of these are valid
1573
+ in a `seat` field:
1574
+
1575
+ ```yaml
1576
+ seat: tashal
1577
+ seat: place-tashal
1578
+ seat: none-place-tashal
1579
+ seat: kethira-none-place-tashal
1580
+ ```
1581
+
1582
+ Each is a **suffix** of the canonical address, which is why the third one names
1583
+ the system and the fourth cannot omit it — see [Shorter forms](#shorter-forms).
1584
+
1585
+ An `affiliation-` prefix there is an **error naming the field and both types** —
1586
+ never a silent widening of what the field accepts. Where a field permits more
1587
+ than one type with no default among them, a bare shortcode is refused
1588
+ outright — an error naming the field and the types it accepts, never a
1589
+ resolution across them.
1590
+
1591
+ **A position's default and its accepted set are two different declarations,
1592
+ and most positions are not ambiguous at all.** The paragraph above is about a
1593
+ position that permits more than one type with no default among them; `seat` is
1594
+ the plainer case, permitting exactly one. Between those sits a third shape,
1595
+ and it is the common one: a position that nominates one of its accepted types
1596
+ as the default while accepting others besides. There a bare shortcode is never
1597
+ a candidate to weigh — it takes the default outright, every time — and the
1598
+ accepted set is what a value that states its own type is checked against
1599
+ instead: naming any member of the set is as valid as naming the default
1600
+ itself, and naming a type outside it is an error against the set, never
1601
+ against the default alone. [The four art slots](#the-four-art-slots) are
1602
+ declared exactly this way — each one accepts a second type beyond the one a
1603
+ bare shortcode takes.
1604
+
1605
+ **Only the type segment defaults.** Package and system are not the field's to
1606
+ supply — it has no opinion about which package holds the target — so reaching
1607
+ another package's note means qualifying, and because parsing is positional
1608
+ counting from the right, qualifying at all means naming the type too.
1609
+ `kethira-tashal` is not "the `tashal` place in `kethira`"; it is two segments, so
1610
+ it reads as type `kethira`, shortcode `tashal`, and fails.
1611
+
1612
+ So the ladder has one rung where the field helps and three where it only checks:
1613
+ a bare shortcode takes its type from the declaration, and every longer form
1614
+ states the type itself and is verified against it.
1615
+
1616
+ This is enforced rather than merely preferred: the build walks every frontmatter
1617
+ value, reports each bracketed link it finds, and reports each value whose
1618
+ qualification contradicts its field. A successful run says so — _no wikilink in
1619
+ frontmatter_ is part of what `package-build links` reports when it passes.
1620
+
1621
+ Brackets belong in prose, where a link sits inside a sentence and needs marking
1622
+ off from the words around it. A frontmatter value has nothing to be marked off
1623
+ from.
1624
+
1625
+ ### Wikilinks
1626
+
1627
+ A **`Wikilink`** is how body prose names something:
1628
+
1629
+ ```text
1630
+ [[<Address>[#<anchor>]|<text>]]
1631
+ ```
1632
+
1633
+ The brackets, the `#<anchor>` and the `|<text>` label are the Wikilink's own.
1634
+ What sits inside them is an `Address` like any other — read by the same grammar,
1635
+ written at any of its lengths, and taking each omitted segment from where the
1636
+ link is written, which for body prose is this package and system `note`. So
1637
+ nothing about resolution belongs to the bracketed form: a wikilink and a
1638
+ frontmatter field ask the same question of the same value, and the findings above
1639
+ are the answers either of them gets.
1640
+
1641
+ The anchor is the Wikilink's, not the Address's. It names a section of the target
1642
+ by the slug that section's heading declares — `[[place-tashal#market|the
1643
+ market]]` — and one naming no section there is the `unknown-anchor` finding.
1644
+ Written alone, `[[#slug|Text]]` addresses a section of the page the link sits on,
1645
+ which is the one form carrying no address at all.
1646
+
1647
+ #### Every wikilink carries a label
1648
+
1649
+ There is one namespace, and the pipe is required:
1650
+
1651
+ | written | resolved as | displays |
1652
+ | ------------------- | ----------- | -------------------------- |
1653
+ | `[[Address\|]]` | an address | the target note's own name |
1654
+ | `[[Address\|Text]]` | an address | `Text` |
1655
+ | `[[Name]]` | nothing | a finding |
1656
+
1657
+ **A link written without a label addresses nothing**, and the correction
1658
+ is always the same: write `[[type-shortcode|Text]]`.
1659
+
1660
+ The bare form does not resolve by display name or alias. `name.aliases` is
1661
+ indexed for search and completion, while an authored link names an Address.
1662
+ A top-level `aliases:` key is refused.
1663
+
1664
+ Requiring the label is also what makes positional parsing safe. Note names
1665
+ contain hyphens — `Grukar-ahk` is a name, not a `Grukar` of type `ahk` — so a
1666
+ target that does not parse as an address is reported as one that does not, rather
1667
+ than split at an arbitrary place or quietly looked up somewhere else.
1668
+
1669
+ The **empty** label is not a way of writing no label. It says _address this
1670
+ target, and show whatever it calls itself_ — so a note renamed later takes its new
1671
+ name at every citation with no link edited. `[[x|]]` is labelled; `[[x]]` is not.
1672
+
1673
+ It is the label that is required, not a target: the anchor-only form above
1674
+ carries its pipe like every other link.
1675
+
1676
+ ### Assets are types
1677
+
1678
+ `icon`, `image` and `audio` are types in the type vocabulary, and an address
1679
+ reaches one exactly as it reaches a being or a skill. Their trees are
1680
+ the one thing about them that differs: they sit beside `content/` rather than
1681
+ inside it, because the thing a note is addressing is a file rather than a note.
1682
+
1683
+ **Three roots, one per type.** The directory is named for what it holds and the
1684
+ type is named for what an address reaches, so the two differ by a letter and the
1685
+ mapping is declared rather than derived from the name:
1686
+
1687
+ | root | type |
1688
+ | --------------- | ------- |
1689
+ | `assets/icons` | `icon` |
1690
+ | `assets/images` | `image` |
1691
+ | `assets/audio` | `audio` |
1692
+
1693
+ **A file's extension decides whether it is an asset at all**, and its root
1694
+ decides which type. Every image-extensioned file anywhere under `assets/icons`
1695
+ is an icon, and every audio-extensioned file under `assets/audio` is an audio
1696
+ asset, however deep it sits.
1697
+
1698
+ That filter is load-bearing rather than tidy-minded: `provenance.yaml` files
1699
+ live **inside** these roots, at any level, and a walk that took every file would
1700
+ read attribution records as assets. A directory under `assets/` that is not one
1701
+ of the four holds no addressable asset — `assets/ui` is a package's own
1702
+ furniture, and nothing addresses it.
1703
+
1704
+ **An asset type carries no system.** Its addresses keep the fourth segment, and
1705
+ it is always `none` — `sohl-none-icon-anvil`, never `sohl-icon-anvil` — so key
1706
+ parsing stays uniform across every type. The path shape is a property of the
1707
+ type rather than a test on a value.
1708
+
1709
+ #### The filename is the shortcode
1710
+
1711
+ ```text
1712
+ assets/icons/game-icons/lorc/anvil.svg → <package>-none-icon-anvil
1713
+ assets/images/beings/creatures/drake.webp → <package>-none-image-drake
1714
+ ```
1715
+
1716
+ **The extension is not part of the address**, and neither are the directories
1717
+ above the file. A root's shortcodes are one flat namespace however deeply the
1718
+ tree nests, so two files under one root sharing a basename are two claims on one
1719
+ address and a build error naming both. Across roots they are no such thing:
1720
+ `icon-anvil` and `image-anvil` are different addresses.
1721
+
1722
+ Leaving the extension out is what makes a format change free. Changing an icon
1723
+ from SVG to WebP is dropping a different file in place, and no note changes;
1724
+ `abysdrksvg` and `abysdrkwebp` would be two addresses for one drake.
1725
+
1726
+ **Beneath the root the structure is arbitrary, exactly as it is under
1727
+ `assets/content`.** A note's address does not depend on where its `.md` sits,
1728
+ and an asset's address does not depend on where its file sits within its root.
1729
+ Both trees are arranged for the people who maintain them —
1730
+ `icons/game-icons/<contributor>/`, `images/beings/creatures/` — and either can be
1731
+ rearranged wholesale without a single reference changing.
1732
+
1733
+ **That freedom is the point.** Whoever maintains a tree organises it however
1734
+ makes sense to them — by contributor, by subject, by the source a set came from,
1735
+ by whatever the next thousand files want — and the build has no opinion. A
1736
+ layout that carried meaning would spend that freedom to buy nothing: moving a
1737
+ file would become a breaking change, and the arrangement would answer to the
1738
+ walk rather than to the person reading it.
1739
+
1740
+ **The root itself is the exception, and only because an asset root is
1741
+ homogeneous.** `assets/content` is mixed: it holds beings, places, skills and
1742
+ the rest side by side, and each note declares its own `type:`, so no directory
1743
+ needs to. An asset root holds one type and nothing else — everything under
1744
+ `assets/icons` is an icon — which is what lets the root supply the type. It has
1745
+ to: a `.webp` carries no frontmatter and has nowhere to say what it is.
1746
+
1747
+ So the two trees follow one rule about layout and differ on exactly one point:
1748
+ a note's type comes from the note, an asset's type comes from its root. That is
1749
+ also why the three roots are a closed list rather than a convention — a fourth
1750
+ directory under `assets/` declares nothing, so nothing in it is addressable.
1751
+ `assets/ui` and `assets/fonts` fall out of that rule rather than needing an
1752
+ exemption.
1753
+
1754
+ **A shortcode is lowercase alphanumerics.** A filename that is not — a version
1755
+ string, a hyphen, a date stamp — cannot be addressed, and the build says so
1756
+ rather than inventing a shortcode for it.
1757
+
1758
+ #### The asset record
1759
+
1760
+ An asset reaches the content index as a record of its own — one line per
1761
+ addressable file, in the same JSON Lines index the notes publish into. It
1762
+ carries no frontmatter, no anchors and no `foundry` block: a file declares
1763
+ nothing about itself, compiles into no document, and publishes no page, so the
1764
+ `address` holds the canonical key and no page slug.
1765
+
1766
+ | `asset` field | Source | What it says |
1767
+ | ------------- | ---------- | ------------------------------------------------------------------ |
1768
+ | `path` | the walk | Where the file sits inside the emitting package's asset directory. |
1769
+ | `attribution` | provenance | The person holding the rights, to whom attribution is legally due. |
1770
+ | `source` | provenance | Where it came from — a URL, or a sentence. |
1771
+ | `ai` | provenance | Whether the file is machine-generated — `true` or `false`. |
1772
+ | `license` | provenance | The licence it is used under — an SPDX identifier, or terms. |
1773
+ | `notes` | provenance | Anything else a person reading the attribution needs. |
1774
+
1775
+ Every field is present on every record, blank where nothing states one. A fixed
1776
+ shape is what lets a consumer read `asset.license` without first asking whether
1777
+ the package happened to record one.
1778
+
1779
+ **`path` is what makes resolution one step.** The record is emitted by the
1780
+ package holding the bytes, so the path is that package's path and each consumer
1781
+ joins its own root onto it:
1782
+
1783
+ | Consumer | `thalorna-none-image-thorn` resolves to |
1784
+ | -------- | ------------------------------------------------------------- |
1785
+ | Web | `<cdn base>/thalorna/images/beings/characters/thorn.webp` |
1786
+ | Book | `<asset base>/thalorna/images/beings/characters/thorn.webp` |
1787
+ | Foundry | `modules/thalorna/assets/images/beings/characters/thorn.webp` |
1788
+
1789
+ Foundry paths use the **Foundry package id**. The two values are both `thalorna`
1790
+ for this image; `harnensemble` content uses `harn-ensemble` in its Foundry paths.
1791
+
1792
+ ##### Where provenance comes from
1793
+
1794
+ A `provenance.yaml` states the provenance fields in the table above, and nothing
1795
+ else: an unknown key is a finding rather than a silent drop, because `licence`
1796
+ beside `license` is otherwise an attribution record that looks complete and
1797
+ carries nothing.
1798
+
1799
+ One address resolves its record in this order:
1800
+
1801
+ 1. A sibling `<filename.ext>.yaml`, which **replaces** an inherited record
1802
+ wholesale rather than merging over it. A sidecar exists precisely because the
1803
+ inherited answer is wrong for this one file, so carrying half of it forward
1804
+ would make the record's meaning depend on a directory two levels up.
1805
+ 2. Otherwise the nearest `provenance.yaml`, searching the file's own directory
1806
+ and then its ancestors, **stopping at the type root**. A record above the root
1807
+ would speak for trees it says nothing about.
1808
+ 3. Otherwise nothing, and the provenance fields are blank. A package that
1809
+ records no attribution is a fact to state, not a walk to fail.
1810
+
1811
+ **The nearest record is the whole answer.** It is never merged with the one
1812
+ above it, so a record states every key it means to claim — which is what lets a
1813
+ reader open one file and know what the tree beneath it carries, instead of
1814
+ resolving a chain by hand.
1815
+
1816
+ That is also why `attribution` and `license` are **required in any record that
1817
+ exists**: a key left out is not inherited from above, it is simply absent, so a
1818
+ record stating neither leaves every file it covers with no rights holder and no
1819
+ terms. Omitting one is a finding. `source`, `ai` and `notes` stay optional,
1820
+ because a blank is a truthful answer for each of them.
1821
+
1822
+ #### A font is not an asset
1823
+
1824
+ `assets/fonts` is not a root, and a font has no address.
1825
+
1826
+ **An asset type exists so a note can name a file and a package can substitute
1827
+ it.** Nothing about a font answers to either half. No note names a typeface —
1828
+ the art slots are `icon`, `tokenIcon`, `bgImage` and `banner` — and substituting
1829
+ one package's font for another's is not something anyone wants.
1830
+
1831
+ **Neither consumer of a font can use an address.** A stylesheet names a file
1832
+ with `url()`, resolved relative to the stylesheet and never through the content
1833
+ index. The book names a _family_ — `#set text(font: "Libertinus Serif")` — and
1834
+ the typesetter matches that against a directory it is pointed at. One wants a
1835
+ relative path, the other a family name; an address serves neither.
1836
+
1837
+ **And the model fits badly.** An address holds one file whose format is free to
1838
+ change, which suits a picture. A font family is a matrix of family, weight and
1839
+ style, and a filename already says which cell a file is — a shortcode would have
1840
+ to re-encode that, worse.
1841
+
1842
+ So fonts stay ordinary files a package ships and points a tool at, and their
1843
+ licences are recorded where a person reads them rather than in an index.
1844
+
1845
+ #### `icon` and `image` are two types, not one
1846
+
1847
+ An icon has to stay coherent drawn into a 32×32 slot, which is why icons are
1848
+ SVG. An image is unbounded, expected to be large, and unreadable at that size.
1849
+ That is a fitness property of the asset itself, and one merged type would leave
1850
+ every consumer inferring the handling from the extension; two types let a guard
1851
+ assert it instead.
1852
+
1853
+ So the two have **separate shortcode namespaces**. `icon-anvil` and
1854
+ `image-anvil` are different addresses for different purposes, and a rewrite rule
1855
+ reaches one without touching the other.
1856
+
1857
+ **An asset address is its own name.** `image-thorn` is an image called `thorn`;
1858
+ any resemblance to `being-thorn` is coincidence, and nothing derives one from
1859
+ the other.
1860
+
1861
+ #### `packagebuild` is a reserved package name
1862
+
1863
+ package-build is an npm package rather than a system or a module, and it ships
1864
+ a set of images — section banners chiefly — that many packages draw on.
1865
+ Addressing them as `packagebuild-none-image-<shortcode>` lets a note reach one
1866
+ without declaring a dependency on some parent system or module it otherwise has
1867
+ no relationship with.
1868
+
1869
+ **Its files reach the website and the book and no Foundry document.** Foundry
1870
+ installs no package of that name and no relationship can be declared with one,
1871
+ so `banner:` — the one art slot with no Foundry destination — is where a
1872
+ `packagebuild` address belongs. A body image naming one, written either way, is
1873
+ refused with the line and column it sits on.
1874
+
1875
+ `packagebuild` is reserved for this toolchain's assets and documentation. The
1876
+ documentation package in this repository uses that namespace for its notes;
1877
+ other repositories cannot claim it. Asset resolution remains special-cased
1878
+ because no Foundry package is installed under that name.
1879
+
1880
+ #### An embedded image is a wikilink
1881
+
1882
+ ```text
1883
+ ![[address|label]]
1884
+ ```
1885
+
1886
+ There is no image grammar. An embed is the wikilink above, with `!` meaning
1887
+ _render it here_ rather than _link to it_, and the syntax supplying the default
1888
+ type the way a field declaration does — `image`. The short-form ladder, the
1889
+ package and system defaults, the lowercase rule, the ambiguity reporting and the
1890
+ findings vocabulary all apply unchanged.
1891
+
1892
+ | written | means |
1893
+ | ----------------------------------- | ---------------------------------------- |
1894
+ | `![[anvil\|]]` | decorative — the common case |
1895
+ | `![[anvil\|An anvil]]` | alt text where it carries meaning |
1896
+ | `![[anvil]]` | unlabelled, and a finding like any other |
1897
+ | `![[sohl-none-image-anvil\|Anvil]]` | qualified, reaching another package |
1898
+
1899
+ The parser distinguishes a missing label from an empty one, so _deliberately
1900
+ decorative_ and _not written_ differ without an exemption.
1901
+
1902
+ **The label is the alt text, and it stays with the referrer.** One image serves
1903
+ many documents, and only the referrer knows what it means where it sits.
1904
+
1905
+ **An embed reaches asset types only.** The syntax invites the broader reading —
1906
+ transclusion of an arbitrary note — so the restriction is a guard rather than a
1907
+ convention.
1908
+
1909
+ **An embed takes the same directive an image does**, in the same place —
1910
+ immediately after the closing `]]`, with no space:
1911
+
1912
+ ```markdown
1913
+ ![[branwldrgr|Brànwâal Dôrgaar]]{float=top-left}
1914
+ ![[branwldrgr|Brànwâal Dôrgaar]]{size=medium float=top-left}
1915
+ ![[thalornamap|Map of Thalorna]]{.full-width}
1916
+ ![[thalornamap|Map of Thalorna]]{.full-width float=top-left}
1917
+ ![[thalornamap|Map of Thalorna]]{float=top-left .full-width}
1918
+ ```
1919
+
1920
+ **The same parser reads both**, so an embed takes a width class, a `size`, a
1921
+ `float`, or a combination in any order, separated by spaces — everything
1922
+ [an image's directive](#width-is-a-class-and-the-ordinary-width-carries-no-marker)
1923
+ takes and nothing beyond it. The width, size, and position vocabularies are the ones
1924
+ above, and they are closed here for the same reason: a directive that is quietly
1925
+ ignored looks exactly like one that worked, and a directive holding a problem is
1926
+ not honoured in part either.
1927
+
1928
+ **A being's portrait is one of these**, opening its `{#appearance}` section by
1929
+ convention rather than declared in a field — see
1930
+ [the lead image](#the-lead-image).
1931
+
1932
+ ### Three states of a note
1933
+
1934
+ A note has three states, and two of them are nothing but conventions the format
1935
+ already carried.
1936
+
1937
+ | State | Body | Marker | Page | Document | Link target |
1938
+ | --------- | ------- | --------------- | ------------ | --------------------------------- | ----------- |
1939
+ | **stub** | empty | none | no | yes, where one would not be empty | no |
1940
+ | **draft** | written | `tags: [draft]` | yes, noticed | yes, noticed | yes |
1941
+ | **full** | written | none | yes | yes | yes |
1942
+
1943
+ "Document" here is the document a note's frontmatter earns. A system's Actor or
1944
+ Item is earned by that system's block, in every state —
1945
+ [a system block is what makes a game document](#a-system-block-is-what-makes-a-game-document).
1946
+
1947
+ **Full is the absence of everything.** It is the default and carries no marker,
1948
+ which is right: the overwhelming majority of a finished corpus should say
1949
+ nothing about its own finishedness.
1950
+
1951
+ There is no new frontmatter key, no new filetype and no second parser. A stub is
1952
+ a `.md` file the walk already finds, and the moment somebody writes a body it
1953
+ becomes an ordinary note — nothing moves, nothing is deleted, and every inbound
1954
+ link starts resolving.
1955
+
1956
+ #### A stub is a note with an empty body
1957
+
1958
+ > **The body is empty when everything after the closing frontmatter fence is
1959
+ > whitespace.** Nothing else is empty.
1960
+
1961
+ The rule is that severe because the test has to be one you can apply by looking
1962
+ at the file. Every softer rule makes two files that look different behave the
1963
+ same.
1964
+
1965
+ | After the fence | Empty? | What happens |
1966
+ | ----------------------- | ------ | --------------------------------------------------------------- |
1967
+ | Nothing, or blank lines | yes | A stub |
1968
+ | An HTML comment | no | A note with a body that renders to nothing. The lint reports it |
1969
+ | A horizontal rule | no | The same |
1970
+ | A lone heading | no | The same |
1971
+ | `_To be written._` | no | The same |
1972
+
1973
+ An HTML comment is deliberately not empty. An invisible marker does not make a valid stub; use an empty body when the note has no prose.
1974
+
1975
+ #### What a stub emits
1976
+
1977
+ A stub's index record is its frontmatter plus the derived keys, **minus
1978
+ `address` and `anchors`**. The absent address is the whole signal, and it is the
1979
+ column a table renderer already reads.
1980
+
1981
+ It keeps everything else:
1982
+
1983
+ - **`file`**, which is what you edit and what every diagnostic names.
1984
+ - **`id`**, which is the identity every diagnostic and every table reads.
1985
+ - **`foundry`**, naming each document that was made.
1986
+
1987
+ #### A document that would be empty is not created
1988
+
1989
+ > **If the resulting document would be empty, it is not created** — and the
1990
+ > index then names no artefact: `uuid`, `path` and `address` are `null`, which
1991
+ > is how a record says one was not made.
1992
+
1993
+ The test is about the **output**, which is what lets one rule cover every case:
1994
+
1995
+ | The note | The document | Made? |
1996
+ | ---------------------------------- | -------------------------------------- | ----- |
1997
+ | `place`, `lore`, `doc`, `scenario` | its JournalEntry, which _is_ the prose | no |
1998
+ | any item type, `macro` | the Item or Macro, from `data:` | yes |
1999
+ | the same note's documentation | a JournalEntry of the same prose | no |
2000
+ | `map` | the Scene, from `data:` | yes |
2001
+
2002
+ An unwritten arcane talent still compiles into its Item, because an Item is its
2003
+ `data:` and that is not empty. What it has no documentation journal for is the
2004
+ same thing an unwritten `place` has no journal for: there is nothing to put in
2005
+ one.
2006
+
2007
+ **An infobox does not count as content.** The panel is a rendering of `data:`
2008
+ that the index already publishes, so an entry holding nothing else would tell a
2009
+ reader exactly what the record told them.
2010
+
2011
+ Nothing anywhere names a document that was not made: a compendium UUID for one
2012
+ would send every consumer that resolves it to a document the pack does not hold.
2013
+
2014
+ What is suppressed with the page is the web address and the link target: a
2015
+ wikilink into a stub is refused, and a table cell listing one prints its name as
2016
+ plain text rather than as a link.
2017
+
2018
+ #### Two types are structural and exempt
2019
+
2020
+ `folder` and `homepage` have no body by design — a folder note's page is a
2021
+ generated section index, and the homepage's is the site's front door. An empty
2022
+ body on either keeps its address and resolves as **full**, because such a note is
2023
+ complete.
2024
+
2025
+ Every type in the vocabulary declares whether an empty body suppresses its page,
2026
+ so adding a type means answering the question rather than inheriting a default.
2027
+
2028
+ #### A stub is still referenceable as data
2029
+
2030
+ `borders.to` and `routes.to` name a place by `Address`, and those are **index
2031
+ relations rather than page links**: a border with a stub on the far side is a
2032
+ valid statement about the world and resolves. So does `parents` for the
2033
+ containment tree and `domains` for tenure — a stub appears in its region's
2034
+ `contains` and on its holder's `holdings`, rendered as plain text. What a stub
2035
+ cannot be is the target of a wikilink, because a wikilink points at a page.
2036
+
2037
+ ### What a note produces
2038
+
2039
+ Note types fall into two groups, and only the first has a mapping table.
2040
+
2041
+ **Types that _may_ produce a system document** — a SoHL or HM3 Actor or Item,
2042
+ one per system, described by the tables further down.
2043
+
2044
+ **Types producing only a core document** — `lore`, `place`, `scenario`, `doc`,
2045
+ `map`, `macro` and `homepage`. A core document is one Foundry defines rather than
2046
+ a system: a JournalEntry, a Scene, a Macro. These are system-agnostic, so such a
2047
+ note carries no `sohl:` or `hm3:` block, has no mapping table, and shows no
2048
+ **system** infoboxes.
2049
+
2050
+ It may still show a **note** infobox, and several should: a scenario's length,
2051
+ party size and required archetypes, or a place's subtype and parent, are exactly
2052
+ what a reader wants at a glance. Being outside both systems is not the same as
2053
+ having nothing worth summarising.
2054
+
2055
+ Every note in **both** groups still produces its JournalEntry and its web page.
2056
+ The difference is only whether a system Actor or Item is created as well.
2057
+
2058
+ #### A system block is what makes a game document
2059
+
2060
+ > **A note produces a system's Actor or Item only where it carries that
2061
+ > system's block.** A note carrying no block for any system produces no Actor
2062
+ > and no Item anywhere, whatever its packs declare.
2063
+
2064
+ The block is not a place to put optional detail — it is the statement that this
2065
+ person or thing is meant to be played. Its absence is a statement too, and it is
2066
+ made silently: the note is passed over by the Actor and Item passes exactly as
2067
+ any note they do not own, with no finding.
2068
+
2069
+ **Prose is untouched by this.** A body with content still compiles into one
2070
+ JournalEntry, one web page and one PDF page, however many blocks the note
2071
+ carries and whether or not it carries any; an empty body still compiles none of
2072
+ the three. So a note has three states, and each reaches the next by one edit in
2073
+ one file, with its address, its id, its index record and every link into it
2074
+ unchanged:
2075
+
2076
+ | The note carries | Produces |
2077
+ | -------------------------- | ---------------------------------------------- |
2078
+ | no block, empty body | an index record — a name that can be queried |
2079
+ | no block, a written body | a page, a PDF page and a JournalEntry |
2080
+ | a block, a written body | all of those, plus that system's Actor or Item |
2081
+ | two blocks, a written body | one document per system, and one of each other |
2082
+
2083
+ Write the person; add a system block when someone will need a token. A setting's
2084
+ cast is mostly people nobody rolls dice against, and a note about one of them
2085
+ carries no block and occupies no slot in a compendium a referee browses.
2086
+
2087
+ **A note carrying no document publishes no UUID for one.** Its address, its
2088
+ page and its documentation journal are published as they are for any other note,
2089
+ and the `foundry` entry that would name an Actor or an Item is absent — the same
2090
+ statement [`pack: none`](#the-pack-a-note-compiles-into) makes about a pack,
2091
+ made about a system.
2092
+
2093
+ **A block for a system this package ships no pack for is an error.** The
2094
+ authored data would be discarded either way, so the build says so, once per note
2095
+ per system, naming the system and the document class:
2096
+
2097
+ ```
2098
+ assets/content/People/Aelric.md:7:1: error: carries a `hm3:` block, and no Actor pack in this package compiles hm3 documents, so the block is discarded — declare `system: hm3` on one of the `packs:`, or remove the block.
2099
+ ```
2100
+
2101
+ A pack declaring no `system:` is compiled by the fallback pass, which reads one
2102
+ system's block — so a systemless Actor pack serves that system and not every
2103
+ system a note might write. A package shipping for a second system declares
2104
+ `system:` on a pack of each class it compiles.
2105
+
2106
+ That includes actors. A being producing its Actor and nothing else would be
2107
+ a system-bearing note with readable content, giving a prose link
2108
+ naming it nowhere to land. It carries a documentation journal like every other
2109
+ such note, addressed
2110
+ `<package>-note-being-<shortcode>` beside the Actor's
2111
+ `<package>-<system>-being-<shortcode>`.
2112
+
2113
+ **A being keeps its prose inline as well.** `system.appearance` and
2114
+ `system.dossier` are still the rendered text, where an item's description is an
2115
+ `@UUID` pointer into its journal. The difference is deliberate and is about
2116
+ size: one item is embedded across hundreds of beings, so baking its description
2117
+ into every copy bloats the compendium by the length of the prose times the
2118
+ number of carriers, and the pointer buys that back. An actor is singular, so the
2119
+ same indirection would cost a reader a click and save nothing.
2120
+
2121
+ ### One note is at most one document per system
2122
+
2123
+ A note produces **at most one document in each system**. That constraint is worth
2124
+ stating because the obvious counter-example is real, and the way it is resolved
2125
+ shapes how weapons are authored.
2126
+
2127
+ SoHL gives a weapon **strike modes**: `system.strikeModes` on a single
2128
+ `weapongear`, each mode melee or missile. A shorkana has four — the edge, the
2129
+ blunt of the swung blade, the haft or pommel, and the throw. HM3 has no such
2130
+ field: it assumes one usage per item, so those four modes are three
2131
+ `weapongear` documents and one `missilegear`.
2132
+
2133
+ **The mechanical values cannot be shared, so the modes cannot be lifted into
2134
+ `data:`.** Impact, heft, reach and draw all differ between the two systems even
2135
+ where the concept matches. `data:` holds what the _thing_ is; how it performs is
2136
+ each system's own, and belongs in that system's block.
2137
+
2138
+ **So the second document becomes a second note.** A `Spear` note carries a
2139
+ `sohl:` block describing four strike modes and an `hm3:` block describing its
2140
+ usual melee profile; a separate `Spear (thrown)` note carries **only** an `hm3:`
2141
+ block, describing the missilegear. That yields two shortcodes, which is what HM3
2142
+ needs, and the companion never appears in SoHL, which is what SoHL needs.
2143
+
2144
+ The population is small. Of HM3's eleven `missilegear` items, seven are distinct
2145
+ objects with a SoHL counterpart — `Arrow (Longbow)`, `Bolt (Crossbow)`,
2146
+ `Dart (Blowgun)`, `Stone (Sling)` and so on, which are ordinary notes carrying
2147
+ both blocks. Only four are companions: `Javelin (thrown)`, `Shorkana (thrown)`,
2148
+ `Spear (thrown)`, `Taburi (thrown)`.
2149
+
2150
+ **Every note carrying an `hm3:` block states its type.** Nothing can derive
2151
+ whether `Spear (thrown)` is a `weapongear` or a `missilegear` — there is no other
2152
+ system to infer from and no subType to read — and the same is true of the note
2153
+ carrying both blocks, whose SoHL strike modes describe every usage at once. So
2154
+ `hm3.type` is _required_ on both, not only on the companion.
2155
+
2156
+ The following special markdown sequences are recognized:
2157
+
2158
+ Footnotes use `[^id]` references and `[^id]:` definitions. Identifiers are
2159
+ local to a note; rendered markers are sequential numbers. A Foundry page
2160
+ collects its referenced footnotes in a Footnotes section at the bottom of that
2161
+ JournalEntryPage, while a book places them at the bottom of the page of
2162
+ reference. A term followed by `: definition` lines creates a definition list.
2163
+ See the [authoring examples](../authoring/links-and-markup.md#footnotes-and-definition-lists).
2164
+
2165
+ ```
2166
+ # Heading {#id .class1 .class2 attr="value"}
2167
+ ```
2168
+
2169
+ Any header can include curly braces. Inside the curly braces:
2170
+
2171
+ - `#id` represents an id anchor named `id` (only one allowed)
2172
+ - `.class1` represents a CSS class named `class1` (any number of classes allowed)
2173
+ - `attr="value"` represents an HTML attribute named `attr` whose value is `value` (any number of attr/value pairs allowed)
2174
+
2175
+ #### Images
2176
+
2177
+ An image is authored in the body, in the place it belongs, and every surface
2178
+ renders it there — the book, the website and a Foundry Journal Page alike. One
2179
+ authored statement, three renderers, which is the single-sourcing every other
2180
+ part of this format follows.
2181
+
2182
+ ```markdown
2183
+ ![[branwldrgr|Brànwâal Dôrgaar]]{float=top-left}
2184
+ ![[branwldrgr|Brànwâal Dôrgaar]]{size=medium float=top-left}
2185
+
2186
+ ![Map of Thalorna](images/maps/thalorna.webp){.full-width}
2187
+ ```
2188
+
2189
+ **An image is a block.** It stands alone in its paragraph, with a blank line
2190
+ either side of it, and every surface renders it as a figure. An image sharing a
2191
+ paragraph with prose is refused, because a width and a position mean nothing
2192
+ applied to a word in the middle of a sentence.
2193
+
2194
+ **The alt text is the caption.** Print has no `alt` attribute and has to put
2195
+ those words somewhere a reader can see them, so every surface draws them under
2196
+ the picture; the two HTML surfaces carry them as `alt` as well. A title —
2197
+ `![alt](src "title")` — is refused rather than dropped in silence: there is one
2198
+ place for those words and this is it.
2199
+
2200
+ ##### Width is a class, and the ordinary width carries no marker
2201
+
2202
+ | `class` value | book | website | Foundry journal |
2203
+ | ------------- | ----------------------------- | ------------------ | --------------- |
2204
+ | _(none)_ | natural width within a column | natural width | natural width |
2205
+ | `.full-width` | page measure | full content width | full page width |
2206
+
2207
+ The simple case needs no spelling, which is why the ordinary width has no name.
2208
+
2209
+ **No pixel values, ever.** A number means something in a browser and nothing
2210
+ coherent in print, and a directive carrying a class and a dimension at once
2211
+ gives one question two answers with no rule for which wins. `{width=800}` is
2212
+ refused.
2213
+
2214
+ ##### Named size is `size`
2215
+
2216
+ | `size` value | website and Foundry | book |
2217
+ | ------------ | ------------------- | ------------ |
2218
+ | `auto` | natural size | natural size |
2219
+ | `small` | 64 CSS pixels | 1.6 cm |
2220
+ | `medium` | 128 CSS pixels | 3.2 cm |
2221
+ | `large` | 256 CSS pixels | 5.6 cm |
2222
+ | `xlarge` | 512 CSS pixels | 8 cm |
2223
+ | `full-width` | content measure | page measure |
2224
+
2225
+ These are maximum display widths for Markdown images and embedded assets;
2226
+ height follows the file's aspect ratio. The available column, page, or browser
2227
+ measure caps every size, and print also caps image height within the page.
2228
+ Omitting `size` means `auto`, which uses the file's natural dimensions within
2229
+ those bounds. `full-width` spans the book's page rather than one column. The
2230
+ `.full-width` class also grants page scope in print; when it accompanies a
2231
+ bounded `size`, the image keeps that bounded width inside the page figure.
2232
+ A `size` may sit before or after `float` in the same directive, and neither
2233
+ changes the other's behavior.
2234
+
2235
+ ##### Position is `float`
2236
+
2237
+ | `float` value | book | website and Foundry journal |
2238
+ | -------------- | -------------------- | ------------------------------- |
2239
+ | `top-left` | top of the column | floated left, text wraps |
2240
+ | `bottom-left` | bottom of the column | floated left, text wraps |
2241
+ | `top-right` | top of the column | floated right, text wraps |
2242
+ | `bottom-right` | bottom of the column | floated right, text wraps |
2243
+ | `center` | top of the column | centred, with no text beside it |
2244
+
2245
+ **Print cannot wrap text around an arbitrary shape.** A Typst float occupies
2246
+ the column measure, so in the book only the vertical half of a position has an
2247
+ effect — top of the column or bottom of it — while the horizontal half does
2248
+ not. The website and a Foundry journal get true CSS wrap from the same
2249
+ directive. Expect the same statement, not the same page.
2250
+
2251
+ An image with no `float` is an ordinary block in the flow, where it was
2252
+ written. A `.full-width` image is set at the **top of a new page**: the book is
2253
+ set in two columns, only a float spans them both, and a float is placed where
2254
+ the page has room rather than where it was written — so the page breaks before
2255
+ the picture, and the prose that follows it runs below it on the same page. The
2256
+ picture can therefore never print above the prose that introduces it or after
2257
+ the prose that follows it, at the cost of the page before it ending short. A
2258
+ picture taller than the page takes a page of its own.
2259
+
2260
+ A `.full-width` image that **also states a `float`** is asking for a float and
2261
+ gets one, with no break before it. A float is placed where it fits, which may be
2262
+ the next page.
2263
+
2264
+ ##### All three vocabularies are closed
2265
+
2266
+ `{.fullwidth}`, `{.full_width}`, `{width=800}` and `{float=middle}` are
2267
+ refused, located by file, line and column, and the build fails. An unrecognised
2268
+ value rendering as the ordinary width is the failure worth preventing, because
2269
+ it looks exactly like a directive that worked.
2270
+
2271
+ A directive holding anything this section does not name is not honoured at all,
2272
+ even in the part that parsed: a half-honoured directive is the same silent
2273
+ failure in a smaller costume. The image keeps its braces and renders them as
2274
+ literal text, so the mistake is visible on the page as well as in the log.
2275
+
2276
+ **Nothing else reaches emitted markup.** The width and position classes and the
2277
+ address are all that leave a note; there is no `style`, no `id` and no arbitrary
2278
+ attribute, because data is never compiled into markup here. An address carries
2279
+ a scheme of `http:` or `https:`, or none at all.
2280
+
2281
+ ##### Where an address resolves
2282
+
2283
+ An image's address follows [the pathname rule](#a-pathname-names-the-package-that-owns-the-file)
2284
+ — its first segment says which package owns the file — and each surface
2285
+ resolves it to what that surface serves:
2286
+
2287
+ - **Foundry** is handed the path inside the install, so `images/map.webp`
2288
+ reaches a journal page as `modules/<package>/assets/images/map.webp`. An
2289
+ address with no such path — a package this build declares no relationship
2290
+ with, or `packagebuild`, which Foundry installs nothing of — is **refused,
2291
+ located by file, line and column**, and the build fails. Foundry is the one
2292
+ surface a correctly written pathname can be dead on while the other two
2293
+ resolve it, and a `src` that resolves against nothing looks exactly like an
2294
+ address that worked.
2295
+ - **The book** is handed a copy of the file, staged out of this package's own
2296
+ asset tree into the build directory before the compiler runs. An address
2297
+ naming a file this package does not ship — another package's, or a URL —
2298
+ cannot be staged, and the book prints the caption alone and says so.
2299
+ - **The website** passes the address through exactly as authored, because a
2300
+ site serves its imagery from its own asset host and this package is not told
2301
+ what that host is. An address that has to resolve there is written as a full
2302
+ URL.
2303
+
2304
+ #### Content tables
2305
+
2306
+ A fenced `sql` block renders a table from the content index. A query that
2307
+ selects no rows is a build error unless its fence states `{allow-empty=true}`.
2308
+
2309
+ SQL fences query the content index through DuckDB. The SQL dialect is DuckDB's.
2310
+
2311
+ ````markdown
2312
+ ```sql
2313
+ SELECT address.slug AS _ref,
2314
+ sohl.kbcat AS _section,
2315
+ name.full AS "Name",
2316
+ sohl.weight AS "Weight"
2317
+ FROM notes
2318
+ WHERE type = 'miscgear'
2319
+ ORDER BY sohl.kbcat, name.full
2320
+ ```
2321
+ ````
2322
+
2323
+ **`FROM notes`** is the content index: one row per note, plus one per
2324
+ documentation entry, so `type = 'miscgear'` selects the items and never their
2325
+ journals. A nested field is addressed exactly as a note authors it —
2326
+ `sohl.weight`, `name.full`, `file.path` — because the index is read as JSON and
2327
+ every nested object is inferred as a struct. A field a note type does not carry
2328
+ reads `NULL` rather than failing.
2329
+
2330
+ **`notes` leaves out stubs; `entries` does not.** Both carry a derived `state`
2331
+ column — `stub`, `draft` or `full` — and `notes` is exactly
2332
+ `SELECT * FROM entries WHERE state <> 'stub'`. So every table already written
2333
+ selects what it always selected, and gains `state` for free; a table that should
2334
+ list the unwritten beside the written changes one word:
2335
+
2336
+ ````markdown
2337
+ ```sql
2338
+ SELECT address.slug AS _ref,
2339
+ name.full AS "Name",
2340
+ state AS "State",
2341
+ m.name AS "Market",
2342
+ data.population AS "People"
2343
+ FROM entries
2344
+ LEFT JOIN market m ON m.value = data.market
2345
+ WHERE type = 'place' AND subType = 'settlement'
2346
+ AND list_contains(data.parents, 'place-aelwyth')
2347
+ ORDER BY data.population DESC NULLS LAST, name.full COLLATE NOCASE
2348
+ ```
2349
+ ````
2350
+
2351
+ A written settlement renders as a link and a stub renders as plain text, from
2352
+ one query with no branch in it: `_ref` is the stub's absent address, and the
2353
+ renderer already prints a bare cell where it is null.
2354
+
2355
+ A fence selecting `FROM notes` that **would** gain rows from `FROM entries` is
2356
+ warned about at the fence, with the count — so adopting stubs in a given table
2357
+ stays a decision somebody makes rather than one they forget.
2358
+
2359
+ The ladder is what makes "how finished is this?" a query:
2360
+
2361
+ ```sql
2362
+ SELECT state, count(*) AS n FROM entries GROUP BY state ORDER BY state
2363
+ ```
2364
+
2365
+ **`market`** is the six-step market scale as a relation — `value`, `name`,
2366
+ `trade` — so a table prints `village` beside the number a note wrote without a
2367
+ second copy of the scale living in authored content.
2368
+
2369
+ **The state is derived, never stored.** No index record carries it, no note can
2370
+ author it, and nothing in `data:` may claim it: the only authored signal is the
2371
+ `draft` tag, which marks the one thing reading the body cannot reveal.
2372
+
2373
+ **Two aliases are read by the renderer rather than printed**, because which
2374
+ column links and where a section breaks are decisions about output, not
2375
+ relational operations:
2376
+
2377
+ | Alias | What it does |
2378
+ | ---------- | ---------------------------------------------------------------------- |
2379
+ | `_ref` | Makes the row's **first** rendered column a wikilink to that address. |
2380
+ | `_section` | Emits a headed table per distinct value, in the order the rows arrive. |
2381
+
2382
+ `_section` is why one query replaces the forty near-identical blocks a grouped
2383
+ table would otherwise need: the authored `ORDER BY` decides the section order too.
2384
+
2385
+ **Beware `packFolder`.** It is a note's _pack_ folder, not its directory — the
2386
+ directory is `file.folder`.
2387
+
2388
+ ##### Reading another package's notes
2389
+
2390
+ Each package this one **depends on** is attached as a schema named after it, so
2391
+ a satellite can tabulate what it builds on:
2392
+
2393
+ ````markdown
2394
+ ```sql
2395
+ SELECT name.full AS "Name", sohl.skillBase AS "Base"
2396
+ FROM sohl.notes
2397
+ WHERE type = 'skill'
2398
+ ORDER BY name.full
2399
+ ```
2400
+ ````
2401
+
2402
+ This package's own notes stay at the unqualified `notes`, and a query may read
2403
+ both at once — joining your beings against the skills they cite is one `FROM`
2404
+ clause. It needs no fetch and no configuration: a dependency's published index
2405
+ is already cached when a compile starts, because resolving addresses across
2406
+ packages needs it.
2407
+
2408
+ Which dataset a query reads is `FROM`'s job rather than a fence property. A
2409
+ fence naming a file would write a build artifact's path into the corpus, so
2410
+ renaming the artifact would mean sweeping every note that cited it.
2411
+
2412
+ ##### Header arguments
2413
+
2414
+ Statements _about the directive_ use the shared braced attribute syntax after
2415
+ the language name:
2416
+
2417
+ ````markdown
2418
+ ```sql {section-level=3 allow-empty=true}
2419
+ SELECT name.full AS "Name", sohl.kbcat AS _section
2420
+ FROM notes WHERE type = 'affliction'
2421
+ ```
2422
+ ````
2423
+
2424
+ The language word stays first so Markdown readers recognize SQL. Attributes are
2425
+ separated by spaces. An unquoted value cannot contain spaces or braces; quote a
2426
+ value containing spaces with double quotes and escape an internal quote as
2427
+ `\"`. Boolean values are exactly `true` or `false`; other spellings are
2428
+ errors. Duplicate and unknown attributes are errors.
2429
+
2430
+ | Attribute | What it does |
2431
+ | ------------------------------------------- | --------------------------------------------------- |
2432
+ | `allow-empty=true` | Permits a query selecting no rows. Default `false`. |
2433
+ | `section-level=1` through `section-level=6` | Sets the heading level for `_section`. Default `2`. |
2434
+
2435
+ ##### Captioned blocks
2436
+
2437
+ `:::caption {#anchor}` labels the next Markdown block, whether it is prose,
2438
+ code, a table, or an image. The closing `:::` ends the caption text; blank lines
2439
+ between the fence and its block are allowed. The ID is unique within the note
2440
+ and contains letters, digits, `_`, or `-`, beginning with a letter. An empty
2441
+ caption, missing ID, missing closer, or missing following block is an error at
2442
+ the opening line.
2443
+
2444
+ ````markdown
2445
+ See [[#trade|]] for the market routes.
2446
+
2447
+ :::caption {#trade}
2448
+ Regional trade routes
2449
+ :::
2450
+
2451
+ ```sql
2452
+ SELECT name.full AS "Market" FROM notes WHERE type = 'place'
2453
+ ```
2454
+ ````
2455
+
2456
+ The block displays **Table 1: Regional trade routes** and `[[#trade|]]`
2457
+ displays **Table 1** as a link. A nonempty label after the pipe takes precedence.
2458
+ The category comes from the following block: a regular code fence is code,
2459
+ a Markdown or expanded SQL table is a table, an image is a figure, and any other
2460
+ block is prose. The website and Foundry number each category within a note;
2461
+ the PDF numbers each category through the book. Foundry gives a captioned block
2462
+ its own JournalEntryPage, so its anchor has a page UUID.
2463
+
2464
+ `:::info` and `:::warn` mark explanatory and cautionary boxes inline. Their
2465
+ content is Markdown, and the boxes carry a label and icon on the web, in
2466
+ Foundry, and in the book. An optional `{#id}` gives a box an HTML anchor.
2467
+ Close each with a whole-line `:::`. Empty, nested, and unclosed boxes are
2468
+ errors at the opening line.
2469
+
2470
+ ```markdown
2471
+ :::info
2472
+ The harbor remains open through winter.
2473
+ :::
2474
+
2475
+ :::warn {#flood-warning}
2476
+ Spring floods close the eastern road.
2477
+ :::
2478
+ ```
2479
+
2480
+ ```
2481
+ :::secret
2482
+ This is secret text
2483
+ :::
2484
+ ```
2485
+
2486
+ Use a whole-line `:::secret` opener and a whole-line `:::` closer to mark a GM passage. Markdown inside the block, including links and emphasis, renders normally. Keep the fences outside code blocks and close each one before starting another. An unclosed or nested block is a build error at the source line.
2487
+
2488
+ In FoundryVTT, the passage is a secret section visible to the GM and owners of the journal:
2489
+
2490
+ ```html
2491
+ <section class="secret" id="secret-example">
2492
+ <p>This is secret text</p>
2493
+ </section>
2494
+ ```
2495
+
2496
+ On webpages, the passage appears as a collapsed spoiler that any reader can open:
2497
+
2498
+ ```html
2499
+ <details>
2500
+ <summary>Spoiler</summary>
2501
+ <p>This is secret text</p>
2502
+ </details>
2503
+ ```
2504
+
2505
+ In books, the passage is visible under a **GM note** label. A printed page has no reader permissions or interactive disclosure control.
2506
+
2507
+ #### Actors
2508
+
2509
+ All actor types, including being and vehicle, have their entire markdown section processed as normal.
2510
+
2511
+ The following H1 headers are treated specially:
2512
+
2513
+ - `# ... {#appearance}`: The contents of this header become the `doc.appearance` property in sohl and `doc.description` in hm3.
2514
+ - `# ... {#dossier}`: The contents of this header become the `doc.dossier` property in sohl and `doc.biography` in hm3.
2515
+ - `# ... {#spoilers}`: The contents of this header are not written to the actor at all.
2516
+
2517
+ For JournalEntries, the following rules apply:
2518
+
2519
+ - `# ... {#spoilers}`: The contents of this header go into a page which is viewable only by the GM (`CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE`).
2520
+
2521
+ **Archetypes** describe characters in broad terms and help match them to an adventure. The list is:
2522
+
2523
+ - warrior: Can hold a line and win a fight.
2524
+ - skirmisher: Fights light — ambush, missile, mobility.
2525
+ - infiltrator: Gets in unseen — locks, stealth, disguise.
2526
+ - mage: Commands arcane practice.
2527
+ - cleric: Commands religious practice and standing.
2528
+ - healer: Treats wounds and illness.
2529
+ - scholar: Reads, researches, and knows things.
2530
+ - courtier: Navigates rank, negotiation, and intrigue.
2531
+ - woodsman: Travels and survives wild country.
2532
+ - mariner: Handles boats and blue water.
2533
+ - artisan: Builds, repairs, and appraises craft work.
2534
+ - trader: Moves goods, values them, and knows markets.
2535
+
2536
+ Note that archetypes are descriptive, not proscriptive, and a character may be described by multiple archetypes at once.
2537
+
2538
+ #### Items
2539
+
2540
+ All item types, including affiliation, affliction, armor, armorlocation, attribute, concoction, containergear, miscgear, mystery, mysticalability, projectile, skill, trauma, weapon, and lore, have their entire markdown section processed as normal.
2541
+
2542
+ An Item can mark its readable description with `# ... {#description}`. That heading starts a JournalEntryPage even when other pages precede it. Its Address is `<package>-note-<type>-<shortcode>#description`; the compiler resolves it to one `@UUID[...]` pointer. SoHL stores that pointer in `system.docHtml`, and HM3 stores the same pointer in `system.description` for Item subtypes that declare the field. An Item without this anchor points to its first page. Other pages remain addressable by their own anchors.
2543
+
2544
+ ### type: being
2545
+
2546
+ Generates a living (or undead, or spirit) being.
2547
+
2548
+ | `data` property | Values | Description |
2549
+ | --------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------ |
2550
+ | `tokenIcon` | `Address` | What a token on the canvas wears — an `icon` address; defaults to `icon` |
2551
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
2552
+ | `archetypes` | `Archetype[]` | What sort of character this is. **Always an array** — `[]` where none apply; `null` is an error. |
2553
+ | `occupation` | `string` | Name of the character's occupation |
2554
+ | `social` | `Map<string, unknown>` | The being's social profile |
2555
+ | `stations` | `Address[]` | Name of the stations the character belongs to |
2556
+ | `lore` | `Address[]` | Other lore concerning this being, such as social standing or law |
2557
+ | `culture` | `Address` | Primary culture, naming a lore note with `subType: culture` |
2558
+ | `homes` | `Address[]` | Place the being calls home |
2559
+ | `affiliations` | `Address[]` | Affilliations (e.g., arcane/divine traditions, polities, etc) |
2560
+ | `socialTies` | `Map<Address, Tie>` | Defining support and opposition, directed from this being to others |
2561
+ | `gender` | `male \| female \| other` | Gender of the character |
2562
+ | `species` | `Address` | Being's species (lore) |
2563
+ | `born` | `date \| unknown` | When the being was born; absent, it was never born |
2564
+ | `calendar` | `Address` | Calendar note used to print the being's dates |
2565
+ | `died` | `date \| unknown` | When the being died; absent, it is alive |
2566
+ | `age` | `34 \| ~34` | Age in years, stated only to override what `born` says; `~` marks an estimate |
2567
+ | `ageYears` | `number` | Written by the compiler beside an `age` estimate — the `~` stripped; a note never authors this |
2568
+ | `height` | `1.91m \| 6' 3"` | Height in metres or feet and inches |
2569
+ | `weight` | `85kg \| 187 lbs` | Body weight in kilograms or pounds |
2570
+ | `frame` | `scant \| light \| medium \| large \| massive` | Relative frame size |
2571
+ | `appearance.eye_color` | `string` | Eye color |
2572
+ | `appearance.hair_color` | `string` | Hair color |
2573
+ | `appearance.skin_color` | `string` | Skin color |
2574
+ | `appearance.complexion` | `string` | Complexion |
2575
+ | `appearance.extra_features` | `string[]` | Extra features |
2576
+
2577
+ Author `data.height` as a string of decimal metres (`1.91m` or `1.91 m`) or
2578
+ whole feet with optional inches (`6'`, `6'3"`, or `6' 3"`). Inches must be
2579
+ between 0 and 11. Author `data.weight` as a string of decimal kilograms (`85kg`
2580
+ or `85 kg`) or whole pounds (`187lbs` or `187 lbs`). The infobox prints both in
2581
+ feet and inches and whole pounds, rounding metric input to the nearest inch or
2582
+ pound. Numeric values are also accepted as metres and kilograms during content
2583
+ conversion; explicit units make an authored value unambiguous. These fields
2584
+ describe a being's body. Item gear weight remains a numeric `data.weight`.
2585
+
2586
+ `data.culture` names the culture in which a character belongs. It is one
2587
+ Address to a `type: lore`, `subType: culture` note. A named character with a
2588
+ known culture states it here; generic beings and templates can omit it.
2589
+ `data.lore` holds other lore concerning the being, such as social standing or
2590
+ law. Record the primary culture in `data.culture` only.
2591
+
2592
+ ```yaml
2593
+ data:
2594
+ culture: lore-vedyariclt
2595
+ lore: [lore-commonerrnk]
2596
+ ```
2597
+
2598
+ #### The people who matter to a being
2599
+
2600
+ `data.socialTies` records relationships that define the subject's dossier, not
2601
+ everyone the subject knows. An absent entry says nothing about that relationship.
2602
+ An acquaintance belongs here when, for example, a guard will recognize the
2603
+ subject at a gate. A father and an independent adult son can have no tie in
2604
+ either note; a daughter who relies on her father may be his `dependent`.
2605
+
2606
+ The keys are bare Addresses with an explicit `being` or `affiliation` type.
2607
+ Values are `patron`, `friend`, `dependent`, `acquaintance`, `rival`, or
2608
+ `nemesis`. A patron supports the subject from greater power or status; a
2609
+ friend supports them from goodwill; a dependent relies on their protection;
2610
+ an acquaintance knows them without a strong disposition; a rival opposes them
2611
+ without implacable hostility; a nemesis opposes them personally and
2612
+ implacably. The infobox groups targets by term and links to their notes.
2613
+
2614
+ ```yaml
2615
+ data:
2616
+ socialTies:
2617
+ being-kaldor: patron
2618
+ being-alys: dependent
2619
+ affiliation-silverguild: nemesis
2620
+ kethira-note-being-kaldas: rival
2621
+ ```
2622
+
2623
+ The full Address names a target in another package. Each target has one tie:
2624
+ writing its short and full Address in the same map still names one target and is
2625
+ an error.
2626
+
2627
+ Each tie is one-sided. The target need not name the subject back, even when
2628
+ `patron` and `dependent` would describe opposite ends of the same support.
2629
+ Kinship, household membership, organizational membership, rank, and office are
2630
+ distinct facts. Their relevant effects may appear as ties, while their details
2631
+ belong in prose, `data.affiliations`, or governance fields. A tie has no
2632
+ mechanical modifier.
2633
+
2634
+ #### When a being was born, when it died, and how old it is
2635
+
2636
+ **Absence asserts a fact rather than recording a gap**, and that is what these
2637
+ three fields are built around.
2638
+
2639
+ - **`born` absent** says the being was **never born** — a construct, a spirit,
2640
+ an elemental, a thing that was made. **`born: unknown`** says it was born and
2641
+ the date is unrecorded, which is the commonest shape in a tree.
2642
+ - **`died` absent** says the being is **alive**, which is the common case and so
2643
+ takes the cheapest form. **`died: unknown`** says it is dead and the date is
2644
+ unrecorded. Both spellings of `died` mean dead.
2645
+ - **`age` is optional and is an override.** Written, it wins over anything the
2646
+ dates say. Unwritten beside a dated `born`, it is the age that follows from
2647
+ the date and the package's declared present — see below. Unwritten beside
2648
+ `born: unknown` or an absent `born`, the age is unknown.
2649
+
2650
+ Most people in a setting do not know their own birth date, so an age beside
2651
+ `born: unknown` is an ordinary complete record and not a note working around the
2652
+ schema. Where the number is somebody's estimate rather than a fact, write it
2653
+ `~34` — the same `~` a date carries. The estimate string is kept exactly as
2654
+ written; the compiler adds `ageYears` beside it, the plain number with the `~`
2655
+ stripped, for anything that needs to sort or filter on the age rather than
2656
+ read it.
2657
+
2658
+ #### The age a dated `born` computes to
2659
+
2660
+ A package states the day its setting stops on `data.present`, on its own
2661
+ `world` or `celestial` `place` note — see _What a body states about itself_
2662
+ under `type: place`. Against that date, a being with a dated `born` and no
2663
+ authored `age` compiles the age it implies: the two dates compared, not the
2664
+ two years subtracted, so a birthday that falls later in the year than the
2665
+ present counts one less than the years alone would say. A package that
2666
+ declares no present computes no age and says nothing about it — the same
2667
+ silence a package with no calendar mechanism meets.
2668
+
2669
+ **An authored `age` beside a dated `born` is expected to agree with what the
2670
+ date computes.** Where it does not, `package-build lint` raises a warning
2671
+ naming both values, so a stale age left behind after a `born` was corrected
2672
+ does not go unnoticed. The warning is silent beside `born: unknown` or an
2673
+ absent `born` — there is nothing there to disagree with — and silent wherever
2674
+ the package declares no present, for the same reason no age is computed at
2675
+ all.
2676
+
2677
+ Use `born:` for a being's birth date. The `birthday:` spelling is accepted with a diagnostic; `born` takes precedence when both are written.
2678
+
2679
+ #### The lead image
2680
+
2681
+ **A being's portrait is the first thing inside its `{#appearance}` section,
2682
+ always.**
2683
+
2684
+ ```markdown
2685
+ # Appearance {#appearance}
2686
+
2687
+ ![[<address>|<the being's full name>]]{float=top-left}
2688
+ ```
2689
+
2690
+ **Nothing about that embed is special.** It is an ordinary embedded image with
2691
+ an ordinary directive, and no pass treats it differently for sitting where it
2692
+ sits. What is strict is the convention: the portrait opens the appearance
2693
+ section, so a reader opening any being note finds the picture in the same place,
2694
+ and an author writing one never has to decide where it goes.
2695
+
2696
+ **The section is the part that is not a convention.** `{#appearance}` is what
2697
+ becomes an actor's `system.appearance`, so a picture written above that heading
2698
+ is outside the section and reaches no document. The heading carries the anchor
2699
+ at **H1** — a deeper heading is not matched, and its whole section is extracted
2700
+ as empty rather than reported.
2701
+
2702
+ The label is the being's full name. That is what a reader needs when the picture
2703
+ does not load, and what a screen reader announces in its place.
2704
+
2705
+ **There is no `portrait` field**, at any level. A portrait is a picture of the
2706
+ subject, and a picture of the subject belongs in the prose that describes it —
2707
+ where an author can see it, move it, caption it or remove it, as with any other
2708
+ image. A field puts it somewhere only a template can reach, and every template
2709
+ then has to agree about where that is.
2710
+
2711
+ Later images are ordinary images too — a second picture of the being, a coat of
2712
+ arms, a map of its holding — and they sit wherever the prose wants them,
2713
+ including in `{#dossier}`.
2714
+
2715
+ **A being with no portrait writes no lead image.** There is no empty form and no
2716
+ placeholder.
2717
+
2718
+ #### A being's embedded items
2719
+
2720
+ `<system>.items` is a list, and each entry compiles into one embedded Item. An
2721
+ entry takes one of two shapes.
2722
+
2723
+ **A copy of a catalogue item** names it with `model:` — an address, read by the
2724
+ same grammar every wikilink is. The entry's remaining keys are merged over the
2725
+ model, so it carries the model's values except where it says otherwise.
2726
+
2727
+ ```yaml
2728
+ sohl:
2729
+ items:
2730
+ - { model: skill-wpnc, system: { masteryLevelBase: 52 } }
2731
+ - { model: sohl-sohl-weapongear-whmr }
2732
+ - { model: sohl-sohl-weapongear-dgr, system: { shortcode: dgr2, name: Offhand dagger } }
2733
+ ```
2734
+
2735
+ | key | required | meaning |
2736
+ | -------- | -------- | ------------------------------------------------------------- |
2737
+ | `model` | yes | The address of the item this entry is a copy of |
2738
+ | `system` | no | Values that override the model's |
2739
+ | `name` | no | A name of this entry's own, where it differs from the model's |
2740
+ | `data` | no | Art this entry names — see _An entry's art_ below |
2741
+
2742
+ **`type:` is not written beside a `model`.** The address already names the type,
2743
+ so a second statement of it is a place to be wrong, and it is refused.
2744
+
2745
+ **The address is written at whatever length says what it means.** Within this
2746
+ package `type-shortcode` is enough; reaching another package means the full
2747
+ `package-system-type-shortcode`, since the forms are suffixes of the canonical
2748
+ address. The system segment defaults from the block the entry sits in — an entry
2749
+ under `sohl.items` defaults to `sohl` — which is why the short form names an
2750
+ **Item** here while the same string in body prose names a page.
2751
+
2752
+ **A custom item** — one that copies nothing — states `name`, `type` **and**
2753
+ `system.shortcode`, all three required, plus whatever else its data model needs.
2754
+ It is written in **block form**, never inline, so the two kinds of entry are
2755
+ distinguishable at a glance:
2756
+
2757
+ ```yaml
2758
+ sohl:
2759
+ items:
2760
+ - name: Whetstone
2761
+ type: miscgear
2762
+ system:
2763
+ shortcode: whetstone
2764
+ weight: 1
2765
+ value: 5
2766
+ durability: 0
2767
+ ```
2768
+
2769
+ An embedded custom item writes its identity in `system.shortcode`; a top-level `shortcode` key in the embedded entry is invalid.
2770
+
2771
+ #### An entry's art
2772
+
2773
+ An entry is an item in every respect but where it is written, so it carries art
2774
+ by the two rules [an item note](#the-four-art-slots) does. It names an `icon`
2775
+ address under its own `data:`, and that resolves into the embedded document's
2776
+ `img`:
2777
+
2778
+ ```yaml
2779
+ sohl:
2780
+ items:
2781
+ - name: Quiver (leather)
2782
+ type: miscgear
2783
+ data:
2784
+ icon: quiver
2785
+ system:
2786
+ shortcode: quiver
2787
+ ```
2788
+
2789
+ `icon` is the only slot an entry has. The other three belong to a note: two
2790
+ describe an Actor or a map, and `banner` is a page's hero image, which an
2791
+ embedded document has no page for.
2792
+
2793
+ **Naming none is the common case, and it takes a default.** An entry copying a
2794
+ `model:` carries the catalogue item's art, and an entry copying nothing takes
2795
+ the default its type pairs in `itemBuilders` — the same default an item note of
2796
+ that type compiles with. An entry naming an address of its own overrides either.
2797
+ Writing `icon: ""` ships the document blank on purpose, and no default replaces
2798
+ it.
2799
+
2800
+ **`data:` reaches no compiled document.** It is where a note names art, and a
2801
+ compiled Item has `img` instead; the container is read and goes no further.
2802
+
2803
+ #### Identifying a being's embedded items
2804
+
2805
+ Each entry in `sohl.items` compiles into one embedded Item, and its `_id` is
2806
+ derived from **what the entry is**, never from where it sits in the list:
2807
+
2808
+ ```
2809
+ _id = makeId(<the actor's id>, "<subType>:<system.shortcode>")
2810
+ ```
2811
+
2812
+ An entry's identity is its **own `system.shortcode`**. The `model:` is not it —
2813
+ a model names the item this entry is a _copy of_, and is never written to the
2814
+ document — so two entries may share one model and are then two embodiments, each
2815
+ stating its own identity:
2816
+
2817
+ ```yaml
2818
+ sohl:
2819
+ items:
2820
+ - model: weapongear-dgr # the catalogue's dagger
2821
+ name: Dagger 1
2822
+ system:
2823
+ shortcode: dgr1 # this dagger's own identity
2824
+ - model: weapongear-dgr
2825
+ name: Dagger 2
2826
+ system:
2827
+ shortcode: dgr2
2828
+ ```
2829
+
2830
+ **Two entries resolving to one identity are a build error naming both.** Without
2831
+ their own `system.shortcode`, both daggers above carry the model's `dgr`,
2832
+ which makes them the same entity to everything that resolves by `(type,
2833
+ shortcode)` — compendium/world reconciliation, template shadowing, cohort
2834
+ membership, effect and expression references. A `name` cannot stand in: it is
2835
+ presentation, free to be localized or to diverge.
2836
+
2837
+ **Reordering the list moves no id.** A key carrying the entry's position
2838
+ so inserting an item renumbered every id after it and a re-import created new
2839
+ documents beside the old ones — while nothing about those documents had changed,
2840
+ only their neighbours. The same is now true of a note's journal pages: an
2841
+ unanchored page is keyed on its heading, so inserting a heading leaves every
2842
+ other page's id where it was. Two sibling pages sharing a heading is likewise a
2843
+ build error, matching the `MD024` lint rule that already refuses it.
2844
+
2845
+ A `sohl` block produces a SoHL actor of type `being`. An `hm3` block produces an HM3 actor. Its document type is **not** derived: `hm3.type` states it, and must be `character` or `creature`. A note that omits it is an error naming the note — see _The note vocabulary, and how it maps_.
2846
+
2847
+ A SoHL "being" document is created, as will an "HM3" document.
2848
+
2849
+ | shared source | → sohl | → hm3 |
2850
+ | ----------------- | ------ | ------------------- |
2851
+ | `data.species` | NA | NA |
2852
+ | `data.gender` | NA | `system.gender` |
2853
+ | `data.occupation` | NA | `system.occupation` |
2854
+
2855
+ `data.species` is a lore Address describing the being's species. HM3's
2856
+ `hm3.system.species` is independent native text; it does not inherit from
2857
+ `data.species`. The in-block `hm3.species` spelling is also accepted, with
2858
+ `hm3.system.species` taking precedence.
2859
+
2860
+ ### type: homepage
2861
+
2862
+ The package's front page, published at `https://www.heroiclands.org/<package>/`.
2863
+ Exactly one note in a content tree declares this type, and it compiles into
2864
+ no Foundry document: it is a page and nothing else.
2865
+
2866
+ It declares a `shortcode` — conventionally `root` — because that is what a
2867
+ link is written with: `[[homepage-root|Text]]` is an ordinary wikilink, and it
2868
+ resolves to `/<package>/`. The shortcode names the page in links; its address
2869
+ is the package root. The page heading reads `name.full` and defaults to
2870
+ `packageBuild.manifest.title`. `description` supplies the page's short
2871
+ description.
2872
+
2873
+ In a compiled Foundry journal, a wikilink to the homepage prints its label
2874
+ as text. The homepage has no compendium document to open.
2875
+
2876
+ The page is its body, published verbatim: no wikilink is resolved on it and
2877
+ no table expanded, so its links are markdown links, package-relative
2878
+ (`kb/rules/`) or external. An index of what the package publishes is not
2879
+ written here and is not generated anywhere — it is a `doc` note carrying a
2880
+ content table, linked from this page like any other.
2881
+
2882
+ The homepage uses the same top-level allowlist as every addressed note. It has
2883
+ no Foundry document id; a pinned `data.id` has no role on this type.
2884
+
2885
+ ### type: vehicle
2886
+
2887
+ Represents a conveyance able to hold goods and people moving from one place to another.
2888
+
2889
+ | `data` property | Values | Description |
2890
+ | ------------------ | --------- | ------------------------------------------------------------------------ |
2891
+ | `tokenIcon` | `Address` | What a token on the canvas wears — an `icon` address; defaults to `icon` |
2892
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
2893
+
2894
+ If `sohl` is present, this becomes a `vehicle` actor.
2895
+
2896
+ It maps nothing beyond the shared rows above, actor row included: a vehicle
2897
+ carries a template priority and no field of its own.
2898
+
2899
+ ### type: affiliation
2900
+
2901
+ Represents a related group of beings.
2902
+
2903
+ Note that affiliations may have multiple parent affiliations.
2904
+
2905
+ **A pantheon is not a subType.** It is a `faithtradition` carrying subordinate
2906
+ faith traditions — the individual religions of that pantheon name it in
2907
+ `parents`. There is no `pantheon` value, because every other subType answers
2908
+ _what kind of body is this_ while a pantheon answers _where does it sit_, and an
2909
+ enum that mixes the two makes a note choose between a kind and a level. The
2910
+ hierarchy already carries the answer, so nothing is lost by deriving it: a faith
2911
+ tradition with faith traditions beneath it is a pantheon.
2912
+
2913
+ The same reasoning is why the three traditions are siblings. `faithtradition`,
2914
+ `arcanetradition` and `spirittradition` partition by _what a practice concerns_ —
2915
+ the divine, magic, the spirit world — and that partition is load-bearing rather
2916
+ than descriptive: a system filtering which mystical practices may associate with
2917
+ an affiliation can only be as precise as the distinction it filters on.
2918
+
2919
+ **subType**:
2920
+
2921
+ - guild: A sworn association of craftsmen holding monopoly over a trade within a locality.
2922
+ - order: A body of members bound by vows or a rule of life to a shared purpose.
2923
+ - polity: A sovereign body ordering the persons within a territory — states, city-states, tribal confederations. Its ranks apply to all who fall under its authority.
2924
+ - faithtradition: A tradition of belief and practice concerning the divine, whether organized or not.
2925
+ - arcanetradition: A tradition of belief and practice concerning magic and its practice, whether organized or not.
2926
+ - spirittradition: A tradition of belief and practice concerning spirits — ancestors, totems, and the numinous world — whether organized or not.
2927
+ - lineage: A body claiming common descent from a known ancestor, whose standing and obligations pass by birth — clans, houses, dynasties, septs.
2928
+ - venture: A band bound by contract or shared undertaking rather than by vow or public authority — free companies, ships' crews, trading expeditions, adventuring parties.
2929
+ - criminal: An association organized to profit from activity its host polity forbids, sustained by its own enforcement rather than by law.
2930
+ - governmental: An organ constituted by a polity to exercise some portion of its authority — ministries, chanceries, courts, exchequers. Its ranks apply only to those who serve in it.
2931
+ - fellowship: A voluntary association without vow, trade monopoly, or public authority — formed for mutual company, aid, or shared practice.
2932
+
2933
+ **GovernanceModel**
2934
+
2935
+ - Autocracy: a single person holds unchecked authority, however acquired
2936
+ - Monarchy: one ruler legitimated by descent, election, or sacred office
2937
+ - Oligarchy: a small closed group rules, whether by birth, wealth, or rank — membership is not
2938
+ conferred by election and its authority is not held for a term
2939
+ - Republic: sovereignty rests in the citizen body and is exercised through offices held for a fixed
2940
+ term by election, which a ruling class supplies the holders of in practice
2941
+ - Council: a deliberating body governs collectively with no single head
2942
+ - Democracy: the general membership decides, directly or through representatives, and any member may
2943
+ hold office
2944
+ - Theocracy: authority derives from divine mandate and rests with its clergy
2945
+ - Meritocracy: position is earned by demonstrated skill, achievement, or expertise
2946
+ - Stratocracy: the armed force is itself the government
2947
+ - Feudal: authority flows through nested personal oaths rather than a central office
2948
+ - Confederation: autonomous members retain sovereignty under a weak common center
2949
+ - Anarchic: no formal governing authority — custom or force fills the gap
2950
+
2951
+ **Republic, Oligarchy and Democracy are three answers to one question**, and the
2952
+ boundaries are testable rather than a matter of taste. Ask who fills the offices
2953
+ and on what terms. If a closed group holds authority outright, with no election
2954
+ and no term, it is an **Oligarchy**. If offices are elective and time-limited but
2955
+ a propertied or senatorial order supplies nearly everyone who holds them, it is a
2956
+ **Republic**. If any member may hold office and the general body decides, it is a
2957
+ **Democracy**.
2958
+
2959
+ The distinction is not academic: it is the difference between a ladder whose top
2960
+ rungs are a class one is born or bought into, and one whose top rungs are an
2961
+ office one is voted into and then vacates. A republic's ladder therefore carries
2962
+ **both** — the civic status (Citizen) and the standing in the governing body above
2963
+ it (Senator) — because a citizen is not a member of the Senate, and conflating the
2964
+ two is the commonest way to get a non-monarchy wrong.
2965
+
2966
+ **Rank**
2967
+ Definition of a level within the organization (e.g., Priest, Layperson, Member, Gang Leader, Master, etc.)
2968
+
2969
+ | Property | Values | Description |
2970
+ | ------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------ |
2971
+ | `level` | number | The ranking within the affiliation, increasing values starting with 1, 0 indicates intentional exclusion (expulsion/excommunication) |
2972
+ | `title` | string | Title associated with the Rank |
2973
+ | `description` | string | Description of the Rank |
2974
+ | `lore` | `Address` | The standing this rank _is_ — a `lore` note of subType `law`, shared with every other body that confers the same thing |
2975
+
2976
+ **A rank's `lore` is shared; its `title` is not.** A Normen kingdom calls it
2977
+ `Thrall` and a Vylarian province calls it `Slave`, and they mean one standing:
2978
+ owned outright, with no standing at law except through an owner. The title is
2979
+ what this body calls it and the description is how this body puts it, but the
2980
+ obligations and rights belong to the standing itself, so they are written once
2981
+ and cited by every ladder that confers it.
2982
+
2983
+ That is also what makes a rank answerable across bodies. Without it, asking what
2984
+ a `Naukrátissa` may do means reading the Bethûan fleet's ladder; with it, the
2985
+ rank names the standing, and the standing says.
2986
+
2987
+ **Standing**: aligned, unaligned, rival, nemesis
2988
+
2989
+ | `data` property | Values | Description |
2990
+ | -------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
2991
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
2992
+ | `demonym` | `string` | What a member of this affiliation is called (a Vylarian) |
2993
+ | `epithet` | `string` | The by-name it is known by — a god's, an order's, a company's |
2994
+ | `symbol` | `string` | Its emblem in words: a feather atop a golden scale, a chisel carving a star |
2995
+ | `governance.model` | `GovernanceModel` | Type of government structure, if applicable |
2996
+ | `governance.summary` | `string` | summary of the governance situation |
2997
+ | `governance.ranks` | `Rank[]` | The ranks available to members of the affiliation |
2998
+ | `governance.offices` | `Map<name, description \| {description, holders}>` | Official offices and their dated holders |
2999
+ | `seat` | `Address` | Where the affiliation's authority sits |
3000
+ | `domains` | `Address[]` | Places over which this affiliation holds sway |
3001
+ | `population` | `number` | Number of people in the affiliation (precision 2 significant digits). |
3002
+ | `economy` | `Address[]` | Economic life: `affiliation` or `lore` Addresses, with an explicit type and no default |
3003
+ | `lore` | `Address[]` | Lore concerning it — the peoples it draws on, the god a faith venerates, its law, its calendar |
3004
+ | `parents` | `Address[]` | Affiliations that this affiliation is subordinate to |
3005
+ | `relations` | `Map<Address, Standing>` | Standing with other affiliations, keyed by the other body's Address |
3006
+
3007
+ **A faith tradition is not its god.** An `affiliation` of subType
3008
+ `faithtradition` is a _religion_ — a practice, with an ordained priesthood, a
3009
+ calendar and a body of observance — and it can outlive belief in the god
3010
+ entirely, while one god may be venerated by several religions that agree on
3011
+ nothing else. So the god is `lore` of subType `deity`, cited from the faith
3012
+ through `lore`, and never a property of it.
3013
+
3014
+ `epithet` and `symbol` stay with the religion for the same reason, and they are
3015
+ not a faith's alone: they are what the members call the thing and what they
3016
+ carve, so a guild has them as much as a cult does. What a god _is_ — its nature,
3017
+ its domains, its aspects — belongs on the deity note, where every religion that
3018
+ venerates it can point at one account.
3019
+
3020
+ **Every one of those describes the organisation.** A membership — which office a
3021
+ being holds, what title it bears, where it stands on the ladder — is recorded on
3022
+ the affiliation _as embedded on that being_, and nothing about it belongs in the
3023
+ catalogue entry. That is the split `governance` draws: the organisation publishes
3024
+ the ranks and offices that exist, and a membership names which of them it holds.
3025
+
3026
+ **Rank is the organisation's, not the member's.** A bare `level: 4` on a
3027
+ membership says nothing on its own; it means _Knight_ only because the polity
3028
+ declared that rung. So the ladder is authored once, on the body that confers it,
3029
+ and a member's rank is an index into it. Level 0 is reserved for the excluded —
3030
+ outlawed, expelled, excommunicated — which is a standing the organisation still
3031
+ recognises, and so still has to define.
3032
+
3033
+ **Offices are named, not ranked.** A Chancellor and a Marshal are both great
3034
+ officers and neither is above the other, so an office is a key with a
3035
+ description rather than a rung — and a being may hold an office at any rank, or a
3036
+ rank with no office at all.
3037
+
3038
+ An office may remain a string description or declare a `description` and a
3039
+ `holders` list. Each holder names a `being` Address; optional `start` and `end`
3040
+ use the date forms above. Omit `end` for a current holder. Set `contested: true`
3041
+ when a holder's term may overlap another's. The build checks addresses, term
3042
+ order, and unmarked overlaps. A being with any `died` value, including
3043
+ `unknown`, cannot be named as a current holder. Office entries accept only
3044
+ `description` and `holders`; holder rows accept only `being`, `start`, `end`,
3045
+ and `contested`.
3046
+
3047
+ ```yaml
3048
+ data:
3049
+ governance:
3050
+ offices:
3051
+ Chancellor:
3052
+ description: Keeps the seal.
3053
+ holders:
3054
+ - being: being-aran
3055
+ start: "datefrom vrcal 14 Vulcar 720 VR"
3056
+ end: "datefrom vrcal 1 Lusenar 725 VR"
3057
+ - { being: being-mara, start: "datefrom vrcal 2 Lusenar 725 VR" }
3058
+ ```
3059
+
3060
+ **`domains` is territorial, and only territorial.** Seventy-seven divine
3061
+ affiliations currently spell `domain:` the other way, holding a deity's sphere of
3062
+ influence as prose — _Love, Beauty, and Prosperity_; _Fertility, Agriculture,
3063
+ Peace, and Healing_. That sense **folds into `description`**, which those notes
3064
+ leave empty in all but one case, and it does not become a field of its own.
3065
+
3066
+ A sphere is a characterisation, not a reference. Nothing compiles against it, no
3067
+ other note points at it, and rendering it as a list would imply a vocabulary that
3068
+ does not exist — whereas `description` is exactly the field for a one-line
3069
+ statement of what a thing is. The fuller treatment already has a home: `lore` of
3070
+ subType `deity` covers a god's nature, domains, epithets and aspects in prose.
3071
+
3072
+ That frees the name for the territorial sense, which earns it: every affiliation
3073
+ subType holds sway somewhere, while the divine sense applies to one.
3074
+
3075
+ **`domains` names what the affiliation holds directly**, and nothing beneath
3076
+ it. A polity whose `domains` names a region holds the region; the settlements
3077
+ within it are the region's, reached through its `contains`, and are not
3078
+ repeated here. A house holds its manors and names each; the crown holds the
3079
+ realm and names the realm. What lies below a holder is answered by the two
3080
+ hierarchies together — `place.parents` for where a place sits, and
3081
+ `affiliation.parents` for whom a holder answers to — so a subinfeudated manor
3082
+ sits in one region by the first and under a lord of another polity by the
3083
+ second, and both pages say so.
3084
+
3085
+ The website reads `domains` across the package and every fetched index and
3086
+ writes the affiliation's page a `holdings` list — every place its `domains`
3087
+ names, each entry `{ title, url, type, subType }`, sorted by `subType` then
3088
+ `title`, and absent when `domains` names nothing. Every place it names carries
3089
+ the affiliation back in `held_by`. Both are derived; a note that writes one has
3090
+ it replaced.
3091
+
3092
+ If `sohl` is present, this becomes an `affiliation` item.
3093
+
3094
+ | shared source | → sohl | → hm3 |
3095
+ | ---------------- | ------------------ | ----- |
3096
+ | `subType` | `system.subType` | NA |
3097
+ | `data.seat` | `system.seat` | NA |
3098
+ | `data.domains` | `system.domain` | NA |
3099
+ | `data.parents` | `system.parents` | NA |
3100
+ | `data.relations` | `system.relations` | NA |
3101
+
3102
+ An affiliation writes SoHL common skills under `sohl.system.commonSkills` as a
3103
+ list of skill Addresses. An abbreviated value names a skill in the authoring
3104
+ package; a skill in the SoHL system package uses its complete Address:
3105
+
3106
+ ```yaml
3107
+ sohl:
3108
+ system:
3109
+ commonSkills:
3110
+ - herb
3111
+ - sohl-sohl-skill-law
3112
+ ```
3113
+
3114
+ The compiled SoHL Item stores the exact published Item UUIDs in
3115
+ `system.commonSkills`. An empty or absent list emits `[]`. A foreign target must
3116
+ come from a declared `relationships.systems` or `relationships.requires` content
3117
+ index. Each UUID uses the module and pack named by that target's published index;
3118
+ a missing skill Item or Item UUID is an error at the authored Address. The
3119
+ content index retains complete Addresses. Journal, web and book infoboxes link
3120
+ to a skill's published documentation journal when one exists and otherwise show
3121
+ its name as plain text.
3122
+
3123
+ `governance` reaches no system field. Ranks and offices are the note's and the
3124
+ web page's — SoHL's affiliation item has nowhere to put them, and inventing a
3125
+ mapping for a field no schema declares is the drift these tables exist to catch.
3126
+ `system.society`, `system.office`, `system.title` and `system.level` are
3127
+ likewise absent here: they are filled on an embedded membership, never from a
3128
+ catalogue note's `data:`.
3129
+
3130
+ **`system.title` is the style of address the office carries**, Ajaw or Warden,
3131
+ which a being holds by virtue of its rank. The note's page heading reads
3132
+ `name.full`. Author the style of address on the membership — the `system.title`
3133
+ of the entry in a being's `sohl.items` — or, on a catalogue note that carries
3134
+ one, at `sohl.system.title`.
3135
+
3136
+ ### type: affliction
3137
+
3138
+ Represents an affliction.
3139
+
3140
+ **subType**:
3141
+
3142
+ - disease: A biological affliction: an illness or parasite that infects the body or mind (e.g. typhoid, tuberculosis, river blindness)
3143
+ - poisontoxin: A chemical affliction: a toxic substance or venom that impairs or kills the host (e.g. hemotoxin, mandrake, wasp venom)
3144
+ - maladiction: A supernatural affliction: a curse, hex, or divine/spiritual blight that assails the body, mind, or aura by arcane, divine, or spirit means. The affliction is a metaphysical agent with a course and outcome
3145
+
3146
+ **TransmissionTypes**
3147
+
3148
+ - none: no transmission mode
3149
+ - airborne: Transmission through the air, such as via droplets or aerosols
3150
+ - contact: Transmission through direct physical (skin) contact
3151
+ - bodyfluid: Transmission through bodily fluids: blood, saliva, etc.
3152
+ - injested: Transmission through ingestion of contaminated substances
3153
+ - proximity: Transmission through close proximity to an infected individual, but separate from airborne or direct contact modes.
3154
+ - vector: Transmission through a vector, such as an insect or animal bite
3155
+ - perception: Transmission through sensory perception, such as sight or sound
3156
+ - arcane: Transmission through arcane means
3157
+ - divine: Transmission through divine means
3158
+ - spirit: Transmission through spirit means
3159
+
3160
+ | `data` property | Values | Description |
3161
+ | ----------------------------- | ------------------- | ----------------------------------------------------------------------------------------- |
3162
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3163
+ | `transmission` | `TransmissionTypes` | Method of transmission |
3164
+ | `outcome` | `death \| cured` | Result after affliction has run its course |
3165
+ | `healingRate` | `number` | Likelihood of positive outcome of healing test |
3166
+ | `contagionIndex` | `number` | how contagious the disease is |
3167
+ | `outcomeTraumas` | `SafeExpression` | Expression returning traumas that result from affliction recovery |
3168
+ | `onsetDurationFormula` | `RollFormula` | Formula to calculate duration until onset after contracting affliction |
3169
+ | `onsetDurationBase` | `number` | That duration in seconds, stated outright instead of rolled |
3170
+ | `healingCheckDurationFormula` | `RollFormula` | Formula to calculate duration until next healing check (measured from last healing check) |
3171
+ | `healingCheckDurationBase` | `number` | That duration in seconds, stated outright instead of rolled |
3172
+ | `resolutionDurationFormula` | `RollFormula` | Formula to calculate duration after onset to resolution |
3173
+ | `resolutionDurationBase` | `number` | That duration in seconds, stated outright instead of rolled |
3174
+
3175
+ If `sohl` is present, this becomes an `affliction` item.
3176
+
3177
+ | shared source | → sohl | → hm3 |
3178
+ | ---------------------------------- | ------------------------------------ | ----- |
3179
+ | `subType` | `system.subType` | NA |
3180
+ | `data.transmission` | `system.transmission` | NA |
3181
+ | `data.outcome` | `system.outcome` | NA |
3182
+ | `data.contagionIndex` | `system.contagionIndexBase` | NA |
3183
+ | `data.outcomeTraumas` | `system.outcomeTraumas` | NA |
3184
+ | `data.onsetDurationFormula` | `system.onsetDurationFormula` | NA |
3185
+ | `data.onsetDurationBase` | `system.onsetDurationBase` | NA |
3186
+ | `data.healingCheckDurationFormula` | `system.healingCheckDurationFormula` | NA |
3187
+ | `data.healingCheckDurationBase` | `system.healingCheckDurationBase` | NA |
3188
+ | `data.resolutionDurationFormula` | `system.resolutionDurationFormula` | NA |
3189
+ | `data.resolutionDurationBase` | `system.resolutionDurationBase` | NA |
3190
+
3191
+ **A timed phase is a triplet, and a note writes two thirds of it.** SoHL stores
3192
+ each phase as `{…DurationFormula, …DurationBase, …Date}`, and the three are
3193
+ authored differently:
3194
+
3195
+ | third | who writes it | what it says |
3196
+ | ------------------ | ------------- | -------------------------------------------------------- |
3197
+ | `…DurationFormula` | the note | what is **rolled** to get the interval |
3198
+ | `…DurationBase` | the note | the interval **outright**, in seconds, instead of a roll |
3199
+ | `…Date` | play | _when the phase actually fired_ |
3200
+
3201
+ Write the **formula** for a phase whose length varies — `"2d6*86400"` for an
3202
+ incubation of two-to-twelve days — and the **base** for one that does not. Both
3203
+ are intervals in **seconds**, and a bare number is a valid formula, so
3204
+ `onsetDurationFormula: 86400` and `onsetDurationBase: 86400` differ only in
3205
+ whether a roll is attempted.
3206
+
3207
+ **Omitting either omits the key**, rather than writing a default over it. That
3208
+ matters because the data model's own `null` is what several phases fall back on:
3209
+ a trauma that sets no `healingCheckDuration*` takes the world's configured
3210
+ healing-check interval, and one that sets no `bloodLossAdvanceDurationBase` does
3211
+ not bleed at all. A compile-time `null` is still a value, and would answer those
3212
+ questions for every note in every world.
3213
+
3214
+ **The `…Date` third is never authored.** It records when the phase fired, which
3215
+ only play can know, so `system.contractDate`, `system.onsetDate`,
3216
+ `system.treatmentDate` and `system.resolutionDate` are runtime state and a note
3217
+ that writes one fails the build. They are world times, and `0` is a valid one,
3218
+ so there is no blank a note could write either; leave them out and the data
3219
+ model's `null` stands.
3220
+
3221
+ `data.healingRate` is page data. SoHL reads an affliction's base healing rate
3222
+ from `sohl.healingRateBase`. `data.outcomeTraumas` supplies the SoHL expression
3223
+ for traumas produced by the affliction's outcome. The optional
3224
+ `sohl.onsetMacroUuid` names a macro run when symptoms begin.
3225
+
3226
+ ### type: armorgear
3227
+
3228
+ Note: `data.quantity` may not be specified. Quantity is always 1.
3229
+
3230
+ | `data` property | Values | Description |
3231
+ | ------------------ | -------- | ------------------------------------------ |
3232
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3233
+ | `weight` | `number` | Gear weight |
3234
+ | `value` | `number` | Gear value |
3235
+ | `quality` | `number` | Gear quality |
3236
+ | `durability` | `number` | Gear durability |
3237
+
3238
+ If a `sohl` property is present, a SoHL item of type "armorgear" is created.
3239
+
3240
+ The note type is `armorgear` in both systems.
3241
+
3242
+ | shared source | → sohl | → hm3 |
3243
+ | ----------------- | ----------------------- | --------------- |
3244
+ | `data.weight` | `system.weightBase` | `system.weight` |
3245
+ | `data.value` | `system.valueBase` | `system.value` |
3246
+ | `data.quality` | `system.qualityBase` | NA |
3247
+ | `data.durability` | `system.durabilityBase` | NA |
3248
+
3249
+ ### type: armorlocation
3250
+
3251
+ | `data` property | Values | Description |
3252
+ | ------------------ | -------- | ------------------------------------------ |
3253
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3254
+
3255
+ if a `hm3` property is present, an HM3 item of type "armorlocation" is created.
3256
+
3257
+ | shared source | → sohl | → hm3 |
3258
+ | ------------- | ------ | ----- |
3259
+ | `notes` | NA | NA |
3260
+
3261
+ ### type: attribute
3262
+
3263
+ | `data` property | Values | Description |
3264
+ | ------------------ | -------- | ------------------------------------------ |
3265
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3266
+
3267
+ if a `sohl` property is present, a SoHL item of type "attribute" is created.
3268
+
3269
+ | shared source | → sohl | → hm3 |
3270
+ | ------------- | ------ | ----- |
3271
+
3272
+ ### type: concoctiongear
3273
+
3274
+ **subType**:
3275
+
3276
+ - mundane: ordinary and common in everyday use, generally simple in composition (often a single dried or otherwise prepared ingredient)
3277
+ - exotic: A complex and valuable concoction, often a mixture of different herbs and/or chemicals, with medicinal or other unique properties or effects, but not magical in nature.
3278
+ - elixir: An arcane alchemical concoction of great power.
3279
+
3280
+ | `data` property | Values | Description |
3281
+ | ------------------ | ------------------------------- | ------------------------------------------------- |
3282
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3283
+ | `weight` | `number` | Gear weight |
3284
+ | `value` | `number` | Gear value |
3285
+ | `quality` | `number` | Gear quality |
3286
+ | `durability` | `number` | Gear durability |
3287
+ | `quantity` | `number` | Gear quantity (default: 1) |
3288
+ | `potency` | `na \| mild \| strong \| great` | Concoction Potency (mundane/exotic concoctions) |
3289
+ | `strength` | `number` | Strength: higher the number, greater the strength |
3290
+
3291
+ if a `sohl` property is present, a SoHL item of type "concoctiongear" is created.
3292
+
3293
+ | shared source | → sohl | → hm3 |
3294
+ | ----------------- | ----------------------- | ----- |
3295
+ | `subType` | `system.subType` | NA |
3296
+ | `data.weight` | `system.weightBase` | NA |
3297
+ | `data.value` | `system.valueBase` | NA |
3298
+ | `data.quality` | `system.qualityBase` | NA |
3299
+ | `data.durability` | `system.durabilityBase` | NA |
3300
+
3301
+ `data.quantity` is page data for a concoction. SoHL sets `system.quantity` to
3302
+ `1` for each compendium item.
3303
+
3304
+ ### type: containergear
3305
+
3306
+ Note: `data.quantity` may not be specified; quantity is always set to 1.
3307
+
3308
+ | `data` property | Values | Description |
3309
+ | ------------------ | -------- | ------------------------------------------ |
3310
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3311
+ | `weight` | `number` | Gear weight |
3312
+ | `value` | `number` | Gear value |
3313
+ | `quality` | `number` | Gear quality |
3314
+ | `durability` | `number` | Gear durability |
3315
+ | `capacity` | `number` | HM3 container capacity (in lbs) |
3316
+
3317
+ if a `sohl` property is present, a SoHL item of type "containergear" is created.
3318
+
3319
+ if a `hm3` property is present, an HM3 item of type "containergear" is created.
3320
+
3321
+ | shared source | → sohl | → hm3 |
3322
+ | ----------------- | ----------------------- | --------------------- |
3323
+ | `data.weight` | `system.weightBase` | `system.weight` |
3324
+ | `data.value` | `system.valueBase` | `system.value` |
3325
+ | `data.quality` | `system.qualityBase` | NA |
3326
+ | `data.durability` | `system.durabilityBase` | NA |
3327
+ | `data.capacity` | NA | `system.capacity.max` |
3328
+
3329
+ SoHL reads a container’s capacity in pounds from `sohl.maxCapacity`.
3330
+
3331
+ ### type: miscgear
3332
+
3333
+ | `data` property | Values | Description |
3334
+ | ------------------ | -------- | ------------------------------------------ |
3335
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3336
+ | `weight` | `number` | Gear weight |
3337
+ | `value` | `number` | Gear value |
3338
+ | `quality` | `number` | Gear quality |
3339
+ | `durability` | `number` | Gear durability |
3340
+ | `quantity` | `number` | HM3 gear quantity (default: 1) |
3341
+
3342
+ if a `sohl` property is present, a SoHL item of type "miscgear" is created.
3343
+
3344
+ if a `hm3` property is present, an HM3 item of type "miscgear" is created.
3345
+
3346
+ | shared source | → sohl | → hm3 |
3347
+ | ----------------- | ----------------------- | ----------------- |
3348
+ | `data.weight` | `system.weightBase` | `system.weight` |
3349
+ | `data.value` | `system.valueBase` | `system.value` |
3350
+ | `data.quality` | `system.qualityBase` | NA |
3351
+ | `data.durability` | `system.durabilityBase` | NA |
3352
+ | `data.quantity` | NA | `system.quantity` |
3353
+
3354
+ SoHL sets `system.quantity` to `1` for each compendium item.
3355
+
3356
+ ### type: mystery
3357
+
3358
+ **subType**:
3359
+
3360
+ - boon: A flat ±N modifier to an associated skill's mastery level, from any source.
3361
+ - boost: One or more temporary mastery boosts to an associated skill (Mastery Boost table).
3362
+ - fate: A mystery that quantifies the ability to alter destiny or fate.
3363
+ - grace: A mystery that quantifies ability to call effectually on divine favor.
3364
+ - birthsign: A mystery that describes the arcane sign under which the being was born.
3365
+ - other: A mystery that does not fit into the other predefined categories.
3366
+ - piety: A mystery that quantifies devotion to a religion.
3367
+
3368
+ **SkillAptitude**: either a single skill
3369
+
3370
+ | `data` property | Values | Description |
3371
+ | ------------------ | ---------------------------------------------------------------------------------- | -------------------------------------------------------------- |
3372
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3373
+ | `assocSkill` | `Address` | Associated skill |
3374
+ | `assocAffiliation` | `Address` | Associated affiliation |
3375
+ | `skillAptitudes` | `Map<Shortcode, number>`, the key a skill's shortcode or `subType:<skill-subtype>` | Bonuses and penalties, per skill or per kind of skill |
3376
+ | `level` | `number` | Magnitude of the mystery |
3377
+ | `charges.value` | `number` | Current number of charges available, _null_ = charges not used |
3378
+ | `charges.max` | `number` | Maximum number of charges, _null_ = no maximum |
3379
+
3380
+ if a `sohl` property is present, a SoHL item of type "mystery" is created.
3381
+
3382
+ `system.assocSkillCode` and `system.assocAffiliationCode` are authored under
3383
+ `sohl.system` as shortcodes and have no shared source; `data.assocSkill` and
3384
+ `data.assocAffiliation` name the same two notes as Addresses, for the page.
3385
+
3386
+ | shared source | → sohl | → hm3 |
3387
+ | --------------------- | ----------------------- | ----- |
3388
+ | `subType` | `system.subType` | NA |
3389
+ | `data.skillAptitudes` | `system.skillAptitudes` | NA |
3390
+ | `data.charges.value` | `system.charges.value` | NA |
3391
+ | `data.charges.max` | `system.charges.max` | NA |
3392
+
3393
+ `data.level` is page data for a mystery. SoHL reads the item’s level from
3394
+ `sohl.levelBase`.
3395
+
3396
+ ### type: mysticalability
3397
+
3398
+ **subType**:
3399
+
3400
+ - spiritrite: A prepared ceremony by which a practitioner petitions the spirit world.
3401
+ - spiritaction: A discrete supernatural act performed through an allied or bound spirit.
3402
+ - spiritpower: A standing power conferred on its bearer by a spirit.
3403
+ - ritualaction: A prescribed ritual act performed to earn the favour of a deity.
3404
+ - divineincantation: A spoken invocation channelling the power of a deity.
3405
+ - arcaneincantation: A formally learned spell, invoked by word and gesture.
3406
+ - arcanetalent: An innate arcane knack, possessed without formal training.
3407
+ - spirittalent: An innate affinity for the spirit world, possessed without training.
3408
+ - alchemy: The preparation of substances imbued with mystical potency.
3409
+ - divination: The practice of obtaining hidden knowledge or foreknowledge by mystical means.
3410
+
3411
+ | `data` property | Values | Description |
3412
+ | ------------------ | --------- | -------------------------------------------------------------- |
3413
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3414
+ | `assocSkill` | `Address` | Associated skill |
3415
+ | `assocAffiliation` | `Address` | Associated affiliation |
3416
+ | `masteryLevel` | `number` | Mastery Level |
3417
+ | `level` | `number` | Magnitude of the mystery |
3418
+ | `charges.value` | `number` | Current number of charges available, _null_ = charges not used |
3419
+ | `charges.max` | `number` | Maximum number of charges, _null_ = no maximum |
3420
+
3421
+ if a `sohl` property is present, a SoHL item of type "mysticalability" is created.
3422
+
3423
+ If an `hm3` property is present, an HM3 item is created, and `hm3.type` states which — `psionic`, `spell` or `invocation`. It is **authored, not derived from `subType`**: the ten mystical-ability subtypes do not partition onto HM3's three documents (a `spiritrite`, an `alchemy` and a `divination` each answer to none of them), so a derivation would be a guess with a plausible shape. A note that omits it is an error naming the note.
3424
+
3425
+ `system.assocSkillCode` and `system.assocAffiliationCode` are authored under
3426
+ `sohl.system` as shortcodes and have no shared source; `data.assocSkill` and
3427
+ `data.assocAffiliation` name the same two notes as Addresses, for the page.
3428
+
3429
+ | shared source | → sohl | → hm3 |
3430
+ | -------------------- | ---------------------- | ------------- |
3431
+ | `subType` | `system.subType` | **see above** |
3432
+ | `data.charges.value` | `system.charges.value` | NA |
3433
+ | `data.charges.max` | `system.charges.max` | NA |
3434
+
3435
+ `data.masteryLevel` and `data.level` are page data for an ability. SoHL reads
3436
+ the item’s base mastery and level from `sohl.masteryLevelBase` and
3437
+ `sohl.levelBase`.
3438
+
3439
+ ### type: projectilegear
3440
+
3441
+ **subType**:
3442
+
3443
+ - none
3444
+ - arrow
3445
+ - bolt
3446
+ - bullet
3447
+ - dart
3448
+ - other
3449
+
3450
+ | `data` property | Values | Description |
3451
+ | ------------------ | -------- | ------------------------------------------ |
3452
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3453
+ | `weight` | `number` | Gear weight |
3454
+ | `value` | `number` | Gear value |
3455
+ | `quality` | `number` | Gear quality |
3456
+ | `durability` | `number` | Gear durability |
3457
+ | `quantity` | `number` | HM3 gear quantity (default: 1) |
3458
+
3459
+ if a `sohl` property is present, a SoHL item of type "projectilegear" is created.
3460
+
3461
+ If an `hm3` property is present, an HM3 item of type "missilegear" is created.
3462
+
3463
+ Note that `weapon` can also produce an HM3 `missilegear`. Since `(type, shortcode)` is a
3464
+ flat namespace, a `projectilegear` and a `weapon` sharing a shortcode would collide on the
3465
+ HM3 side while remaining distinct on the SoHL side.
3466
+
3467
+ | shared source | → sohl | → hm3 |
3468
+ | ----------------- | ----------------------- | ----------------- |
3469
+ | `subType` | `system.subType` | NA |
3470
+ | `data.weight` | `system.weightBase` | `system.weight` |
3471
+ | `data.value` | `system.valueBase` | `system.value` |
3472
+ | `data.quality` | `system.qualityBase` | NA |
3473
+ | `data.durability` | `system.durabilityBase` | NA |
3474
+ | `data.quantity` | NA | `system.quantity` |
3475
+
3476
+ SoHL sets `system.quantity` to `1` for each compendium item.
3477
+
3478
+ ### type: skill
3479
+
3480
+ **subType**:
3481
+
3482
+ - social
3483
+ - nature
3484
+ - craft
3485
+ - lore
3486
+ - language
3487
+ - script
3488
+ - mystical
3489
+ - physical
3490
+ - combat
3491
+ - combattechnique
3492
+
3493
+ | `data` property | Values | Description |
3494
+ | ------------------ | --------- | ------------------------------------------ |
3495
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3496
+ | `masteryLevel` | `number` | HM3 mastery level |
3497
+ | `parentSkill` | `Address` | Parent skill this skill specializes |
3498
+
3499
+ if a `sohl` property is present, a SoHL item of type "skill" is created.
3500
+
3501
+ If an `hm3` property is present, an HM3 item of type "skill" is created.
3502
+
3503
+ Note: `hm3.system.type` (skill types) use the values "Craft", "Physical", "Communication", "Combat", "Magic", and "Ritual". These do not cleanly map to the `subType` values. Because of this, the `hm3.system.type` value must be specified with the appropriate value when defining HM3 skills.
3504
+
3505
+ `system.parentSkillCode` is authored under `sohl.system` as the shortcode of the
3506
+ skill this one specializes, and has no shared source; `data.parentSkill` names that
3507
+ skill as an Address, for the page.
3508
+
3509
+ | shared source | → sohl | → hm3 |
3510
+ | ------------------- | ---------------- | --------------------- |
3511
+ | `subType` | `system.subType` | See notes above |
3512
+ | `data.masteryLevel` | NA | `system.masteryLevel` |
3513
+
3514
+ SoHL reads a skill’s base mastery from `sohl.masteryLevelBase`.
3515
+
3516
+ ### type: trauma
3517
+
3518
+ **subType**:
3519
+
3520
+ - injury: Physical harm caused by an external force.
3521
+ - fear: Emotional response to a perceived threat or danger.
3522
+ - morale: Emotional state affecting group cohesion and individual morale.
3523
+ - pall: Influence of existential chaos, death, or life-draining forces.
3524
+ - psycond: Mental and emotional disorder.
3525
+ - physcond: A persistent physical condition of the body (descriptive; e.g. albinism, a limp, poor eyesight)
3526
+ - auralshock: Severe shock to the aura, resulting in temporary loss of aura-related abilities.
3527
+ - fatigue: Physical or mental exhaustion resulting from prolonged activity or stress.
3528
+ - infection: Swelling or inflammation exacerbating an existing condition or injury, often fatal.
3529
+ - shock: A prolonged physiological state of shock lasting hours or days, following severe trauma or blood loss — distinct from the transient combat-shock states.
3530
+ - coma: A prolonged state of unconsciousness.
3531
+
3532
+ | `data` property | Values | Description |
3533
+ | --------------------------------- | ------------- | ---------------------------------------------------------------------- |
3534
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3535
+ | `healingCheckDurationFormula` | `RollFormula` | Formula for the interval between healing checks |
3536
+ | `healingCheckDurationBase` | `number` | That interval in seconds, stated outright instead of rolled |
3537
+ | `bloodLossAdvanceDurationFormula` | `RollFormula` | Formula for the interval between blood-loss advances |
3538
+ | `bloodLossAdvanceDurationBase` | `number` | That interval in seconds. Setting it is what makes the wound bleed |
3539
+ | `courseDurationFormula` | `RollFormula` | Formula for the interval between course tests — shock, coma, infection |
3540
+ | `courseDurationBase` | `number` | That interval in seconds, stated outright instead of rolled |
3541
+
3542
+ if a `sohl` property is present, a SoHL item of type "trauma" is created.
3543
+
3544
+ If an `hm3` property is present, an HM3 item is created. `hm3.type` must be specified as either `injury` or `trait`.
3545
+
3546
+ | shared source | → sohl | → hm3 |
3547
+ | -------------------------------------- | ---------------------------------------- | ----- |
3548
+ | `subType` | `system.subType` | NA |
3549
+ | `data.healingCheckDurationFormula` | `system.healingCheckDurationFormula` | NA |
3550
+ | `data.healingCheckDurationBase` | `system.healingCheckDurationBase` | NA |
3551
+ | `data.bloodLossAdvanceDurationFormula` | `system.bloodLossAdvanceDurationFormula` | NA |
3552
+ | `data.bloodLossAdvanceDurationBase` | `system.bloodLossAdvanceDurationBase` | NA |
3553
+ | `data.courseDurationFormula` | `system.courseDurationFormula` | NA |
3554
+ | `data.courseDurationBase` | `system.courseDurationBase` | NA |
3555
+
3556
+ **A trauma's three timed phases follow the triplet rule** stated under
3557
+ `affliction`: write the formula or the base, both in seconds, and omitting each
3558
+ leaves the key out so the data model answers. Two of them fall back to a **world
3559
+ setting** when the trauma sets neither half — the healing check and the course —
3560
+ which is why a default written here would be wrong rather than merely
3561
+ redundant. `bloodLossAdvanceDurationBase` is the switch that makes a wound
3562
+ bleed: a trauma that sets it bleeds, one that leaves it unset does not.
3563
+
3564
+ **`system.contractDate` and `system.treatmentDate` are never authored.** They
3565
+ are the world times the injury was taken and last treated — runtime state, for
3566
+ the reason `affliction`'s four dates are — so a note that writes one fails the
3567
+ build.
3568
+
3569
+ ### type: weapongear
3570
+
3571
+ Note: `data.quantity` may not be specified. Quantity is always 1.
3572
+
3573
+ | `data` property | Values | Description |
3574
+ | ------------------ | -------- | ------------------------------------------ |
3575
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3576
+ | `weight` | `number` | Gear weight |
3577
+ | `value` | `number` | Gear value |
3578
+ | `quality` | `number` | Gear quality |
3579
+ | `durability` | `number` | Gear durability |
3580
+
3581
+ if a `sohl` property is present, a SoHL item of type "weapongear" is created,
3582
+ carrying every strike mode the weapon has — melee and missile alike — on
3583
+ `system.strikeModes`.
3584
+
3585
+ If an `hm3` property is present, an HM3 item is created, and `hm3.type` states whether it is a `weapongear` or a `missilegear`. **`weapon` has no `subType`**: SoHL distinguishes a weapon's uses with strike modes rather than by kind, and HM3 has one document per usage, so nothing but the note can say which usage it describes. Every note carrying an `hm3:` block states it — the one carrying both blocks as well as the companion carrying only `hm3:`, which is usually a `missilegear` — see _One note is at most one document per system_.
3586
+
3587
+ | shared source | → sohl | → hm3 |
3588
+ | ----------------- | ----------------------- | --------------- |
3589
+ | `data.weight` | `system.weightBase` | `system.weight` |
3590
+ | `data.value` | `system.valueBase` | `system.value` |
3591
+ | `data.quality` | `system.qualityBase` | NA |
3592
+ | `data.durability` | `system.durabilityBase` | NA |
3593
+
3594
+ ### type: lore
3595
+
3596
+ In-world knowledge a sage in the setting could study or know. A lore note describes
3597
+ the setting itself, rather than instructions or other apparatus for the GM.
3598
+
3599
+ **subType**:
3600
+
3601
+ - cosmology: The structure of reality — planes, realms, creation, and the ordering of what exists.
3602
+ - deity: Individual gods and their attributed natures, domains, epithets, and aspects.
3603
+ - theology: How the divine is held to operate — worship, sacrifice, afterlife, sin and grace.
3604
+ - arcana: How magic is held to operate — mechanism, traditions, and philosophies of practice.
3605
+ - spirit: The non-divine numinous — spirits, celestials, fae, and their natures.
3606
+ - economy: How wealth moves — barter, coinage, trade networks, credit, and measure.
3607
+ - law: How obligation is ordered and enforced — citizenship, custom, courts, and tenure.
3608
+ - calendar: How time is reckoned and marked — dating, seasons, festivals, and astrology.
3609
+ - history: What has happened — eras, events, chronicles, and genealogies of rule.
3610
+ - material: A physical constituent, such as mithril, buckram, or silk, and its qualities,
3611
+ including durability, value, and regional varieties.
3612
+ - folk: Related sapient beings of a single or tightly related species: kindreds, ancestries.
3613
+ - culture: A social grouping of individuals with common beliefs, mores, and values.
3614
+ - custom: How a people does a thing — a rite, observance, or usage.
3615
+ - bestiary: A kind of creature that is not a people — beasts, monsters, and the made things
3616
+ that were never born. What `folk` covers for the sapient, this covers for everything else.
3617
+ - gathering: A scheduled public occasion people travel to — a tournament or martial games, a
3618
+ great market or fair, a religious festival, a ceremony or rite. What these share is assembly
3619
+ on a cycle: a place, a time, and something contested or observed. Held apart from `calendar`,
3620
+ which covers the _reckoning_ — a festival's date is calendar and the festival is not, and a
3621
+ tournament is not a matter of time-reckoning at all — and from `culture`, which is a grouping
3622
+ of people rather than an occasion they attend.
3623
+
3624
+ | `data` property | Values | Description |
3625
+ | --------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
3626
+ | `epoch` | `date` | Which in-world day the world's clock reads zero on |
3627
+ | `months` | `{ name, abbreviation?, days }[]` | The months this calendar keeps, in order — the list sums to the world's year |
3628
+ | `weekdays` | `{ name, abbreviation? }[]` | The days of the week it names, in order; a calendar with no week writes none |
3629
+ | `seasons` | `{ name, abbreviation?, monthStart?, monthEnd?, dayStart?, dayEnd? }[]` | The seasons it marks, bounded by month or by day of year |
3630
+ | `eras` | `{ shortcode, name, marker?, abbreviation?, proclaimedBy?, start, end?, label? }[]` | The year-counts kept in it; a marker names one era and uses this calendar's months |
3631
+ | `dateFormats` | `Map<slot, format>` | Calendaria display formats for this calendar |
3632
+ | `event` | `Map<field, value>` | A dated occurrence, with its sources, locations, reach, and relationships |
3633
+
3634
+ `data.event` is available on every `lore` subType. It holds structured
3635
+ chronology metadata, including the event's kind, date, sources, and the places
3636
+ it affects. The shared format checks that it is a map; a content package may
3637
+ check the details and relationships within that map. It is metadata for
3638
+ chronology tools and does not appear as an infobox row.
3639
+
3640
+ #### What a calendar note declares
3641
+
3642
+ **The calendar keys belong to `subType: calendar` and to no other genre of
3643
+ `lore`.** A note carrying one under any other subType is refused, and a calendar
3644
+ note that states no `months` and no `epoch` is refused too: a calendar divides
3645
+ the year and says where the count begins, and one that does neither is prose
3646
+ about time rather than a calendar.
3647
+
3648
+ ```yaml
3649
+ shortcode: commoncal
3650
+ name:
3651
+ full: The Common Calendar
3652
+ type: lore
3653
+ subType: calendar
3654
+ data:
3655
+ epoch: 720.1
3656
+ months:
3657
+ - { name: Floralis, abbreviation: Flor, days: 30 }
3658
+ - { name: Lusenar, abbreviation: Luse, days: 31 }
3659
+ # … ten more, summing to the world's year
3660
+ seasons:
3661
+ - { name: Spring, monthStart: 1, monthEnd: 3 }
3662
+ eras:
3663
+ - shortcode: founding
3664
+ name: After the Founding
3665
+ marker: VR
3666
+ abbreviation: AF
3667
+ proclaimedBy: vylarinmpr
3668
+ start: 1.1
3669
+ dateFormats: { full: "D MMMM, YYYY", yearLabel: "[Year] YYYY" }
3670
+ ```
3671
+
3672
+ `dateFormats` accepts `short`, `long`, `full`, `time`, `weekHeader`,
3673
+ `yearHeader`, `yearLabel`, and `crossCalendar`. Each value is a Calendaria
3674
+ format string. Its date tokens may appear bare; ordinary words belong in
3675
+ `[brackets]` or `{braces}` so they print literally. A bare letter run that is
3676
+ not a Calendaria token is an error. The `time12` slot is outside this
3677
+ vocabulary.
3678
+ The conversion commands use the calendar's month names and era labels;
3679
+ `dateFormats` supplies Foundry and Calendaria display patterns.
3680
+
3681
+ **Position in the list is position in the year.** Days that belong to no month
3682
+ are a short month like any other: a five-day festival is one entry, and where it
3683
+ sits in the list is where it falls in the year. There is no intercalary
3684
+ mechanism, because there is nothing for one to do.
3685
+
3686
+ **How long the year is, how the day divides and how the moon moves are facts
3687
+ about the world, not about a calendar**, so no calendar note states any of them
3688
+ — they are written once on the world's own `place` note and on its moon's, and
3689
+ every calendar is compiled against them. The one arithmetic check follows from
3690
+ that: `months` must sum to `year.days`, and a list that does not describes a
3691
+ different world rather than a different calendar.
3692
+
3693
+ **An era is a way of counting years within a calendar**, so its rows sit beside
3694
+ the months. Each states its own `shortcode`, unique within the note. A calendar
3695
+ may contain several era rows, each with its own year origin. The
3696
+ `abbreviation` is its preferred short printed label; a declared marker is
3697
+ also accepted when reading a named date.
3698
+ `proclaimedBy` is optional: a reckoning whose proclaiming body is unknown, or
3699
+ whose body has no note, says so by leaving it out.
3700
+
3701
+ `label` optionally wraps a printable date for readers. A string is used for
3702
+ positive years; a map may give `after` and `before` forms. Each form contains
3703
+ exactly one `{date}` slot. That slot receives the year magnitude and any month
3704
+ and day the date states, including a leading `~` for approximation. The
3705
+ `before` form handles a negative year without printing its minus sign. A
3706
+ missing form produces no prose label, and the machine form is displayed.
3707
+
3708
+ ```yaml
3709
+ eras:
3710
+ - shortcode: founding
3711
+ name: After the Founding
3712
+ marker: VR
3713
+ start: 1
3714
+ label: { after: "{date} AF", before: "{date} BF" }
3715
+ ```
3716
+
3717
+ **A calendar with no week writes no `weekdays`.** An empty list and an absent
3718
+ one say the same thing, and nothing downstream shows a weekday for a calendar
3719
+ that names none.
3720
+
3721
+ ### type: map
3722
+
3723
+ A map note has its own prose and a Foundry Scene. A map can depict a place or an encounter. Several maps can depict the same place; the map names that place with `sohl.place`, while the place note does not list its maps.
3724
+
3725
+ **subType**:
3726
+
3727
+ - `battlemap`: A tactical Scene built in Foundry.
3728
+ - `localmap`: A settlement or local-area Scene built in Foundry.
3729
+ - `regionalmap`: A large-scale chart with an authored image and scale.
3730
+ - `totm`: A gridless image Scene for theatre-of-the-mind play.
3731
+
3732
+ | `data` property | Values | Description |
3733
+ | ----------------- | ------------ | ---------------------------------------------------------- |
3734
+ | `scene` | object | Exported Foundry Scene for battle and local maps |
3735
+ | `fixup` | object[] | Asset address replacements in an exported Scene |
3736
+ | `bgImage` | Address | Background image for regional and theatre-of-the-mind maps |
3737
+ | `scale` | object | Regional distance and unit per grid unit |
3738
+ | `dimensions` | `[int, int]` | Optional shared dimensions metadata |
3739
+ | `pxPerGrid` | number | Optional shared grid size metadata |
3740
+ | `navName` | string | Short Scene navigation name |
3741
+ | `levelName` | string | Embedded Level name |
3742
+ | `backgroundColor` | string | Colour outside the art |
3743
+ | `overlay` | string | Foreground art path |
3744
+ | `walls` | object | Regional wall geometry |
3745
+ | `doors` | object | Regional door geometry |
3746
+ | `lights` | object | Regional light geometry |
3747
+ | `tiles` | object | Regional tile geometry |
3748
+ | `sounds` | object | Regional sound geometry |
3749
+ | `regions` | object | Regional region geometry |
3750
+ | `notes` | object | Regional pin geometry |
3751
+ | `place` | Address | Depicted place, when one exists |
3752
+
3753
+ #### Battle and local maps
3754
+
3755
+ Author the Scene in Foundry, export it, convert the export to YAML, and put the resulting object at `data.scene`. Preserve its walls, lights, tiles, regions, levels, environment, flags, and canvas settings. The export can name art from another Foundry package; that path is used as authored in Foundry. The Scene needs positive whole-number `width` and `height`, at least one Level, a valid `initialLevel` when present, and a unique 16-character `_id` on each embedded document. The build derives the top-level Scene `_id` from the map note, adds compendium keys and build metadata, and otherwise preserves the exported fields.
3756
+
3757
+ A pin's `text` can contain `#anchor`, where the map note has a heading such as `# Big Bad Wolf {#myanchor1}`. With `text: '#myanchor1'`, the compiled pin opens the JournalEntry and page generated from this map note and displays **Big Bad Wolf**. Its position, icon, other settings, and embedded `_id` remain as exported. A missing anchor is a build error. Pins whose text does not use `#anchor` retain their exported text and references.
3758
+
3759
+ `data.fixup` replaces exported asset paths without editing the Scene object by
3760
+ hand. Each entry has `path`, `type: address`, and `value`, an asset Address in
3761
+ the local or a fetched content index. Paths start at `data.scene` and use a
3762
+ property path with array indices or stable embedded `_id` selectors. A path
3763
+ must name one existing string or null field, and the address must resolve; any
3764
+ mistake fails the build. An `_id` selector remains stable when Foundry reorders
3765
+ an array on export.
3766
+
3767
+ ```yaml
3768
+ data:
3769
+ fixup:
3770
+ - { path: ".notes[NNNNNNNNNNNNNNNN].texture.src", type: address, value: sohl-none-icon-book }
3771
+ - { path: ".sounds[0].path", type: address, value: sohl-none-audio-forest }
3772
+ ```
3773
+
3774
+ Fixups change the compiled copy only. The exported Scene in the note stays
3775
+ intact, and pin anchor binding runs after fixups. A package name such as
3776
+ `fvtt` is an ordinary address package name: it needs published asset records
3777
+ before its addresses can resolve.
3778
+
3779
+ ```yaml
3780
+ shortcode: wolfden
3781
+ name: { full: The Wolf's Den }
3782
+ type: map
3783
+ subType: battlemap
3784
+ data:
3785
+ scene:
3786
+ name: Wolf Den Scene
3787
+ width: 1900
3788
+ height: 2600
3789
+ grid: { type: 1 size=100 distance: 5 units: ft }
3790
+ initialLevel: defaultLevel0000
3791
+ levels:
3792
+ - { _id: defaultLevel0000, background: { src: modules/maps/den.webp } }
3793
+ notes:
3794
+ - { _id: NNNNNNNNNNNNNNNN, text: "#myanchor1", x: 950, y: 2350 }
3795
+ ---
3796
+ # Big Bad Wolf {#myanchor1}
3797
+ ```
3798
+
3799
+ When the Scene has pins, its Adventure bundle carries the Scene and the JournalEntry generated from its Markdown. Importing that bundle preserves the IDs the pins address.
3800
+
3801
+ #### Regional maps
3802
+
3803
+ A `regionalmap` uses `data.bgImage`, an image Address, and `sohl.dimensions: [width, height]` plus `sohl.pxPerGrid` to build a gridless Scene. `data.scale: {distance: positive number, unit: nonempty string}` sets the distance per grid unit. Its shared `data` fields can also name `overlay`, `backgroundColor`, `levelName`, `navName`, `walls`, `doors`, `lights`, `tiles`, `sounds`, `regions`, and `notes`; the latter geometry uses the map-note conventions described in the SoHL map authoring guide. `data.scene` is not used for a regional map.
3804
+
3805
+ An SVG `data.bgImage` remains vector art on the site and in the book. The Foundry build stages a PNG at the note's stated dimensions with the installed SVG renderer; the source SVG is unchanged. Its aspect ratio must match the stated dimensions. A selected regional SVG map takes a full page in the PDF.
3806
+
3807
+ #### Theatre-of-the-mind maps
3808
+
3809
+ A `totm` map names its background with `data.bgImage` and its canvas size with
3810
+ `sohl.dimensions: [width, height]`. It builds a gridless Scene with token vision
3811
+ off. It needs no `sohl.pxPerGrid` or `data.scale`. The Markdown body remains the
3812
+ map's prose.
3813
+
3814
+ ### type: place
3815
+
3816
+ **subType**:
3817
+
3818
+ - world: A self-contained whole in which places exist — a planet, plane, or realm.
3819
+ - region: A bounded division of a world or larger region — continents, marches, uplands, provinces.
3820
+ - settlement: A place where folk dwell together — cities, towns, villages, holdings, camps.
3821
+ - site: A place significant by what was made or done there — ruins, monoliths, henges, works, battlefields.
3822
+ - structure: A single building or habitation — halls, keeps, temples, inns, towers.
3823
+ - feature: A place significant by its terrain — forests, rivers, falls, passes, fords.
3824
+ - celestial: A body observed from a world rather than located on one — a sun, a planet, a comet.
3825
+
3826
+ | `data` property | Values | Description |
3827
+ | --------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
3828
+ | `calendar` | `Address` | Calendar note used to print this place's dates |
3829
+ | `demonym` | `string` | What a person from this place is called — a Vylarian |
3830
+ | `purpose` | `placeCharacter` tag | The reason a settlement, site, or structure exists |
3831
+ | `lore` | `Address[]` | Lore concerning this place — its peoples, its law, its calendar, its history |
3832
+ | `parents` | `Address[]` | Enclosing places within which this place is located |
3833
+ | `population` | `number` | Approximate population (precision 2 significant digits) |
3834
+ | `market` | `number` | Market class, 1 to 6 — what trade a settlement supports |
3835
+ | `borders` | `{ to, bearing }[]` | Places sharing a frontier with this one, and where each lies from here |
3836
+ | `routes` | `{ to, bearing, mode, days, terrain?, leagues? }[]` | Journeys from this place's centre to another place |
3837
+ | `world.equatorialCircumferenceKm` | `number` | The distance round the body at its equator, in kilometres |
3838
+ | `world.surfaceGravityG` | `number` | Surface gravity, as a multiple of Earth's |
3839
+ | `world.axialTiltDegrees` | `number` | The tilt of the axis, in degrees |
3840
+ | `year.days` | `number` | How many days the body's year holds |
3841
+ | `year.hoursPerDay` | `number` | How many hours the day divides into |
3842
+ | `year.minutesPerHour` | `number` | How many minutes the hour divides into |
3843
+ | `year.secondsPerMinute` | `number` | How many seconds the minute divides into |
3844
+ | `present` | `date` | The day the setting stops and play begins |
3845
+ | `body.diameterKm` | `number` | The body's diameter, in kilometres |
3846
+ | `body.orbitalRadiusKm` | `number` | How far it orbits from the body it circles, in kilometres |
3847
+ | `body.orbit` | `string` | The orbit's shape — `circular` means the cycle never varies |
3848
+ | `body.inclined` | `boolean` | Whether the orbit is inclined to the plane the world orbits in |
3849
+ | `moon.cycle` | `number` | How many days it takes to return to the same phase |
3850
+ | `moon.newOn` | `date` | A day it was new, written in the reference calendar |
3851
+ | `moon.eclipses` | `string` | `never`, `rare`, `occasional` or `frequent` |
3852
+
3853
+ #### Why a place exists
3854
+
3855
+ `data.purpose` selects one of the place's own `placeCharacter` tags. It is
3856
+ optional on settlements, sites, and structures. A mining settlement on a coast
3857
+ can carry `tags: [mining, coastal]` and `data.purpose: mining`: the coast locates
3858
+ it; the mine explains its existence. The value must belong to the declared
3859
+ `placeCharacter` vocabulary and also appear among that note's tags. The tags
3860
+ include `ford`, `portage`, `pass`, and `well` for places founded around a crossing,
3861
+ a break in navigation, a mountain route, or scarce water. The body explains the
3862
+ specific history; the selected tag gives tables a consistent grouping value.
3863
+
3864
+ #### What a body states about itself
3865
+
3866
+ **The four groups above are the world's own facts, and they are legal on a
3867
+ `world` or a `celestial` note and nowhere else.** A region, a settlement, a
3868
+ site, a structure or a feature is somewhere _within_ a world and says nothing
3869
+ about the world's year.
3870
+
3871
+ ```yaml
3872
+ # the world — type: place, subType: world
3873
+ data:
3874
+ world: { equatorialCircumferenceKm: 40000, surfaceGravityG: 1.0, axialTiltDegrees: 23.5 }
3875
+ year: { days: 365, hoursPerDay: 24, minutesPerHour: 60, secondsPerMinute: 60 }
3876
+ present: 720
3877
+
3878
+ # its moon — a note of its own, because a moon is a body and not a property
3879
+ data:
3880
+ body: { diameterKm: 3800, orbitalRadiusKm: 388600, orbit: circular, inclined: true }
3881
+ moon: { cycle: 30, newOn: 720/1/1, eclipses: rare }
3882
+ ```
3883
+
3884
+ **`year.days` is where the year's length lives, and every calendar in the
3885
+ package is compiled against it.** A package whose notes state no `year` has no
3886
+ calendar mechanism at all: no sum is checked, and nothing is emitted. That is
3887
+ the right answer for a package building a setting whose world facts somebody
3888
+ else wrote down.
3889
+
3890
+ **One year and one moon to a package.** Two years is two settings and nothing
3891
+ can say which one a calendar divides; a `moon` with no `year` anywhere is
3892
+ refused, because a cycle counted in days has no year to be measured against.
3893
+
3894
+ **`present` is the day an age is computed against**, written in the grammar a
3895
+ note's dates are written in, so a setting whose present is best said in an era
3896
+ says it that way.
3897
+
3898
+ **`world.axialTiltDegrees`, the circumference, the gravity, the moon's diameter
3899
+ and its orbital radius are read by nobody.** They are there so a reader knows
3900
+ what kind of world this is. What the build consumes is the year, the day's
3901
+ divisions, the present, the moon's cycle and epoch, and whether its orbit is
3902
+ circular and inclined.
3903
+
3904
+ #### What a settlement's market supports
3905
+
3906
+ `population` states how many people a settlement holds; `market` states what a
3907
+ traveller can buy there. Those are different questions — two settlements of
3908
+ eight hundred are a different proposition when one holds a weekly market and
3909
+ the other a chartered fair with a moneylender — so a note states both, either,
3910
+ or neither.
3911
+
3912
+ The value is a **class**, not a measurement. The numbers order six steps and do
3913
+ nothing else: a `5` is not a market two fifths larger than a `3`, it is a city
3914
+ rather than a town. `package-build lint` refuses a value off the scale instead
3915
+ of rounding it onto one, so a reader can trust that a number means the same
3916
+ thing on every page.
3917
+
3918
+ | `market` value | the settlement is | what can be had there |
3919
+ | -------------- | ----------------- | ------------------------------------------------------------------------------------------------- |
3920
+ | `1` | a hamlet | no market — what neighbours trade among themselves |
3921
+ | `2` | a village | a weekly market: staples, a smith, what a household cannot make |
3922
+ | `3` | a town | a regular market, most common goods, several trades working full time |
3923
+ | `4` | a market town | a chartered fair, goods carried from a week away, a moneylender |
3924
+ | `5` | a city | anything ordinary, in quantity; foreign goods; a guild structure |
3925
+ | `6` | a great city | the rare and the imported as a matter of course; banking; the market from which other markets buy |
3926
+
3927
+ The key is meaningful on a settlement, and the lint checks no `subType`
3928
+ condition — as it checks none for `population`. Which places a fact says
3929
+ something about is the author's judgement, and the `data:` container is closed
3930
+ per type rather than per subType.
3931
+
3932
+ #### Where a place is, in relation to other places
3933
+
3934
+ `parents` states what a place is **within**. Two more properties state what
3935
+ it is **next to** and what it is **reachable from**, as data rather than as
3936
+ prose, so that two notes describing one frontier cannot quietly disagree about
3937
+ which side of it each is on:
3938
+
3939
+ ```yaml
3940
+ data:
3941
+ borders: # places sharing a frontier
3942
+ - { to: dunharargn, bearing: NE } # both required
3943
+ - { to: bethuargn, bearing: W }
3944
+ routes: # journeys from this place's centre
3945
+ - { to: steinmark, bearing: SW, mode: land, days: 3, terrain: [road, mountains] }
3946
+ - { to: denizara, bearing: W, mode: ship, days: 5 }
3947
+ - { to: kethramir, bearing: E, mode: land, days: 30, terrain: [dunes], leagues: 90 }
3948
+ ```
3949
+
3950
+ - `to` — required; an `Address` naming the other place. Its type segment defaults
3951
+ to `place` and `place` is the whole of its accepted set, so `vylar`,
3952
+ `place-vylar` and `thalorna-none-place-vylar` name one place and a `to` naming
3953
+ anything else is an error. **A bare shortcode names a place in this package**,
3954
+ which is what the fully qualified form is for: naming a place in a declared
3955
+ dependency's index, so two packages using one shortcode never answer for each
3956
+ other.
3957
+ - `bearing` — required; where the neighbour or the destination lies from here.
3958
+ - `mode` — required on a route; how the journey is travelled.
3959
+ - `days` — required on a route; a marker meaning "about this, under normal
3960
+ conditions", from the scale below. Any other value is an error.
3961
+ - `terrain` — optional on a route; the terrains crossed, in travel order, from
3962
+ the registry below. Each terrain names the modes that cross it, so open sea
3963
+ on a land route — or a road on a ship route — is an error.
3964
+ - `leagues` — optional on a route; a distance, only where the note states one.
3965
+
3966
+ **A border is a shared frontier between areas.** Regions commonly state one;
3967
+ a settlement does so only when it actually shares a frontier with another
3968
+ place. Most settlements use `parents` for where they sit and `routes` for
3969
+ journeys between them. A pair appears in `routes` once per `mode` — a port and its
3970
+ neighbour may be three days by land and one by ship — and in `borders` once.
3971
+ **A place never borders its own parent or child**: containment is `parents`,
3972
+ and restating it as adjacency makes the two contradict each other.
3973
+
3974
+ **Both ends state the relation, and `package-build lint` checks that they
3975
+ agree.** A border at `NE` from one side is a border at `SW` from the other; a
3976
+ route stated by land in three days from one side is stated by land in three
3977
+ days from the other, at the opposite bearing. A neighbour that states nothing
3978
+ back is a warning naming both notes — the pair is half-written, not wrong. A
3979
+ neighbour that states a different bearing, a different `days` or a different
3980
+ `mode` is an error, because one of the two notes is wrong and a reader cannot
3981
+ tell which. Every `to` must resolve to a place, every value must come from its
3982
+ closed set, and each finding is located at the entry that states it.
3983
+
3984
+ The terrain names form one shared registry in package-build. A route crossing
3985
+ a package boundary uses the same terrain and mode rules at both ends.
3986
+
3987
+ For every place that participates in a border or route, the map builder draws
3988
+ an itinerary from that place. A site page carries it as a linked SVG. A PDF
3989
+ book that includes the place gives its map a separate full page after the
3990
+ entry. A configured Foundry Scene pack carries a gridless map backed by a
3991
+ generated PNG, with Notes linked to importable place journals. Places with no
3992
+ relations create no itinerary output.
3993
+
3994
+ **A relation that crosses a package boundary is written in full at both ends.**
3995
+ An omitted `<package>` segment is the package being built, in whatever note the
3996
+ value is read from — a note of this package's own, or a record a consumer fetched
3997
+ out of a dependency's index — so a short `to` names a different place in a
3998
+ different build. Only the fully qualified form names one place everywhere, which
3999
+ is why the long form is not optional for a border or a route between packages,
4000
+ and why a short `to` in a fetched record names the package being built rather
4001
+ than the package that wrote it.
4002
+
4003
+ | `bearing` value | where the other place lies |
4004
+ | --------------- | -------------------------- |
4005
+ | `N` | north |
4006
+ | `NE` | north-east |
4007
+ | `E` | east |
4008
+ | `SE` | south-east |
4009
+ | `S` | south |
4010
+ | `SW` | south-west |
4011
+ | `W` | west |
4012
+ | `NW` | north-west |
4013
+
4014
+ | `mode` value | travelled |
4015
+ | ------------ | ---------------------------- |
4016
+ | `land` | on foot, mounted, or by cart |
4017
+ | `boat` | on a river or a lake |
4018
+ | `ship` | on open sea |
4019
+
4020
+ | `days` value | meaning |
4021
+ | ------------ | -------------------------------------------- |
4022
+ | `1` | a day |
4023
+ | `2` | two days |
4024
+ | `3` | three days |
4025
+ | `5` | about five days |
4026
+ | `10` | a ten-day week |
4027
+ | `20` | two ten-day weeks |
4028
+ | `30` | three ten-day weeks |
4029
+ | `45` | a month and a half |
4030
+ | `60` | two months |
4031
+ | `90` | a season |
4032
+ | `180` | many months |
4033
+ | `360` | who knows — a journey nobody reckons in days |
4034
+
4035
+ | `terrain` value | crossed by |
4036
+ | --------------- | ------------------ |
4037
+ | `road` | land |
4038
+ | `track` | land |
4039
+ | `plain` | land |
4040
+ | `steppe` | land |
4041
+ | `hills` | land |
4042
+ | `mountains` | land |
4043
+ | `forest` | land |
4044
+ | `jungle` | land |
4045
+ | `marsh` | land |
4046
+ | `dunes` | land |
4047
+ | `desert` | land |
4048
+ | `ice` | land |
4049
+ | `coast` | land, boat or ship |
4050
+ | `open-sea` | ship |
4051
+ | `river` | boat |
4052
+ | `lake` | boat |
4053
+
4054
+ #### What lies within a place, and who holds it
4055
+
4056
+ `parents` states what a place is within; an affiliation's `domains` states
4057
+ what it holds. The website reads both across the package and every fetched
4058
+ index and writes each place's page two lists, each entry
4059
+ `{ title, url, type, subType }` and each absent when empty:
4060
+
4061
+ - `contains` — every place whose `parents` names this one, sorted by
4062
+ `subType` then `title`. A region lists its settlements, sites, structures
4063
+ and features; a settlement lists the structures within it.
4064
+ - `held_by` — every affiliation whose `domains` names this one, sorted by
4065
+ `title`.
4066
+
4067
+ Both are derived; a note that writes one has it replaced. **`domains` is
4068
+ never expanded**: a region held by a polity lists the polity in `held_by`, and
4069
+ a settlement within that region lists only the house or order whose `domains`
4070
+ names it — its lord's lord is reached through `affiliation.parents`.
4071
+
4072
+ **A settlement, a site or a structure is held by someone.** One of those that
4073
+ no affiliation's `domains` names — in this package or in a fetched index — is
4074
+ a warning from `package-build lint`, `unheld land`, located at the note's
4075
+ `type:` line, so a gap in tenure shows while a note is written. A `world`, a
4076
+ `region` and a `feature` are exempt: a region is held through its polity's
4077
+ `domains`, and a river by nobody.
4078
+
4079
+ #### Population figures
4080
+
4081
+ `population` is an approximate count, written to two significant digits. A
4082
+ place states one, and so does an affiliation — how many people it counts,
4083
+ wherever they are. `package-build lint` reads those figures against each other
4084
+ and reports four warnings, each named by the phrase its message begins with.
4085
+
4086
+ - **`over-held land`** — the polities whose `domains` name a place count more
4087
+ people than the place states. A polity subordinate to another polity holding
4088
+ the same place is already inside that polity's figure and is not counted
4089
+ twice. A **settlement is exempt**: the polity of a city-state holds the city
4090
+ and counts the hinterland around it, so its figure is properly the larger.
4091
+ - **`over-full region`** — the regions whose `parents` name a place count more
4092
+ people than the place states. A region naming both its parent and its
4093
+ grandparent counts under the nearer of the two, so a place written into two
4094
+ levels of the hierarchy is not counted at both.
4095
+ - **`oversized settlement`** — a settlement states more people than a place
4096
+ containing it does, whether its own region or any place above that.
4097
+ - **`disputed figure`** — a `doc` note carries a figure beside a wikilink to
4098
+ the place or affiliation it belongs to, and the two disagree. The finding is
4099
+ located at the number the page wrote, and a figure written twice is located
4100
+ twice.
4101
+
4102
+ The first three are reported on the note's own `data.population`; the fourth
4103
+ on the citation.
4104
+
4105
+ **A figure is read as a population only where it carries `~`.** That marker is
4106
+ what separates a population from the other numbers a table puts beside a place
4107
+ — a distance, a stage count, a tally of days — so `| [[place-rgn\|X]] | ~2,000 |`
4108
+ is a claim about people and `| [[place-rgn\|X]] | 3 |` is not.
4109
+
4110
+ **Rounding is admitted.** Two significant digits carry a half-unit of error,
4111
+ widest at the bottom of a decade, so a place whose figure falls up to five per
4112
+ cent below what it contains is not a finding.
4113
+
4114
+ **An unstated figure is silent.** A place or affiliation with no `population`
4115
+ contributes nothing to a sum and is never the subject of a finding, so a
4116
+ half-written region is quiet rather than noisy. A fetched index carries
4117
+ `population` alongside `parents` and `domains` when its author stated a
4118
+ figure. An older fetched index with no figure remains silent.
4119
+
4120
+ **There is deliberately no urban-share rule.** The share of a region's people
4121
+ living in its named settlements cannot be measured from the notes: the
4122
+ settlement layer names the notable places and is complete nowhere, so the ratio
4123
+ reports how much of a region has been written rather than how much of it is
4124
+ urban.
4125
+
4126
+ **A place declares what is true of its ground.** Its governing affiliations
4127
+ carry their own `domains`; maps name the place they depict through `data.place`.
4128
+ The place's `lore` links cover its peoples, calendar, law and history. Political
4129
+ languages belong to a polity's `sohl.system.commonSkills`, while the note's
4130
+ `description` provides its page summary.
4131
+
4132
+ ### type: scenario
4133
+
4134
+ Content prepared to be played — a situation with its cast, places, and possible outcomes.
4135
+
4136
+ **subType**:
4137
+
4138
+ - campaign: A long arc toward a goal, spanning many adventures — carries standing cast, factions, and its own timeline.
4139
+ - adventure: A self-contained undertaking with a specific objective, playable in a few sessions.
4140
+ - encounter: A single scene or challenge, reusable within an adventure or on its own.
4141
+
4142
+ | `data` property | Values | Description |
4143
+ | ------------------ | -------------------------------------------- | ------------------------------------------------------------------------------ |
4144
+ | `parents` | `Address[]` | List of parent scenarios of this scenario (campaigns, etc.) |
4145
+ | `locations` | `Address[]` | List of locations associated with this scenario |
4146
+ | `cast` | `Address[]` | individuals associated with this scenario |
4147
+ | `factions` | `Address[]` | Affiliations associated with this scenario |
4148
+ | `follows` | `Address[]` | Prerequisite scenarios that should be completed before beginning this scenario |
4149
+ | `status` | `draft \| playtested \| published` | Playability status of this scenario |
4150
+ | `party.size` | `solo \| small \| standard \| large \| host` | Suggested party size (solo=1, small=2-3, standard=4-6, large=6-7, host=7+) |
4151
+ | `party.archetypes` | `Archetype[]` | Archetypes of characters suitable for completion |
4152
+
4153
+ ### type: doc
4154
+
4155
+ **subType**:
4156
+
4157
+ - rules: The rules of the game, independent of medium — valid at a table with paper and dice.
4158
+ - userguide: How to operate the Foundry implementation to play by the rules.
4159
+ - reference: Out-of-world lookup material about the setting or system — correspondences, conversions, glossaries.
4160
+ - howto: A task with an outcome, written as the steps that reach it.
4161
+ - concept: An explanation of how something works and why it is shaped that way, read to understand rather than to follow.
4162
+ - settingguide: An orientation to a setting or region, bringing its places, peoples, institutions, and daily life into one usable frame with links to detailed notes.
4163
+
4164
+ A `doc` declares no properties of its own.
4165
+
4166
+ Choose `settingguide` for an entry point that stands on its own while guiding
4167
+ players and GMs into a setting. A `concept` explains one subject, a `reference`
4168
+ supports lookup, and a `userguide` explains how to operate the Foundry
4169
+ implementation. The setting guide can use ordinary SQL content tables and
4170
+ `:::secret` passages; its genre does not change its Address, journal, or page
4171
+ rendering.
4172
+
4173
+ ```markdown
4174
+ ---
4175
+ shortcode: reedflatsguide
4176
+ name: { full: The Reed Flats Guide }
4177
+ type: doc
4178
+ subType: settingguide
4179
+ description: A starting point for adventures in the Reed Flats.
4180
+ ---
4181
+
4182
+ The Reed Flats are a network of marsh settlements linked by river trade.
4183
+ Their routes, customs, and rival groups shape everyday journeys.
4184
+
4185
+ :::secret
4186
+ The old ferry route is known to the local guides.
4187
+ :::
4188
+ ```
4189
+
4190
+ The field is a genre and only a genre: it says what kind of page this is, never
4191
+ who reads it. A page written for a developer is a `howto` or a `concept` like
4192
+ any other, and the audience is the section it sits in.
4193
+
4194
+ **A page that introduces a type is an ordinary note, named by convention.**
4195
+ Write `type: doc`, `subType: reference`, `shortcode: <type>` — so the
4196
+ affiliations introduction is `doc-affiliation`, addressed and linked like
4197
+ anything else, and typically carrying a generated table of what it introduces.
4198
+ It has no build path of its own, and it is the only kind of index there is: a
4199
+ site is its homepage and its pages, and nothing is generated between them.
4200
+
4201
+ There is no landing page and no section. A section appears in **no address**: a
4202
+ page publishes at `/<package>/<type>-<shortcode>/`, which names no directory. A
4203
+ section is what Hugo calls a content directory, and the note format does not
4204
+ carry one.
4205
+
4206
+ So a `doc`'s `subType` is a **genre** and nothing else, closed to the values
4207
+ above.
4208
+
4209
+ ### type: macro
4210
+
4211
+ A script offered on the macro bar, plus the prose explaining what it does and
4212
+ when to reach for it. Produces a Foundry **Macro** and, from the same note, the
4213
+ JournalEntry every note produces — so a macro's documentation is a document a
4214
+ player can open, not a comment nobody reads.
4215
+
4216
+ **The script is a page of the note, addressed by an anchor.** The macro's
4217
+ `command` is the first **language-tagged** JavaScript fence on the page whose
4218
+ heading carries `{#script}`:
4219
+
4220
+ ````markdown
4221
+ # Script {#script}
4222
+
4223
+ ```js
4224
+ await CONFIG.SOHL.class.Utility.currentCombatantAttack();
4225
+ ```
4226
+ ````
4227
+
4228
+ Three rules follow, and each is deliberate. The anchor names the **page**, not
4229
+ the heading text, so the heading may be worded freely and
4230
+ `[[macro-autoattack#script]]` still opens exactly this page. The fence must
4231
+ be **tagged** — an untagged fence is a code sample whose language nobody stated,
4232
+ and treating it as executable would make an illustrative snippet the macro. And
4233
+ only the **first** tagged fence counts, so a note may document its macro
4234
+ with examples that are plainly not the macro. A note with no `{#script}` page,
4235
+ or no tagged fence on it, is a **build error**: a macro with no command is a
4236
+ macro-bar button that does nothing.
4237
+
4238
+ **The executable copy is read from the raw markdown.** The journal's copy of the
4239
+ same fence has been through table expansion and wikilink conversion first, so
4240
+ the two diverge on purpose — the journal renders prose _about_ the script, while
4241
+ the macro runs exactly what the author typed.
4242
+
4243
+ **This is not compiling data into code.** A Macro's `command` is authored source
4244
+ shipped as content and run by Foundry's own macro runner under the permission
4245
+ model that governs every macro in a world. Nothing evaluates, compiles, or
4246
+ revives anything; the compiler copies text from a fence into a JSON field.
4247
+
4248
+ **Both settings are `sohl` properties, not `data` ones.** The compiler reads
4249
+ them with the same accessor every `sohl` field uses — the `sohl:` block first,
4250
+ then the note's top level — so `data.macroType` is not read, and a macro is not
4251
+ a journal-only note the way `place`, `lore` and `scenario` are: it produces a
4252
+ Foundry **Macro**, and these two describe that document.
4253
+
4254
+ | `sohl` property | Values | Description |
4255
+ | --------------- | --------------------------- | -------------------------------------------------------------------- |
4256
+ | `macroType` | `script` | The Foundry macro type. Defaults to `script`, and `chat` is an error |
4257
+ | `macroScope` | `global \| actors \| actor` | How far the macro reaches. Defaults to `global` |
4258
+
4259
+ **`macroType: chat` is an error, not an unimplemented feature.** A chat macro's
4260
+ `command` is chat text rather than source, so none of the `{#script}` fence
4261
+ rules describe it, and compiling one through this path would ship a macro whose
4262
+ body was a code block posted verbatim into chat. Chat macros as content would
4263
+ need an authoring convention of their own.
4264
+
4265
+ The note's `data.icon` resolves the way every other note's does; a note that
4266
+ authors none takes Foundry's own `icons/svg/dice-target.svg`.
4267
+
4268
+ ### type: bundle
4269
+
4270
+ A bundle of notes to be taken as a single unit — an `Adventure` in Foundry VTT.
4271
+
4272
+ | `data` property | Values | Description |
4273
+ | --------------- | ----------- | ------------------------------------------------------ |
4274
+ | `contents` | `Address[]` | The documents the Adventure holds; `[]` when unstated. |
4275
+
4276
+ ```yaml
4277
+ ---
4278
+ type: bundle
4279
+ shortcode: hegovynvale
4280
+ name:
4281
+ full: The Hegóvyn Vale
4282
+ data:
4283
+ contents:
4284
+ - map-hegovynvale
4285
+ - miscgear-bowlcer
4286
+ - being-aurochs
4287
+ ---
4288
+ Prose describing what the bundle is for.
4289
+ ```
4290
+
4291
+ An `Adventure` carries **copies** of what it holds, not references: importing one
4292
+ creates or updates each document in the world, after which they live
4293
+ independently. So a bundle is not a folder — a folder is a live grouping that
4294
+ persists in the pack.
4295
+
4296
+ **Each address names the note's own document.** That is the same rule `pack:`
4297
+ follows, so there is one answer and not two. A note that compiles into _two_
4298
+ documents — an item and the JournalEntry its prose became — puts the second in a
4299
+ bundle when the bundle names its `note` Address:
4300
+ `sohl-miscgear-bowlcer` is the Item, `miscgear-bowlcer` its description page.
4301
+
4302
+ **An address that resolves to nothing fails the build.** A `folder` address is
4303
+ refused with a message of its own: a folder materialises in every pack holding
4304
+ something filed in it, so it belongs to no one pack and there is no single copy
4305
+ to take.
4306
+
4307
+ **The note's prose becomes the Adventure's `description`**, which is what
4308
+ Foundry renders on the import card. A bundle is something you hand someone, so
4309
+ its prose belongs on the document itself — which is why, unlike an item, a
4310
+ bundle earns no separate documentation journal.
4311
+
4312
+ Each Adventure is written to the pack the note's `pack` names — the shared
4313
+ routing field every type uses, not one of the bundle's own — defaulting to the
4314
+ configured `Adventure` pack, conventionally `adventures`. `<system>.pack`
4315
+ overrides it for that system, as it does everywhere else.
4316
+
4317
+ **It cannot be the `adventures` companion**, though, where a repository also
4318
+ compiles map notes: that pack is written by the scenes pass, and a companion is
4319
+ written by its parent pass rather than routed to. A repository that authors
4320
+ bundles declares an Adventure pack of its own, and one that declares none is
4321
+ told so by name.
4322
+
4323
+ **A pack's `system:` constrains what its Adventures may hold.** An `Adventure`
4324
+ has no `system` field, so a bundle spanning two systems cannot be one document
4325
+ that knows it spans them: it is one Adventure per system, and the pack each is
4326
+ written to is what carries the system. A pack declaring `system: hm3` sees the
4327
+ HM3 packs and the system-neutral ones, so a member that publishes no HM3
4328
+ document is **left out rather than failing** — and named, because an installer
4329
+ that quietly ships half its contents is worse than one that fails. A pack
4330
+ declaring no system scopes nothing away, and a member it cannot find is a dead
4331
+ address.
4332
+
4333
+ **The bundles pass runs last**, after every pass producing what a bundle can
4334
+ hold — Item, Actor, JournalEntry, Macro and Scene. That ordering is derived from
4335
+ what the pass declares it reads, not from the order `packs:` happens to list, so
4336
+ an Adventure pack declared first still compiles last.
4337
+
4338
+ ### type: folder
4339
+
4340
+ Foundry's `Folder` groups compiled documents. A folder note supplies its name, parent, and color. Packs name folder notes by Address.
4341
+
4342
+ ```yaml
4343
+ ---
4344
+ type: folder
4345
+ shortcode: possessionscooking
4346
+ name:
4347
+ full: Cooking
4348
+ data:
4349
+ parent: possessionsmiscgear
4350
+ color: "#7a4b2a"
4351
+ ---
4352
+ ```
4353
+
4354
+ | `data` property | Values | Description |
4355
+ | --------------- | ----------------------------------------- | ------------------------------------------------------------------------------ |
4356
+ | `parent` | `Address`, or a map of them keyed by pack | The folder this one sits in — one address, or one per pack. Unset at the root. |
4357
+ | `color` | `"#RRGGBB"`, a string | The folder's colour. Unset for Foundry's default. |
4358
+
4359
+ A folder is addressed `<package>-none-folder-<shortcode>` — **`none`**, because a
4360
+ `Folder` is a core Foundry document like a `JournalEntry` or a `Scene`, not a
4361
+ system's. Its shortcode is [an address segment](#the-canonical-address) like
4362
+ every other, so it is strictly lowercase alphanumeric: `possessionscooking`,
4363
+ never `possessions-cooking`, which would read as two segments and resolve to
4364
+ nothing.
4365
+
4366
+ **`color` must be quoted**, and YAML gives no third option: `color: #7a4b2a`
4367
+ parses as `null` (a `#` after a space opens a comment) and `color: 000000` parses
4368
+ as the number `0`. Write `"#RRGGBB"` to retain the string.
4369
+
4370
+ `parent` is an address, so a dangling one is an ordinary dead-address finding
4371
+ rather than a special-cased `Unknown folder id`, and a cycle is refused. Both are
4372
+ reported when the tree is read, not when something happens to reference the
4373
+ folder that carries them.
4374
+
4375
+ **`parent` may be a map keyed by pack**, because a folder's _identity_ is one
4376
+ thing and its _hierarchy_ is another. The same folder is deliberately filed
4377
+ under different parents in different packs: an item compendium is browsed by
4378
+ kind, a journal compendium is read by subject.
4379
+
4380
+ ```yaml
4381
+ data:
4382
+ parent:
4383
+ default: ~ # at the root of the items pack
4384
+ journals: descriptions # under Rules/Descriptions in the journals pack
4385
+ ```
4386
+
4387
+ `default` is every pack that is not named; an explicit `~` under a pack key means
4388
+ _at the root there_, which is a different statement from saying nothing. A plain
4389
+ scalar — the everyday spelling, and the right one wherever the hierarchies agree
4390
+ — is exactly `{ default: <value> }`. The folder keeps **one id** across every
4391
+ pack it materialises in, which is what files a documentation journal beside the
4392
+ item it describes; only its parent differs.
4393
+
4394
+ Every key but `default` names a **pack the package declares**, and one that names
4395
+ none is a finding of its own. It cannot be a harmless surplus: the compile asks
4396
+ the map for the pack it is writing and falls back to `default` when there is no
4397
+ such key, so a mistyped `journal:` files the folder wherever the default puts it
4398
+ — exactly the hierarchy the key was written to override, and silently.
4399
+
4400
+ **A folder note carries no prose.** It is structure, not content, so it produces
4401
+ no documentation journal and takes no part in `docEntryTypes`.
4402
+ In a compiled Foundry journal, a wikilink to a folder prints its label as text:
4403
+ the folder can materialise in several packs and has no single compendium UUID.
4404
+
4405
+ **It declares no pack.** Which packs a folder materialises in is derived from
4406
+ [what references it](#the-compendium-folder), and its ancestors materialise with
4407
+ it; a folder nothing references materialises nowhere.
4408
+
4409
+ **Its Foundry `_id` is derived from its address**, stable across runs, so a new
4410
+ folder needs no invented id. An authored `id` is kept where one is present —
4411
+ which is what lets a tree sweep its folder YAML into notes without a world that
4412
+ already holds those folders losing them. Two folders claiming one id is a build
4413
+ error.
4414
+
4415
+ The derivation is `makeId("folder", "<package>-none-folder-<shortcode>")`, and
4416
+ the namespace is **`folder`**, not the `document` one [every other note hashes
4417
+ under](#the-document-id): a folder and an item sharing a shortcode would
4418
+ otherwise derive the same id, and Foundry keys the two in separate collections
4419
+ so neither would complain. The content index publishes this value, so a consumer
4420
+ reads a folder's id rather than recomputing one.