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