@heroiclands/package-build 22.14.2 → 22.14.4

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 (67) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/content-build.mjs +6 -6
  3. package/bin/package-build.mjs +10 -0
  4. package/config.mjs +18 -1
  5. package/content-config.mjs +2 -2
  6. package/docs/api.md +66 -37
  7. package/docs/configuration.md +16 -7
  8. package/docs/content-format.md +172 -200
  9. package/docs/getting-started.md +46 -14
  10. package/docs/project-setup.md +5 -6
  11. package/engine/address.mjs +3 -5
  12. package/engine/asset-bindings.mjs +66 -0
  13. package/engine/calendar-notes.mjs +74 -0
  14. package/engine/changelog-lint.mjs +2 -2
  15. package/engine/content-images.mjs +20 -10
  16. package/engine/content-index.mjs +63 -36
  17. package/engine/content-lint.mjs +1 -2
  18. package/engine/content-package.mjs +2 -2
  19. package/engine/content-tables.mjs +4 -8
  20. package/engine/folder-notes.mjs +1 -2
  21. package/engine/foundry-entries.mjs +21 -6
  22. package/engine/frontmatter-lint.mjs +3 -4
  23. package/engine/frontmatter.mjs +5 -8
  24. package/engine/helpers.mjs +3 -4
  25. package/engine/index.mjs +3 -0
  26. package/engine/infobox.mjs +66 -2
  27. package/engine/item-registry.mjs +2 -2
  28. package/engine/note-vocabulary.mjs +83 -5
  29. package/engine/pack-config.mjs +2 -2
  30. package/engine/pack-router.mjs +2 -2
  31. package/engine/pdf-render.mjs +31 -8
  32. package/engine/pdf-toc.mjs +3 -3
  33. package/engine/retired-fields.mjs +1 -1
  34. package/engine/scene-levels.mjs +4 -4
  35. package/engine/schema-check.mjs +1 -1
  36. package/engine/site-build.mjs +134 -121
  37. package/engine/site-preview.mjs +293 -0
  38. package/engine/site-root.mjs +20 -2
  39. package/engine/social-tie-terms.mjs +11 -0
  40. package/engine/social-ties.mjs +84 -0
  41. package/engine/sql-tables.mjs +4 -9
  42. package/hm3/actors.mjs +1 -1
  43. package/package.json +3 -3
  44. package/sohl/item-fields.mjs +2 -2
  45. package/types/config.d.mts +8 -0
  46. package/types/content-config.d.mts +1 -1
  47. package/types/engine/address.d.mts +3 -5
  48. package/types/engine/asset-bindings.d.mts +6 -0
  49. package/types/engine/calendar-notes.d.mts +12 -0
  50. package/types/engine/content-images.d.mts +7 -4
  51. package/types/engine/content-index.d.mts +11 -0
  52. package/types/engine/content-package.d.mts +2 -2
  53. package/types/engine/folder-notes.d.mts +1 -2
  54. package/types/engine/frontmatter-lint.d.mts +1 -2
  55. package/types/engine/frontmatter.d.mts +2 -4
  56. package/types/engine/helpers.d.mts +3 -4
  57. package/types/engine/index.d.mts +1 -0
  58. package/types/engine/item-registry.d.mts +2 -2
  59. package/types/engine/note-vocabulary.d.mts +5 -6
  60. package/types/engine/pack-config.d.mts +1 -1
  61. package/types/engine/pack-router.d.mts +1 -1
  62. package/types/engine/pdf-render.d.mts +10 -1
  63. package/types/engine/pdf-toc.d.mts +1 -1
  64. package/types/engine/site-build.d.mts +36 -3
  65. package/types/engine/site-preview.d.mts +34 -0
  66. package/types/engine/social-tie-terms.d.mts +5 -0
  67. package/types/engine/social-ties.d.mts +10 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  ### Documentation
4
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>`
5
+ Markdown generates a JournalEntry: content before the first H1 becomes its "Introduction" page, and each H1 begins another page. The document Address is `<package>-note-<note_type>-<shortcode>`.
6
6
 
7
7
  An **infobox** is prepended to the entry's first page, before the prose. It is
8
8
  not a page of its own: a page is what a Foundry UUID addresses, and a summary a
@@ -51,7 +51,7 @@ system makes the page checkable — _every page carries exactly the boxes its ty
51
51
  maps to_ is an assertion the build can make, so a missing infobox is a failure
52
52
  rather than something nobody notices.
53
53
 
54
- For Web Pages, the entire markdown content will be converted into an HTML page, with appropriate infoboxes.
54
+ For web pages, the full Markdown body renders as HTML with its infoboxes.
55
55
 
56
56
  ### The infobox
57
57
 
@@ -154,7 +154,7 @@ a reader reassemble one fact from two cells, and reads worst in the book, whose
154
154
  label column is a narrow small-caps rule. A medium cannot supply it either —
155
155
  appending `d` to a price means knowing which row is the price, which is the one
156
156
  thing a generic renderer must never know. A row carrying a unit is `text`,
157
- because a number with a unit on it is no longer a number.
157
+ because a number with a unit is text for presentation.
158
158
 
159
159
  **What a field is called** is a **presentation overlay** on that same
160
160
  declaration, per system. It carries two things a compiler's field list cannot,
@@ -346,14 +346,14 @@ index, silently, and the index still renders. That is the same failure a
346
346
  misspelled `data:` key is, and it gets the same answer — the vocabulary
347
347
  is declared, so a near miss is a finding that names what you probably meant.
348
348
 
349
- | group | applies to | tags |
350
- | ------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
351
- | **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` |
352
- | **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` |
353
- | **place scale** | `place` / `region` | `continent` |
354
- | **being station** | `being` | `tradesfolk`, `common-folk`, `soldiery`, `administration`, `clergy`, `mages`, `underworld`, `dependents`, `guilded`, `unguilded` |
355
- | **being kind** | `being` | `character`, `creature` |
356
- | **state** | any | `draft` |
349
+ | group | applies to | tags |
350
+ | ------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
351
+ | **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` |
352
+ | **place character** | `place` | `fortified`, `temple`, `market`, `trading`, `merchant`, `mining`, `fishing`, `naval`, `military`, `imperial`, `provincial`, `coastal`, `river`, `lakeside`, `hill`, `mountain`, `valley`, `forest`, `woodland`, `inland`, `island`, `frontier`, `border`, `craft`, `caravan`, `pilgrimage`, `holy`, `sacred`, `free`, `ford`, `portage`, `pass`, `well` |
353
+ | **place scale** | `place` / `region` | `continent` |
354
+ | **being station** | `being` | `tradesfolk`, `common-folk`, `soldiery`, `administration`, `clergy`, `mages`, `underworld`, `dependents`, `guilded`, `unguilded` |
355
+ | **being kind** | `being` | `character`, `creature` |
356
+ | **state** | any | `draft` |
357
357
 
