@heroiclands/package-build 23.1.1 → 23.2.1

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 (247) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/bin/content-commands.mjs +68 -149
  3. package/bin/package-build.mjs +56 -10
  4. package/ci/check-banners.mjs +42 -6
  5. package/ci/confirm-npm-version.mjs +135 -0
  6. package/ci/content-format-reference.mjs +14 -5
  7. package/config.mjs +1 -0
  8. package/container.mjs +185 -4
  9. package/content-config.mjs +333 -210
  10. package/deploy.mjs +116 -10
  11. package/docs/api.md +238 -246
  12. package/docs/authoring/assets.md +11 -5
  13. package/docs/authoring/frontmatter.md +1 -1
  14. package/docs/authoring/links-and-markup.md +87 -0
  15. package/docs/commands.md +138 -99
  16. package/docs/configuration.md +277 -125
  17. package/docs/content-format.md +2 -0
  18. package/docs/diagnostics.md +48 -13
  19. package/docs/guides/book.md +341 -21
  20. package/docs/guides/e2e.md +13 -2
  21. package/docs/guides/icons.md +14 -4
  22. package/docs/guides/module-project.md +18 -8
  23. package/docs/guides/release-and-deploy.md +32 -0
  24. package/docs/guides/site.md +5 -2
  25. package/docs/project-setup.md +30 -10
  26. package/docs/reference/format-details.md +373 -170
  27. package/docs/reference/note-types.md +128 -109
  28. package/docs/reference/theme-pages.md +31 -6
  29. package/e2e.mjs +140 -28
  30. package/engine/actor-compiler.mjs +22 -31
  31. package/engine/address-charset.mjs +4 -17
  32. package/engine/address-fields.mjs +1 -1
  33. package/engine/address-render.mjs +12 -1
  34. package/engine/address-values.mjs +6 -0
  35. package/engine/address.mjs +46 -33
  36. package/engine/art-fields.mjs +30 -5
  37. package/engine/art-slots.mjs +10 -1
  38. package/engine/asset-bindings.mjs +12 -1
  39. package/engine/base-compiler.mjs +34 -65
  40. package/engine/being-age.mjs +2 -7
  41. package/engine/being-measurements.mjs +12 -1
  42. package/engine/calendar-artifacts.mjs +6 -0
  43. package/engine/calendar-choice.mjs +12 -1
  44. package/engine/calendar-format.mjs +12 -1
  45. package/engine/calendar-human.mjs +12 -1
  46. package/engine/compile-corpus.mjs +3 -1
  47. package/engine/content-address.mjs +9 -12
  48. package/engine/content-alerts.mjs +13 -1
  49. package/engine/content-blocks.mjs +14 -3
  50. package/engine/content-embeds.mjs +11 -3
  51. package/engine/content-figures.mjs +9 -0
  52. package/engine/content-footnotes.mjs +12 -1
  53. package/engine/content-format-check.mjs +11 -262
  54. package/engine/content-format.yaml +5 -2
  55. package/engine/content-images.mjs +3 -0
  56. package/engine/content-index.mjs +443 -20
  57. package/engine/content-links.mjs +29 -35
  58. package/engine/content-lint.mjs +2 -3
  59. package/engine/content-poetry.mjs +12 -1
  60. package/engine/content-slug.mjs +2 -14
  61. package/engine/content-spans.mjs +13 -1
  62. package/engine/content-tables.mjs +12 -1
  63. package/engine/culture-choice.mjs +210 -13
  64. package/engine/data-keys.mjs +19 -16
  65. package/engine/date-conversion.mjs +12 -1
  66. package/engine/declaration-check.mjs +9 -0
  67. package/engine/derived-fields.mjs +6 -7
  68. package/engine/diagnostics.mjs +34 -4
  69. package/engine/docs-checks.mjs +9 -0
  70. package/engine/document-subtypes.mjs +47 -34
  71. package/engine/event-views.mjs +139 -14
  72. package/engine/exported-scene.mjs +92 -28
  73. package/engine/extension-attributes.mjs +12 -1
  74. package/engine/field-spec.mjs +11 -102
  75. package/engine/folder-notes.mjs +0 -64
  76. package/engine/foreign-catalog.mjs +2 -1
  77. package/engine/format-generated.mjs +9 -0
  78. package/engine/foundry-entries.mjs +10 -28
  79. package/engine/frontmatter-lint.mjs +83 -392
  80. package/engine/frontmatter.mjs +20 -70
  81. package/engine/generate.mjs +10 -15
  82. package/engine/heading-attributes.mjs +12 -1
  83. package/engine/helpers.mjs +41 -102
  84. package/engine/holdings.mjs +1 -1
  85. package/engine/homepage.mjs +3 -10
  86. package/engine/ids.mjs +1 -167
  87. package/engine/index-records.mjs +6 -4
  88. package/engine/index.mjs +0 -3
  89. package/engine/infobox-render.mjs +15 -3
  90. package/engine/infobox.mjs +13 -14
  91. package/engine/item-compiler.mjs +4 -10
  92. package/engine/item-docs.mjs +3 -5
  93. package/engine/item-registry.mjs +2 -10
  94. package/engine/journals.mjs +1 -5
  95. package/engine/literature-notes.mjs +4 -12
  96. package/engine/macros.mjs +1 -2
  97. package/engine/map-graphviz.mjs +12 -1
  98. package/engine/markdown-expressions.mjs +12 -1
  99. package/engine/markdownlint-run.mjs +567 -0
  100. package/engine/metadata-index.mjs +42 -12
  101. package/engine/note-addresses.mjs +143 -35
  102. package/engine/note-claims.mjs +14 -28
  103. package/engine/note-dates.mjs +3 -3
  104. package/engine/note-frontmatter.mjs +6 -0
  105. package/engine/note-ids.mjs +8 -14
  106. package/engine/note-package.mjs +8 -25
  107. package/engine/note-state.mjs +1 -3
  108. package/engine/note-vocabulary.mjs +148 -84
  109. package/engine/number-words.mjs +12 -1
  110. package/engine/pack-config.mjs +15 -25
  111. package/engine/pack-router.mjs +10 -16
  112. package/engine/pathnames.mjs +8 -22
  113. package/engine/pdf-build.mjs +773 -121
  114. package/engine/pdf-coverage.mjs +188 -0
  115. package/engine/pdf-index.mjs +54 -7
  116. package/engine/pdf-pages.mjs +247 -0
  117. package/engine/pdf-render.mjs +114 -4
  118. package/engine/pdf-toc.mjs +29 -32
  119. package/engine/project-init.mjs +46 -6
  120. package/engine/prose-config.mjs +1 -1
  121. package/engine/prose-lint.mjs +52 -88
  122. package/engine/readability-lint.mjs +9 -0
  123. package/engine/reckoning-markers.mjs +12 -1
  124. package/engine/reference-targets.mjs +7 -0
  125. package/engine/runtime-only-fields.mjs +5 -8
  126. package/engine/scene-levels.mjs +2 -2
  127. package/engine/scenes.mjs +1 -1
  128. package/engine/shortcode-references.mjs +7 -0
  129. package/engine/site-build.mjs +30 -16
  130. package/engine/site-index.mjs +10 -16
  131. package/engine/site-preview.mjs +6 -0
  132. package/engine/site-root.mjs +8 -1
  133. package/engine/social-tie-terms.mjs +12 -1
  134. package/engine/social-ties.mjs +3 -0
  135. package/engine/sql-tables.mjs +56 -20
  136. package/engine/standing-terms.mjs +11 -1
  137. package/engine/standings.mjs +11 -27
  138. package/engine/system-block.mjs +39 -141
  139. package/engine/system-vocabulary.mjs +6 -0
  140. package/engine/walk-files.mjs +69 -0
  141. package/engine/web-wikilinks.mjs +23 -3
  142. package/engine/wikilink-syntax.mjs +32 -21
  143. package/engine/wikilinks.mjs +27 -16
  144. package/githooks/attribution.mjs +8 -0
  145. package/githooks/commit-message.mjs +9 -0
  146. package/hm3/actors.mjs +9 -48
  147. package/hm3/default-item-art.mjs +9 -9
  148. package/hm3/item-fields.mjs +2 -9
  149. package/hm3/template-priority.mjs +6 -7
  150. package/hugo-theme/data/banner-kinds.yaml +78 -0
  151. package/hugo-theme/data/banners.yaml +2 -3
  152. package/hugo-theme/layouts/404.html +3 -3
  153. package/hugo-theme/layouts/partials/banner-url.html +31 -7
  154. package/hugo-theme/layouts/partials/hero-banner.html +13 -14
  155. package/hugo-theme/layouts/partials/related.html +1 -1
  156. package/hugo-theme/layouts/partials/warn-missing-banner.html +1 -1
  157. package/hugo-theme/static/css/style.css +1 -1
  158. package/labels.mjs +2 -2
  159. package/manifest.mjs +11 -4
  160. package/markdownlint-config.mjs +1 -1
  161. package/package.json +6 -3
  162. package/release.mjs +21 -14
  163. package/sohl/actors.mjs +16 -27
  164. package/sohl/default-item-art.mjs +3 -3
  165. package/sohl/document-subtypes.mjs +1 -16
  166. package/sohl/infobox.mjs +2 -3
  167. package/sohl/item-builders.mjs +5 -8
  168. package/sohl/item-fields.mjs +5 -35
  169. package/sohl/items.mjs +0 -4
  170. package/sohl/kb-passes.mjs +3 -3
  171. package/types/container.d.mts +116 -5
  172. package/types/content-config.d.mts +83 -35
  173. package/types/deploy.d.mts +42 -6
  174. package/types/e2e.d.mts +35 -3
  175. package/types/engine/address-charset.d.mts +4 -17
  176. package/types/engine/address.d.mts +17 -8
  177. package/types/engine/art-fields.d.mts +7 -4
  178. package/types/engine/art-slots.d.mts +6 -0
  179. package/types/engine/base-compiler.d.mts +15 -17
  180. package/types/engine/content-address.d.mts +9 -12
  181. package/types/engine/content-embeds.d.mts +4 -2
  182. package/types/engine/content-format-check.d.mts +0 -64
  183. package/types/engine/content-index.d.mts +82 -3
  184. package/types/engine/content-links.d.mts +2 -4
  185. package/types/engine/culture-choice.d.mts +51 -8
  186. package/types/engine/data-keys.d.mts +2 -2
  187. package/types/engine/derived-fields.d.mts +1 -2
  188. package/types/engine/diagnostics.d.mts +27 -4
  189. package/types/engine/document-subtypes.d.mts +12 -31
  190. package/types/engine/event-views.d.mts +14 -2
  191. package/types/engine/exported-scene.d.mts +3 -2
  192. package/types/engine/field-spec.d.mts +13 -122
  193. package/types/engine/folder-notes.d.mts +0 -39
  194. package/types/engine/foreign-catalog.d.mts +2 -1
  195. package/types/engine/foundry-entries.d.mts +4 -5
  196. package/types/engine/frontmatter-lint.d.mts +2 -2
  197. package/types/engine/frontmatter.d.mts +19 -45
  198. package/types/engine/generate.d.mts +1 -1
  199. package/types/engine/helpers.d.mts +25 -42
  200. package/types/engine/homepage.d.mts +3 -10
  201. package/types/engine/ids.d.mts +0 -114
  202. package/types/engine/index-records.d.mts +6 -4
  203. package/types/engine/index.d.mts +0 -1
  204. package/types/engine/infobox-render.d.mts +7 -1
  205. package/types/engine/infobox.d.mts +3 -2
  206. package/types/engine/item-docs.d.mts +1 -1
  207. package/types/engine/item-registry.d.mts +2 -4
  208. package/types/engine/literature-notes.d.mts +1 -1
  209. package/types/engine/markdownlint-run.d.mts +40 -0
  210. package/types/engine/metadata-index.d.mts +9 -1
  211. package/types/engine/note-addresses.d.mts +27 -2
  212. package/types/engine/note-claims.d.mts +3 -5
  213. package/types/engine/note-ids.d.mts +2 -4
  214. package/types/engine/note-package.d.mts +1 -1
  215. package/types/engine/note-state.d.mts +1 -3
  216. package/types/engine/note-vocabulary.d.mts +28 -21
  217. package/types/engine/pack-config.d.mts +2 -6
  218. package/types/engine/pack-router.d.mts +4 -4
  219. package/types/engine/pathnames.d.mts +3 -3
  220. package/types/engine/pdf-build.d.mts +98 -12
  221. package/types/engine/pdf-coverage.d.mts +60 -0
  222. package/types/engine/pdf-index.d.mts +34 -5
  223. package/types/engine/pdf-pages.d.mts +143 -0
  224. package/types/engine/pdf-render.d.mts +45 -1
  225. package/types/engine/pdf-toc.d.mts +12 -14
  226. package/types/engine/prose-config.d.mts +1 -1
  227. package/types/engine/prose-lint.d.mts +11 -10
  228. package/types/engine/runtime-only-fields.d.mts +2 -2
  229. package/types/engine/site-build.d.mts +9 -5
  230. package/types/engine/site-index.d.mts +4 -5
  231. package/types/engine/site-root.d.mts +1 -1
  232. package/types/engine/sql-tables.d.mts +31 -2
  233. package/types/engine/standing-terms.d.mts +2 -1
  234. package/types/engine/standings.d.mts +7 -9
  235. package/types/engine/system-block.d.mts +10 -44
  236. package/types/engine/walk-files.d.mts +15 -0
  237. package/types/engine/wikilink-syntax.d.mts +18 -19
  238. package/types/hm3/actors.d.mts +1 -2
  239. package/types/hm3/item-fields.d.mts +2 -4
  240. package/types/hm3/template-priority.d.mts +6 -7
  241. package/types/manifest.d.mts +3 -2
  242. package/types/release.d.mts +8 -7
  243. package/types/sohl/actors.d.mts +2 -1
  244. package/types/sohl/document-subtypes.d.mts +1 -2
  245. package/types/sohl/item-fields.d.mts +3 -7
  246. package/engine/retired-fields.mjs +0 -631
  247. package/types/engine/retired-fields.d.mts +0 -354
