@heroiclands/package-build 22.15.0 → 22.16.0

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