@heroiclands/package-build 22.14.2 → 22.14.3

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 (65) hide show
  1. package/CHANGELOG.md +20 -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 +63 -35
  7. package/docs/configuration.md +16 -7
  8. package/docs/content-format.md +150 -181
  9. package/docs/getting-started.md +42 -11
  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-index.mjs +63 -36
  16. package/engine/content-lint.mjs +1 -2
  17. package/engine/content-package.mjs +2 -2
  18. package/engine/content-tables.mjs +4 -8
  19. package/engine/folder-notes.mjs +1 -2
  20. package/engine/foundry-entries.mjs +21 -6
  21. package/engine/frontmatter-lint.mjs +3 -4
  22. package/engine/frontmatter.mjs +5 -8
  23. package/engine/helpers.mjs +3 -4
  24. package/engine/index.mjs +3 -0
  25. package/engine/infobox.mjs +66 -2
  26. package/engine/item-registry.mjs +2 -2
  27. package/engine/note-vocabulary.mjs +83 -5
  28. package/engine/pack-config.mjs +2 -2
  29. package/engine/pack-router.mjs +2 -2
  30. package/engine/pdf-render.mjs +1 -1
  31. package/engine/pdf-toc.mjs +3 -3
  32. package/engine/retired-fields.mjs +1 -1
  33. package/engine/scene-levels.mjs +4 -4
  34. package/engine/schema-check.mjs +1 -1
  35. package/engine/site-build.mjs +134 -121
  36. package/engine/site-preview.mjs +293 -0
  37. package/engine/site-root.mjs +20 -2
  38. package/engine/social-tie-terms.mjs +11 -0
  39. package/engine/social-ties.mjs +84 -0
  40. package/engine/sql-tables.mjs +4 -9
  41. package/hm3/actors.mjs +1 -1
  42. package/package.json +3 -3
  43. package/sohl/item-fields.mjs +2 -2
  44. package/types/config.d.mts +8 -0
  45. package/types/content-config.d.mts +1 -1
  46. package/types/engine/address.d.mts +3 -5
  47. package/types/engine/asset-bindings.d.mts +6 -0
  48. package/types/engine/calendar-notes.d.mts +12 -0
  49. package/types/engine/content-index.d.mts +11 -0
  50. package/types/engine/content-package.d.mts +2 -2
  51. package/types/engine/folder-notes.d.mts +1 -2
  52. package/types/engine/frontmatter-lint.d.mts +1 -2
  53. package/types/engine/frontmatter.d.mts +2 -4
  54. package/types/engine/helpers.d.mts +3 -4
  55. package/types/engine/index.d.mts +1 -0
  56. package/types/engine/item-registry.d.mts +2 -2
  57. package/types/engine/note-vocabulary.d.mts +5 -6
  58. package/types/engine/pack-config.d.mts +1 -1
  59. package/types/engine/pack-router.d.mts +1 -1
  60. package/types/engine/pdf-render.d.mts +1 -1
  61. package/types/engine/pdf-toc.d.mts +1 -1
  62. package/types/engine/site-build.d.mts +36 -3
  63. package/types/engine/site-preview.d.mts +34 -0
  64. package/types/engine/social-tie-terms.d.mts +5 -0
  65. 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
@@ -2422,7 +2377,7 @@ For JournalEntries, the following rules apply:
2422
2377
 
2423
2378
  - `# ... {#spoilers}`: The contents of this header go into a page which is viewable only by the GM (`CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE`).
2424
2379
 
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:
2380
+ **Archetypes** describe characters in broad terms and help match them to an adventure. The list is:
2426
2381
 
2427
2382
  - warrior: Can hold a line and win a fight.
2428
2383
  - skirmisher: Fights light — ambush, missile, mobility.
@@ -2462,10 +2417,11 @@ Generates a living (or undead, or spirit) being.
2462
2417
  | `lore` | `Address[]` | Lore concerning this being — the people it is of, the standing it holds, the law it lives under |
2463
2418
  | `homes` | `Address[]` | Place the being calls home |
2464
2419
  | `affiliations` | `Address[]` | Affilliations (e.g., arcane/divine traditions, polities, etc) |