@@ -104,6 +104,11 @@ A note profile begins with `Name`. For a being with a top-level `subType`, it
104
104
  follows with `Type: Character`, `Type: NPC`, or `Type: Creature`. A being with
105
105
  no subtype has no `Type` row.
106
106
 
107
+ The `Culture` row shows the note's resolved culture, so a place or an
108
+ affiliation shows the culture it inherits. A culture note has no `Culture` row:
109
+ its resolved culture is its own Address, whether it leaves the field out or
110
+ names itself. See [Culture in the index](#culture-in-the-index).
111
+
107
112
  **Embedded models link to documentation in an infobox.** The model Address
108
113
  continues to identify the native Item used for compilation. Its displayed link
109
114
  uses that same package, type and Shortcode under `note`, which names the
@@ -135,7 +140,7 @@ or it is a **source system's own detail**, which no surface's box shows:
135
140
  | a calendar's `months`, `weekdays`, `seasons`, `namedDays`, `eras`, `formats` | calendar machinery, not a summary row |
136
141
  | a map's `scene`, `fixup` | canvas machinery, not a summary row |
137
142
  | `harnworld` | HârnWorld source detail, which no surface's box shows |
138
- | the art slots | an image, which rule 2 keeps out of the box |
143
+ | the art slots, and a map's `thumbnail` | an image, which rule 2 keeps out of the box |
139
144
 
140
145
  **A field whose value holds maps is drawn by what its declaration says the
141
146
  maps are**, and a map is never set as text:
@@ -735,14 +740,14 @@ no SoHL form, so its SoHL column is. Nothing else varies — which is why these
735
740
  rows are worth stating once: repeated sixteen times they buried the differences
736
741
  that matter.
737
742
 
738
- **`data.icon` maps onto a document that has one, and not every type's does.**
739
- The row is shared because the key is legal on every note whatever its type, not
740
- because every document carries artwork: `doc`, `place`, `lore` and `scenario`
741
- compile into a JournalEntry, which has no image of any kind, and a `folder`
742
- compiles into a Foundry `Folder`, which has none either. A `homepage` compiles
743
- into no compendium document at all. On any of those the mapping has no
744
- destination, so the resolved value is simply dropped — the note validates, the
745
- tree compiles, and the value goes nowhere.
743
+ **`data.icon` is declared only for the types whose document carries art.**
744
+ The row is not shared: `being`, every Item type, `macro` and `bundle` compile
745
+ into a document with an image, and only those declare `icon`. `doc`, `place`,
746
+ `lore` and `scenario` compile into a JournalEntry, which has no image of any
747
+ kind; a `map` compiles into a Scene whose art is its own; a `folder` compiles
748
+ into a Foundry `Folder`; and a `homepage` compiles into no compendium document
749
+ at all. On any of those `icon` is not a declared property, so the note is
750
+ refused at the key's own line rather than compiled with the value dropped.
746
751
 
747
752
  **What a note writes is an address; what a document carries is a path.**
748
753
  `data.icon` is an `Address` defaulting to type `icon`, so a being writes
@@ -751,20 +756,13 @@ file the owning package ships and writes the resulting path into `img`. The two
751
756
  sides of this row are therefore in two different currencies, which is why the
752
757
  row reads `data.icon → img` rather than naming one thing twice.
753
758
 
754
- Since a dropped value looks exactly like a value never written, the frontmatter
755
- lint **reports it**: an art key authored on a type whose passes emit none is a
756
- warning naming the note and the key. A warning rather than an error, because the
757
- note still compiles correctly and the key is not certainly unwanted — a note's
758
- top level is the generated page's front matter as well, so a site template may
759
- read there what no document carries. `null` is never reported: that is the
760
- blessed way to say "this note names no art", and on a type with no art it is a
761
- true and harmless thing to say.
762
-
763
- Which types those are is **not a list**. It is asked of the passes: a type routes
764
- to a document, a document to the pass that compiles it, and each pass declares
765
- the art it writes (`emitsArt`). A second table of iconless types would be a table
766
- free to drift from what is actually emitted, which is the defect rather than the
767
- check.
759
+ Which types declare `icon` is **not a list**. It is asked of the passes: a type
760
+ routes to a document, a document to the pass that compiles it, and each pass
761
+ declares the art it writes (`emitsArt`). A pass that emits `icon` is a type that
762
+ declares the key. Any other art key authored where no pass emits it, such as a
763
+ `tokenIcon` on an Item type, is a warning naming the note and the key, because
764
+ the note still compiles correctly and a site template may read there what no
765
+ document carries. `null` is never reported.
768
766
 
769
767
  #### The art slots
770
768
 
@@ -774,11 +772,14 @@ exactly as `seat` declares `place` — a bare shortcode takes its type from the
774
772
  declaration, and qualification climbs the same short-form ladder every other
775
773
  link uses.
776
774
 
777
- | field | default type | accepts | resolves into | on |
778
- | ----------- | ------------ | --------------- | ----------------------------- | ----------------------------------------- |
779
- | `icon` | `icon` | `icon`, `image` | `document.img` | every Actor/Item type, and embedded items |
780
- | `tokenIcon` | `icon` | `icon`, `image` | `prototypeToken.texture.src` | Actor types |
781
- | `banner` | `image` | `icon`, `image` | the site hero image, 1792×768 | any note; not a Foundry field |
775
+ | field | default type | accepts | resolves into | on |
776
+ | ----------- | ------------ | --------------- | ----------------------------- | ------------------------------------------- |
777
+ | `icon` | `icon` | `icon`, `image` | `document.img` | `being`, every Item type, `macro`, `bundle` |
778
+ | `tokenIcon` | `icon` | `icon`, `image` | `prototypeToken.texture.src` | Actor types |
779
+ | `banner` | `image` | `icon`, `image` | the site hero image, 1792×768 | any note; not a Foundry field |
780
+
781
+ A map's `thumbnail` is not an art slot. It is a map field resolved into the
782
+ Scene's `thumb` as a fixup — see [the thumbnail](#the-thumbnail).
782
783
 
783
784
  Every slot is authored under `data:`:
784
785
 
@@ -788,6 +789,11 @@ data:
788
789
  banner: packagebuild-none-image-skillbnr
789
790
  ```
790
791
 
792
+ **An empty value is an error.** `icon: ""`, `tokenIcon: ""` and `banner: ""`
793
+ are each a finding at the key, in `data:` or in a system block. A slot names an
794
+ image, or the key is removed and the document takes its type's default;
795
+ `icon: null` says the same as removing it.
796
+
791
797
  **Every slot accepts a second type beyond the one it defaults to**, and the two
792
798
  defaults pair with the same accepted set: `icon` and `tokenIcon` default to
793
799
  `icon`, `banner` defaults to `image`, and every slot accepts `icon` **and**
@@ -815,16 +821,15 @@ are, so a key legal everywhere is stated once here rather than repeated in
815
821
  twenty-five tables where the one that was mistyped would be the one nobody
816
822
  noticed:
817
823
 
818
- | shared `data` property | Values | Description |
819
- | ---------------------- | --------------------- | -------------------------------------------------------------------- |
820
- | `id` | `string` | A pinned document identity. |
821
- | `pack` | `string` | The shared compendium route. |
822
- | `packFolder` | `Address` or pack map | The shared compendium folder Address. |
823
- | `harnworld` | `{ realm?, ritual? }` | HârnWorld source details. |
824
- | `harnworld.realm` | `string` | The HârnWorld realm the subject belongs to. |
825
- | `harnworld.ritual` | `string[]` | The HârnWorld religions the subject observes. |
826
- | `icon` | `Address` | The document's profile art — an `icon` address, resolved into `img`. |
827
- | `banner` | `Address` | The page's hero image — an `image` address, cut to 1792×768. |
824
+ | shared `data` property | Values | Description |
825
+ | ---------------------- | --------------------- | ------------------------------------------------------------ |
826
+ | `id` | `string` | A pinned document identity. |
827
+ | `pack` | `string` | The shared compendium route. |
828
+ | `packFolder` | `Address` or pack map | The shared compendium folder Address. |
829
+ | `harnworld` | `{ realm?, ritual? }` | HârnWorld source details. |
830
+ | `harnworld.realm` | `string` | The HârnWorld realm the subject belongs to. |
831
+ | `harnworld.ritual` | `string[]` | The HârnWorld religions the subject observes. |
832
+ | `banner` | `Address` | The page's hero image — an `image` address, cut to 1792×768. |
828
833
 
829
834
  `data.harnworld` takes two keys and nothing else: `realm`, a string naming the
830
835
  HârnWorld realm the subject belongs to, and `ritual`, a list of strings naming
@@ -837,10 +842,9 @@ data:
837
842
  ritual: [Larani, Peoni]
838
843
  ```
839
844
 
840
- `icon` is legal everywhere because most types compile into a document that
841
- carries one; where a type's passes emit none, the lint says so and the value is
842
- left alone, because a page template may still read it. `banner` reaches no
843
- compiled document at all, and a page is what every note publishes.
845
+ `icon` is declared only for the types whose document carries art; see
846
+ the art slots below. `banner` reaches no compiled document at
847
+ all, and a page is what every note publishes, so it is legal everywhere.
844
848
 
845
849
  `tokenIcon` is narrower, and the Actor types' own tables name it. A map takes
846
850
  no art slot: its Scene is exported from Foundry and names its own art.
@@ -870,12 +874,9 @@ header carries. `data.tokenIcon` is what a token on the canvas wears. Both are
870
874
  addresses and both resolve the same way; only the destination differs. An Item
871
875
  has one piece of art, so this row applies to actor types alone.
872
876
 
873
- **A `data:` source is still read at the top level, for now.** `data:` did
874
- not invent the facts it holds — it gathered them out of the top level — so every
875
- key it collected has a **pre-`data:` spelling** that is read after the declared
876
- one and reported as retiring. This is the shared level's counterpart to the
877
- in-block `<system>.<key>` retirement, and the two are separate: a note may have
878
- moved one and not the other.
877
+ **A `data:` source is authored in `data:` alone.** The same key written at the
878
+ top level is outside the closed top-level region, and written inside a system
879
+ block it is reported at its own line with the `data:` key it belongs to.
879
880
 
880
881
  **No image key is one of those.** A top-level `img:` is not authored at all:
881
882
  `data.icon` is a different key holding a different kind of value, and a note
@@ -1006,10 +1007,10 @@ nothing of (`/systems/dnd5e/icons/spell.webp`).
1006
1007
  replacement. It is a Foundry address written where an ownership statement
1007
1008
  belongs: it resolves for Foundry and for nothing else, because neither the
1008
1009
  website nor the book has any such directory. `systems/sohl/assets/ui/logo.webp`
1009
- is written `sohl/assets/ui/logo.webp`, and `systems/hm3/images/svg/sword.svg` —
1010
+ is written `sohl/assets/ui/logo.webp`, and `systems/hm3/assets/icons/sword.svg` —
1010
1011
  where the package serves its pictures from its own root — is written
1011
- `hm3/assets/images/svg/sword.svg`, because `assets/` is where a package's files
1012
- sit in every derived form.
1012
+ `hm3/assets/icons/sword.svg`, because `assets/` is where a package's files sit
1013
+ in every derived form.
1013
1014
 
1014
1015
  `worlds/` is not a Foundry root for this purpose. A package may not ship files
1015
1016
  out of a world, so a note writing one has made a different mistake and gets the
@@ -1052,8 +1053,11 @@ writes `banner: skillbnr` and a note borrowing another package's plate writes
1052
1053
  owning package, and each surface joins the suffix onto its own root — the site
1053
1054
  onto `site.assets`, the book onto the directory it stages banners into.
1054
1055
 
1055
- **`banner: null` is how a note says it wants no plate.** It is a true statement
1056
- on any type, and the frontmatter lint never reports it.
1056
+ **`banner: none` is how a note declines its hero image.** The word is never
1057
+ read as an Address: it resolves to no image, the page publishes a blank
1058
+ `data.banner`, and the theme draws the band with no picture and no warning. An
1059
+ Address that names no image is a finding at its own line and column, and `""`
1060
+ is an error. `banner: null` names no art and the page takes its type's default.
1057
1061
 
1058
1062
  #### The pack a note compiles into
1059
1063
 
@@ -1147,20 +1151,9 @@ which of several competing templates a player is actually offered.
1147
1151
  The shared mapping table above names it `data.templatePriority`, targeting
1148
1152
  `system.templatePriority` in SoHL and `flags.hm3.templatePriority` in HM3.
1149
1153
 
1150
- > **`archetype` is the retiring spelling.** It is still read, in the `sohl:`
1151
- > block and at the top level, so a tree sweeps on its own schedule
1152
- > — but the frontmatter linter refuses it, and
1153
- > what is compiled and emitted is `templatePriority` on all three sides.
1154
- >
1155
- > It is more than a rename, and the collision is **already live** rather than
1156
- > pending: `archetypes` is specified above as the _sort_ a character is, and a
1157
- > being's row declares it. So a number deciding which template wins and a list of
1158
- > what sort of character this is would otherwise be distinguished **only by a
1159
- > plural `s`**.
1160
- >
1161
- > A note carrying both spellings with **different** values is refused rather than
1162
- > resolved quietly — `templatePriority: null` and `archetype: 0` say opposite
1163
- > things, and picking either silently would decide it on the author's behalf.
1154
+ > `archetypes` is a different field: specified above as the _sort_ a character
1155
+ > is, and declared by a being's row. The priority that decides which template
1156
+ > wins is always `templatePriority`, on all three sides.
1164
1157
 
1165
1158
  ```yaml
1166
1159
  data:
@@ -1311,8 +1304,7 @@ the journals pack having to declare anything. A folder nothing references
1311
1304
  materialises nowhere.
1312
1305
 
1313
1306
  One folder note and one Address supply every pack that references it.
1314
- `data.packFolder` names a folder by Address. A top-level `folder:` key is
1315
- refused with a finding that names `data.packFolder` and the authored line.
1307
+ `data.packFolder` names a folder by Address.
1316
1308
 
1317
1309
  #### The knowledgebase category
1318
1310
 
@@ -1472,9 +1464,25 @@ note, while `[[none-macro-autoattack|Automated Attack]]` names the Foundry Macro
1472
1464
 
1473
1465
  **A complete Address is lowercase throughout.** Every segment — package,
1474
1466
  system, type and shortcode alike — contains only lowercase ASCII letters and
1475
- digits (`^[a-z0-9]+$`). Declared Address fields reject uppercase segments.
1476
- A model reference accepts mixed-case Shortcodes and resolves their lowercase
1477
- Address identity; this does not change a native embedded item's Shortcode.
1467
+ digits (`^[a-z0-9]+$`). Every Address is validated as written, whether a field
1468
+ declares it or a wikilink or embed writes it, so a capital in any segment, the
1469
+ shortcode included, is an error at the value or at the link. Nothing is
1470
+ lowercased on the author's behalf. Where the lowercase spelling is a declared
1471
+ shortcode, the error names it:
1472
+
1473
+ ```text
1474
+ Beings/Guard.md:8:15: error: `sohl.items.0.model` shortcode `LLeg` must be lowercase; the declared shortcode is `lleg`
1475
+ ```
1476
+
1477
+ A link is reported the same way, at its own line and column:
1478
+
1479
+ ```text
1480
+ Skills/Jumping.md:10:5: error: address [[skill-Clmb]] shortcode `Clmb` must be lowercase; the declared shortcode is `clmb`
1481
+ ```
1482
+
1483
+ Each such reference in a note's frontmatter is reported at its own position,
1484
+ and the note is left out of the index, so the build fails rather than
1485
+ compiling without it.
1478
1486
 
1479
1487
  **Parsing is positional counting from the right, and nothing else.** Every
1480
1488
  segment is lowercase alphanumeric — shortcodes, **types** and **subTypes** are
@@ -1546,18 +1554,18 @@ A `draft` note exists, resolves, compiles and publishes; a link to it renders
1546
1554
  visibly marked. An Address landing nowhere is a typo or an omission and earns
1547
1555
  an error.
1548
1556
 
1549
- There are six ways a link can fail, and each is one **error** with one message
1550
- wherever it is met:
1557
+ Each way a link can fail is one **error** with one message wherever it is met:
1551
1558
 
1552
- | finding | what it means | the fix |
1553
- | ---------------- | --------------------------------------------- | ------------------------------------------------------ |
1554
- | `unlabelled` | no `\|`, so the link addresses nothing | write `[[type-shortcode\|Text]]` |
1555
- | `not-an-address` | labelled, but the target is not an address | write the address, not the name |
1556
- | `not-lowercase` | any segment of the address is capitalised | lowercase it — every segment is lowercase |
1557
- | `unknown-type` | qualified, but names no type this build knows | correct the type segment |
1558
- | `unresolved` | parses as an address; nothing publishes it | fix the shortcode, or qualify to reach another package |
1559
- | `ambiguous` | _unreachable; kept for the manifest_ | — |
1560
- | `unknown-anchor` | the address resolves; the `#section` does not | correct the anchor |
1559
+ | finding | what it means | the fix |
1560
+ | ------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
1561
+ | `unlabelled` | no `\|`, so the link addresses nothing | write `[[type-shortcode\|Text]]` |
1562
+ | `not-an-address` | labelled, but the target is not an address | write the address, not the name |
1563
+ | `not-lowercase` | a package, system or type segment is capitalised | lowercase it — every segment is lowercase |
1564
+ | `shortcode-not-lowercase` | the shortcode is capitalised; the message names the declared shortcode when the lowercase spelling is one | lowercase it — a shortcode is lowercase |
1565
+ | `unknown-type` | qualified, but names no type this build knows | correct the type segment |
1566
+ | `unresolved` | parses as an address; nothing publishes it | fix the shortcode, or qualify to reach another package |
1567
+ | `ambiguous` | _unreachable; kept for the manifest_ | — |
1568
+ | `unknown-anchor` | the address resolves; the `#section` does not | correct the anchor |
1561
1569
 
1562
1570
  `ambiguous` is a reserved reason in the result vocabulary. An omitted segment
1563
1571
  defaults rather than wildcarding, so each written target expands to one
@@ -1590,7 +1598,7 @@ prose: a field value is a reference something else will compile against, and it
1590
1598
  should say exactly what it points at.
1591
1599
 
1592
1600
  **A field can supply the type.** A field naming one kind of note declares its
1593
- default — `seat` a `place`, `parents` an `affiliation`, `stations` a `lore` — so
1601
+ default — `seat` a `place`, `parents` an `affiliation`, `lore` a `lore` — so
1594
1602
  the type segment defaults from the declaration and a bare shortcode is the
1595
1603
  ordinary case, not an abbreviation of one. That is why the examples above read
1596
1604
  `tashal` rather than `place-tashal`: the shorter form carries the same
@@ -1747,7 +1755,6 @@ is always the same: write `[[type-shortcode|Text]]`.
1747
1755
 
1748
1756
  The bare form does not resolve by display name or alias. `name.aliases` is
1749
1757
  indexed for search and completion, while an authored link names an Address.
1750
- A top-level `aliases:` key is refused.
1751
1758
 
1752
1759
  Requiring the label is also what makes positional parsing safe. Note names
1753
1760
  contain hyphens — `Grukar-ahk` is a name, not a `Grukar` of type `ahk` — so a
@@ -1755,7 +1762,7 @@ target that does not parse as an address is reported as one that does not, rathe
1755
1762
  than split at an arbitrary place or quietly looked up somewhere else.
1756
1763
 
1757
1764
  The **empty** label is not a way of writing no label. It says _address this
1758
- target, and show whatever it calls itself_ — so a note renamed later takes its new
1765
+ target, and show whatever it calls itself_ — so a note whose name changes takes its new
1759
1766
  name at every citation with no link edited. `[[x|]]` is labelled; `[[x]]` is not.
1760
1767
 
1761
1768
  It is the label that is required, not a target: the anchor-only form above
@@ -2611,7 +2618,9 @@ documentation entry, so `type = 'miscgear'` selects the items and never their
2611
2618
  journals. A nested field is addressed exactly as a note authors it —
2612
2619
  `sohl.weight`, `name.full`, `file.path` — because the index is read as JSON and
2613
2620
  every nested object is inferred as a struct. A field a note type does not carry
2614
- reads `NULL` rather than failing.
2621
+ reads `NULL` rather than failing. `data.culture` is the one field that reads as
2622
+ a derived value: the note's own culture, or the one it inherits. See
2623
+ [Culture in the index](#culture-in-the-index).
2615
2624
 
2616
2625
  **`notes` leaves out stubs; `entries` does not.** Both carry a derived `state`
2617
2626
  column — `stub`, `draft` or `full` — and `notes` is exactly
@@ -2706,6 +2715,136 @@ table would otherwise need: the authored `ORDER BY` decides the section order to
2706
2715
  **Beware `packFolder`.** It is a note's _pack_ folder, not its directory — the
2707
2716
  directory is `file.folder`.
2708
2717
 
2718
+ ##### Culture in the index
2719
+
2720
+ `data.culture` in the index is the note's **resolved** culture, which is its
2721
+ authored value where it states one and a derived value where it does not. The
2722
+ resolved value is written into the index as a canonical Address string, such as
2723
+ `reedflats-note-lore-reedfolk`, so a table selects by it directly.
2724
+
2725
+ The culture of a note, in order:
2726
+
2727
+ 1. Its own `data.culture`, when it states one.
2728
+ 2. For a `lore` note of `subType: culture`: itself. A culture note may leave the
2729
+ field out or state it as its own Address, and its resolved culture is its own
2730
+ Address either way.
2731
+ 3. For a `place` or an `affiliation`: the resolved cultures of its
2732
+ `data.parents` — places through their parent places, affiliations through
2733
+ their parent affiliations — found recursively, so a culture stated three
2734
+ levels up reaches the bottom.
2735
+ 4. For an affiliation of `subType: polity` that still has none: the resolved
2736
+ cultures of its places, pooled as one set — its `data.seat` and every place
2737
+ whose `data.government` names the polity.
2738
+ 5. Otherwise none. A parent or place that resolves to nothing contributes
2739
+ nothing.
2740
+
2741
+ A `being` and a `doc` of `subType: settingguide` read as the value they state and
2742
+ nothing more; every other `lore` note reads as its own value, and every other
2743
+ note type reads as none.
2744
+
2745
+ Sources that agree give that culture. Sources that name different cultures, on a
2746
+ note that states none of its own, are an **error**, and that note gets no
2747
+ culture. The error is located at the `parents:` key; for the pooled places of a
2748
+ polity it is located at the `seat:` key, or at the `data:` line when the polity
2749
+ states no seat. Stating `data.culture` on the note settles the matter, because
2750
+ an own value is never compared with its sources. A note whose parent raised the
2751
+ error reads that parent as resolving to nothing, so one disagreement is reported
2752
+ once, at the note where the sources meet.
2753
+
2754
+ Here a region states a culture and the places inside it carry it down:
2755
+
2756
+ ```markdown
2757
+ ---
2758
+ shortcode: reedflats
2759
+ name: { full: Reed Flats }
2760
+ type: place
2761
+ subType: region
2762
+ description: A marsh region.
2763
+ data:
2764
+ culture: lore-reedfolk
2765
+ ---
2766
+ ```
2767
+
2768
+ ```markdown
2769
+ ---
2770
+ shortcode: saltmarsh
2771
+ name: { full: Saltmarsh }
2772
+ type: place
2773
+ subType: district
2774
+ description: A salt marsh.
2775
+ data:
2776
+ parents: [place-reedflats]
2777
+ ---
2778
+ ```
2779
+
2780
+ ```markdown
2781
+ ---
2782
+ shortcode: eelwick
2783
+ name: { full: Eelwick }
2784
+ type: place
2785
+ subType: settlement
2786
+ description: A fishing village.
2787
+ data:
2788
+ parents: [place-saltmarsh]
2789
+ ---
2790
+ ```
2791
+
2792
+ `saltmarsh` and `eelwick` state no culture and index as `reedflats-note-lore-reedfolk`.
2793
+ Because the index carries the resolved value, one query selects the region and
2794
+ everything beneath it, and `IS NULL` selects the notes that have no culture:
2795
+
2796
+ ````markdown
2797
+ ```sql
2798
+ SELECT address.slug AS _ref, name.full AS "Name"
2799
+ FROM notes
2800
+ WHERE type = 'place' AND data.culture = 'reedflats-note-lore-reedfolk'
2801
+ ORDER BY name.full
2802
+ ```
2803
+ ````
2804
+
2805
+ ````markdown
2806
+ ```sql
2807
+ SELECT address.slug AS _ref, name.full AS "Name"
2808
+ FROM notes
2809
+ WHERE type = 'place' AND data.culture IS NULL
2810
+ ```
2811
+ ````
2812
+
2813
+ A place whose parents sit in different cultures must say which it belongs to.
2814
+ With a second region `stonecrest` that states `culture: lore-hillfolk`, a place
2815
+ on the border between the two:
2816
+
2817
+ ```markdown
2818
+ ---
2819
+ shortcode: causeway
2820
+ name: { full: The Causeway }
2821
+ type: place
2822
+ subType: site
2823
+ description: A road between marsh and ridge.
2824
+ data:
2825
+ parents: [place-reedflats, place-stonecrest]
2826
+ ---
2827
+ ```
2828
+
2829
+ fails the content-index build with one located error per conflict:
2830
+
2831
+ ```text
2832
+ assets/content/Places/Causeway.md:8:3: error: data.parents resolve to different cultures (reedflats-note-lore-reedfolk via reedflats, reedflats-note-lore-hillfolk via stonecrest); state data.culture
2833
+ ```
2834
+
2835
+ The conflict fails the content-index build, and every other command that reads
2836
+ the index without collecting problems, listing every conflict in one run. Adding
2837
+ `culture: lore-reedfolk` under `data:` on `causeway` removes it. A fetched
2838
+ dependency's index carries each note's resolved culture, so a parent in another
2839
+ package contributes the culture that package resolved for it.
2840
+
2841
+ The index record carries the resolved culture, and every pass that reads the
2842
+ record reads it too: lint, the pack compile and the site build all see a
2843
+ place's inherited culture, and a culture note's own Address, as its
2844
+ `data.culture`. A resolved value always passes the checks that scope the field,
2845
+ because a culture note resolves to itself and an inherited value names a culture
2846
+ note. The infobox shows a note's resolved culture, except a culture note's own.
2847
+
2709
2848
  ##### Reading another package's notes
2710
2849
 
2711
2850
  Each package this one **depends on** is attached as a schema named after it, so
@@ -3352,35 +3491,35 @@ Generates a living (or undead, or spirit) being.
3352
3491
 
3353
3492
  See [a being's subtype describes the subject](#a-beings-subtype-describes-the-subject).
3354
3493
 
3355
- | `data` property | Values | Description |
3356
- | ------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
3357
- | `tokenIcon` | `Address` | What a token on the canvas wears — an `icon` address; defaults to `icon` |
3358
- | `templatePriority` | `number` | Template priority, _null_ = not a template |
3359
- | `archetypes` | `Archetype[]` | What sort of character this is. **Always an array** — character and npc require at least one; creature may omit it or write `[]`; `null` is an error. |
3360
- | `occupation` | `string` | Name of the character's occupation |
3361
- | `stations` | `Address[]` | Name of the stations the character belongs to |
3362
- | `lore` | `Address[]` | Other lore concerning this being, such as social standing or law |
3363
- | `culture` | `Address` | Primary culture, naming a lore note with `subType: culture` |
3364
- | `homes` | `Address[]` | Place the being calls home |
3365
- | `affiliations` | `Map<Address, Membership>` | Bodies the being belongs to, keyed by Address, each entry the standing it holds there |
3366
- | `affiliations.<Address>.rank` | `integer`, required | The level on that body's own `governance.ranks` ladder; `1` is an ordinary member, `0` someone cast out |
3367
- | `affiliations.<Address>.office` | `string` | A post that body names in its `governance.offices`, where the being holds one |
3368
- | `socialTies` | `Map<Address, Tie>` | Defining support and opposition, directed from this being to others |
3369
- | `gender` | `male \| female \| other` | Gender of the character |
3370
- | `species` | `Address` | Being's species (lore) |
3371
- | `born` | `date \| unknown` | When the being was born; absent, it was never born |
3372
- | `calendar` | `Address` | Calendar note used to print the being's dates |
3373
- | `died` | `date \| unknown` | When the being died; absent, it is alive |
3374
- | `age` | `34 \| ~34` | Age in years, stated only to override what `born` says; `~` marks an estimate |
3375
- | `ageYears` | `number` | Written by the compiler beside an `age` estimate — the `~` stripped; a note never authors this |
3376
- | `height` | `1.91m \| 6' 3"` | Height in metres or feet and inches |
3377
- | `weight` | `85kg \| 187 lbs` | Body weight in kilograms or pounds |
3378
- | `frame` | `scant \| light \| medium \| large \| massive` | Relative frame size |
3379
- | `appearance.eye_color` | `string` | Eye color |
3380
- | `appearance.hair_color` | `string` | Hair color |
3381
- | `appearance.skin_color` | `string` | Skin color |
3382
- | `appearance.complexion` | `string` | Complexion |
3383
- | `appearance.extra_features` | `string[]` | Extra features |
3494
+ | `data` property | Values | Description |
3495
+ | ---------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
3496
+ | `tokenIcon` | `Address` | What a token on the canvas wears — an `icon` address; defaults to `icon` |
3497
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
3498
+ | `archetypes` | `Archetype[]` | What sort of character this is. **Always an array** — character and npc require at least one; creature may omit it or write `[]`; `null` is an error. |
3499
+ | `occupation` | `string` | Name of the character's occupation |
3500
+ | `lore` | `Address[]` | Other lore concerning this being, such as social standing or law |
3501
+ | `culture` | `Address` | Primary culture, naming a lore note with `subType: culture` |
3502
+ | `homes` | `Address[]` | Place the being calls home |
3503
+ | `affiliations` | `Map<Address, Membership>` | Bodies the being belongs to, keyed by Address, each entry the standing it holds there |
3504
+ | `affiliations.<Address>.rank` | `integer`, required | The level on that body's own `governance.ranks` ladder; `1` is an ordinary member, `0` someone cast out |
3505
+ | `affiliations.<Address>.office` | `string` | A post that body names in its `governance.offices`, where the being holds one |
3506
+ | `affiliations.<Address>.societies` | `string[]` | The societies the membership is held in, by name; SoHL carries the first as `system.society` |
3507
+ | `socialTies` | `Map<Address, Tie>` | Defining support and opposition, directed from this being to others |
3508
+ | `gender` | `male \| female \| other` | Gender of the character |
3509
+ | `species` | `Address` | Being's species (lore) |
3510
+ | `born` | `date \| unknown` | When the being was born; absent, it was never born |
3511
+ | `calendar` | `Address` | Calendar note that prints the being's dates |
3512
+ | `died` | `date \| unknown` | When the being died; absent, it is alive |
3513
+ | `age` | `34 \| ~34` | Age in years, stated only to override what `born` says; `~` marks an estimate |
3514
+ | `ageYears` | `number` | Written by the compiler beside an `age` estimate — the `~` stripped; a note never authors this |
3515
+ | `height` | `1.91m \| 6' 3"` | Height in metres or feet and inches |
3516
+ | `weight` | `85kg \| 187 lbs` | Body weight in kilograms or pounds |
3517
+ | `frame` | `scant \| light \| medium \| large \| massive` | Relative frame size |
3518
+ | `appearance.eye_color` | `string` | Eye color |
3519
+ | `appearance.hair_color` | `string` | Hair color |
3520
+ | `appearance.skin_color` | `string` | Skin color |
3521
+ | `appearance.complexion` | `string` | Complexion |
3522
+ | `appearance.extra_features` | `string[]` | Extra features |
3384
3523
 
3385
3524
  Author `data.height` as a string of decimal metres (`1.91m` or `1.91 m`) or
3386
3525
  whole feet with optional inches (`6'`, `6'3"`, or `6' 3"`). Inches must be
@@ -3406,14 +3545,15 @@ data:
3406
3545
 
3407
3546
  #### The bodies a being belongs to, and its standing in each
3408
3547
 
3409
- **Membership**: `{rank: number, office: string}` — `rank` required, `office` not.
3548
+ **Membership**: `{rank: number, office: string, societies: string[]}` — `rank`
3549
+ required, `office` and `societies` not.
3410
3550
 
3411
3551
  `data.affiliations` is a **map keyed by the body's Address**, and each entry is
3412
3552
  the standing the being holds in that body. The key is what makes the pair
3413
3553
  expressible: a being in two bodies holds a standing in each, and two bodies may
3414
3554
  call a rung the same word.
3415
3555
 
3416
- An entry carries two keys and nothing else:
3556
+ An entry carries three keys and nothing else:
3417
3557
 
3418
3558
  - **`rank`** — **required**, a **number**, the level on that body's own
3419
3559
  `governance.ranks` ladder. The body declares `{level, title, description}` for
@@ -3421,6 +3561,9 @@ An entry carries two keys and nothing else:
3421
3561
  for what its level 5 is called.
3422
3562
  - **`office`** — optional, a **string**, matched against the keys of that body's
3423
3563
  `governance.offices` map.
3564
+ - **`societies`** — optional, a **list of names**, the societies the membership
3565
+ is held in. It is always a list, even with one name; a bare string is an
3566
+ error at the key.
3424
3567
 
3425
3568
  **Every entry states a `rank`**, because belonging to a body is holding some
3426
3569
  standing in it. Where nothing more is known the rung is `1`, the ordinary
@@ -3438,15 +3581,15 @@ An entry carrying a rank and no office is complete.
3438
3581
  ```yaml
3439
3582
  data:
3440
3583
  affiliations:
3441
- affiliation-vrystwldtrbs: { rank: 5, office: War Chief }
3584
+ affiliation-vrystwldtrbs: { rank: 5, office: War Chief, societies: [Vrystwald] }
3442
3585
  affiliation-greenwardens: { rank: 1 }
3443
3586
  affiliation-silverguild: { rank: 0 }
3444
3587
  ```
3445
3588
 
3446
3589
  Both values are checked against the body the entry is keyed by. A `rank` that
3447
3590
  is not a level the body confers and an `office` that is not one of its keys are
3448
- each an error naming the file, line and column — so a renamed rung or a renamed
3449
- post fails on every being pointing at it. Naming one body twice, by its short
3591
+ each an error naming the file, line and column — so a rung or a post that does
3592
+ not exist fails on every being pointing at it. Naming one body twice, by its short
3450
3593
  and full Address, still names one body and is an error.
3451
3594
 
3452
3595
  The infobox prints one line per membership, in the order the note writes them,
@@ -3456,14 +3599,16 @@ Vrystwald Tribes`, or `Hárár (5), of Vrystwald Tribes` with no office. The tit
3456
3599
  is read from the body's own `governance.ranks`; where the body declares no rung
3457
3600
  at that level, or its ladder is not available, the rung reads as `Rank 5` —
3458
3601
  `Rank 5, of Vrystwald Tribes`. The body's name links to its page, and is plain
3459
- text where it publishes none. A membership written in the list form, with no
3460
- standing, is the body's name alone. The lines are the same on the website, in
3602
+ text where it publishes none. A membership stating no
3603
+ standing is the body's name alone. The lines are the same on the website, in
3461
3604
  a Foundry journal and in the book. On the body's own page each office is a row
3462
3605
  anchored by its name, so the post a being holds is described where the body
3463
3606
  declares it.
3464
3607
 
3465
3608
  For SoHL, a being embeds one affiliation item per entry: `rank` **is** that
3466
- item's `system.level` and `office` its `system.office`. A standing is a fact
3609
+ item's `system.level`, `office` its `system.office`, and the first name in
3610
+ `societies` its `system.society`. The SoHL item holds one society, so only the
3611
+ first name reaches it. A standing is a fact
3467
3612
  about the setting rather than about one system, so the `sohl:` block states
3468
3613
  none of it.
3469
3614
 
@@ -3546,7 +3691,7 @@ absent `born` — there is nothing there to disagree with — and silent whereve
3546
3691
  the package declares no present, for the same reason no age is computed at
3547
3692
  all.
3548
3693
 
3549
- Use `born:` for a being's birth date. The `birthday:` spelling is accepted with a diagnostic; `born` takes precedence when both are written.
3694
+ Use `born:` for a being's birth date.
3550
3695
 
3551
3696
  #### The lead image
3552
3697
 
@@ -3666,8 +3811,7 @@ embedded document has no page for.
3666
3811
  `model:` carries the catalogue item's art, and an entry copying nothing takes
3667
3812
  the default its type pairs in `itemBuilders` — the same default an item note of
3668
3813
  that type compiles with. An entry naming an address of its own overrides either.
3669
- Writing `icon: ""` ships the document blank on purpose, and no default replaces
3670
- it.
3814
+ An empty `icon: ""` is an error, as it is on a note.
3671
3815
 
3672
3816
  **`data:` reaches no compiled document.** It is where a note names art, and a
3673
3817
  compiled Item has `img` instead; the container is read and goes no further.
@@ -3919,9 +4063,16 @@ rank names the standing, and the standing says.
3919
4063
  | `economy` | `Address[]` | Economic life: `affiliation` or `lore` Addresses, with an explicit type and no default |
3920
4064
  | `lore` | `Address[]` | Lore concerning it — the peoples it draws on, the god a faith venerates, its law, its calendar |
3921
4065
  | `parents` | `Address[]` | Affiliations that this affiliation is subordinate to |
4066
+ | `culture` | `Address` | The people this affiliation belongs to, naming a lore note with `subType: culture` |
3922
4067
  | `relations` | `Map<Address, Standing>` | Standing with other affiliations, keyed by the other body's Address |
3923
4068
  | `relations.<Address>` | `aligned`, `unaligned`, `rival` or `nemesis` | The standing this body holds toward that one; any other value is an error at the value |
3924
4069
 
4070
+ **An affiliation names the people it belongs to in `data.culture`.** An
4071
+ affiliation that names none takes its culture from its `data.parents`. A
4072
+ `polity` that still has none takes the culture of its places: its `data.seat`
4073
+ together with every place whose `data.government` names it. See
4074
+ [Culture in the index](#culture-in-the-index).
4075
+
3925
4076
  **A faith tradition is not its god.** An `affiliation` of subType
3926
4077
  `faithtradition` is a _religion_ — a practice, with an ordained priesthood, a
3927
4078
  calendar and a body of observance — and it can outlive belief in the god
@@ -4556,44 +4707,48 @@ data:
4556
4707
  The saga lists under **In song and story** on `place-ironfells`, which holds the
4557
4708
  sack, on `place-east` and on `lore-fords`.
4558
4709
 
4559
- Calendar fields are written only on a `calendar` note, and `culture`, `form`, `subjects` and
4560
- `language` only on a `literature` note; written on any other lore subType, each is an error at
4561
- its own key.
4562
-
4563
- | `data` property | Values | Description |
4564
- | -------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
4565
- | `epoch` | canonical `<year>.<day>` | The canonical day that equals calendar year 1, day 1 |
4566
- | `months` | `{ name, abbreviation?, days }[]` | Ordered months; their days sum to the world's year |
4567
- | `months[].name` | `string`, required | The month's name |
4568
- | `months[].abbreviation` | `string` | Its short form, for a compact date |
4569
- | `months[].days` | `integer`, required | How many days the month holds |
4570
- | `weekdays` | `{ name, abbreviation? }[]` | Ordered week days, indexed from zero; weeks run continuously |
4571
- | `weekdays[].name` | `string`, required | The day's name |
4572
- | `weekdays[].abbreviation` | `string` | Its short form, for a compact date |
4573
- | `seasons` | `{ name, abbreviation?, start }[]` | Seasons starting on one-based days of year |
4574
- | `seasons[].name` | `string`, required | The season's name |
4575
- | `seasons[].abbreviation` | `string` | Its short form, for a compact date |
4576
- | `seasons[].start` | `integer`, required | The one-based day of the year it begins; starts increase down the list |
4577
- | `namedDays` | `{ name, abbreviation?, day }[]` | Names assigned to one-based days of year |
4578
- | `namedDays[].name` | `string`, required | The day's name |
4579
- | `namedDays[].abbreviation` | `string` | Its short form, for a compact date |
4580
- | `namedDays[].day` | `integer`, required | The one-based day of the year it names, distinct within the calendar |
4581
- | `eras` | `{ shortcode, name, abbreviation?, marker?, proclaimedBy?, start, label? }[]` | Year counts; `start` is `null` or an in-calendar year |
4582
- | `eras[].shortcode` | `string`, required | The era's own segment of `<calendar>.<era>`, unique within the calendar |
4583
- | `eras[].name` | `string`, required | The era's name |
4584
- | `eras[].marker` | `string` | Uppercase letters and digits, beginning with a letter, unique across the corpus; an authored date names the era by it |
4585
- | `eras[].abbreviation` | `string` | Its short form, for a compact date |
4586
- | `eras[].proclaimedBy` | `Address` | The body that began the reckoning |
4587
- | `eras[].start` | `null` or `integer`, required | The calendar year the era begins; `null` for the era before year 1 |
4588
- | `eras[].label` | `string` or `{ after?, before? }` | How a date in the era reads, around one `{date}` slot; a string is `after` |
4589
- | `eras[].label.after` | `string` | How a date from the era's start reads, with exactly one `{date}` |
4590
- | `eras[].label.before` | `string` | How a date before the era's start reads, with exactly one `{date}` |
4591
- | `formats` | map of names to Calendaria patterns | Named date formats; `std` is the preferred default |
4592
- | `culture` | `Address` | `literature` only: the people whose work it is, naming a lore note with `subType: culture` |
4593
- | `form` | `string` | `literature` only: the kind of work in its people's own terms — `epic`, `saga`, `praise-song`, `elegy`. Free text |
4594
- | `subjects` | `Address[]` | `literature` only: the beings, places, gods and other notes the work concerns, and any event, as `note#id` |
4595
- | `language` | `Address` | `literature` only: the tongue it is composed in, naming a skill note with `subType: language` |
4596
- | `events` | event entries — see [Events](#events) | What happened to or at this subject: dated, attributed events |
4710
+ Calendar fields are written only on a `calendar` note, and `form`, `subjects` and `language`
4711
+ only on a `literature` note; written on any other lore subType, each is an error at its own key.
4712
+ `culture` may be written on any lore subType. A culture note is itself the culture, so it
4713
+ leaves the field out or names itself, by its shortcode or by any longer form of its Address;
4714
+ naming any other note is an error at the value. A `lore` note that states no culture has
4715
+ none, since a `lore` note has no `data.parents` to inherit through; a culture note reads as
4716
+ its own Address in the index. See [Culture in the index](#culture-in-the-index).
4717
+
4718
+ | `data` property | Values | Description |
4719
+ | -------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
4720
+ | `epoch` | canonical `<year>.<day>` | The canonical day that equals calendar year 1, day 1 |
4721
+ | `months` | `{ name, abbreviation?, days }[]` | Ordered months; their days sum to the world's year |
4722
+ | `months[].name` | `string`, required | The month's name |
4723
+ | `months[].abbreviation` | `string` | Its short form, for a compact date |
4724
+ | `months[].days` | `integer`, required | How many days the month holds |
4725
+ | `weekdays` | `{ name, abbreviation? }[]` | Ordered week days, indexed from zero; weeks run continuously |
4726
+ | `weekdays[].name` | `string`, required | The day's name |
4727
+ | `weekdays[].abbreviation` | `string` | Its short form, for a compact date |
4728
+ | `seasons` | `{ name, abbreviation?, start }[]` | Seasons starting on one-based days of year |
4729
+ | `seasons[].name` | `string`, required | The season's name |
4730
+ | `seasons[].abbreviation` | `string` | Its short form, for a compact date |
4731
+ | `seasons[].start` | `integer`, required | The one-based day of the year it begins; starts increase down the list |
4732
+ | `namedDays` | `{ name, abbreviation?, day }[]` | Names assigned to one-based days of year |
4733
+ | `namedDays[].name` | `string`, required | The day's name |
4734
+ | `namedDays[].abbreviation` | `string` | Its short form, for a compact date |
4735
+ | `namedDays[].day` | `integer`, required | The one-based day of the year it names, distinct within the calendar |
4736
+ | `eras` | `{ shortcode, name, abbreviation?, marker?, proclaimedBy?, start, label? }[]` | Year counts; `start` is `null` or an in-calendar year |
4737
+ | `eras[].shortcode` | `string`, required | The era's own segment of `<calendar>.<era>`, unique within the calendar |
4738
+ | `eras[].name` | `string`, required | The era's name |
4739
+ | `eras[].marker` | `string` | Uppercase letters and digits, beginning with a letter, unique across the corpus; an authored date names the era by it |
4740
+ | `eras[].abbreviation` | `string` | Its short form, for a compact date |
4741
+ | `eras[].proclaimedBy` | `Address` | The body that began the reckoning |
4742
+ | `eras[].start` | `null` or `integer`, required | The calendar year the era begins; `null` for the era before year 1 |
4743
+ | `eras[].label` | `string` or `{ after?, before? }` | How a date in the era reads, around one `{date}` slot; a string is `after` |
4744
+ | `eras[].label.after` | `string` | How a date from the era's start reads, with exactly one `{date}` |
4745
+ | `eras[].label.before` | `string` | How a date before the era's start reads, with exactly one `{date}` |
4746
+ | `formats` | map of names to Calendaria patterns | Named date formats; `std` is the preferred default |
4747
+ | `culture` | `Address` | The people this lore belongs to — for a `literature` note, the people whose work it is — naming a lore note with `subType: culture`; not on a `culture` note |
4748
+ | `form` | `string` | `literature` only: the kind of work in its people's own terms — `epic`, `saga`, `praise-song`, `elegy`. Free text |
4749
+ | `subjects` | `Address[]` | `literature` only: the beings, places, gods and other notes the work concerns, and any event, as `note#id` |
4750
+ | `language` | `Address` | `literature` only: the tongue it is composed in, naming a skill note with `subType: language` |
4751
+ | `events` | event entries — see [Events](#events) | What happened to or at this subject: dated, attributed events |
4597
4752
 
4598
4753
  `data.events` is available on every `lore` subType, and is described in full under [Events](#events).
4599
4754
 
@@ -4700,7 +4855,7 @@ Braced custom forms such as `{era}` are equivalent to bracketed custom forms. A
4700
4855
 
4701
4856
  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 `data.place`, while the place note does not list its maps.
4702
4857
 
4703
- **The Scene is made in Foundry.** Walls, lights, sounds, regions, levels and pins are drawn in Foundry's Scene editor, exported, and carried by the note at `data.scene`. The build passes the Scene through: it applies `data.fixup`, binds pins marked `#anchor` to this note's pages, and checks nothing else inside it.
4858
+ **The Scene is made in Foundry.** Walls, lights, sounds, regions, levels and pins are drawn in Foundry's Scene editor, exported, and carried by the note at `data.scene`. The build passes the Scene through: it applies the fixups — `data.fixup` and `data.thumbnail` — binds pins marked `#anchor` to this note's pages, and checks nothing else inside it.
4704
4859
 
4705
4860
  **subType**:
4706
4861
 
@@ -4718,6 +4873,7 @@ A subtype says what kind of map the note is. Every subtype carries an exported S
4718
4873
  | `fixup[].path` | `string`, required | The exported field to replace, as a property path from `data.scene` |
4719
4874
  | `fixup[].type` | `address`, required | What `value` is |
4720
4875
  | `fixup[].value` | `Address`, required | The asset Address whose path replaces the exported one |
4876
+ | `thumbnail` | Address | The map's thumbnail, an `image` Address written into the Scene's `thumb` as a fixup — see [The thumbnail](#the-thumbnail) |
4721
4877
  | `place` | Address | Depicted place, when one exists |
4722
4878
 
4723
4879
  `data.scene` must be an object, and that is the only check made of it: its keys
@@ -4775,6 +4931,32 @@ data:
4775
4931
  # Big Bad Wolf {#myanchor1}
4776
4932
  ```
4777
4933
 
4934
+ #### The thumbnail
4935
+
4936
+ `data.thumbnail` names the picture Foundry shows for the Scene in its
4937
+ directory and compendium lists. It is an `image` Address, and a bare shortcode
4938
+ reads as one: `thumbnail: wolfdenthumb` names `image-wolfdenthumb`. An Address
4939
+ of any other type, `icon` included, is an error at the key.
4940
+
4941
+ The thumbnail is a fixup the note does not have to spell. The map vocabulary
4942
+ declares that `thumbnail` belongs in the Scene's `thumb`, so the build adds
4943
+ `{ path: ".thumb", type: address, value: <thumbnail> }` to the note's fixups
4944
+ and applies it with the rest: the Address must resolve, and the exported Scene
4945
+ must carry `thumb`, as every Foundry export does. A `data.fixup` entry whose
4946
+ `path` is also `.thumb` is an error, because one would overwrite the other.
4947
+ The thumbnail is independent of the background art, which keeps its exported
4948
+ path or its own fixup.
4949
+
4950
+ ```yaml
4951
+ data:
4952
+ thumbnail: wolfdenthumb
4953
+ scene:
4954
+ name: Wolf Den Scene
4955
+ thumb: null
4956
+ levels:
4957
+ - { _id: defaultLevel0000, background: { src: modules/maps/den.webp } }
4958
+ ```
4959
+
4778
4960
  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. The maps naming one `data.place` share one Adventure.
4779
4961
 
4780
4962
  #### A map in the book
@@ -4800,11 +4982,12 @@ level with no background prints nothing.
4800
4982
  | `data` property | Values | Description |
4801
4983
  | --------------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
4802
4984
  | `events` | event entries — see [Events](#events) | What happened to or at this subject: dated, attributed events |
4803
- | `calendar` | `Address` | Calendar note used to print this place's dates |
4985
+ | `calendar` | `Address` | Calendar note that prints this place's dates |
4804
4986
  | `demonym` | `string` | What a person from this place is called — a Vylarian |
4805
4987
  | `purpose` | `placeCharacter` tag | The reason a settlement, site, or structure exists |
4806
4988
  | `lore` | `Address[]` | Lore concerning this place — its peoples, its law, its calendar, its history |
4807
4989
  | `parents` | `Address[]` | Enclosing places within which this place is located |
4990
+ | `culture` | `Address` | The people this place belongs to, naming a lore note with `subType: culture` |
4808
4991
  | `population` | `number` | Approximate population (precision 2 significant digits) |
4809
4992
  | `government` | `Address` or `null` (default type: `affiliation`) | Governing affiliation; explicit null means complete anarchy. Omission warns only for positive population. |
4810
4993
  | `market` | `number` | Market class, 1 to 6 — what trade a settlement supports |
@@ -5102,6 +5285,11 @@ urban.
5102
5285
 
5103
5286
  **A place declares what is true of its ground.** It names its governing affiliation
5104
5287
  in `data.government`; maps name the place they depict through `data.place`.
5288
+ A place may name the people it belongs to in `data.culture`. A place that names
5289
+ none takes its culture from its `data.parents`, so a region states the culture
5290
+ once and the districts and settlements inside it carry it without repeating it;
5291
+ [Culture in the index](#culture-in-the-index) gives the rule and a worked
5292
+ example.
5105
5293
  The place's `lore` links cover its peoples, calendar, law and history. Political
5106
5294
  languages belong to a polity's `sohl.system.commonSkills`, while the note's
5107
5295
  `description` provides its page summary.
@@ -5136,9 +5324,22 @@ Content prepared to be played — a situation with its cast, places, and possibl
5136
5324
  - reference: Out-of-world lookup material about the setting or system — correspondences, conversions, glossaries.
5137
5325
  - howto: A task with an outcome, written as the steps that reach it.
5138
5326
  - concept: An explanation of how something works and why it is shaped that way, read to understand rather than to follow.
5139
- - 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.
5140
-
5141
- A `doc` declares no properties of its own.
5327
+ - settingguide: A culture's guide to the setting, which names its culture in `data.culture`. It orients a reader to a setting or region, bringing its places, peoples, institutions, and daily life into one usable frame with links to detailed notes.
5328
+
5329
+ | `data` property | Values | Description |
5330
+ | --------------- | --------- | --------------------------------------------------------------------------------------------------------------------- |
5331
+ | `culture` | `Address` | `settingguide` only, and required there: the culture the guide introduces, naming a lore note with `subType: culture` |
5332
+
5333
+ A setting guide introduces one culture's view of the setting, so it names that
5334
+ culture, and a `doc` of any other `subType` has no culture of its own to name.
5335
+ A setting guide without `data.culture` is an error at its `subType:` line;
5336
+ `data.culture` on any other `doc` subType is an error at its key. The value
5337
+ names a `lore` note of `subType: culture`, and a setting guide's culture is the
5338
+ one it states: it inherits nothing, because a `doc` has no `data.parents`. The
5339
+ other notes that may state a culture are described under
5340
+ [`place`](#type-place), [`affiliation`](#type-affiliation) and
5341
+ [`lore`](#type-lore), and [Culture in the index](#culture-in-the-index) gives
5342
+ the value each one reads as.
5142
5343
 
5143
5344
  Choose `settingguide` for an entry point that stands on its own while guiding
5144
5345
  players and GMs into a setting. A `concept` explains one subject, a `reference`
@@ -5154,6 +5355,8 @@ name: { full: The Reed Flats Guide }
5154
5355
  type: doc
5155
5356
  subType: settingguide
5156
5357
  description: A starting point for adventures in the Reed Flats.
5358
+ data:
5359
+ culture: lore-reedfolk
5157
5360
  ---
5158
5361
 
5159
5362
  The Reed Flats are a network of marsh settlements linked by river trade.