@heroiclands/package-build 8.1.0 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +748 -0
  2. package/CONTENT.md +273 -13
  3. package/bin/content-build.mjs +479 -123
  4. package/bin/package-build.mjs +27 -69
  5. package/bin/report.mjs +1 -2
  6. package/bundle.mjs +2 -10
  7. package/config.mjs +31 -106
  8. package/container.mjs +13 -57
  9. package/content-config.mjs +300 -188
  10. package/coverage.mjs +14 -55
  11. package/deploy.mjs +4 -13
  12. package/docs/content-format.md +1418 -0
  13. package/e2e.mjs +16 -55
  14. package/engine/address-charset.mjs +62 -0
  15. package/engine/address-diff.mjs +1 -4
  16. package/engine/alias-index.mjs +153 -0
  17. package/engine/base-compiler.mjs +203 -31
  18. package/engine/code-fences.mjs +4 -13
  19. package/engine/compendiums.mjs +13 -37
  20. package/engine/content-address.mjs +6 -10
  21. package/engine/content-format-check.mjs +570 -0
  22. package/engine/content-format.mjs +253 -0
  23. package/engine/content-links.mjs +144 -99
  24. package/engine/content-lint.mjs +12 -16
  25. package/engine/content-slug.mjs +2 -6
  26. package/engine/content-tables.mjs +26 -79
  27. package/engine/diagnostics.mjs +37 -15
  28. package/engine/document-subtypes.mjs +440 -0
  29. package/engine/field-reference.mjs +6 -20
  30. package/engine/field-spec.mjs +49 -45
  31. package/engine/foreign-catalog.mjs +7 -22
  32. package/engine/foreign-manifests.mjs +1 -4
  33. package/engine/frontmatter-lint.mjs +347 -43
  34. package/engine/frontmatter.mjs +3 -8
  35. package/engine/generate.mjs +36 -20
  36. package/engine/helpers.mjs +54 -81
  37. package/engine/homepage.mjs +4 -15
  38. package/engine/ids.mjs +21 -12
  39. package/engine/index.mjs +15 -0
  40. package/engine/item-registry.mjs +72 -9
  41. package/engine/journals.mjs +4 -14
  42. package/engine/kb-manifest.mjs +41 -24
  43. package/engine/macros.mjs +2 -10
  44. package/engine/manifest-emit.mjs +6 -17
  45. package/engine/map-notes.mjs +53 -87
  46. package/engine/note-claims.mjs +383 -0
  47. package/engine/note-package.mjs +1 -4
  48. package/engine/note-vocabulary.mjs +678 -0
  49. package/engine/pack-config.mjs +56 -60
  50. package/engine/pack-router.mjs +18 -8
  51. package/engine/prose-config.mjs +20 -4
  52. package/engine/prose-lint.mjs +61 -17
  53. package/engine/region-events.mjs +1 -3
  54. package/engine/retired-fields.mjs +117 -3
  55. package/engine/scene-levels.mjs +8 -22
  56. package/engine/scenes.mjs +31 -47
  57. package/engine/schema-check.mjs +348 -7
  58. package/engine/schema-extract.mjs +11 -39
  59. package/engine/site-build.mjs +13 -38
  60. package/engine/site-index.mjs +40 -35
  61. package/engine/system-block.mjs +513 -0
  62. package/engine/web-wikilinks.mjs +115 -92
  63. package/engine/wikilink-syntax.mjs +30 -0
  64. package/engine/wikilinks.mjs +147 -183
  65. package/index.mjs +1 -5
  66. package/lang.mjs +1 -3
  67. package/manifest.mjs +10 -37
  68. package/markdownlint-config.mjs +1 -5
  69. package/package.json +6 -2
  70. package/sohl/actors.mjs +251 -68
  71. package/sohl/being-info.mjs +3 -6
  72. package/sohl/document-subtypes.mjs +82 -0
  73. package/sohl/index.mjs +4 -6
  74. package/sohl/item-builders.mjs +1 -3
  75. package/sohl/item-fields.mjs +16 -34
  76. package/sohl/items.mjs +111 -17
  77. package/sohl/kb-passes.mjs +29 -39
  78. package/sohl/note-schemas.mjs +11 -7
  79. package/sohl/skill-base.mjs +7 -23
  80. package/stage.mjs +3 -13
  81. package/templates.mjs +4 -15
  82. package/types/bundle.d.mts +1 -1
  83. package/types/container.d.mts +2 -2
  84. package/types/content-config.d.mts +48 -4
  85. package/types/coverage.d.mts +1 -1
  86. package/types/e2e.d.mts +4 -4
  87. package/types/engine/address-charset.d.mts +45 -0
  88. package/types/engine/alias-index.d.mts +122 -0
  89. package/types/engine/base-compiler.d.mts +132 -4
  90. package/types/engine/content-address.d.mts +2 -2
  91. package/types/engine/content-format-check.d.mts +163 -0
  92. package/types/engine/content-format.d.mts +101 -0
  93. package/types/engine/content-links.d.mts +16 -1
  94. package/types/engine/content-lint.d.mts +6 -0
  95. package/types/engine/diagnostics.d.mts +29 -0
  96. package/types/engine/document-subtypes.d.mts +233 -0
  97. package/types/engine/field-spec.d.mts +76 -23
  98. package/types/engine/frontmatter-lint.d.mts +47 -2
  99. package/types/engine/generate.d.mts +15 -2
  100. package/types/engine/helpers.d.mts +22 -14
  101. package/types/engine/ids.d.mts +10 -0
  102. package/types/engine/index.d.mts +5 -0
  103. package/types/engine/item-registry.d.mts +21 -2
  104. package/types/engine/kb-manifest.d.mts +35 -8
  105. package/types/engine/map-notes.d.mts +21 -11
  106. package/types/engine/note-claims.d.mts +113 -0
  107. package/types/engine/note-vocabulary.d.mts +251 -0
  108. package/types/engine/pack-config.d.mts +4 -3
  109. package/types/engine/pack-router.d.mts +4 -4
  110. package/types/engine/prose-lint.d.mts +6 -2
  111. package/types/engine/retired-fields.d.mts +73 -2
  112. package/types/engine/schema-check.d.mts +182 -0
  113. package/types/engine/schema-extract.d.mts +1 -1
  114. package/types/engine/site-index.d.mts +1 -1
  115. package/types/engine/system-block.d.mts +281 -0
  116. package/types/engine/web-wikilinks.d.mts +23 -12
  117. package/types/engine/wikilink-syntax.d.mts +29 -0
  118. package/types/manifest.d.mts +1 -1
  119. package/types/sohl/actors.d.mts +62 -6
  120. package/types/sohl/document-subtypes.d.mts +14 -0
  121. package/types/sohl/index.d.mts +1 -0
  122. package/types/sohl/items.d.mts +21 -0