2420
+ | `socialTies` | `Map<Address, Tie>` | Defining support and opposition, directed from this being to others |
2465
2421
  | `gender` | `male \| female \| other` | Gender of the character |
2466
2422
  | `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 |
2423
+ | `born` | `date \| unknown` | When the being was born; absent, it was never born |
2424
+ | `died` | `date \| unknown` | When the being died; absent, it is alive |
2469
2425
  | `age` | `34 \| ~34` | Age in years, stated only to override what `born` says; `~` marks an estimate |
2470
2426
  | `ageYears` | `number` | Written by the compiler beside an `age` estimate — the `~` stripped; a note never authors this |
2471
2427
  | `height` | `number` | Height in meters |
@@ -2477,6 +2433,29 @@ Generates a living (or undead, or spirit) being.
2477
2433
  | `appearance.complexion` | `string` | Complexion |
2478
2434
  | `appearance.extra_features` | `string[]` | Extra features |
2479
2435
 
2436
+ #### The people who matter to a being
2437
+
2438
+ `data.socialTies` records relationships that define the subject's dossier, not
2439
+ everyone the subject knows. An absent entry says nothing about that relationship.
2440
+ An acquaintance belongs here when, for example, a guard will recognize the
2441
+ subject at a gate. A father and an independent adult son can have no tie in
2442
+ either note; a daughter who relies on her father may be his `dependent`.
2443
+
2444
+ The keys are bare Addresses with an explicit `being` or `affiliation` type.
2445
+ Values are `patron`, `friend`, `dependent`, `acquaintance`, `rival`, or
2446
+ `nemesis`. A patron supports the subject from greater power or status; a
2447
+ friend supports them from goodwill; a dependent relies on their protection;
2448
+ an acquaintance knows them without a strong disposition; a rival opposes them
2449
+ without implacable hostility; a nemesis opposes them personally and
2450
+ implacably. The infobox groups targets by term and links to their notes.
2451
+
2452
+ Each tie is one-sided. The target need not name the subject back, even when
2453
+ `patron` and `dependent` would describe opposite ends of the same support.
2454
+ Kinship, household membership, organizational membership, rank, and office are
2455
+ distinct facts. Their relevant effects may appear as ties, while their details
2456
+ belong in prose, `data.affiliations`, or governance fields. A tie has no
2457
+ mechanical modifier.
2458
+
2480
2459
  #### When a being was born, when it died, and how old it is
2481
2460
 
2482
2461
  **Absence asserts a fact rather than recording a gap**, and that is what these
@@ -2694,11 +2673,9 @@ unanchored page is keyed on its heading, so inserting a heading leaves every
2694
2673
  other page's id where it was. Two sibling pages sharing a heading is likewise a
2695
2674
  build error, matching the `MD024` lint rule that already refuses it.
2696
2675
 
2697
- If a `sohl` property is present, a SoHL actor of type "being" will be created.
2676
+ 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
2677
 
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.
2678
+ A SoHL "being" document is created, as will an "HM3" document.
2702
2679
 
2703
2680
  | shared source | → sohl | → hm3 |
2704
2681
  | ----------------- | ------ | ------------------- |
@@ -2829,8 +2806,7 @@ Definition of a level within the organization (e.g., Priest, Layperson, Member,
2829
2806
  owned outright, with no standing at law except through an owner. The title is
2830
2807
  what this body calls it and the description is how this body puts it, but the
2831
2808
  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.
2809
+ and cited by every ladder that confers it.
2834
2810
 
2835
2811
  That is also what makes a rank answerable across bodies. Without it, asking what
2836
2812
  a `Naukrátissa` may do means reading the Bethûan fleet's ladder; with it, the
@@ -3063,9 +3039,9 @@ Note: `data.quantity` may not be specified. Quantity is always 1.
3063
3039
  | `quality` | `number` | Gear quality |
3064
3040
  | `durability` | `number` | Gear durability |
3065
3041
 
3066
- If a `sohl` property is present, a SoHL item of type "armorgear" will be created.
3042
+ If a `sohl` property is present, a SoHL item of type "armorgear" is created.
3067
3043
 
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.
3044
+ The note type is `armorgear` in both systems.
3069
3045
 
3070
3046
  | shared source | → sohl | → hm3 |
3071
3047
  | ----------------- | ----------------------- | --------------- |
@@ -3080,7 +3056,7 @@ The note type is `armorgear` in both cases. The `gear` suffix was briefly rename
3080
3056
  | ------------------ | -------- | ------------------------------------------ |
3081
3057
  | `templatePriority` | `number` | Template priority, _null_ = not a template |
3082
3058
 
3083
- if a `hm3` property is present, an HM3 item of type "armorlocation" will be created.
3059
+ if a `hm3` property is present, an HM3 item of type "armorlocation" is created.
3084
3060
 
3085
3061
  | shared source | → sohl | → hm3 |
3086
3062
  | ------------- | ------ | ----- |
@@ -3092,7 +3068,7 @@ if a `hm3` property is present, an HM3 item of type "armorlocation" will be crea
3092
3068
  | ------------------ | -------- | ------------------------------------------ |
3093
3069
  | `templatePriority` | `number` | Template priority, _null_ = not a template |
3094
3070
 
3095
- if a `sohl` property is present, a SoHL item of type "attribute" will be created.
3071
+ if a `sohl` property is present, a SoHL item of type "attribute" is created.
3096
3072
 
3097
3073
  | shared source | → sohl | → hm3 |
3098
3074
  | ------------- | ------ | ----- |
@@ -3116,7 +3092,7 @@ if a `sohl` property is present, a SoHL item of type "attribute" will be created
3116
3092
  | `potency` | `na \| mild \| strong \| great` | Concoction Potency (mundane/exotic concoctions) |
3117
3093
  | `strength` | `number` | Strength: higher the number, greater the strength |
3118
3094
 
3119
- if a `sohl` property is present, a SoHL item of type "concoctiongear" will be created.
3095
+ if a `sohl` property is present, a SoHL item of type "concoctiongear" is created.
3120
3096
 
3121
3097
  | shared source | → sohl | → hm3 |
3122
3098
  | ----------------- | ----------------------- | ----- |
@@ -3142,9 +3118,9 @@ Note: `data.quantity` may not be specified; quantity is always set to 1.
3142
3118
  | `durability` | `number` | Gear durability |
3143
3119
  | `capacity` | `number` | HM3 container capacity (in lbs) |
3144
3120
 
3145
- if a `sohl` property is present, a SoHL item of type "containergear" will be created.
3121
+ if a `sohl` property is present, a SoHL item of type "containergear" is created.
3146
3122
 
3147
- if a `hm3` property is present, an HM3 item of type "containergear" will be created.
3123
+ if a `hm3` property is present, an HM3 item of type "containergear" is created.
3148
3124
 
3149
3125
  | shared source | → sohl | → hm3 |
3150
3126
  | ----------------- | ----------------------- | --------------------- |
@@ -3167,9 +3143,9 @@ SoHL reads a container’s capacity in pounds from `sohl.maxCapacity`.
3167
3143
  | `durability` | `number` | Gear durability |
3168
3144
  | `quantity` | `number` | HM3 gear quantity (default: 1) |
3169
3145
 
3170
- if a `sohl` property is present, a SoHL item of type "miscgear" will be created.
3146
+ if a `sohl` property is present, a SoHL item of type "miscgear" is created.
3171
3147
 
3172
- if a `hm3` property is present, an HM3 item of type "miscgear" will be created.
3148
+ if a `hm3` property is present, an HM3 item of type "miscgear" is created.
3173
3149
 
3174
3150
  | shared source | → sohl | → hm3 |
3175
3151
  | ----------------- | ----------------------- | ----------------- |
@@ -3205,7 +3181,7 @@ SoHL sets `system.quantity` to `1` for each compendium item.
3205
3181
  | `charges.value` | `number` | Current number of charges available, _null_ = charges not used |
3206
3182
  | `charges.max` | `number` | Maximum number of charges, _null_ = no maximum |
3207
3183
 
3208
- if a `sohl` property is present, a SoHL item of type "mystery" will be created.
3184
+ if a `sohl` property is present, a SoHL item of type "mystery" is created.
3209
3185
 
3210
3186
  `system.assocSkillCode` and `system.assocAffiliationCode` are authored under
3211
3187
  `sohl.system` as shortcodes and have no shared source; `data.assocSkill` and
@@ -3246,7 +3222,7 @@ if a `sohl` property is present, a SoHL item of type "mystery" will be created.
3246
3222
  | `charges.value` | `number` | Current number of charges available, _null_ = charges not used |
3247
3223
  | `charges.max` | `number` | Maximum number of charges, _null_ = no maximum |
3248
3224
 
3249
- if a `sohl` property is present, a SoHL item of type "mysticalability" will be created.
3225
+ if a `sohl` property is present, a SoHL item of type "mysticalability" is created.
3250
3226
 
3251
3227
  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
3228
 
@@ -3284,9 +3260,9 @@ the item’s base mastery and level from `sohl.masteryLevelBase` and
3284
3260
  | `durability` | `number` | Gear durability |
3285
3261
  | `quantity` | `number` | HM3 gear quantity (default: 1) |
3286
3262
 
3287
- if a `sohl` property is present, a SoHL item of type "projectilegear" will be created.
3263
+ if a `sohl` property is present, a SoHL item of type "projectilegear" is created.
3288
3264
 
3289
- If an `hm3` property is present, then an HM3 item of type "missilegear" will be created.
3265
+ If an `hm3` property is present, an HM3 item of type "missilegear" is created.
3290
3266
 
3291
3267
  Note that `weapon` can also produce an HM3 `missilegear`. Since `(type, shortcode)` is a
3292
3268
  flat namespace, a `projectilegear` and a `weapon` sharing a shortcode would collide on the
@@ -3324,9 +3300,9 @@ SoHL sets `system.quantity` to `1` for each compendium item.
3324
3300
  | `masteryLevel` | `number` | HM3 mastery level |
3325
3301
  | `parentSkill` | `Address` | Parent skill this skill specializes |
3326
3302
 
3327
- if a `sohl` property is present, a SoHL item of type "skill" will be created.
3303
+ if a `sohl` property is present, a SoHL item of type "skill" is created.
3328
3304
 
3329
- If an `hm3` property is present, then an HM3 item of type "skill" will be created.
3305
+ If an `hm3` property is present, an HM3 item of type "skill" is created.
3330
3306
 
3331
3307
  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
3308
 
@@ -3367,7 +3343,7 @@ SoHL reads a skill’s base mastery from `sohl.masteryLevelBase`.
3367
3343
  | `courseDurationFormula` | `RollFormula` | Formula for the interval between course tests — shock, coma, infection |
3368
3344
  | `courseDurationBase` | `number` | That interval in seconds, stated outright instead of rolled |
3369
3345
 
3370
- if a `sohl` property is present, a SoHL item of type "trauma" will be created.
3346
+ if a `sohl` property is present, a SoHL item of type "trauma" is created.
3371
3347
 
3372
3348
  If an `hm3` property is present, an HM3 item is created. `hm3.type` must be specified as either `injury` or `trait`.
3373
3349
 
@@ -3406,7 +3382,7 @@ Note: `data.quantity` may not be specified. Quantity is always 1.
3406
3382
  | `quality` | `number` | Gear quality |
3407
3383
  | `durability` | `number` | Gear durability |
3408
3384
 
3409
- if a `sohl` property is present, a SoHL item of type "weapongear" will be created,
3385
+ if a `sohl` property is present, a SoHL item of type "weapongear" is created,
3410
3386
  carrying every strike mode the weapon has — melee and missile alike — on
3411
3387
  `system.strikeModes`.
3412
3388
 
@@ -3453,6 +3429,7 @@ In-world information about people, places, or concepts.
3453
3429
  | `weekdays` | `{ name, abbreviation? }[]` | The days of the week it names, in order; a calendar with no week writes none |
3454
3430
  | `seasons` | `{ name, abbreviation?, monthStart?, monthEnd?, dayStart?, dayEnd? }[]` | The seasons it marks, bounded by month or by day of year |
3455
3431
  | `eras` | `{ shortcode, name, abbreviation?, proclaimedBy?, start, end? }[]` | The year-counts kept in it, each addressed `<calendar shortcode>.<era shortcode>` |
3432
+ | `dateFormats` | `Map<slot, format>` | Calendaria display formats for this calendar |
3456
3433
  | `event` | `Map<field, value>` | A dated occurrence, with its sources, locations, reach, and relationships |
3457
3434
 
3458
3435
  `data.event` is available on every `lore` subType. It holds structured
@@ -3463,7 +3440,7 @@ chronology tools and does not appear as an infobox row.
3463
3440
 
3464
3441
  #### What a calendar note declares
3465
3442
 
3466
- **Those five keys belong to `subType: calendar` and to no other genre of
3443
+ **The calendar keys belong to `subType: calendar` and to no other genre of
3467
3444
  `lore`.** A note carrying one under any other subType is refused, and a calendar
3468
3445
  note that states no `months` and no `epoch` is refused too: a calendar divides
3469
3446
  the year and says where the count begins, and one that does neither is prose
@@ -3489,8 +3466,16 @@ data:
3489
3466
  abbreviation: AF
3490
3467
  proclaimedBy: vylarinmpr
3491
3468
  start: 1
3469
+ dateFormats: { full: "D MMMM, YYYY", yearLabel: "[Year] YYYY" }
3492
3470
  ```