358
358
  **`draft` is the one tag either build reads.** It does two things and nothing
359
359
  else: a link _into_ a draft note renders marked, and the note itself states that
@@ -739,9 +739,7 @@ moved one and not the other.
739
739
 
740
740
  **No image key is one of those.** A top-level `img:` is not authored at all:
741
741
  `data.icon` is a different key holding a different kind of value, and a note
742
- writing `img:` is naming a file where an address belongs. `portrait:` is not
743
- relocated either, at any level — a portrait is an embed in the body, and the
744
- key that used to declare one is not a key.
742
+ writing `img:` is naming a file where an address belongs. `portrait:` is not authorable at any level; a portrait is an embed in the body.
745
743
 
746
744
  **Two of the eight are Item-only in SoHL.** `actionDefs` and `notes` are declared
747
745
  on every SoHL Item subtype and on no SoHL Actor, so on a `being` or a `vehicle`
@@ -822,6 +820,14 @@ does not know still resolves on the website and in the book — those need only
822
820
  the name and the suffix — and has no Foundry address, which is refused rather
823
821
  than guessed.
824
822
 
823
+ An art package that replaces another package's images can declare
824
+ `packageBuild.assets[].bindsTo` on the relevant asset copy. Each file's basename
825
+ then acts as a shortcode for the declared package and type. The related
826
+ `relationships.requires[].contentIndex` supplies the address index; with that
827
+ index enabled, an asset with no matching note is a located build error. See
828
+ [asset bindings](configuration.md#packagebuildstagedir-and-packagebuildassets)
829
+ for the configuration and cache requirements.
830
+
825
831
  **The host is configuration.** `site.assets` in `package-build.config.yaml` is
826
832
  the root the web form is joined onto, and a package-owned image on a page with
827
833
  none set is an error naming that key.
@@ -885,7 +891,7 @@ data:
885
891
 
886
892
  It is deliberately close to the retired `package:` and deliberately not the same
887
893
  word: `package:` said which _distribution_ owned a note — now the repository's
888
- `contentPackage`, and no longer authorable — while `pack:` says which
894
+ `contentPackage`, and not authorable — while `pack:` says which
889
895
  _compendium_ receives its document.
890
896
 
891
897
  **It names the pack for the note's _own_ document.** A document derived from it
@@ -1002,9 +1008,8 @@ real priority** — the one SoHL's own templates ship at — not an absence.
1002
1008
  **Where it lands differs by system, because HM3's data model has no field for
1003
1009
  it.** SoHL records it in `system`; HM3 keeps it under its own flag scope,
1004
1010
  `flags.hm3`, and a note that is not a template writes nothing there rather than a
1005
- `null` nothing reads. Both of HM3's passes write it — an Item's flag was missing
1006
- until an item note's priority reached
1007
- SoHL and stop at HM3, with nothing said on either side.
1011
+ `null` nothing reads. Both HM3 Item and Actor passes write their own flag when
1012
+ the note declares a priority.
1008
1013
 
1009
1014
  **How a winner is chosen.** Opening a Create dialog gathers every candidate
1010
1015
  across the world and every matching compendium, _including other modules'_. Those
@@ -1067,16 +1072,10 @@ correctly and is wrong for a folder, and wrong in a way nothing outside the
1067
1072
  build can detect: the result is a plausible 16-character id that resolves to
1068
1073
  nothing.
1069
1074
 
1070
- **Why the address and not an authored string.** A note used to declare an
1071
- opaque 16-character `id` — 6,343 of them across the four content trees — which
1072
- said nothing its address did not, could not be read or reviewed, and was
1073
- guaranteed by nothing: `content-lint` refuses a **duplicate address** across
1074
- every pack of a document type, which is exactly the scope a primary document's
1075
- id must be unique within, and it said nothing at all about a duplicate `id`. So
1076
- the derived id inherits a guard that already exists, where the authored one had
1077
- none. It is the same principle that turned `folder: ONXsqZAIZr2qzxTb` into
1078
- `packFolder: <address>` above: an opaque derived identity does not belong in
1079
- authored content.
1075
+ **The Address determines the default id.** `content-lint` refuses a duplicate
1076
+ Address across the packs of a document type, so the derived id shares that
1077
+ uniqueness scope. An author pins a document's identity with `data.id` when it
1078
+ must remain stable across an Address change.
1080
1079
 
1081
1080
  **An authored `data.id` wins**, and that is how a document's identity is
1082
1081
  **pinned**:
@@ -1139,27 +1138,9 @@ it — so a documentation journal is filed beside the item it describes without
1139
1138
  the journals pack having to declare anything. A folder nothing references
1140
1139
  materialises nowhere.
1141
1140
 
1142
- That derivation is what makes a whole class of defect unrepresentable. The
1143
- folder declared twice, once per pack, sits in two files free to disagree:
1144
- `sohl-thalorna` was missing 57 of its item folders from its journal folder file
1145
- and `sohl-kethira-basic` had no journal folder file at all, so both emitted
1146
- documentation journals into folders their own pack never declared — silently.
1147
- With one folder note and one address there is no second file to disagree with
1148
- the first.
1149
-
1150
- > **`packFolder` was a path** for one release (`Possessions/Misc_Gear/Cooking`).
1151
- > A path encoded the hierarchy in the value, so reparenting a folder made every
1152
- > note naming it wrong — a structural edit became a corpus-wide rewrite. The
1153
- > path form is **removed**, not deprecated: nothing authored it yet, which is
1154
- > the whole reason the change was cheap enough to make.
1155
-
1156
- > **`folder:` was a Foundry id**, resolved against a per-pack
1157
- > `*-folders.yaml` — five files per tree. Both halves are **retired** together
1158
- > : the id spelling has nothing left to resolve against once the YAML is
1159
- > gone, and the YAML has no reader once the spelling is refused. A note that
1160
- > still writes `folder:` fails the build, naming `packFolder` and the line to
1161
- > rewrite, rather than being ignored — a retired field left ignored reads to
1162
- > its author as though it still works.
1141
+ One folder note and one Address supply every pack that references it.
1142
+ `data.packFolder` names a folder by Address. A top-level `folder:` key is
1143
+ refused with a finding that names `data.packFolder` and the authored line.
1163
1144
 
1164
1145
  #### The knowledgebase category
1165
1146
 
@@ -1343,21 +1324,11 @@ hyphenated value is reported where it wrote it:
1343
1324
  Trauma/Blood_Loss.md:3:1: error: `subType` "blood-loss" is not a well-formed subType — a subType is lowercase letters and digits only (^[a-z0-9]+$), the same charset a type, a shortcode and a contentPackage are held to. It is a vocabulary term the whole toolchain keys on, and one closed set away from being an address segment again, so a charset that held for every term but this one would be a rule nobody could state in a sentence
1344
1325
  ```
1345
1326
 
1346
- One declared value broke the rule and has been renamed: a `doc`'s `user-guide`
1347
- is now **`userguide`**. The old spelling was accepted for one transitional
1348
- release, as a warning naming the replacement, so the 43 `sohl` notes authoring
1349
- it were not invalidated by the release that renamed them. Every consumer tree
1350
- has swept, so the acceptance is gone and `user-guide` is refused by the
1351
- charset check — it contains a hyphen, which is the reason that always applied.
1352
- No retirement-specific code outlived the sweep.
1353
-
1354
- That is a guarantee rather than an observation, and it holds: of **4,456 distinct
1355
- shortcodes** across the four content trees, not one contains a character outside
1356
- `[a-z0-9]`. It is load-bearing, so relaxing the charset later would break
1357
- resolution with nothing to say so.
1358
-
1359
- `type/shortcode` with a slash is the legacy form, still resolved so links written
1360
- before the vault migrated do not silently die. A slash is _unconditionally_ an
1327
+ The `doc` subType is **`userguide`**. A hyphenated `user-guide` is refused by
1328
+ the shared charset check. Shortcodes likewise contain only lowercase letters
1329
+ and digits, preserving one unambiguous Address grammar.
1330
+
1331
+ `type/shortcode` with a slash is accepted. A slash is _unconditionally_ an
1361
1332
  address separator — pipe or no pipe — so an unknown type before one is an error
1362
1333
  rather than something to guess at.
1363
1334
 
@@ -1403,13 +1374,9 @@ Which of the two a field holds is the field's own declaration, stated in the
1403
1374
  An address that names no note **fails the build** — in the link checker,
1404
1375
  in the pack compilers and in the site build alike.
1405
1376
 
1406
- It was a warning in the checker and, in the site build, nothing at all while any
1407
- linkable package had no vendored manifest. The reasoning was that `[[Sunless
1408
- Vault]]` might be a placeholder for a note somebody meant to write. That was a
1409
- property of the **bare** form, which is retired, and the intent behind it has a
1410
- real spelling now: a note tagged `draft` exists, resolves, compiles and
1411
- publishes, and a link to it renders visibly marked. So an address landing
1412
- nowhere is a typo or an omission, and both want fixing.
1377
+ A `draft` note exists, resolves, compiles and publishes; a link to it renders
1378
+ visibly marked. An Address landing nowhere is a typo or an omission and earns
1379
+ an error.
1413
1380
 
1414
1381
  There are six ways a link can fail, and each is one **error** with one message
1415
1382
  wherever it is met:
@@ -1424,11 +1391,9 @@ wherever it is met:
1424
1391
  | `ambiguous` | _unreachable; kept for the manifest_ | — |
1425
1392
  | `unknown-anchor` | the address resolves; the `#section` does not | correct the anchor |
1426
1393
 
1427
- `ambiguous` no longer fires. An omitted segment defaults rather than wildcarding,
1428
- so a written target expands to one canonical address and a lookup returns one
1429
- entry or none — two claimants is a state the grammar can no longer reach. The
1430
- reason is retained so a consumer switching on it does not break, and because a
1431
- vendored manifest built by an older toolchain may still carry the finding.
1394
+ `ambiguous` is a reserved reason in the result vocabulary. An omitted segment
1395
+ defaults rather than wildcarding, so each written target expands to one
1396
+ canonical Address and a lookup returns one entry or none.
1432
1397
 
1433
1398
  The vocabulary and the messages live in one module (`engine/wikilink-syntax.mjs`)
1434
1399
  precisely because an author meets whichever build ran first. Three resolvers read
@@ -1565,19 +1530,9 @@ There is one namespace, and the pipe is required:
1565
1530
  **A link written without a label addresses nothing**, and the correction
1566
1531
  is always the same: write `[[type-shortcode|Text]]`.
1567
1532
 
1568
- The bare form does not name an **alias** — a note's own display name, or one of
1569
- the names it listed in `aliases:` — looked up within the citing note's type. It
1570
- was measured before it was retired, and the namespace was empty in practice:
1571
- across 8,305 wikilinks in three content trees, **not one** bare link resolved to
1572
- a note. What the index behind it did do was fold every note's `name.full` into
1573
- itself, so two notes of one type could not share a display name — a rules page
1574
- and a user guide page both called "Gear" were a build failure whose every
1575
- available fix moved a published URL.
1576
-
1577
- The top-level `aliases:` that fed it is **retired** and refused. The nested
1578
- `name.aliases:` is **not**: it is reserved for a use that does not exist yet, so
1579
- it is permitted and read by nothing — no index, no resolver, no lint rule, no
1580
- derived address. A note carrying one behaves exactly as one without it.
1533
+ The bare form does not resolve by display name or alias. `name.aliases` is
1534
+ indexed for search and completion, while an authored link names an Address.
1535
+ A top-level `aliases:` key is refused.
1581
1536
 
1582
1537
  Requiring the label is also what makes positional parsing safe. Note names
1583
1538
  contain hyphens — `Grukar-ahk` is a name, not a `Grukar` of type `ahk` — so a
@@ -2111,10 +2066,10 @@ place for those words and this is it.
2111
2066
 
2112
2067
  ##### Width is a class, and the ordinary width carries no marker
2113
2068
 
2114
- | `class` value | book | website | Foundry journal |
2115
- | ------------- | ------------- | ------------------ | ---------------- |
2116
- | _(none)_ | one column | the text measure | the page measure |
2117
- | `.full-width` | the full page | full content width | full page width |
2069
+ | `class` value | book | website | Foundry journal |
2070
+ | ------------- | ----------------------------- | ------------------ | --------------- |
2071
+ | _(none)_ | natural width within a column | natural width | natural width |
2072
+ | `.full-width` | page measure | full content width | full page width |
2118
2073
 
2119
2074
  The simple case needs no spelling, which is why the ordinary width has no name.
2120
2075
 
@@ -2125,21 +2080,24 @@ refused.
2125
2080
 
2126
2081
  ##### Named size is `size:`
2127
2082
 
2128
- | `size` value | display |
2129
- | ------------ | ------------------------ |
2130
- | `auto` | same as omitting `size:` |
2131
- | `small` | same as omitting `size:` |
2132
- | `medium` | same as omitting `size:` |
2133
- | `large` | same as omitting `size:` |
2134
- | `xlarge` | same as omitting `size:` |
2135
- | `full-width` | same as omitting `size:` |
2136
-
2137
- The parser accepts and records these names for Markdown images and embedded
2138
- assets. **A `size:` value does not change the displayed dimensions** on the
2139
- website, in Foundry, or in the book. With no `size:`, the parser records `auto`;
2140
- the existing rendering rules apply. In particular, book images occupy a column
2141
- unless they carry `.full-width`. A `size:` may sit before or after `float:` in
2142
- the same directive, and neither changes the other's behavior.
2083
+ | `size` value | website and Foundry | book |
2084
+ | ------------ | ------------------- | ------------ |
2085
+ | `auto` | natural size | natural size |
2086
+ | `small` | 64 CSS pixels | 1.6 cm |
2087
+ | `medium` | 128 CSS pixels | 3.2 cm |
2088
+ | `large` | 256 CSS pixels | 5.6 cm |
2089
+ | `xlarge` | 512 CSS pixels | 8 cm |
2090
+ | `full-width` | content measure | page measure |
2091
+
2092
+ These are maximum display widths for Markdown images and embedded assets;
2093
+ height follows the file's aspect ratio. The available column, page, or browser
2094
+ measure caps every size, and print also caps image height within the page.
2095
+ Omitting `size:` means `auto`, which uses the file's natural dimensions within
2096
+ those bounds. `full-width` spans the book's page rather than one column. The
2097
+ `.full-width` class also grants page scope in print; when it accompanies a
2098
+ bounded `size:`, the image keeps that bounded width inside the page figure.
2099
+ A `size:` may sit before or after `float:` in the same directive, and neither
2100
+ changes the other's behavior.
2143
2101
 
2144
2102
  ##### Position is `float:`
2145
2103
 
@@ -2422,7 +2380,7 @@ For JournalEntries, the following rules apply:
2422
2380
 
2423
2381
  - `# ... {#spoilers}`: The contents of this header go into a page which is viewable only by the GM (`CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE`).
2424
2382
 
2425
- **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:
2383
+ **Archetypes** describe characters in broad terms and help match them to an adventure. The list is:
2426
2384
 
2427
2385
  - warrior: Can hold a line and win a fight.
2428
2386
  - skirmisher: Fights light — ambush, missile, mobility.
@@ -2462,10 +2420,11 @@ Generates a living (or undead, or spirit) being.
2462
2420
  | `lore` | `Address[]` | Lore concerning this being — the people it is of, the standing it holds, the law it lives under |
2463
2421
  | `homes` | `Address[]` | Place the being calls home |
2464
2422
  | `affiliations` | `Address[]` | Affilliations (e.g., arcane/divine traditions, polities, etc) |
2423
+ | `socialTies` | `Map<Address, Tie>` | Defining support and opposition, directed from this being to others |
2465
2424
  | `gender` | `male \| female \| other` | Gender of the character |
2466
2425
  | `species` | `Address` | Being's species (lore) |
2467
- | `born` | `YYYY/MM/DD \| unknown` | When the being was born; absent, it was never born |
2468
- | `died` | `YYYY/MM/DD \| unknown` | When the being died; absent, it is alive |
2426
+ | `born` | `date \| unknown` | When the being was born; absent, it was never born |
2427
+ | `died` | `date \| unknown` | When the being died; absent, it is alive |
2469
2428
  | `age` | `34 \| ~34` | Age in years, stated only to override what `born` says; `~` marks an estimate |
2470
2429
  | `ageYears` | `number` | Written by the compiler beside an `age` estimate — the `~` stripped; a note never authors this |
2471
2430
  | `height` | `number` | Height in meters |
@@ -2477,6 +2436,29 @@ Generates a living (or undead, or spirit) being.
2477
2436
  | `appearance.complexion` | `string` | Complexion |
2478
2437
  | `appearance.extra_features` | `string[]` | Extra features |
2479
2438
 
2439
+ #### The people who matter to a being
2440
+
2441
+ `data.socialTies` records relationships that define the subject's dossier, not
2442
+ everyone the subject knows. An absent entry says nothing about that relationship.
2443
+ An acquaintance belongs here when, for example, a guard will recognize the
2444
+ subject at a gate. A father and an independent adult son can have no tie in
2445
+ either note; a daughter who relies on her father may be his `dependent`.
2446
+
2447
+ The keys are bare Addresses with an explicit `being` or `affiliation` type.
2448
+ Values are `patron`, `friend`, `dependent`, `acquaintance`, `rival`, or
2449
+ `nemesis`. A patron supports the subject from greater power or status; a
2450
+ friend supports them from goodwill; a dependent relies on their protection;
2451
+ an acquaintance knows them without a strong disposition; a rival opposes them
2452
+ without implacable hostility; a nemesis opposes them personally and
2453
+ implacably. The infobox groups targets by term and links to their notes.
2454
+
2455
+ Each tie is one-sided. The target need not name the subject back, even when
2456
+ `patron` and `dependent` would describe opposite ends of the same support.
2457
+ Kinship, household membership, organizational membership, rank, and office are
2458
+ distinct facts. Their relevant effects may appear as ties, while their details
2459
+ belong in prose, `data.affiliations`, or governance fields. A tie has no
2460
+ mechanical modifier.
2461
+
2480
2462
  #### When a being was born, when it died, and how old it is
2481
2463
 
2482
2464
  **Absence asserts a fact rather than recording a gap**, and that is what these
@@ -2694,11 +2676,9 @@ unanchored page is keyed on its heading, so inserting a heading leaves every
2694
2676
  other page's id where it was. Two sibling pages sharing a heading is likewise a
2695
2677
  build error, matching the `MD024` lint rule that already refuses it.
2696
2678
 
2697
- If a `sohl` property is present, a SoHL actor of type "being" will be created.
2679
+ A `sohl` block produces a SoHL actor of type `being`. An `hm3` block produces an HM3 actor. Its document type is **not** derived: `hm3.type` states it, and must be `character` or `creature`. A note that omits it is an error naming the note — see _The note vocabulary, and how it maps_.
2698
2680
 
2699
- If an `hm3` property is present, an HM3 actor is created. Its document type is **not** derived: `hm3.type` states it, and must be `character` or `creature`. A note that omits it is an error naming the note — see _The note vocabulary, and how it maps_.
2700
-
2701
- A SoHL "being" document will be created, as will an "HM3" document.
2681
+ A SoHL "being" document is created, as will an "HM3" document.
2702
2682
 
2703
2683
  | shared source | → sohl | → hm3 |
2704
2684
  | ----------------- | ------ | ------------------- |
@@ -2829,8 +2809,7 @@ Definition of a level within the organization (e.g., Priest, Layperson, Member,
2829
2809
  owned outright, with no standing at law except through an owner. The title is
2830
2810
  what this body calls it and the description is how this body puts it, but the
2831
2811
  obligations and rights belong to the standing itself, so they are written once
2832
- and cited by every ladder that confers it. In the authored corpus 237 distinct
2833
- titles across 2,602 rank entries resolve onto 43 standings.
2812
+ and cited by every ladder that confers it.
2834
2813
 
2835
2814
  That is also what makes a rank answerable across bodies. Without it, asking what
2836
2815
  a `Naukrátissa` may do means reading the Bethûan fleet's ladder; with it, the
@@ -3063,9 +3042,9 @@ Note: `data.quantity` may not be specified. Quantity is always 1.
3063
3042
  | `quality` | `number` | Gear quality |
3064
3043
  | `durability` | `number` | Gear durability |
3065
3044
 
3066
- If a `sohl` property is present, a SoHL item of type "armorgear" will be created.
3045
+ If a `sohl` property is present, a SoHL item of type "armorgear" is created.
3067
3046
 
3068
- The note type is `armorgear` in both cases. The `gear` suffix was briefly renamed away on the argument that it named a SoHL document subtype rather than the thing the note is about; that rename is reversed. Nothing had adopted the bare spelling — every note in every tree still writes the suffix — and dropping it from three of the five gear types while `weapongear` and `containergear` kept theirs cost more consistency than the argument bought.
3047
+ The note type is `armorgear` in both systems.
3069
3048
 
3070
3049
  | shared source | → sohl | → hm3 |
3071
3050
  | ----------------- | ----------------------- | --------------- |
@@ -3080,7 +3059,7 @@ The note type is `armorgear` in both cases. The `gear` suffix was briefly rename
3080
3059
  | ------------------ | -------- | ------------------------------------------ |
3081
3060
  | `templatePriority` | `number` | Template priority, _null_ = not a template |
3082
3061
 
3083
- if a `hm3` property is present, an HM3 item of type "armorlocation" will be created.
3062
+ if a `hm3` property is present, an HM3 item of type "armorlocation" is created.
3084
3063
 
3085
3064
  | shared source | → sohl | → hm3 |
3086
3065
  | ------------- | ------ | ----- |
@@ -3092,7 +3071,7 @@ if a `hm3` property is present, an HM3 item of type "armorlocation" will be crea
3092
3071
  | ------------------ | -------- | ------------------------------------------ |
3093
3072
  | `templatePriority` | `number` | Template priority, _null_ = not a template |
3094
3073
 
3095
- if a `sohl` property is present, a SoHL item of type "attribute" will be created.
3074
+ if a `sohl` property is present, a SoHL item of type "attribute" is created.
3096
3075
 
3097
3076
  | shared source | → sohl | → hm3 |
3098
3077
  | ------------- | ------ | ----- |
@@ -3116,7 +3095,7 @@ if a `sohl` property is present, a SoHL item of type "attribute" will be created
3116
3095
  | `potency` | `na \| mild \| strong \| great` | Concoction Potency (mundane/exotic concoctions) |
3117
3096
  | `strength` | `number` | Strength: higher the number, greater the strength |
3118
3097
 
3119
- if a `sohl` property is present, a SoHL item of type "concoctiongear" will be created.
3098
+ if a `sohl` property is present, a SoHL item of type "concoctiongear" is created.
3120
3099
 
3121
3100
  | shared source | → sohl | → hm3 |
3122
3101
  | ----------------- | ----------------------- | ----- |
@@ -3142,9 +3121,9 @@ Note: `data.quantity` may not be specified; quantity is always set to 1.
3142
3121
  | `durability` | `number` | Gear durability |
3143
3122
  | `capacity` | `number` | HM3 container capacity (in lbs) |
3144
3123
 
3145
- if a `sohl` property is present, a SoHL item of type "containergear" will be created.
3124
+ if a `sohl` property is present, a SoHL item of type "containergear" is created.
3146
3125
 
3147
- if a `hm3` property is present, an HM3 item of type "containergear" will be created.
3126
+ if a `hm3` property is present, an HM3 item of type "containergear" is created.
3148
3127
 
3149
3128
  | shared source | → sohl | → hm3 |
3150
3129
  | ----------------- | ----------------------- | --------------------- |
@@ -3167,9 +3146,9 @@ SoHL reads a container’s capacity in pounds from `sohl.maxCapacity`.
3167
3146
  | `durability` | `number` | Gear durability |
3168
3147
  | `quantity` | `number` | HM3 gear quantity (default: 1) |
3169
3148
 
3170
- if a `sohl` property is present, a SoHL item of type "miscgear" will be created.
3149
+ if a `sohl` property is present, a SoHL item of type "miscgear" is created.
3171
3150
 
3172
- if a `hm3` property is present, an HM3 item of type "miscgear" will be created.
3151
+ if a `hm3` property is present, an HM3 item of type "miscgear" is created.
3173
3152
 
3174
3153
  | shared source | → sohl | → hm3 |
3175
3154
  | ----------------- | ----------------------- | ----------------- |
@@ -3205,7 +3184,7 @@ SoHL sets `system.quantity` to `1` for each compendium item.
3205
3184
  | `charges.value` | `number` | Current number of charges available, _null_ = charges not used |
3206
3185
  | `charges.max` | `number` | Maximum number of charges, _null_ = no maximum |
3207
3186
 
3208
- if a `sohl` property is present, a SoHL item of type "mystery" will be created.
3187
+ if a `sohl` property is present, a SoHL item of type "mystery" is created.
3209
3188
 
3210
3189
  `system.assocSkillCode` and `system.assocAffiliationCode` are authored under
3211
3190
  `sohl.system` as shortcodes and have no shared source; `data.assocSkill` and
@@ -3246,7 +3225,7 @@ if a `sohl` property is present, a SoHL item of type "mystery" will be created.
3246
3225
  | `charges.value` | `number` | Current number of charges available, _null_ = charges not used |
3247
3226
  | `charges.max` | `number` | Maximum number of charges, _null_ = no maximum |
3248
3227
 
3249
- if a `sohl` property is present, a SoHL item of type "mysticalability" will be created.
3228
+ if a `sohl` property is present, a SoHL item of type "mysticalability" is created.
3250
3229
 
3251
3230
  If an `hm3` property is present, an HM3 item is created, and `hm3.type` states which — `psionic`, `spell` or `invocation`. It is **authored, not derived from `subType`**: the ten mystical-ability subtypes do not partition onto HM3's three documents (a `spiritrite`, an `alchemy` and a `divination` each answer to none of them), so a derivation would be a guess with a plausible shape. A note that omits it is an error naming the note.
3252
3231
 
@@ -3284,9 +3263,9 @@ the item’s base mastery and level from `sohl.masteryLevelBase` and
3284
3263
  | `durability` | `number` | Gear durability |
3285
3264
  | `quantity` | `number` | HM3 gear quantity (default: 1) |
3286
3265
 
3287
- if a `sohl` property is present, a SoHL item of type "projectilegear" will be created.
3266
+ if a `sohl` property is present, a SoHL item of type "projectilegear" is created.
3288
3267
 
3289
- If an `hm3` property is present, then an HM3 item of type "missilegear" will be created.
3268
+ If an `hm3` property is present, an HM3 item of type "missilegear" is created.
3290
3269
 
3291
3270
  Note that `weapon` can also produce an HM3 `missilegear`. Since `(type, shortcode)` is a
3292
3271
  flat namespace, a `projectilegear` and a `weapon` sharing a shortcode would collide on the
@@ -3324,9 +3303,9 @@ SoHL sets `system.quantity` to `1` for each compendium item.
3324
3303
  | `masteryLevel` | `number` | HM3 mastery level |
3325
3304
  | `parentSkill` | `Address` | Parent skill this skill specializes |
3326
3305
 
3327
- if a `sohl` property is present, a SoHL item of type "skill" will be created.
3306
+ if a `sohl` property is present, a SoHL item of type "skill" is created.
3328
3307
 
3329
- If an `hm3` property is present, then an HM3 item of type "skill" will be created.
3308
+ If an `hm3` property is present, an HM3 item of type "skill" is created.
3330
3309
 
3331
3310
  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.
3332
3311
 
@@ -3367,7 +3346,7 @@ SoHL reads a skill’s base mastery from `sohl.masteryLevelBase`.
3367
3346
  | `courseDurationFormula` | `RollFormula` | Formula for the interval between course tests — shock, coma, infection |
3368
3347
  | `courseDurationBase` | `number` | That interval in seconds, stated outright instead of rolled |
3369
3348
 
3370
- if a `sohl` property is present, a SoHL item of type "trauma" will be created.
3349
+ if a `sohl` property is present, a SoHL item of type "trauma" is created.
3371
3350
 
3372
3351
  If an `hm3` property is present, an HM3 item is created. `hm3.type` must be specified as either `injury` or `trait`.
3373
3352
 
@@ -3406,7 +3385,7 @@ Note: `data.quantity` may not be specified. Quantity is always 1.
3406
3385
  | `quality` | `number` | Gear quality |
3407
3386
  | `durability` | `number` | Gear durability |
3408
3387
 
3409
- if a `sohl` property is present, a SoHL item of type "weapongear" will be created,
3388
+ if a `sohl` property is present, a SoHL item of type "weapongear" is created,
3410
3389
  carrying every strike mode the weapon has — melee and missile alike — on
3411
3390
  `system.strikeModes`.
3412
3391
 
@@ -3453,6 +3432,7 @@ In-world information about people, places, or concepts.
3453
3432
  | `weekdays` | `{ name, abbreviation? }[]` | The days of the week it names, in order; a calendar with no week writes none |
3454
3433
  | `seasons` | `{ name, abbreviation?, monthStart?, monthEnd?, dayStart?, dayEnd? }[]` | The seasons it marks, bounded by month or by day of year |
3455
3434
  | `eras` | `{ shortcode, name, abbreviation?, proclaimedBy?, start, end? }[]` | The year-counts kept in it, each addressed `<calendar shortcode>.<era shortcode>` |
3435
+ | `dateFormats` | `Map<slot, format>` | Calendaria display formats for this calendar |
3456
3436
  | `event` | `Map<field, value>` | A dated occurrence, with its sources, locations, reach, and relationships |
3457
3437
 
3458
3438
  `data.event` is available on every `lore` subType. It holds structured
@@ -3463,7 +3443,7 @@ chronology tools and does not appear as an infobox row.
3463
3443
 
3464
3444
  #### What a calendar note declares
3465
3445
 
3466
- **Those five keys belong to `subType: calendar` and to no other genre of
3446
+ **The calendar keys belong to `subType: calendar` and to no other genre of
3467
3447
  `lore`.** A note carrying one under any other subType is refused, and a calendar
3468
3448
  note that states no `months` and no `epoch` is refused too: a calendar divides
3469
3449
  the year and says where the count begins, and one that does neither is prose
@@ -3489,8 +3469,16 @@ data:
3489
3469
  abbreviation: AF
3490
3470
  proclaimedBy: vylarinmpr
3491
3471
  start: 1
3472
+ dateFormats: { full: "D MMMM, YYYY", yearLabel: "[Year] YYYY" }
3492
3473
  ```
3493
3474
 
3475
+ `dateFormats` accepts `short`, `long`, `full`, `time`, `weekHeader`,
3476
+ `yearHeader`, `yearLabel`, and `crossCalendar`. Each value is a Calendaria
3477
+ format string. Its date tokens may appear bare; ordinary words belong in
3478
+ `[brackets]` or `{braces}` so they print literally. A bare letter run that is
3479
+ not a Calendaria token is an error. The `time12` slot is outside this
3480
+ vocabulary.
3481
+
3494
3482
  **Position in the list is position in the year.** Days that belong to no month
3495
3483
  are a short month like any other: a five-day festival is one entry, and where it
3496
3484
  sits in the list is where it falls in the year. There is no intercalary
@@ -3545,12 +3533,6 @@ depicts what it depicts. A place's maps are therefore derived — every map whos
3545
3533
  The three differ only in the canvas defaults derived for them, which is why they
3546
3534
  are subTypes of one type rather than three types.
3547
3535
 
3548
- > The three are also readable as **types**, which is the shape notes in the
3549
- > wild still carry: a note writing `type: battlemap` is reported and told what
3550
- > to write instead, exactly as a note writing `type: character` is. A
3551
- > consumer's `sections` config keys off the type, so it takes one `map` entry
3552
- > rather than three.
3553
-
3554
3536
  **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.
3555
3537
 
3556
3538
  The `data:` fields, of which three are required:
@@ -3619,39 +3601,17 @@ address, not filling in a Foundry field.
3619
3601
 
3620
3602
  **Two unit conventions, deliberately.** Geometry — walls, doors, lights, tiles,
3621
3603
  sounds, region shapes — is authored in **pixels**, Foundry's native storage,
3622
- because a traced battlemap's walls do not lie on grid intersections (measured:
3623
- 97.8% do not). Map pins are authored in **grid squares**, commonly
3604
+ because traced walls often fall between grid intersections. Map pins are
3605
+ authored in **grid squares**, commonly
3624
3606
  half-integers, because that is how a person reads a position off a map. The two
3625
3607
  are told apart by their key: `position:` and segment or shape coordinates are
3626
3608
  pixels, `at:` is grid squares. Mixing them fails silently and visually in
3627
3609
  Foundry, so the build refuses rather than resolving.
3628
3610
 
3629
- > **These fields are read from `sohl:` today, and should move to `data:`.** A
3630
- > Scene is a core Foundry document — nothing about a map's geometry is
3631
- > system-specific, and HM3 would want the identical Scene. Authoring it under
3632
- > `sohl:` means a map produces nothing for a system-agnostic build and carries a
3633
- > SoHL infobox implying a specificity it does not have. It is the same class of
3634
- > mistake as storing the template priority in flags: the data went where the only
3635
- > available container was, rather than where it belongs. Three notes carry it today.
3636
-
3637
- **A map is always a leaf.** Its frontmatter references nothing outside itself.
3638
- `notes:` is a list of `[anchor, GridLocation]`, and each anchor names a heading in
3639
- **this map note's own body** — never an external note. A pin therefore opens the
3640
- map's own journal page, so the prose describing a spot on the map lives with the
3641
- map that shows it.
3642
-
3643
- That constraint is the point, not an accident of the format. A map that names no
3644
- other note can be reused by any place, any scenario and any package, and can be
3645
- moved between them without dragging references along. It also cannot carry a
3646
- dangling reference: only things pointing _at_ a map can break, which is the safe
3647
- direction for the failure to run.
3648
-
3649
- **So the reference runs from the place.** A `place` names its maps through
3650
- `data.maps`; the map says nothing about which places it depicts. That is the
3651
- opposite of how the pins run, and deliberately so — the pins point inward to keep
3652
- the map self-contained, and the place points outward because it is the thing that
3653
- knows which maps belong to it. A keep with three floor plans is a place naming
3654
- three maps, and none of those maps needs to know it is a keep.
3611
+ **Map pins stay within the map.** `notes:` names anchors in this map note's own
3612
+ body, so a pin opens the journal page describing that spot. `data.place` names
3613
+ the place depicted by the map when one exists. A place's map listing is derived
3614
+ from those map notes, so each map states the relationship once.
3655
3615
 
3656
3616
  ### type: place
3657
3617
 
@@ -3668,6 +3628,7 @@ three maps, and none of those maps needs to know it is a keep.
3668
3628
  | `data` property | Values | Description |
3669
3629
  | --------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
3670
3630
  | `demonym` | `string` | What a person from this place is called — a Vylarian |
3631
+ | `purpose` | `placeCharacter` tag | The reason a settlement, site, or structure exists |
3671
3632
  | `lore` | `Address[]` | Lore concerning this place — its peoples, its law, its calendar, its history |
3672
3633
  | `parents` | `Address[]` | Enclosing places within which this place is located |
3673
3634
  | `population` | `number` | Approximate population (precision 2 significant digits) |
@@ -3690,6 +3651,17 @@ three maps, and none of those maps needs to know it is a keep.
3690
3651
  | `moon.newOn` | `date` | A day it was new, written in the reference calendar |
3691
3652
  | `moon.eclipses` | `string` | `never`, `rare`, `occasional` or `frequent` |
3692
3653
 
3654
+ #### Why a place exists
3655
+
3656
+ `data.purpose` selects one of the place's own `placeCharacter` tags. It is
3657
+ optional on settlements, sites, and structures. A mining settlement on a coast
3658
+ can carry `tags: [mining, coastal]` and `data.purpose: mining`: the coast locates
3659
+ it; the mine explains its existence. The value must belong to the declared
3660
+ `placeCharacter` vocabulary and also appear among that note's tags. The tags
3661
+ include `ford`, `portage`, `pass`, and `well` for places founded around a crossing,
3662
+ a break in navigation, a mountain route, or scarce water. The body explains the
3663
+ specific history; the selected tag gives tables a consistent grouping value.
3664
+
3693
3665
  #### What a body states about itself
3694
3666
 
3695
3667
  **The four groups above are the world's own facts, and they are legal on a
@@ -3941,28 +3913,11 @@ settlement layer names the notable places and is complete nowhere, so the ratio
3941
3913
  reports how much of a region has been written rather than how much of it is
3942
3914
  urban.
3943
3915
 
3944
- **A place declares only what is true of ground.** Four properties were removed
3945
- because they were true of something else, and each removal has a home to go to.
3946
-
3947
- **`languages` is a fact about a polity.** A place's languages change when its
3948
- ruler changes, which is what makes them the ruler's property — and
3949
- `sohl.system.commonSkills` holds them. The authored corpus agrees: of 206
3950
- places carrying `languages`, 190 were settlements and 16 were regions, and not one
3951
- was a site, a structure or a feature. A ruin has no language.
3952
-
3953
- **`peoples` widens to `lore`.** It was the only lore-pointing property a place
3954
- had, so a place with a calendar, a body of law or a local history had nowhere to
3955
- cite it. Nothing is lost by widening: the target's own subType already
3956
- distinguishes a `folk` from a `law`, which is the same reason `affiliation`
3957
- carries no `pantheons`.
3958
-
3959
- **`summary` duplicated `description`**, which every note already has and which is
3960
- what the page renders.
3961
-
3962
- **`affiliations` and `maps` were the wrong end of a relation.** `affiliations` is
3963
- the inverse of `affiliation.domains`, and a relation authored from both ends
3964
- drifts the moment one is edited. `maps` moves onto the map, which now names the
3965
- place it depicts — see `type: map` below.
3916
+ **A place declares what is true of its ground.** Its governing affiliations
3917
+ carry their own `domains`; maps name the place they depict through `data.place`.
3918
+ The place's `lore` links cover its peoples, calendar, law and history. Political
3919
+ languages belong to a polity's `sohl.system.commonSkills`, while the note's
3920
+ `description` provides its page summary.
3966
3921
 
3967
3922
  ### type: scenario
3968
3923
 
@@ -3994,9 +3949,34 @@ Content prepared to be played — a situation with its cast, places, and possibl
3994
3949
  - reference: Out-of-world lookup material about the setting or system — correspondences, conversions, glossaries.
3995
3950
  - howto: A task with an outcome, written as the steps that reach it.
3996
3951
  - concept: An explanation of how something works and why it is shaped that way, read to understand rather than to follow.
3952
+ - settingguide: An orientation to a setting or region, bringing its places, peoples, institutions, and daily life into one usable frame with links to detailed notes.
3997
3953
 
3998
3954
  A `doc` declares no properties of its own.
3999
3955
 
3956
+ Choose `settingguide` for an entry point that stands on its own while guiding
3957
+ players and GMs into a setting. A `concept` explains one subject, a `reference`
3958
+ supports lookup, and a `userguide` explains how to operate the Foundry
3959
+ implementation. The setting guide can use ordinary SQL content tables and
3960
+ `:::secret` passages; its genre does not change its Address, journal, or page
3961
+ rendering.
3962
+
3963
+ ```markdown
3964
+ ---
3965
+ shortcode: reedflatsguide
3966
+ name: { full: The Reed Flats Guide }
3967
+ type: doc
3968
+ subType: settingguide
3969
+ description: A starting point for adventures in the Reed Flats.
3970
+ ---
3971
+
3972
+ The Reed Flats are a network of marsh settlements linked by river trade.
3973
+ Their routes, customs, and rival groups shape everyday journeys.
3974
+
3975
+ :::secret
3976
+ The old ferry route is known to the local guides.
3977
+ :::
3978
+ ```
3979
+
4000
3980
  The field is a genre and only a genre: it says what kind of page this is, never
4001
3981
  who reads it. A page written for a developer is a `howto` or a `concept` like
4002
3982
  any other, and the audience is the section it sits in.
@@ -4066,13 +4046,6 @@ Foundry **Macro**, and these two describe that document.
4066
4046
  | `macroType` | `script` | The Foundry macro type. Defaults to `script`, and `chat` is an error |
4067
4047
  | `macroScope` | `global \| actors \| actor` | How far the macro reaches. Defaults to `global` |
4068
4048
 
4069
- > **These fields are read from `sohl:` today, and should move to `data:`.** A
4070
- > Macro is a core Foundry document — nothing about a script's type or scope is
4071
- > system-specific — so authoring them under `sohl:` puts them where the only
4072
- > available container was rather than where they belong. It is the same mistake
4073
- > the map fields make, and no authored note carries either field today, so the
4074
- > move costs nothing.
4075
-
4076
4049
  **`macroType: chat` is an error, not an unimplemented feature.** A chat macro's
4077
4050
  `command` is chat text rather than source, so none of the `{#script}` fence
4078
4051
  rules describe it, and compiling one through this path would ship a macro whose
@@ -4185,8 +4158,7 @@ nothing.
4185
4158
 
4186
4159
  **`color` must be quoted**, and YAML gives no third option: `color: #7a4b2a`
4187
4160
  parses as `null` (a `#` after a space opens a comment) and `color: 000000` parses
4188
- as the number `0`. All 639 colour values across the five trees are already
4189
- written `"#RRGGBB"`.
4161
+ as the number `0`. Write `"#RRGGBB"` to retain the string.
4190
4162
 
4191
4163
  `parent` is an address, so a dangling one is an ordinary dead-address finding
4192
4164
  rather than a special-cased `Unknown folder id`, and a cycle is refused. Both are