@@ -0,0 +1,1418 @@
1
+ ## Content format: how a note becomes documents
2
+
3
+ ### Documentation
4
+
5
+ All markdown will end up generating a JournalNote document: If anything appears before the first H1 header, that will be placed in an "Introduction" page, and then every H1 header will become a subsequent page. The link id of this document will be `<package>-none-<note_type>-<shortcode>`
6
+
7
+ If there are `hm3` or `sohl` sections in the frontmatter, those will be used to generate InfoBoxes, which will be displayed as a separate page (named "SoHL InfoBox" and "HM3 InfoBox", with anchors sohlinfobox and hm3infobox)
8
+
9
+ **There are two kinds of infobox.**
10
+
11
+ A **note infobox** summarises the subject itself, from `data:` — a scenario's
12
+ length, party size and required archetypes; a place's subtype and its parent; a
13
+ weapon's weight and value. None of that is SoHL or HM3 information, and it
14
+ deserves a summary panel all the same. Any type that declares `data:` fields has
15
+ one.
16
+
17
+ A **system infobox** summarises what one system makes of the note, drawn from
18
+ that system's block. There is one per system, and they are not alike: the two
19
+ systems describe a weapon with different values for impact, heft, reach and draw,
20
+ so neither box can stand for the other.
21
+
22
+ **Which boxes appear is decided by the note type; what they say is decided by the
23
+ note.** A type that both systems support always shows both system boxes, and a
24
+ box whose system this particular note produces no document for reads
25
+ **"Not available"**.
26
+
27
+ | note type | system boxes shown |
28
+ | ----------------------------------------------------------------- | -------------------------------------------------- |
29
+ | `weapon`, `skill`, `being`, `armor`, `containergear`, `miscgear`… | SoHL **and** HM3 — either may read _Not available_ |
30
+ | `affiliation`, `affliction`, `attribute`, `concoction`, `mystery` | SoHL only |
31
+ | `armorlocation` | HM3 only |
32
+
33
+ This is stated rather than inferred from an empty block, because an absence is a
34
+ poor signal: noticing that something is missing requires already knowing it
35
+ should have been there, and a reader meeting one page has no way to know. A box
36
+ that says _Not available_ tells them outright.
37
+
38
+ It also separates two facts that an absence would conflate. `Spear (thrown)` is
39
+ HM3-only while spears plainly exist in SoHL — _that_ note has no SoHL form, and
40
+ its SoHL box says so. HM3, by contrast, has no affiliations, mysteries or
41
+ attributes **at all**; on those pages a box reading "Not available" would suggest
42
+ a gap in the note when the truth is about the system's scope, so no HM3 box is
43
+ drawn.
44
+
45
+ The set of boxes needs no new declaration: the note-type → document-subtype map
46
+ already records which systems each type reaches. Rendering one box per mapped
47
+ system makes the page checkable — _every page carries exactly the boxes its type
48
+ maps to_ is an assertion the build can make, so a missing infobox is a failure
49
+ rather than something nobody notices.
50
+
51
+ For Web Pages, the entire markdown content will be converted into an HTML page, with appropriate infoboxes.
52
+
53
+ ### Frontmatter has three regions
54
+
55
+ A note's frontmatter divides into three parts, and the difference matters because
56
+ only one of them is open:
57
+
58
+ | region | describes | unknown keys |
59
+ | -------------------- | ---------------------------------------------------------------------------------------------- | -------------------------- |
60
+ | **top level** | the note as a published artefact — `id`, `type`, `subType`, `shortcode`, `description`, `tags` | **passed through to Hugo** |
61
+ | **`data:`** | the subject itself — system-agnostic, specific to the note type | **an error** |
62
+ | **`sohl:` / `hm3:`** | the subject as one system's documents | **an error** |
63
+
64
+ **Top level is deliberately open.** Every key is copied into the generated web
65
+ page's front matter, so an unrecognised key is a Hugo or theme parameter this
66
+ build has no standing to refuse. `description` is the everyday case: it is not a
67
+ document field at all, it is the page's description.
68
+
69
+ **`data:` is deliberately closed.** It holds the type-specific facts about the
70
+ subject — a weapon's weight, an affliction's transmission, a being's species —
71
+ and every note type declares which keys it may carry. A misspelled key there is a
72
+ finding that names the key you meant; the same misspelling at top level would
73
+ silently become a theme parameter, which is exactly why these cannot live
74
+ together.
75
+
76
+ **Tags are open, except the ones that classify.** `tags:` shares the top level's
77
+ openness: a tag naming a theme, a region or a working state — `underworld`,
78
+ `byzaria`, `draft` — is the author's own and this build has no opinion about it.
79
+ A tag that classifies the subject is different, because something queries it. A
80
+ settlement tagged `village` appears in the list of villages and an untagged one
81
+ does not, so `vilage` does not merely look wrong: it removes the note from an
82
+ index, silently, and the index still renders. That is the same failure a
83
+ misspelled `data:` key used to be, and it gets the same answer — the vocabulary
84
+ is declared, so a near miss is a finding that names what you probably meant.
85
+
86
+ | group | applies to | tags |
87
+ | ------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
88
+ | **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` |
89
+ | **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` |
90
+ | **place scale** | `place` / `region` | `continent` |
91
+ | **being station** | `being` | `tradesfolk`, `common-folk`, `soldiery`, `administration`, `clergy`, `mages`, `underworld`, `dependents`, `guilded`, `unguilded` |
92
+ | **state** | any | `draft` |
93
+
94
+ **The group's scope is what makes the check work.** A group names the types it
95
+ applies to, and a place's kinds are only ever checked on a place. Without that
96
+ the rule is wrong on every note it touches: `azravan` on a faith, `barter` on an
97
+ economy note and `secret` on three lore notes each sit a typo's distance from
98
+ `caravan`, `border` and `sacred`, and none of them is a mistake. Scoped, the same
99
+ corpus reports nothing — while a settlement tagged `vilage` is still caught, even
100
+ beside a correct `town`.
101
+
102
+ **Kind and character are separate because one slot could not hold both.** A
103
+ fishing village is a `village` that is `fishing`, and the field this list
104
+ replaced had to spell that `Fishing Village` as a value of its own — which is why
105
+ a query for villages found two of the eleven that existed.
106
+
107
+ **A station is not a rank.** Which kind of body a person belongs to — the
108
+ clergy, the soldiery, the tradesfolk — is a different axis from where they stand
109
+ inside one, which is what `data.lore` carries by naming the rank. A tag holds the
110
+ first because a person may be several at once and because nothing ranks
111
+ `clergy` against `mages`.
112
+
113
+ **A continent is a region carrying a tag, not a subtype**, because structurally it
114
+ is a region: the same fields, the same parent chain, everything but scale.
115
+
116
+ The mapping tables below describe the **document** destinations. A key that
117
+ appears in no table still reaches the web page; it simply reaches no Foundry
118
+ document.
119
+
120
+ **The shared source is a default; the system block overrides it.** Where a field
121
+ can be reached two ways — `data.weight` through the mapping table, or
122
+ `sohl.system.weightBase` written directly — the value in the system block wins.
123
+ `data:` says what is true of the thing in general; a system says what is true of
124
+ it _there_, and a system that disagrees is not in error. A weapon weighs what
125
+ `data.weight` says unless SoHL says otherwise, and then SoHL is right for SoHL.
126
+
127
+ This is the same rule as `hm3.type` overriding a derived document type, applied
128
+ to fields: derive from the shared source, and let the system state the exception.
129
+
130
+ **A `WikiLink` becomes a shortcode where the target field expects one.** SoHL
131
+ stores cross-references as shortcode strings, which is what the `Code` suffix
132
+ marks: `data.assocSkill` is a link to a skill note, and `system.assocSkillCode`
133
+ holds that note's shortcode. The resolution happens at build time, and a link
134
+ that resolves to nothing is an error naming the note — never a blank field. Where
135
+ a target field has no `Code` suffix, the link is stored as the reference the
136
+ field expects.
137
+
138
+ ### The note vocabulary, and how it maps
139
+
140
+ A note has its own `type` and `subType`. **This is a third vocabulary** — not
141
+ SoHL's and not HM3's. It says what the note _is_ and the scope it covers,
142
+ irrespective of whether it becomes an Actor, an Item, a JournalEntry or nothing
143
+ at all in any particular system.
144
+
145
+ Each system then declares a map from the note's `(type, subType)` onto its own
146
+ document type — and, for SoHL, its own `system.subType`. The map is **declared**,
147
+ never inferred from a coincidence of names: `skill`, `weapongear`, `armorgear`,
148
+ `containergear` and `miscgear` exist in both systems with _different_ data
149
+ models, so name-matching there would not fail, it would succeed wrongly.
150
+
151
+ Because the map derives the document type, `hm3.type` and `sohl.system.subType`
152
+ are **overrides**, not required declarations. A note states them only when the
153
+ map cannot decide, or decides wrongly.
154
+
155
+ A note whose `(type, subType)` has no mapping for a system it carries a block
156
+ for is an **error naming the note**, never a silent skip and never a guess at the
157
+ first matching value. Where a mapping is missing for a whole class of note, that
158
+ is a gap in the vocabulary rather than something to write into every note: an
159
+ override that thousands of notes need is a missing subType value.
160
+
161
+ ### Mappings every type shares
162
+
163
+ Eight rows were identical in all sixteen tables below, so they are stated once
164
+ here and omitted there. Each per-type table shows only what is particular to that
165
+ type.
166
+
167
+ | shared source | → sohl | → hm3 |
168
+ | ------------- | ------ | ----- |
169
+
170
+ **A column reads NA wherever the type produces no document in that system.** An
171
+ `affiliation` has no HM3 form, so its whole HM3 column is NA; `armorlocation` has
172
+ no SoHL form, so its SoHL column is. Nothing else varies — which is why these
173
+ rows are worth stating once: repeated sixteen times they buried the differences
174
+ that matter.
175
+
176
+ Actor types (`being`, `vehicle`) add one more:
177
+
178
+ | shared source | → sohl | → hm3 |
179
+ | ------------- | ------ | ----- |
180
+
181
+ **One exception.** HM3's `armorlocation` declares no `notes` — it is the one
182
+ subtype that extends the Foundry base directly with no templates — so `notes` is
183
+ NA on both sides for that type, and its table says so.
184
+
185
+ ### WikiLinks
186
+
187
+ Twenty-seven fields in the tables below take a `WikiLink`, and a link is written
188
+ `[[target]]` or `[[target|label]]`. The target is an **address**.
189
+
190
+ #### The canonical address
191
+
192
+ ```text
193
+ <package>-<system>-<note_type>-<shortcode>
194
+ ```
195
+
196
+ Read it from the right: the last segment is always the shortcode, the one before
197
+ it always the note type, the one before that always the system, and the first the
198
+ package. Nothing is positional-by-guess — a segment means what its position says
199
+ it means.
200
+
201
+ The `<system>` segment is `none` for a note that belongs to no system, which is
202
+ most of them: `harnadventures-none-being-grod`. A note that exists only for one
203
+ system names it.
204
+
205
+ #### Shorter forms
206
+
207
+ The full address is the unambiguous form, and almost nothing uses it — 92 of
208
+ 12,056 links in the current trees. The rest rely on shorter forms, each dropping
209
+ segments from the left:
210
+
211
+ | form | resolves by |
212
+ | ------------------------------- | -------------------------------------------- |
213
+ | `package-system-type-shortcode` | exactly, always |
214
+ | `package-type-shortcode` | `(type, shortcode)` within the named package |
215
+ | `type-shortcode` | `(type, shortcode)` within this package |
216
+ | `shortcode` | `shortcode` within this package — see below |
217
+
218
+ The last row is the everyday case, and it means two different things by
219
+ context. In a **frontmatter field** the type segment is supplied by the field's
220
+ own declaration, so `tashal` is a complete address. In **body prose** there is no
221
+ field to supply it, so a bare shortcode is resolved across types and must match
222
+ exactly one; an ambiguity is an error naming the candidates.
223
+
224
+ **Parsing is positional counting from the right, and nothing else.** Every
225
+ segment is alphanumeric — shortcodes are `^[A-Za-z0-9]+$`, types are bare words,
226
+ systems come from a closed registry, and `contentPackage` is alphanumeric — so
227
+ the hyphen is purely a separator. There is no longest-match against a roster and
228
+ no vocabulary check before splitting.
229
+
230
+ That is a guarantee rather than an observation, and it holds: of **4,456 distinct
231
+ shortcodes** across the four content trees, not one contains a character outside
232
+ `[A-Za-z0-9]`. It is load-bearing, so relaxing the charset later would break
233
+ resolution with nothing to say so.
234
+
235
+ `type/shortcode` with a slash is the legacy form, still resolved so links written
236
+ before the vault migrated do not silently die. A slash is _unconditionally_ an
237
+ address separator — pipe or no pipe — so an unknown type before one is an error
238
+ rather than a fallback to the alias index.
239
+
240
+ #### The pipe decides how a target resolves
241
+
242
+ There are two namespaces, and the pipe says which one to consult:
243
+
244
+ | written | resolved as | displays |
245
+ | -------------------- | ----------- | -------------------------- |
246
+ | `[[Alias]]` | an alias | the alias, as written |
247
+ | `[[WikiLink\|]]` | an address | the target note's own name |
248
+ | `[[WikiLink\|Text]]` | an address | `Text` |
249
+
250
+ **No pipe means look this up in the alias index. A pipe means parse this as an
251
+ address.** The two never compete, and neither falls back to the other: a failure
252
+ in one namespace is a dead link, not an invitation to try the other.
253
+
254
+ That is what makes positional parsing safe. Note names contain hyphens —
255
+ `Grukar-ahk` is a name, not a `Grukar` of type `ahk` — and under a
256
+ resolve-by-shape rule the reader would have to consult the type vocabulary before
257
+ it dared split. Under the pipe rule it never faces the question: an unpiped
258
+ target is taken whole, hyphens and all, and only a piped target is ever split.
259
+
260
+ The empty label is not an oversight and not a way of writing no label. It says
261
+ _address this target, and show whatever it calls itself_ — so a note renamed later
262
+ takes its new name at every citation with no link edited. Omitting the pipe says
263
+ something else entirely: the phrase in the brackets is the alias, and the alias is
264
+ also what the reader sees.
265
+
266
+ So the two are worth keeping distinct even though both display a name: one is a
267
+ promise to follow the target, the other is a phrase the author chose.
268
+
269
+ #### An alias resolves only within its own type
270
+
271
+ A bare `[[awareness]]` in a `skill` note finds the `skill` whose alias is
272
+ `awareness` — not an `affliction` that happens to share the name. The alias index
273
+ is keyed by `(source note's type, alias)`, so the same word may be an alias in
274
+ several types without colliding.
275
+
276
+ Two notes of the **same** type claiming one alias is an error, and the report
277
+ names the claimants rather than the note that merely cited them. An ambiguous
278
+ alias never resolves to whichever was indexed first.
279
+
280
+ #### In frontmatter, a link is a bare address — never an alias
281
+
282
+ A `WikiLink` **field** takes the address with no brackets:
283
+
284
+ ```yaml
285
+ data:
286
+ parents:
287
+ - hexhodai
288
+ seat: tashal
289
+ ```
290
+
291
+ not `[[hexhodai]]`. The field is declared as a `WikiLink`, so the schema already
292
+ knows the value is an address and reads it as one; brackets would be punctuation
293
+ the reader has to strip before it can do anything.
294
+
295
+ **Aliases are not permitted here.** A frontmatter value is always parsed by the
296
+ address grammar above, so a single-segment value such as `hexhodai` is a
297
+ _shortcode_, not an alias — the same spelling means different things in a
298
+ frontmatter field and in body prose, and this is the rule that says which.
299
+
300
+ The reason is that frontmatter is structure rather than prose. An alias is an
301
+ authorial convenience for writing a sentence that reads well; a field value is a
302
+ reference something else will compile against, and it should say exactly what it
303
+ points at. There is also nowhere to put the pipe: the distinction body text draws
304
+ with punctuation has no equivalent in a YAML scalar, so the region as a whole
305
+ picks one namespace and keeps it.
306
+
307
+ **The field supplies the type.** Every `WikiLink` field declares the note type it
308
+ targets — `seat` a `place`, `parents` an `affiliation`, `stations` a `lore` — so
309
+ the type segment defaults from the declaration and a bare shortcode is the
310
+ ordinary case, not an abbreviation of one. That is why the examples above read
311
+ `tashal` rather than `place-tashal`: the shorter form carries the same
312
+ information, because the field already said what kind of thing it points at.
313
+
314
+ **The declaration constrains the type; it does not shorten the address.** A field
315
+ value may be written at any length, and every length is equally correct so long
316
+ as the type it names is the one the field declares. All four of these are valid
317
+ in a `seat` field:
318
+
319
+ ```yaml
320
+ seat: tashal
321
+ seat: place-tashal
322
+ seat: kethira-place-tashal
323
+ seat: kethira-none-place-tashal
324
+ ```
325
+
326
+ An `affiliation-` prefix there is an **error naming the field and both types** —
327
+ never a silent widening of what the field accepts. Where a field permits more
328
+ than one type, a bare shortcode must resolve to exactly one of them, and an
329
+ ambiguity is an error naming the candidates rather than a first match.
330
+
331
+ **Only the type segment defaults.** Package and system are not the field's to
332
+ supply — it has no opinion about which package holds the target — so reaching
333
+ another package's note means qualifying, and because parsing is positional
334
+ counting from the right, qualifying at all means naming the type too.
335
+ `kethira-tashal` is not "the `tashal` place in `kethira`"; it is two segments, so
336
+ it reads as type `kethira`, shortcode `tashal`, and fails.
337
+
338
+ So the ladder has one rung where the field helps and three where it only checks:
339
+ a bare shortcode takes its type from the declaration, and every longer form
340
+ states the type itself and is verified against it.
341
+
342
+ This also settles the alias question on its own. Aliases resolve within the
343
+ _source_ note's type, and a field almost always points at a different type — a
344
+ being's `stations` are `lore` notes — so an alias in a field would be looked up
345
+ in the wrong namespace even where one existed to find.
346
+
347
+ This is enforced rather than merely preferred: the build walks every frontmatter
348
+ value, reports each bracketed link it finds, each value that resolves only as an
349
+ alias, and each value whose qualification contradicts its field. A successful run says so — _no wikilink in frontmatter_ is part
350
+ of what `content-build links` reports when it passes.
351
+
352
+ Brackets belong in prose, where a link sits inside a sentence and needs marking
353
+ off from the words around it. A frontmatter value has nothing to be marked off
354
+ from.
355
+
356
+ #### Not yet implemented
357
+
358
+ Two rules in this section are settled but unbuilt, and describe the target rather
359
+ than current behaviour:
360
+
361
+ - **The `<system>` segment.** `readQualifier` reads package, type and shortcode;
362
+ there is no system segment. A four-segment target today parses as
363
+ `package-type-shortcode` with a hyphenated shortcode, or fails.
364
+ - **The pipe rule.** Today the pipe affects only the _label_: resolution runs the
365
+ same either way, trying the address first and falling back to the alias — so a
366
+ target is read by shape rather than by punctuation, and an author cannot say
367
+ which of the two they meant.
368
+
369
+ The corpus is close to the rule already. Of 12,056 links, 10,413 are
370
+ `[[type-shortcode|Label]]`, which is correct as written. Two authored links use
371
+ an unpiped multi-segment target, and 69 use a pipe with a note name where an
372
+ address belongs; those are the migration.
373
+
374
+ ### What a note produces
375
+
376
+ Note types fall into two groups, and only the first has a mapping table.
377
+
378
+ **Types producing a system document** — a SoHL or HM3 Actor or Item, one per
379
+ system, described by the tables further down.
380
+
381
+ **Types producing only a core document** — `lore`, `place`, `scenario`, `doc`,
382
+ `map`, `macro` and `homepage`. A core document is one Foundry defines rather than
383
+ a system: a JournalEntry, a Scene, a Macro. These are system-agnostic, so such a
384
+ note carries no `sohl:` or `hm3:` block, has no mapping table, and shows no
385
+ **system** infoboxes.
386
+
387
+ It may still show a **note** infobox, and several should: a scenario's length,
388
+ party size and required archetypes, or a place's subtype and parent, are exactly
389
+ what a reader wants at a glance. Being outside both systems is not the same as
390
+ having nothing worth summarising.
391
+
392
+ Every note in **both** groups still produces its JournalEntry and its web page.
393
+ The difference is only whether a system Actor or Item is created as well.
394
+
395
+ ### One note is at most one document per system
396
+
397
+ A note produces **at most one document in each system**. That constraint is worth
398
+ stating because the obvious counter-example is real, and the way it is resolved
399
+ shapes how weapons are authored.
400
+
401
+ SoHL gives a weapon **strike modes**: `system.strikeModes` on a single
402
+ `weapongear`, each mode melee or missile. A shorkana has four — the edge, the
403
+ blunt of the swung blade, the haft or pommel, and the throw. HM3 has no such
404
+ field: it assumes one usage per item, so those four modes are three
405
+ `weapongear` documents and one `missilegear`.
406
+
407
+ **The mechanical values cannot be shared, so the modes cannot be lifted into
408
+ `data:`.** Impact, heft, reach and draw all differ between the two systems even
409
+ where the concept matches. `data:` holds what the _thing_ is; how it performs is
410
+ each system's own, and belongs in that system's block.
411
+
412
+ **So the second document becomes a second note.** A `Spear` note carries a
413
+ `sohl:` block describing four strike modes and an `hm3:` block describing its
414
+ usual melee profile; a separate `Spear (thrown)` note carries **only** an `hm3:`
415
+ block, describing the missilegear. That yields two shortcodes, which is what HM3
416
+ needs, and the companion never appears in SoHL, which is what SoHL needs.
417
+
418
+ The population is small. Of HM3's eleven `missilegear` items, seven are distinct
419
+ objects with a SoHL counterpart — `Arrow (Longbow)`, `Bolt (Crossbow)`,
420
+ `Dart (Blowgun)`, `Stone (Sling)` and so on, which are ordinary notes carrying
421
+ both blocks. Only four are companions: `Javelin (thrown)`, `Shorkana (thrown)`,
422
+ `Spear (thrown)`, `Taburi (thrown)`.
423
+
424
+ **A companion note requires its system's type.** With one block and no
425
+ counterpart, nothing can derive whether `Spear (thrown)` is a `weapongear` or a
426
+ `missilegear` — there is no other system to infer from and no subType to read. So
427
+ `hm3.type` is _required_ on a single-block note, and is an override only on notes
428
+ that carry both.
429
+
430
+ The following special markdown sequences are recognized:
431
+
432
+ ```
433
+ # Heading {#id .class1 .class2 attr="value"}
434
+ ```
435
+
436
+ Any header can include curly braces. Inside the curly braces:
437
+
438
+ - `#id` represents an id anchor named `id` (only one allowed)
439
+ - `.class1` represents a CSS class named `class1` (any number of classes allowed)
440
+ - `attr="value"` represents an HTML attribute named `attr` whose value is `value` (any number of attr/value pairs allowed)
441
+
442
+ ```
443
+ :::secret
444
+ This is secret text
445
+ :::
446
+ ```
447
+
448
+ A fenced off area of text may be marked as secret, indicating that the text will be treated as secret text. In FoundryVTT, this will be achieved with
449
+
450
+ ```html
451
+ <section class="secret">
452
+ <p>This is secret text</p>
453
+ </section>
454
+ ```
455
+
456
+ On webpages, this will be achieved with the use of
457
+
458
+ ```html
459
+ <details>
460
+ <summary>Spoiler</summary>
461
+ <p>This is secret text</p>
462
+ </details>
463
+ ```
464
+
465
+ #### Actors
466
+
467
+ All actor types, including being and vehicle, have their entire markdown section processed as normal.
468
+
469
+ The following H1 headers are treated specially:
470
+
471
+ - `# ... {#appearance}`: The contents of this header become the `doc.appearance` property in sohl and `doc.description` in hm3.
472
+ - `# ... {#dossier}`: The contents of this header become the `doc.dossier` property in sohl and `doc.biography` in hm3.
473
+ - `# ... {#spoilers}`: The contents of this header are not written to the actor at all.
474
+
475
+ For JournalEntries, the following rules apply:
476
+
477
+ - `# ... {#spoilers}`: The contents of this header go into a page which is viewable only by the GM (`CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE`).
478
+ - The `portrait` frontmatter property will be added as a new JournalPage of type image with the name "Portrait" and anchor "portrait".
479
+
480
+ **Archetype** is often used to describe a character in broad terms. These are often useful when determining whether a character matches a particular adventure. The list of character archetypes are:
481
+
482
+ - warrior: Can hold a line and win a fight.
483
+ - skirmisher: Fights light — ambush, missile, mobility.
484
+ - infiltrator: Gets in unseen — locks, stealth, disguise.
485
+ - mage: Commands arcane practice.
486
+ - cleric: Commands religious practice and standing.
487
+ - healer: Treats wounds and illness.
488
+ - scholar: Reads, researches, and knows things.
489
+ - courtier: Navigates rank, negotiation, and intrigue.
490
+ - woodsman: Travels and survives wild country.
491
+ - mariner: Handles boats and blue water.
492
+ - artisan: Builds, repairs, and appraises craft work.
493
+ - trader: Moves goods, values them, and knows markets.
494
+
495
+ Note that archetypes are descriptive, not proscriptive, and a character may be described by multiple archetypes at once.
496
+
497
+ #### Items
498
+
499
+ 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.
500
+
501
+ The following H1 headers are treated specially:
502
+
503
+ - `# ... {#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.
504
+
505
+ ### type: being
506
+
507
+ Generates a living (or undead, or spirit) being.
508
+
509
+ | `data` property | Values | Description |
510
+ | --------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------- |
511
+ | `portrait` | `string` | File path to the portrait image |
512
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
513
+ | `archetypes` | `Archetype[]` | List of archtypical behaviors |
514
+ | `occupation` | `string` | Name of the character's occupation |
515
+ | `stations` | `WikiLink[]` | Name of the stations the character belongs to |
516
+ | `lore` | `WikiLink[]` | Lore concerning this being — the people it is of, the standing it holds, the law it lives under |
517
+ | `homes` | `WikiLink[]` | Place the being calls home |
518
+ | `affiliations` | `WikiLink[]` | Affilliations (e.g., arcane/divine traditions, polities, etc) |
519
+ | `gender` | `male \| female \| other` | Gender of the character |
520
+ | `species` | `WikiLink` | Being's species (lore) |
521
+ | `age` | `number` | Age of the character |
522
+ | `birthday` | `YYYY/MM/DD` | Date of birth of the character |
523
+ | `height` | `number` | Height in meters |
524
+ | `weight` | `number` | Weight in kilograms |
525
+ | `frame` | `scant \| light \| medium \| large \| massive` | Relative frame size |
526
+ | `appearance.eye_color` | `string` | Eye color |
527
+ | `appearance.hair_color` | `string` | Hair color |
528
+ | `appearance.skin_color` | `string` | Skin color |
529
+ | `appearance.complexion` | `string` | Complexion |
530
+ | `appearance.extra_features` | `string[]` | Extra features |
531
+
532
+ If a `sohl` property is present, a SoHL actor of type "being" will be created.
533
+
534
+ If an `hm3` property is present, an HM3 actor is created. Its document type is derived from the note's `subType`; `hm3.type` overrides that, and must be `character` or `creature`.
535
+
536
+ A SoHL "being" document will be created, as will an "HM3" document.
537
+
538
+ | shared source | → sohl | → hm3 |
539
+ | ----------------------- | ----------------- | ---------------------------- |
540
+ | `data.portrait` | `system.portrait` | `system.bioImage` |
541
+ | `data.templatePriority` | `system.template` | `flags.hm3.templatePriority` |
542
+ | `data.species` | NA | `system.species` |
543
+ | `data.gender` | NA | `system.gender` |
544
+ | `data.occupation` | NA | `system.occupation` |
545
+
546
+ ### type: homepage
547
+
548
+ A homepage for the module will be created.
549
+
550
+ The contents will be available in a page at `https://www.heroiclands.org/<package>/`.
551
+
552
+ It will also generate a single JournalEntry located at the top level of the "journals" compendium named "\_Introduction".
553
+
554
+ ### type: vehicle
555
+
556
+ Represents a conveyance able to hold goods and people moving from one place to another.
557
+
558
+ | `data` property | Values | Description |
559
+ | ------------------ | -------- | ------------------------------------------ |
560
+ | `portrait` | `string` | File path to the portrait image |
561
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
562
+
563
+ If `sohl` is present, this becomes a `vehicle` actor.
564
+
565
+ | shared source | → sohl | → hm3 |
566
+ | ----------------------- | ----------------- | ----- |
567
+ | `data.portrait` | `system.portrait` | NA |
568
+ | `data.templatePriority` | `system.template` | NA |
569
+
570
+ ### type: affiliation
571
+
572
+ Represents a related group of beings.
573
+
574
+ Note that affiliations may have multiple parent affiliations.
575
+
576
+ **A pantheon is not a subType.** It is a `faithtradition` carrying subordinate
577
+ faith traditions — the individual religions of that pantheon name it in
578
+ `parents`. There is no `pantheon` value, because every other subType answers
579
+ _what kind of body is this_ while a pantheon answers _where does it sit_, and an
580
+ enum that mixes the two makes a note choose between a kind and a level. The
581
+ hierarchy already carries the answer, so nothing is lost by deriving it: a faith
582
+ tradition with faith traditions beneath it is a pantheon.
583
+
584
+ The same reasoning is why the three traditions are siblings. `faithtradition`,
585
+ `arcanetradition` and `spirittradition` partition by _what a practice concerns_ —
586
+ the divine, magic, the spirit world — and that partition is load-bearing rather
587
+ than descriptive: a system filtering which mystical practices may associate with
588
+ an affiliation can only be as precise as the distinction it filters on.
589
+
590
+ subType
591
+
592
+ - guild: A sworn association of craftsmen holding monopoly over a trade within a locality.
593
+ - order: A body of members bound by vows or a rule of life to a shared purpose.
594
+ - 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.
595
+ - faithtradition: A tradition of belief and practice concerning the divine, whether organized or not.
596
+ - arcanetradition: A tradition of belief and practice concerning magic and its practice, whether organized or not.
597
+ - spirittradition: A tradition of belief and practice concerning spirits — ancestors, totems, and the numinous world — whether organized or not.
598
+ - lineage: A body claiming common descent from a known ancestor, whose standing and obligations pass by birth — clans, houses, dynasties, septs.
599
+ - venture: A band bound by contract or shared undertaking rather than by vow or public authority — free companies, ships' crews, trading expeditions, adventuring parties.
600
+ - criminal: An association organized to profit from activity its host polity forbids, sustained by its own enforcement rather than by law.
601
+ - 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.
602
+ - fellowship: A voluntary association without vow, trade monopoly, or public authority — formed for mutual company, aid, or shared practice.
603
+
604
+ **GovernanceModel**
605
+
606
+ - Autocracy: a single person holds unchecked authority, however acquired
607
+ - Monarchy: one ruler legitimated by descent, election, or sacred office
608
+ - Oligarchy: a small closed group rules, whether by birth, wealth, or rank — membership is not
609
+ conferred by election and its authority is not held for a term
610
+ - Republic: sovereignty rests in the citizen body and is exercised through offices held for a fixed
611
+ term by election, which a ruling class supplies the holders of in practice
612
+ - Council: a deliberating body governs collectively with no single head
613
+ - Democracy: the general membership decides, directly or through representatives, and any member may
614
+ hold office
615
+ - Theocracy: authority derives from divine mandate and rests with its clergy
616
+ - Meritocracy: position is earned by demonstrated skill, achievement, or expertise
617
+ - Stratocracy: the armed force is itself the government
618
+ - Feudal: authority flows through nested personal oaths rather than a central office
619
+ - Confederation: autonomous members retain sovereignty under a weak common center
620
+ - Anarchic: no formal governing authority — custom or force fills the gap
621
+
622
+ **Republic, Oligarchy and Democracy are three answers to one question**, and the
623
+ boundaries are testable rather than a matter of taste. Ask who fills the offices
624
+ and on what terms. If a closed group holds authority outright, with no election
625
+ and no term, it is an **Oligarchy**. If offices are elective and time-limited but
626
+ a propertied or senatorial order supplies nearly everyone who holds them, it is a
627
+ **Republic**. If any member may hold office and the general body decides, it is a
628
+ **Democracy**.
629
+
630
+ The distinction is not academic: it is the difference between a ladder whose top
631
+ rungs are a class one is born or bought into, and one whose top rungs are an
632
+ office one is voted into and then vacates. A republic's ladder therefore carries
633
+ **both** — the civic status (Citizen) and the standing in the governing body above
634
+ it (Senator) — because a citizen is not a member of the Senate, and conflating the
635
+ two is the commonest way to get a non-monarchy wrong.
636
+
637
+ **Rank**
638
+ Definition of a level within the organization (e.g., Priest, Layperson, Member, Gang Leader, Master, etc.)
639
+
640
+ | Property | Values | Description |
641
+ | ------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------ |
642
+ | `level` | number | The ranking within the affiliation, increasing values starting with 1, 0 indicates intentional exclusion (expulsion/excommunication) |
643
+ | `title` | string | Title associated with the Rank |
644
+ | `description` | string | Description of the Rank |
645
+ | `lore` | `WikiLink` | The standing this rank _is_ — a `lore` note of subType `law`, shared with every other body that confers the same thing |
646
+
647
+ **A rank's `lore` is shared; its `title` is not.** A Normen kingdom calls it
648
+ `Thrall` and a Vylarian province calls it `Slave`, and they mean one standing:
649
+ owned outright, with no standing at law except through an owner. The title is
650
+ what this body calls it and the description is how this body puts it, but the
651
+ obligations and rights belong to the standing itself, so they are written once
652
+ and cited by every ladder that confers it. In the authored corpus 237 distinct
653
+ titles across 2,602 rank entries resolve onto 43 standings.
654
+
655
+ That is also what makes a rank answerable across bodies. Without it, asking what
656
+ a `Naukrátissa` may do means reading the Bethûan fleet's ladder; with it, the
657
+ rank names the standing, and the standing says.
658
+
659
+ **Standing**: aligned, unaligned, rival, nemesis
660
+
661
+ | `data` property | Values | Description |
662
+ | -------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------- |
663
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
664
+ | `demonym` | `string` | What a member of this affiliation is called (a Vylarian) |
665
+ | `epithet` | `string` | The by-name it is known by — a god's, an order's, a company's |
666
+ | `symbol` | `string` | Its emblem in words: a feather atop a golden scale, a chisel carving a star |
667
+ | `governance.model` | `GovernanceModel` | Type of government structure, if applicable |
668
+ | `governance.summary` | `string` | summary of the governance situation |
669
+ | `governance.ranks` | `Rank[]` | The ranks available to members of the affiliation |
670
+ | `governance.offices` | `Map<name, description>` | Official offices in the affiliation |
671
+ | `commonSkills` | `WikiLink[]` | Common skills among members (languages, etc.) |
672
+ | `seat` | `WikiLink` | Where the affiliation's authority sits |
673
+ | `domains` | `WikiLink[]` | Places over which this affiliation holds sway |
674
+ | `population` | `number` | Number of people in the affiliation (precision 2 significant digits). |
675
+ | `economy` | `WikiLink[]` | Any wikilink referring to the economic activity of the affiliation (produced goods, currency systems, etc) |
676
+ | `lore` | `WikiLink[]` | Lore concerning it — the peoples it draws on, the god a faith venerates, its law, its calendar |
677
+ | `parents` | `WikiLink[]` | Affiliations that this affiliation is subordinate to |
678
+ | `relations` | `Map<WikiLink, Standing>` | Relations with other affiliations |
679
+
680
+ **A faith tradition is not its god.** An `affiliation` of subType
681
+ `faithtradition` is a _religion_ — a practice, with an ordained priesthood, a
682
+ calendar and a body of observance — and it can outlive belief in the god
683
+ entirely, while one god may be venerated by several religions that agree on
684
+ nothing else. So the god is `lore` of subType `deity`, cited from the faith
685
+ through `lore`, and never a property of it.
686
+
687
+ `epithet` and `symbol` stay with the religion for the same reason, and they are
688
+ not a faith's alone: they are what the members call the thing and what they
689
+ carve, so a guild has them as much as a cult does. What a god _is_ — its nature,
690
+ its domains, its aspects — belongs on the deity note, where every religion that
691
+ venerates it can point at one account.
692
+
693
+ **Every one of those describes the organisation.** A membership — which office a
694
+ being holds, what title it bears, where it stands on the ladder — is recorded on
695
+ the affiliation _as embedded on that being_, and nothing about it belongs in the
696
+ catalogue entry. That is the split `governance` draws: the organisation publishes
697
+ the ranks and offices that exist, and a membership names which of them it holds.
698
+
699
+ **Rank is the organisation's, not the member's.** A bare `level: 4` on a
700
+ membership says nothing on its own; it means _Knight_ only because the polity
701
+ declared that rung. So the ladder is authored once, on the body that confers it,
702
+ and a member's rank is an index into it. Level 0 is reserved for the excluded —
703
+ outlawed, expelled, excommunicated — which is a standing the organisation still
704
+ recognises, and so still has to define.
705
+
706
+ **Offices are named, not ranked.** A Chancellor and a Marshal are both great
707
+ officers and neither is above the other, so an office is a key with a
708
+ description rather than a rung — and a being may hold an office at any rank, or a
709
+ rank with no office at all.
710
+
711
+ **`domains` is territorial, and only territorial.** Seventy-seven divine
712
+ affiliations currently spell `domain:` the other way, holding a deity's sphere of
713
+ influence as prose — _Love, Beauty, and Prosperity_; _Fertility, Agriculture,
714
+ Peace, and Healing_. That sense **folds into `description`**, which those notes
715
+ leave empty in all but one case, and it does not become a field of its own.
716
+
717
+ A sphere is a characterisation, not a reference. Nothing compiles against it, no
718
+ other note points at it, and rendering it as a list would imply a vocabulary that
719
+ does not exist — whereas `description` is exactly the field for a one-line
720
+ statement of what a thing is. The fuller treatment already has a home: `lore` of
721
+ subType `deity` covers a god's nature, domains, epithets and aspects in prose.
722
+
723
+ That frees the name for the territorial sense, which earns it: every affiliation
724
+ subType holds sway somewhere, while the divine sense applies to one.
725
+
726
+ If `sohl` is present, this becomes an `affiliation` item.
727
+
728
+ | shared source | → sohl | → hm3 |
729
+ | ---------------- | ------------------ | ----- |
730
+ | `subType` | `system.subType` | NA |
731
+ | `data.seat` | `system.seat` | NA |
732
+ | `data.domains` | `system.domain` | NA |
733
+ | `data.parents` | `system.parents` | NA |
734
+ | `data.relations` | `system.relations` | NA |
735
+
736
+ `governance` reaches no system field. Ranks and offices are the note's and the
737
+ web page's — SoHL's affiliation item has nowhere to put them, and inventing a
738
+ mapping for a field no schema declares is the drift these tables exist to catch.
739
+ `system.society`, `system.office`, `system.title` and `system.level` are
740
+ likewise absent here: they are filled on an embedded membership, never from a
741
+ catalogue note's `data:`.
742
+
743
+ ### type: affliction
744
+
745
+ Represents an affliction.
746
+
747
+ **subType**
748
+
749
+ - disease: A biological affliction: an illness or parasite that infects the body or mind (e.g. typhoid, tuberculosis, river blindness)
750
+ - poisontoxin: A chemical affliction: a toxic substance or venom that impairs or kills the host (e.g. hemotoxin, mandrake, wasp venom)
751
+ - 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
752
+
753
+ **TransmissionTypes**
754
+
755
+ - none: no transmission mode
756
+ - airborne: Transmission through the air, such as via droplets or aerosols
757
+ - contact: Transmission through direct physical (skin) contact
758
+ - bodyfluid: Transmission through bodily fluids: blood, saliva, etc.
759
+ - injested: Transmission through ingestion of contaminated substances
760
+ - proximity: Transmission through close proximity to an infected individual, but separate from airborne or direct contact modes.
761
+ - vector: Transmission through a vector, such as an insect or animal bite
762
+ - perception: Transmission through sensory perception, such as sight or sound
763
+ - arcane: Transmission through arcane means
764
+ - divine: Transmission through divine means
765
+ - spirit: Transmission through spirit means
766
+
767
+ | `data` property | Values | Description |
768
+ | ----------------------------- | ------------------- | ----------------------------------------------------------------------------------------- |
769
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
770
+ | `transmission` | `TransmissionTypes` | Method of transmission |
771
+ | `outcome` | `death \| cured` | Result after affliction has run its course |
772
+ | `healingRate` | `number` | Likelihood of positive outcome of healing test |
773
+ | `contagionIndex` | `number` | how contagious the disease is |
774
+ | `outcomeTraumas` | `SafeExpression` | Expression returning traumas that result from affliction recovery |
775
+ | `onsetDurationFormula` | `RollFormula` | Formula to calculate duration until onset after contracting affliction |
776
+ | `healingCheckDurationFormula` | `RollFormula` | Formula to calculate duration until next healing check (measured from last healing check) |
777
+ | `resolutionDurationFormula` | `RollFormula` | Formula to calculate duration after onset to resolution |
778
+
779
+ If `sohl` is present, this becomes an `affliction` item.
780
+
781
+ | shared source | → sohl | → hm3 |
782
+ | ---------------------------------- | ------------------------------------ | ----- |
783
+ | `subType` | `system.subType` | NA |
784
+ | `data.transmission` | `system.transmission` | NA |
785
+ | `data.outcome` | `system.outcome` | NA |
786
+ | `data.healingRate` | `system.healingRateBase` | NA |
787
+ | `data.contagionIndex` | `system.contagionIndexBase` | NA |
788
+ | `data.outcomeTraumas` | `system.outcomeTraumas` | NA |
789
+ | `data.onsetDurationFormula` | `system.onsetDurationFormula` | NA |
790
+ | `data.healingCheckDurationFormula` | `system.healingCheckDurationFormula` | NA |
791
+ | `data.resolutionDurationFormula` | `system.resolutionDurationFormula` | NA |
792
+
793
+ ### type: armorgear
794
+
795
+ Note: `data.quantity` may not be specified. Quantity is always 1.
796
+
797
+ | `data` property | Values | Description |
798
+ | ------------------ | -------- | ------------------------------------------ |
799
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
800
+ | `weight` | `number` | Gear weight |
801
+ | `value` | `number` | Gear value |
802
+ | `quality` | `number` | Gear quality |
803
+ | `durability` | `number` | Gear durability |
804
+
805
+ If a `sohl` property is present, a SoHL item of type "armorgear" will be created.
806
+
807
+ if a `hm3` property is present, an HM3 item of type "armorgear" will be created.
808
+
809
+ | shared source | → sohl | → hm3 |
810
+ | ----------------- | ----------------------- | --------------- |
811
+ | `data.weight` | `system.weightBase` | `system.weight` |
812
+ | `data.value` | `system.valueBase` | `system.value` |
813
+ | `data.quality` | `system.qualityBase` | NA |
814
+ | `data.durability` | `system.durabilityBase` | NA |
815
+
816
+ ### type: armorlocation
817
+
818
+ | `data` property | Values | Description |
819
+ | ------------------ | -------- | ------------------------------------------ |
820
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
821
+
822
+ if a `hm3` property is present, an HM3 item of type "armorlocation" will be created.
823
+
824
+ | shared source | → sohl | → hm3 |
825
+ | ------------- | ------ | ----- |
826
+ | `notes` | NA | NA |
827
+
828
+ ### type: attribute
829
+
830
+ | `data` property | Values | Description |
831
+ | ------------------ | -------- | ------------------------------------------ |
832
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
833
+
834
+ if a `sohl` property is present, a SoHL item of type "attribute" will be created.
835
+
836
+ | shared source | → sohl | → hm3 |
837
+ | ------------- | ------ | ----- |
838
+
839
+ ### type: concoctiongear
840
+
841
+ **subType**:
842
+
843
+ - mundane: ordinary and common in everyday use, generally simple in composition (often a single dried or otherwise prepared ingredient)
844
+ - 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.
845
+ - elixir: An arcane alchemical concoction of great power.
846
+
847
+ | `data` property | Values | Description |
848
+ | ------------------ | ------------------------------- | ------------------------------------------------- |
849
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
850
+ | `weight` | `number` | Gear weight |
851
+ | `value` | `number` | Gear value |
852
+ | `quality` | `number` | Gear quality |
853
+ | `durability` | `number` | Gear durability |
854
+ | `quantity` | `number` | Gear quantity (default: 1) |
855
+ | `potency` | `na \| mild \| strong \| great` | Concoction Potency (mundane/exotic concoctions) |
856
+ | `strength` | `number` | Strength: higher the number, greater the strength |
857
+
858
+ if a `sohl` property is present, a SoHL item of type "concoctiongear" will be created.
859
+
860
+ | shared source | → sohl | → hm3 |
861
+ | ----------------- | ----------------------- | ----- |
862
+ | `subType` | `system.subType` | NA |
863
+ | `data.weight` | `system.weightBase` | NA |
864
+ | `data.value` | `system.valueBase` | NA |
865
+ | `data.quality` | `system.qualityBase` | NA |
866
+ | `data.durability` | `system.durabilityBase` | NA |
867
+ | `data.quantity` | `system.quantity` | NA |
868
+
869
+ ### type: containergear
870
+
871
+ Note: `data.quantity` may not be specified; quantity is always set to 1.
872
+
873
+ | `data` property | Values | Description |
874
+ | ------------------ | -------- | ------------------------------------------ |
875
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
876
+ | `weight` | `number` | Gear weight |
877
+ | `value` | `number` | Gear value |
878
+ | `quality` | `number` | Gear quality |
879
+ | `durability` | `number` | Gear durability |
880
+ | `capacity` | `number` | Container capacity (in lbs) |
881
+
882
+ if a `sohl` property is present, a SoHL item of type "containergear" will be created.
883
+
884
+ if a `hm3` property is present, an HM3 item of type "containergear" will be created.
885
+
886
+ | shared source | → sohl | → hm3 |
887
+ | ----------------- | ------------------------ | --------------------- |
888
+ | `data.weight` | `system.weightBase` | `system.weight` |
889
+ | `data.value` | `system.valueBase` | `system.value` |
890
+ | `data.quality` | `system.qualityBase` | NA |
891
+ | `data.durability` | `system.durabilityBase` | NA |
892
+ | `data.capacity` | `system.maxCapacityBase` | `system.capacity.max` |
893
+
894
+ ### type: miscgear
895
+
896
+ | `data` property | Values | Description |
897
+ | ------------------ | -------- | ------------------------------------------ |
898
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
899
+ | `weight` | `number` | Gear weight |
900
+ | `value` | `number` | Gear value |
901
+ | `quality` | `number` | Gear quality |
902
+ | `durability` | `number` | Gear durability |
903
+ | `quantity` | `number` | Gear quantity (default: 1) |
904
+
905
+ if a `sohl` property is present, a SoHL item of type "miscgear" will be created.
906
+
907
+ if a `hm3` property is present, an HM3 item of type "miscgear" will be created.
908
+
909
+ | shared source | → sohl | → hm3 |
910
+ | ----------------- | ----------------------- | ----------------- |
911
+ | `data.weight` | `system.weightBase` | `system.weight` |
912
+ | `data.value` | `system.valueBase` | `system.value` |
913
+ | `data.quality` | `system.qualityBase` | NA |
914
+ | `data.durability` | `system.durabilityBase` | NA |
915
+ | `data.quantity` | `system.quantity` | `system.quantity` |
916
+
917
+ ### type: mystery
918
+
919
+ **subType**:
920
+
921
+ - boon: A flat ±N modifier to an associated skill's mastery level, from any source.
922
+ - boost: One or more temporary mastery boosts to an associated skill (Mastery Boost table).
923
+ - fate: A mystery that quantifies the ability to alter destiny or fate.
924
+ - grace: A mystery that quantifies ability to call effectually on divine favor.
925
+ - birthsign: A mystery that describes the arcane sign under which the being was born.
926
+ - other: A mystery that does not fit into the other predefined categories.
927
+ - piety: A mystery that quantifies devotion to a religion.
928
+
929
+ **SkillAptitude**: either a single skill
930
+
931
+ | `data` property | Values | Description |
932
+ | ------------------ | --------------------------------------- | -------------------------------------------------------------- |
933
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
934
+ | `assocSkill` | `WikiLink` | Associated skill |
935
+ | `assocAffiliation` | `WikiLink` | Associated affiliation |
936
+ | `skillAptitudes` | `WikiLink` or `subType:<skill-subtype>` | Bonuses/penalties to skills (or types of skills) |
937
+ | `level` | `number` | Magnitude of the mystery |
938
+ | `charges.value` | `number` | Current number of charges available, _null_ = charges not used |
939
+ | `charges.max` | `number` | Maximum number of charges, _null_ = no maximum |
940
+
941
+ if a `sohl` property is present, a SoHL item of type "mystery" will be created.
942
+
943
+ | shared source | → sohl | → hm3 |
944
+ | ----------------------- | ----------------------------- | ----- |
945
+ | `subType` | `system.subType` | NA |
946
+ | `data.assocSkill` | `system.assocSkillCode` | NA |
947
+ | `data.assocAffiliation` | `system.assocAffiliationCode` | NA |
948
+ | `data.skillAptitudes` | `system.skillAptitudes` | NA |
949
+ | `data.level` | `system.levelBase` | NA |
950
+ | `data.charges.value` | `system.charges.value` | NA |
951
+ | `data.charges.max` | `system.charges.max` | NA |
952
+
953
+ ### type: mysticalability
954
+
955
+ **subType**:
956
+
957
+ - spiritrite: A prepared ceremony by which a practitioner petitions the spirit world.
958
+ - spiritaction: A discrete supernatural act performed through an allied or bound spirit.
959
+ - spiritpower: A standing power conferred on its bearer by a spirit.
960
+ - ritualaction: A prescribed ritual act performed to earn the favour of a deity.
961
+ - divineincantation: A spoken invocation channelling the power of a deity.
962
+ - arcaneincantation: A formally learned spell, invoked by word and gesture.
963
+ - arcanetalent: An innate arcane knack, possessed without formal training.
964
+ - spirittalent: An innate affinity for the spirit world, possessed without training.
965
+ - alchemy: The preparation of substances imbued with mystical potency.
966
+ - divination: The practice of obtaining hidden knowledge or foreknowledge by mystical means.
967
+
968
+ | `data` property | Values | Description |
969
+ | ------------------ | ---------- | -------------------------------------------------------------- |
970
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
971
+ | `assocSkill` | `WikiLink` | Associated skill |
972
+ | `assocAffiliation` | `WikiLink` | Associated affiliation |
973
+ | `masteryLevel` | `number` | Mastery Level |
974
+ | `level` | `number` | Magnitude of the mystery |
975
+ | `charges.value` | `number` | Current number of charges available, _null_ = charges not used |
976
+ | `charges.max` | `number` | Maximum number of charges, _null_ = no maximum |
977
+
978
+ if a `sohl` property is present, a SoHL item of type "mysticalability" will be created.
979
+
980
+ If an `hm3` property is present, an HM3 item is created. The following mappings are performed:
981
+
982
+ - if `subType` === `arcaneinvocation`, `hm3.type` = `spell`
983
+ - if `subType` === `divineinvocation`, `hm3.type` = `invocation`
984
+ - if `subType` === `arcanetalent`, `hm3.type` = `psionic`
985
+
986
+ If `hm3.type` is specified, it must be `psionic`, `spell` or `invocation`.
987
+
988
+ | shared source | → sohl | → hm3 |
989
+ | ----------------------- | ----------------------------- | ------------- |
990
+ | `subType` | `system.subType` | **see above** |
991
+ | `data.assocSkill` | `system.assocSkillCode` | NA |
992
+ | `data.assocAffiliation` | `system.assocAffiliationCode` | NA |
993
+ | `data.masteryLevel` | `system.masteryLevelBase` | NA |
994
+ | `data.level` | `system.levelBase` | NA |
995
+ | `data.charges.value` | `system.charges.value` | NA |
996
+ | `data.charges.max` | `system.charges.max` | NA |
997
+
998
+ ### type: projectilegear
999
+
1000
+ **subTypes**:
1001
+
1002
+ - none
1003
+ - arrow
1004
+ - bolt
1005
+ - bullet
1006
+ - dart
1007
+ - other
1008
+
1009
+ | `data` property | Values | Description |
1010
+ | ------------------ | -------- | ------------------------------------------ |
1011
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
1012
+ | `weight` | `number` | Gear weight |
1013
+ | `value` | `number` | Gear value |
1014
+ | `quality` | `number` | Gear quality |
1015
+ | `durability` | `number` | Gear durability |
1016
+ | `quantity` | `number` | Gear quantity (default: 1) |
1017
+
1018
+ if a `sohl` property is present, a SoHL item of type "projectilegear" will be created.
1019
+
1020
+ If an `hm3` property is present, then an HM3 item of type "missilegear" will be created.
1021
+
1022
+ Note that `weapon` can also produce an HM3 `missilegear`. Since `(type, shortcode)` is a
1023
+ flat namespace, a `projectile` and a `weapon` sharing a shortcode would collide on the
1024
+ HM3 side while remaining distinct on the SoHL side.
1025
+
1026
+ | shared source | → sohl | → hm3 |
1027
+ | ----------------- | ----------------------- | ----------------- |
1028
+ | `subType` | `system.subType` | NA |
1029
+ | `data.weight` | `system.weightBase` | `system.weight` |
1030
+ | `data.value` | `system.valueBase` | `system.value` |
1031
+ | `data.quality` | `system.qualityBase` | NA |
1032
+ | `data.durability` | `system.durabilityBase` | NA |
1033
+ | `data.quantity` | `system.quantity` | `system.quantity` |
1034
+
1035
+ ### type: skill
1036
+
1037
+ **subTypes**:
1038
+
1039
+ - social
1040
+ - nature
1041
+ - craft
1042
+ - lore
1043
+ - language
1044
+ - script
1045
+ - mystical
1046
+ - physical
1047
+ - combat
1048
+ - combattechnique
1049
+
1050
+ | `data` property | Values | Description |
1051
+ | ------------------ | ---------- | ------------------------------------------ |
1052
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
1053
+ | `masteryLevel` | `number` | Mastery Level |
1054
+ | `parentSkill` | `WikiLink` | Parent skill this skill specializes |
1055
+
1056
+ if a `sohl` property is present, a SoHL item of type "skill" will be created.
1057
+
1058
+ If an `hm3` property is present, then an HM3 item of type "skill" will be created.
1059
+
1060
+ 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.
1061
+
1062
+ | shared source | → sohl | → hm3 |
1063
+ | ------------------- | ------------------------- | --------------------- |
1064
+ | `subType` | `system.subType` | See notes above |
1065
+ | `data.masteryLevel` | `system.masteryLevelBase` | `system.masteryLevel` |
1066
+ | `data.parentSkill` | `system.parentSkillCode` | NA |
1067
+
1068
+ ### type: trauma
1069
+
1070
+ **subType**:
1071
+
1072
+ - injury: Physical harm caused by an external force.
1073
+ - fear: Emotional response to a perceived threat or danger.
1074
+ - morale: Emotional state affecting group cohesion and individual morale.
1075
+ - pall: Influence of existential chaos, death, or life-draining forces.
1076
+ - psycond: Mental and emotional disorder.
1077
+ - physcond: A persistent physical condition of the body (descriptive; e.g. albinism, a limp, poor eyesight)
1078
+ - auralshock: Severe shock to the aura, resulting in temporary loss of aura-related abilities.
1079
+ - fatigue: Physical or mental exhaustion resulting from prolonged activity or stress.
1080
+ - infection: Swelling or inflammation exacerbating an existing condition or injury, often fatal.
1081
+ - shock: A prolonged physiological state of shock lasting hours or days, following severe trauma or blood loss — distinct from the transient combat-shock states.
1082
+ - coma: A prolonged state of unconsciousness.
1083
+
1084
+ | `data` property | Values | Description |
1085
+ | ------------------ | -------- | ------------------------------------------ |
1086
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
1087
+
1088
+ if a `sohl` property is present, a SoHL item of type "trauma" will be created.
1089
+
1090
+ If an `hm3` property is present, an HM3 item is created. `hm3.type` must be specified as either `injury` or `trait`.
1091
+
1092
+ | shared source | → sohl | → hm3 |
1093
+ | ------------- | ---------------- | ----- |
1094
+ | `subType` | `system.subType` | NA |
1095
+
1096
+ ### type: weapongear
1097
+
1098
+ Note: `data.quantity` may not be specified. Quantity is always 1.
1099
+
1100
+ | `data` property | Values | Description |
1101
+ | ------------------ | -------- | ------------------------------------------ |
1102
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
1103
+ | `weight` | `number` | Gear weight |
1104
+ | `value` | `number` | Gear value |
1105
+ | `quality` | `number` | Gear quality |
1106
+ | `durability` | `number` | Gear durability |
1107
+
1108
+ if a `sohl` property is present, a SoHL item of type "weapongear" will be created,
1109
+ carrying every strike mode the weapon has — melee and missile alike — on
1110
+ `system.strikeModes`.
1111
+
1112
+ If an `hm3` property is present, an HM3 item is created. **`weapon` has no `subType`**: SoHL distinguishes a weapon's uses with strike modes rather than by kind, and HM3's document type follows from which of those the note describes. A note carrying both blocks is a `weapongear` in each; a companion note carrying only `hm3:` states `hm3.type` itself, and is usually a `missilegear` — see _One note is at most one document per system_.
1113
+
1114
+ | shared source | → sohl | → hm3 |
1115
+ | ----------------- | ----------------------- | --------------- |
1116
+ | `data.weight` | `system.weightBase` | `system.weight` |
1117
+ | `data.value` | `system.valueBase` | `system.value` |
1118
+ | `data.quality` | `system.qualityBase` | NA |
1119
+ | `data.durability` | `system.durabilityBase` | NA |
1120
+
1121
+ ### type: lore
1122
+
1123
+ In-world information about people, places, or concepts.
1124
+
1125
+ subType:
1126
+
1127
+ - cosmology: The structure of reality — planes, realms, creation, and the ordering of what exists.
1128
+ - deity: Individual gods and their attributed natures, domains, epithets, and aspects.
1129
+ - theology: How the divine is held to operate — worship, sacrifice, afterlife, sin and grace.
1130
+ - arcana: How magic is held to operate — mechanism, traditions, and philosophies of practice.
1131
+ - spirit: The non-divine numinous — spirits, celestials, fae, and their natures.
1132
+ - economy: How wealth moves — barter, coinage, trade networks, credit, and measure.
1133
+ - law: How obligation is ordered and enforced — citizenship, custom, courts, and tenure.
1134
+ - calendar: How time is reckoned and marked — dating, seasons, festivals, and astrology.
1135
+ - history: What has happened — eras, events, chronicles, and genealogies of rule.
1136
+ - material: Substances and their properties — minerals, reagents, herbs, and preparations.
1137
+ - folk: Related sapient beings of a single or tightly related species: kindreds, ancestries.
1138
+ - culture: A social grouping of individuals with common beliefs, mores, and values.
1139
+ - bestiary: A kind of creature that is not a people — beasts, monsters, and the made things
1140
+ that were never born. What `folk` covers for the sapient, this covers for everything else.
1141
+
1142
+ | `data` property | Values | Description |
1143
+ | --------------- | ------ | ----------- |
1144
+
1145
+ ### type: map
1146
+
1147
+ A map — the visual rendering of a place or an encounter, plus the pinned
1148
+ locations described in its own body. Produces a Foundry **Scene**, and a web page carrying the map image
1149
+ and its description.
1150
+
1151
+ A map is a note in its own right rather than a property of a `place`, for three
1152
+ reasons. A place commonly has **several** maps — a floor per level of a keep, or
1153
+ a feature's detail map alongside its local-area map — and a scene nested in a
1154
+ place could only ever be one. A Scene is a substantial document in itself: walls,
1155
+ doors, lights, tiles, sounds, region shapes and pins, authored in two deliberate
1156
+ unit conventions. And a map has prose and named locations of its own, which makes
1157
+ it a document rather than a field.
1158
+
1159
+ A map may also exist with no place at all — an ambush on a road is an encounter
1160
+ map and not a named location.
1161
+
1162
+ **The place is named on the map, not the other way round.** A place commonly has
1163
+ several maps, so listing them from the place is the end that goes stale; naming
1164
+ the place from the map is the end that cannot, because a map is written once and
1165
+ depicts what it depicts. A place's maps are therefore derived — every map whose
1166
+ `place` is this one — and the relation exists in exactly one place.
1167
+
1168
+ subType:
1169
+
1170
+ - battlemap: Tactical scale, for a scene played out square by square.
1171
+ - localmap: Roughly a kilometre across — a settlement, a holding, a small valley.
1172
+ - regionalmap: Large scale, covering a region or a journey between places.
1173
+
1174
+ The three differ only in the canvas defaults derived for them, which is why they
1175
+ are subTypes of one type rather than three types.
1176
+
1177
+ > The three were **types** until package-build#174, which is the shape the notes
1178
+ > in the wild still carry. Both are read: a note still writing `type: battlemap`
1179
+ > is reported and told what to write instead, exactly as a note writing
1180
+ > `type: character` is (SoHL#1580). A consumer's `sections` config keys off the
1181
+ > type, so it takes one `map` entry where it carried three.
1182
+
1183
+ **NoteLocation** is `[GridLocation, anchor]` where the `anchor` is an anchor identified in the body of the note, and `GridLocation` represents a particular grid location on the document.
1184
+
1185
+ The `data:` fields, of which three are required:
1186
+
1187
+ | `data` property | Values | Description |
1188
+ | ----------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1189
+ | `img` | `string` | **Required.** The path to the map art. Becomes the level's `background.src` — what tokens stand on. Authored at the note's **top level**, not here — see below |
1190
+ | `dimensions` | `[int, int]` | **Required.** `[width, height]` in whole pixels, the art's own size |
1191
+ | `pxPerGrid` | `integer` | **Required.** Whole pixels per grid square; must match the art |
1192
+ | `navName` | `string` | Short name for Foundry's scene navigation bar |
1193
+ | `levelName` | `string` | The name of the embedded level. Defaults to `Ground` |
1194
+ | `backgroundColor` | `ColorHexValue` | Shown where the art does not reach. Defaults to `#999999` |
1195
+ | `overlay` | `string` | Path to **foreground** art |
1196
+ | `walls` | `WallSegment[]` | List of wall segments |
1197
+ | `doors` | `Door[]` | List of doors |
1198
+ | `lights` | `Light[]` | List of lights |
1199
+ | `tiles` | `Tile[]` | List of tiles |
1200
+ | `sounds` | `Sound[]` | List of sounds |
1201
+ | `regions` | `SceneRegion[]` | List of scene regions |
1202
+ | `place` | `WikiLink` | The place this map depicts. Optional, because an encounter map depicts no named place — but that is the exception, and a map without one is a map of nowhere |
1203
+ | `notes` | `NoteLocation[]` | grid coordinates of note markers mapped to anchors in this document |
1204
+
1205
+ Everything else a Scene holds is **derived**, not authored: padding, grid type,
1206
+ grid distance and units, token vision and fog mode all come from the subType, and
1207
+ ids, ordering and elevation are synthesised. A map note carries the map's
1208
+ essence, not a Scene's schema — the same division a weapon note makes against an
1209
+ Item's.
1210
+
1211
+ **`levels:` is never authored, and must never be.** A Scene has to ship at least
1212
+ one Level or it has no map at all, and nothing supplies one after the fact: the
1213
+ client-side `_preCreate` that would create it does not run during offline pack
1214
+ compilation, and the server-side migration shim is version-gated on
1215
+ `_stats.coreVersion`, so a pack stamped 14.x or later skips it entirely. The
1216
+ single Level is synthesised from `img`, `overlay`, `levelName` and
1217
+ `backgroundColor`.
1218
+
1219
+ > **`img`, at the note's top level, as every other type's artwork is.** A map
1220
+ > alone named it `image` and read it out of the `sohl:` block, so one idea had
1221
+ > two spellings and this table had to hedge rather than state a rule
1222
+ > ([package-build#142](https://github.com/HeroicLands/package-build/issues/142)).
1223
+ > Art is not system-specific — a Scene is a core Foundry document, and a second
1224
+ > system would want the identical art — so the field sits beside every other
1225
+ > note's `img` rather than inside a system block.
1226
+ >
1227
+ > `image` is **retired in favour of it**, in the three steps `package:` took
1228
+ > (#56), and this is the first: both spellings are read, `img` wins where a note
1229
+ > carries both, and a note still writing `image` gets a located **warning**
1230
+ > rather than a refusal. It compiles to the byte-identical document, so failing
1231
+ > a build over it would red a tree that has done nothing wrong.
1232
+
1233
+ **Two unit conventions, deliberately.** Geometry — walls, doors, lights, tiles,
1234
+ sounds, region shapes — is authored in **pixels**, Foundry's native storage,
1235
+ because a traced battlemap's walls do not lie on grid intersections (measured:
1236
+ 97.8% do not). Map pins are authored in **grid squares**, commonly
1237
+ half-integers, because that is how a person reads a position off a map. The two
1238
+ are told apart by their key: `position:` and segment or shape coordinates are
1239
+ pixels, `at:` is grid squares. Mixing them fails silently and visually in
1240
+ Foundry, so the build refuses rather than resolving.
1241
+
1242
+ > **These fields are read from `sohl:` today, and should move to `data:`.** A
1243
+ > Scene is a core Foundry document — nothing about a map's geometry is
1244
+ > system-specific, and HM3 would want the identical Scene. Authoring it under
1245
+ > `sohl:` means a map produces nothing for a system-agnostic build and carries a
1246
+ > SoHL infobox implying a specificity it does not have. It is the same class of
1247
+ > mistake as storing `archetype` in flags: the data went where the only available
1248
+ > container was, rather than where it belongs. Three notes carry it today.
1249
+
1250
+ **A map is always a leaf.** Its frontmatter references nothing outside itself.
1251
+ `notes:` is a list of `[anchor, GridLocation]`, and each anchor names a heading in
1252
+ **this map note's own body** — never an external note. A pin therefore opens the
1253
+ map's own journal page, so the prose describing a spot on the map lives with the
1254
+ map that shows it.
1255
+
1256
+ That constraint is the point, not an accident of the format. A map that names no
1257
+ other note can be reused by any place, any scenario and any package, and can be
1258
+ moved between them without dragging references along. It also cannot carry a
1259
+ dangling reference: only things pointing _at_ a map can break, which is the safe
1260
+ direction for the failure to run.
1261
+
1262
+ **So the reference runs from the place.** A `place` names its maps through
1263
+ `data.maps`; the map says nothing about which places it depicts. That is the
1264
+ opposite of how the pins run, and deliberately so — the pins point inward to keep
1265
+ the map self-contained, and the place points outward because it is the thing that
1266
+ knows which maps belong to it. A keep with three floor plans is a place naming
1267
+ three maps, and none of those maps needs to know it is a keep.
1268
+
1269
+ ### type: place
1270
+
1271
+ subType:
1272
+
1273
+ - world: A self-contained whole in which places exist — a planet, plane, or realm.
1274
+ - region: A bounded division of a world or larger region — continents, marches, uplands, provinces.
1275
+ - settlement: A place where folk dwell together — cities, towns, villages, holdings, camps.
1276
+ - site: A place significant by what was made or done there — ruins, monoliths, henges, works, battlefields.
1277
+ - structure: A single building or habitation — halls, keeps, temples, inns, towers.
1278
+ - feature: A place significant by its terrain — forests, rivers, falls, passes, fords.
1279
+
1280
+ | `data` property | Values | Description |
1281
+ | --------------- | ------------ | ---------------------------------------------------------------------------- |
1282
+ | `demonym` | `string` | What a person from this place is called — a Vylarian |
1283
+ | `lore` | `WikiLink[]` | Lore concerning this place — its peoples, its law, its calendar, its history |
1284
+ | `parents` | `WikiLink[]` | Enclosing places within which this place is located |
1285
+ | `population` | `number` | Approximate population (precision 2 significant digits) |
1286
+
1287
+ **A place declares only what is true of ground.** Four properties were removed
1288
+ because they were true of something else, and each removal has a home to go to.
1289
+
1290
+ **`languages` is a fact about a polity.** A place's languages change when its
1291
+ ruler changes, which is what makes them the ruler's property — and
1292
+ `affiliation.commonSkills` already holds them. The authored corpus agrees: of 206
1293
+ places carrying `languages`, 190 were settlements and 16 were regions, and not one
1294
+ was a site, a structure or a feature. A ruin has no language.
1295
+
1296
+ **`peoples` widens to `lore`.** It was the only lore-pointing property a place
1297
+ had, so a place with a calendar, a body of law or a local history had nowhere to
1298
+ cite it. Nothing is lost by widening: the target's own subType already
1299
+ distinguishes a `folk` from a `law`, which is the same reason `affiliation`
1300
+ carries no `pantheons`.
1301
+
1302
+ **`summary` duplicated `description`**, which every note already has and which is
1303
+ what the page renders.
1304
+
1305
+ **`affiliations` and `maps` were the wrong end of a relation.** `affiliations` is
1306
+ the inverse of `affiliation.domains`, and a relation authored from both ends
1307
+ drifts the moment one is edited. `maps` moves onto the map, which now names the
1308
+ place it depicts — see `type: map` below.
1309
+
1310
+ ### type: scenario
1311
+
1312
+ Content prepared to be played — a situation with its cast, places, and possible outcomes.
1313
+
1314
+ subType:
1315
+
1316
+ - campaign: A long arc toward a goal, spanning many adventures — carries standing cast, factions, and its own timeline.
1317
+ - adventure: A self-contained undertaking with a specific objective, playable in a few sessions.
1318
+ - encounter: A single scene or challenge, reusable within an adventure or on its own.
1319
+
1320
+ | `data` property | Values | Description |
1321
+ | ------------------ | -------------------------------------------- | ------------------------------------------------------------------------------ |
1322
+ | `parents` | `WikiLink[]` | List of parent scenarios of this scenario (campaigns, etc.) |
1323
+ | `locations` | `WikiLink[]` | List of locations associated with this scenario |
1324
+ | `cast` | `WikiLink[]` | individuals associated with this scenario |
1325
+ | `factions` | `WikiLink[]` | Affiliations associated with this scenario |
1326
+ | `follows` | `WikiLink[]` | Prerequisite scenarios that should be completed before beginning this scenario |
1327
+ | `status` | `draft \| playtested \| published` | Playability status of this scenario |
1328
+ | `party.size` | `solo \| small \| standard \| large \| host` | Suggested party size (solo=1, small=2-3, standard=4-6, large=6-7, host=7+) |
1329
+ | `party.archetypes` | `Archetype` | Archetypes of characters suitable for completion |
1330
+
1331
+ ### type: doc
1332
+
1333
+ subType:
1334
+
1335
+ - rules: The rules of the game, independent of medium — valid at a table with paper and dice.
1336
+ - user-guide: How to operate the Foundry implementation to play by the rules.
1337
+ - reference: Out-of-world lookup material about the setting or system — correspondences, conversions, glossaries.
1338
+ - collection: A note whose content is **derived from the tree** — a roster, a table of
1339
+ settlements, an index of languages, almost always a query. It differs from `reference` in
1340
+ where its content comes from, not in who reads it: a reference **states** facts of its own,
1341
+ and a collection **derives** them. The author's test is whether the page would still say
1342
+ anything if every other note vanished. A collection can therefore collect nothing and still
1343
+ render — an empty table looks exactly like a full one until you read it — which is the
1344
+ failure this value exists to make nameable.
1345
+
1346
+ | `data` property | Values | Description |
1347
+ | --------------- | -------- | --------------------------------------------------------------------------------------------------------- |
1348
+ | `section` | `string` | For a `collection` only: the URL segment of the section it heads. Omit it and the note's own slug is used |
1349
+
1350
+ **`section` is the segment, not a label.** A collection is a section's landing
1351
+ page, so what it names is where that section lives — and two collections may not
1352
+ claim the same one. A collection that lists a _subset_ of a section therefore
1353
+ names none: five of `sohl-thalorna`'s list places and three list affiliations, and
1354
+ each falls back to its own slug rather than fighting over `/place/`.
1355
+
1356
+ It is authored rather than derived because the note's own title is presentation:
1357
+ a collection called "Creatures" heads the `being` section, and slugging the title
1358
+ would put it somewhere else.
1359
+
1360
+ ### type: macro
1361
+
1362
+ A script offered on the macro bar, plus the prose explaining what it does and
1363
+ when to reach for it. Produces a Foundry **Macro** and, from the same note, the
1364
+ JournalEntry every note produces — so a macro's documentation is a document a
1365
+ player can open, not a comment nobody reads.
1366
+
1367
+ **The script is a page of the note, addressed by an anchor.** The macro's
1368
+ `command` is the first **language-tagged** JavaScript fence on the page whose
1369
+ heading carries `{#script}`:
1370
+
1371
+ ````markdown
1372
+ # Script {#script}
1373
+
1374
+ ```js
1375
+ await CONFIG.SOHL.class.Utility.currentCombatantAttack();
1376
+ ```
1377
+ ````
1378
+
1379
+ Three rules follow, and each is deliberate. The anchor names the **page**, not
1380
+ the heading text, so the heading may be worded freely and
1381
+ `[[docmacro-autoattack#script]]` still opens exactly this page. The fence must
1382
+ be **tagged** — an untagged fence is a code sample whose language nobody stated,
1383
+ and treating it as executable would make an illustrative snippet the macro. And
1384
+ only the **first** tagged fence counts, so a note may document its macro
1385
+ with examples that are plainly not the macro. A note with no `{#script}` page,
1386
+ or no tagged fence on it, is a **build error**: a macro with no command is a
1387
+ macro-bar button that does nothing.
1388
+
1389
+ **The executable copy is read from the raw markdown.** The journal's copy of the
1390
+ same fence has been through table expansion and wikilink conversion first, so
1391
+ the two diverge on purpose — the journal renders prose _about_ the script, while
1392
+ the macro runs exactly what the author typed.
1393
+
1394
+ **This is not compiling data into code.** A Macro's `command` is authored source
1395
+ shipped as content and run by Foundry's own macro runner under the permission
1396
+ model that governs every macro in a world. Nothing evaluates, compiles, or
1397
+ revives anything; the compiler copies text from a fence into a JSON field.
1398
+
1399
+ | `data` property | Values | Description |
1400
+ | --------------- | --------------------------- | -------------------------------------------------------------------- |
1401
+ | `macroType` | `script` | The Foundry macro type. Defaults to `script`, and `chat` is an error |
1402
+ | `macroScope` | `global \| actors \| actor` | How far the macro reaches. Defaults to `global` |
1403
+
1404
+ > **These fields are read from `sohl:` today, and should move to `data:`.** A
1405
+ > Macro is a core Foundry document — nothing about a script's type or scope is
1406
+ > system-specific — so authoring them under `sohl:` puts them where the only
1407
+ > available container was rather than where they belong. It is the same mistake
1408
+ > the map fields make, and no authored note carries either field today, so the
1409
+ > move costs nothing.
1410
+
1411
+ **`macroType: chat` is an error, not an unimplemented feature.** A chat macro's
1412
+ `command` is chat text rather than source, so none of the `{#script}` fence
1413
+ rules describe it, and compiling one through this path would ship a macro whose
1414
+ body was a code block posted verbatim into chat. Chat macros as content would
1415
+ need an authoring convention of their own.
1416
+
1417
+ The note's `img` is a content-relative path resolved the way every other note's
1418
+ is; a note that authors none takes Foundry's own `icons/svg/dice-target.svg`.