3493
3471
 
3472
+ `dateFormats` accepts `short`, `long`, `full`, `time`, `weekHeader`,
3473
+ `yearHeader`, `yearLabel`, and `crossCalendar`. Each value is a Calendaria
3474
+ format string. Its date tokens may appear bare; ordinary words belong in
3475
+ `[brackets]` or `{braces}` so they print literally. A bare letter run that is
3476
+ not a Calendaria token is an error. The `time12` slot is outside this
3477
+ vocabulary.
3478
+
3494
3479
  **Position in the list is position in the year.** Days that belong to no month
3495
3480
  are a short month like any other: a five-day festival is one entry, and where it
3496
3481
  sits in the list is where it falls in the year. There is no intercalary
@@ -3545,12 +3530,6 @@ depicts what it depicts. A place's maps are therefore derived — every map whos
3545
3530
  The three differ only in the canvas defaults derived for them, which is why they
3546
3531
  are subTypes of one type rather than three types.
3547
3532
 
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
3533
  **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
3534
 
3556
3535
  The `data:` fields, of which three are required:
@@ -3619,39 +3598,17 @@ address, not filling in a Foundry field.
3619
3598
 
3620
3599
  **Two unit conventions, deliberately.** Geometry — walls, doors, lights, tiles,
3621
3600
  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
3601
+ because traced walls often fall between grid intersections. Map pins are
3602
+ authored in **grid squares**, commonly
3624
3603
  half-integers, because that is how a person reads a position off a map. The two
3625
3604
  are told apart by their key: `position:` and segment or shape coordinates are
3626
3605
  pixels, `at:` is grid squares. Mixing them fails silently and visually in
3627
3606
  Foundry, so the build refuses rather than resolving.
3628
3607
 
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.
3608
+ **Map pins stay within the map.** `notes:` names anchors in this map note's own
3609
+ body, so a pin opens the journal page describing that spot. `data.place` names
3610
+ the place depicted by the map when one exists. A place's map listing is derived
3611
+ from those map notes, so each map states the relationship once.
3655
3612
 
3656
3613
  ### type: place
3657
3614
 
@@ -3668,6 +3625,7 @@ three maps, and none of those maps needs to know it is a keep.
3668
3625
  | `data` property | Values | Description |
3669
3626
  | --------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
3670
3627
  | `demonym` | `string` | What a person from this place is called — a Vylarian |
3628
+ | `purpose` | `placeCharacter` tag | The reason a settlement, site, or structure exists |
3671
3629
  | `lore` | `Address[]` | Lore concerning this place — its peoples, its law, its calendar, its history |
3672
3630
  | `parents` | `Address[]` | Enclosing places within which this place is located |
3673
3631
  | `population` | `number` | Approximate population (precision 2 significant digits) |
@@ -3690,6 +3648,17 @@ three maps, and none of those maps needs to know it is a keep.
3690
3648
  | `moon.newOn` | `date` | A day it was new, written in the reference calendar |
3691
3649
  | `moon.eclipses` | `string` | `never`, `rare`, `occasional` or `frequent` |
3692
3650
 
3651
+ #### Why a place exists
3652
+
3653
+ `data.purpose` selects one of the place's own `placeCharacter` tags. It is
3654
+ optional on settlements, sites, and structures. A mining settlement on a coast
3655
+ can carry `tags: [mining, coastal]` and `data.purpose: mining`: the coast locates
3656
+ it; the mine explains its existence. The value must belong to the declared
3657
+ `placeCharacter` vocabulary and also appear among that note's tags. The tags
3658
+ include `ford`, `portage`, `pass`, and `well` for places founded around a crossing,
3659
+ a break in navigation, a mountain route, or scarce water. The body explains the
3660
+ specific history; the selected tag gives tables a consistent grouping value.
3661
+
3693
3662
  #### What a body states about itself
3694
3663
 
3695
3664
  **The four groups above are the world's own facts, and they are legal on a
@@ -3941,28 +3910,11 @@ settlement layer names the notable places and is complete nowhere, so the ratio
3941
3910
  reports how much of a region has been written rather than how much of it is
3942
3911
  urban.
3943
3912
 
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.
3913
+ **A place declares what is true of its ground.** Its governing affiliations
3914
+ carry their own `domains`; maps name the place they depict through `data.place`.
3915
+ The place's `lore` links cover its peoples, calendar, law and history. Political
3916
+ languages belong to a polity's `sohl.system.commonSkills`, while the note's
3917
+ `description` provides its page summary.
3966
3918
 
3967
3919
  ### type: scenario
3968
3920
 
@@ -3994,9 +3946,34 @@ Content prepared to be played — a situation with its cast, places, and possibl
3994
3946
  - reference: Out-of-world lookup material about the setting or system — correspondences, conversions, glossaries.
3995
3947
  - howto: A task with an outcome, written as the steps that reach it.
3996
3948
  - concept: An explanation of how something works and why it is shaped that way, read to understand rather than to follow.
3949
+ - 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
3950
 
3998
3951
  A `doc` declares no properties of its own.
3999
3952
 
3953
+ Choose `settingguide` for an entry point that stands on its own while guiding
3954
+ players and GMs into a setting. A `concept` explains one subject, a `reference`
3955
+ supports lookup, and a `userguide` explains how to operate the Foundry
3956
+ implementation. The setting guide can use ordinary SQL content tables and
3957
+ `:::secret` passages; its genre does not change its Address, journal, or page
3958
+ rendering.
3959
+
3960
+ ```markdown
3961
+ ---
3962
+ shortcode: reedflatsguide
3963
+ name: { full: The Reed Flats Guide }
3964
+ type: doc
3965
+ subType: settingguide
3966
+ description: A starting point for adventures in the Reed Flats.
3967
+ ---
3968
+
3969
+ The Reed Flats are a network of marsh settlements linked by river trade.
3970
+ Their routes, customs, and rival groups shape everyday journeys.
3971
+
3972
+ :::secret
3973
+ The old ferry route is known to the local guides.
3974
+ :::
3975
+ ```
3976
+
4000
3977
  The field is a genre and only a genre: it says what kind of page this is, never
4001
3978
  who reads it. A page written for a developer is a `howto` or a `concept` like
4002
3979
  any other, and the audience is the section it sits in.
@@ -4066,13 +4043,6 @@ Foundry **Macro**, and these two describe that document.
4066
4043
  | `macroType` | `script` | The Foundry macro type. Defaults to `script`, and `chat` is an error |
4067
4044
  | `macroScope` | `global \| actors \| actor` | How far the macro reaches. Defaults to `global` |
4068
4045
 
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
4046
  **`macroType: chat` is an error, not an unimplemented feature.** A chat macro's
4077
4047
  `command` is chat text rather than source, so none of the `{#script}` fence
4078
4048
  rules describe it, and compiling one through this path would ship a macro whose
@@ -4185,8 +4155,7 @@ nothing.
4185
4155
 
4186
4156
  **`color` must be quoted**, and YAML gives no third option: `color: #7a4b2a`
4187
4157
  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"`.
4158
+ as the number `0`. Write `"#RRGGBB"` to retain the string.
4190
4159
 
4191
4160
  `parent` is an address, so a dangling one is an ordinary dead-address finding
4192
4161
  rather than a special-cased `Unknown folder id`, and a cycle is refused. Both are