@heroiclands/package-build 23.4.1 → 23.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +29 -25
  3. package/assets/images/provenance.yaml +1 -0
  4. package/bin/package-build.mjs +259 -28
  5. package/ci/prettier.mjs +73 -0
  6. package/config.mjs +10 -0
  7. package/container.mjs +232 -1
  8. package/content-config.mjs +5 -1
  9. package/docs/api.md +100 -52
  10. package/docs/authoring/assets.md +16 -1
  11. package/docs/commands.md +154 -7
  12. package/docs/configuration.md +39 -2
  13. package/docs/content-format.md +30 -6
  14. package/docs/guides/e2e.md +38 -0
  15. package/docs/guides/icons.md +5 -0
  16. package/docs/guides/release-and-deploy.md +36 -1
  17. package/docs/reference/format-details.md +180 -34
  18. package/docs/reference/note-types.md +69 -69
  19. package/engine/asset-index.mjs +63 -22
  20. package/engine/asset-references.mjs +355 -0
  21. package/engine/asset-shipping.mjs +363 -0
  22. package/engine/asset-types.mjs +70 -0
  23. package/engine/base-compiler.mjs +24 -1
  24. package/engine/cdn-publish.mjs +618 -0
  25. package/engine/index.mjs +6 -0
  26. package/engine/note-vocabulary.mjs +4 -1
  27. package/engine/pathnames.mjs +21 -10
  28. package/engine/rank-ladder.mjs +40 -16
  29. package/engine/scenes.mjs +1 -1
  30. package/engine/standing-terms.mjs +3 -1
  31. package/package.json +4 -3
  32. package/packagebuild-metadata.jsonl +13 -13
  33. package/release.mjs +69 -7
  34. package/sohl/actors.mjs +12 -4
  35. package/sohl/default-item-art.mjs +23 -19
  36. package/types/config.d.mts +7 -0
  37. package/types/container.d.mts +137 -1
  38. package/types/engine/asset-index.d.mts +3 -2
  39. package/types/engine/asset-references.d.mts +136 -0
  40. package/types/engine/asset-shipping.d.mts +240 -0
  41. package/types/engine/asset-types.d.mts +36 -0
  42. package/types/engine/base-compiler.d.mts +7 -1
  43. package/types/engine/cdn-publish.d.mts +311 -0
  44. package/types/engine/index.d.mts +2 -0
  45. package/types/engine/pathnames.d.mts +2 -1
  46. package/types/engine/rank-ladder.d.mts +25 -6
  47. package/types/release.d.mts +5 -3
  48. package/types/sohl/default-item-art.d.mts +2 -1
@@ -917,7 +917,9 @@ of ownership** and every surface derives its own address from it.
917
917
 
918
918
  **The first segment names the package, when an `assets/` follows it.**
919
919
  Everything after that `assets/` is the _suffix_ — the path inside the package's
920
- shipped tree, and the one piece every derived form is built from. A pathname
920
+ `assets/` directory, and the one piece every derived form is built from. A
921
+ suffix under `icons/` or `images/` reaches Foundry and the website in the
922
+ deployed layout, by type root and basename alone. A pathname
921
923
  that does not open `<package>/assets/` belongs to the package being compiled,
922
924
  and the whole of it is the suffix.
923
925
 
@@ -931,7 +933,7 @@ ships as the Foundry module `harn-ensemble`) writing `images/map.webp`:
931
933
 
932
934
  | Surface | Address |
933
935
  | ----------- | ---------------------------------------------------------- |
934
- | **Foundry** | `modules/harn-ensemble/assets/images/map.webp` |
936
+ | **Foundry** | `modules/harn-ensemble/images/map.webp` |
935
937
  | **Local** | `assets/images/map.webp` |
936
938
  | **Web** | `https://cdn.heroiclands.org/harnensemble/images/map.webp` |
937
939
  | **Book** | `assets/images/map.webp`, staged beside the Typst source |
@@ -941,9 +943,9 @@ system ships and this repository does not hold:
941
943
 
942
944
  | Surface | Address |
943
945
  | ----------- | ------------------------------------------------------------- |
944
- | **Foundry** | `systems/sohl/assets/icons/noun/shieldnn.svg` |
946
+ | **Foundry** | `systems/sohl/icons/shieldnn.svg` |
945
947
  | **Local** | `assets/icons/noun/shieldnn.svg`, in the `sohl` repository |
946
- | **Web** | `https://cdn.heroiclands.org/sohl/icons/noun/shieldnn.svg` |
948
+ | **Web** | `https://cdn.heroiclands.org/sohl/icons/shieldnn.svg` |
947
949
  | **Book** | not carried, unless a declared replacement carries it instead |
948
950
 
949
951
  **The content package name and Foundry id are separate values.**
@@ -1863,7 +1865,7 @@ nothing about itself, compiles into no document, and publishes no page, so the
1863
1865
  | `path` | the walk | Where the file sits inside the emitting package's asset directory. |
1864
1866
  | `attribution` | provenance | The person holding the rights, to whom attribution is legally due. |
1865
1867
  | `source` | provenance | Where it came from — a URL, or a sentence. |
1866
- | `ai` | provenance | Whether the file is machine-generated — strictly the YAML boolean `true` or `false`; any other value is a finding, never a stringified record. Unstated resolves to `false`. |
1868
+ | `ai` | provenance | Whether the file is machine-generated — strictly the YAML boolean `true` or `false`; any other value is a finding, never a stringified record. Required: a record that leaves it out is a finding, never read as `false`. |
1867
1869
  | `license` | provenance | The licence it is used under — an SPDX identifier, or terms. |
1868
1870
  | `notes` | provenance | Anything else a person reading the attribution needs. |
1869
1871
  | `role` | provenance | What the picture is for — `portrait`, `emblem`, `banner`, `plate` or `map`. Absent for an ordinary picture; a value outside that set is a finding. The `image` type only — a value on an `icon` address is a finding and is dropped. |
@@ -1874,17 +1876,21 @@ Every field is present on every record, blank where nothing states one. A fixed
1874
1876
  shape is what lets a consumer read `asset.license` without first asking whether
1875
1877
  the package happened to record one.
1876
1878
 
1877
- **`path` is what makes resolution one step.** The record is emitted by the
1878
- package holding the bytes, so the path is that package's path and each consumer
1879
- joins its own root onto it:
1880
-
1881
- | Consumer | `thalorna-none-image-thorn` resolves to |
1882
- | -------- | ------------------------------------------------------------- |
1883
- | Web | `<cdn base>/thalorna/images/beings/characters/thorn.webp` |
1884
- | Book | `<asset base>/thalorna/images/beings/characters/thorn.webp` |
1885
- | Foundry | `modules/thalorna/assets/images/beings/characters/thorn.webp` |
1886
-
1887
- Foundry paths use the **Foundry package id**. The two values are both `thalorna`
1879
+ **`path` is the source path.** The record is emitted by the package holding
1880
+ the bytes, so the path is where the file sits in that package's repository. The
1881
+ book stages from it; Foundry and the website serve the **deployed layout**, the
1882
+ type root and the basename alone:
1883
+
1884
+ | Consumer | `thalorna-none-image-thorn` resolves to |
1885
+ | -------- | ----------------------------------------------------------- |
1886
+ | Web | `<cdn base>/thalorna/images/thorn.webp` |
1887
+ | Book | `<asset base>/thalorna/images/beings/characters/thorn.webp` |
1888
+ | Foundry | `modules/thalorna/images/thorn.webp` |
1889
+
1890
+ Foundry and the website serve a picture under `icons/` or `images/` flat, by
1891
+ type root and basename — see [what a package ships](#what-a-package-ships);
1892
+ every other file keeps its path under `assets/`, and the local and book forms
1893
+ keep the repository's arrangement. Foundry paths use the **Foundry package id**. The two values are both `thalorna`
1888
1894
  for this image; `harnensemble` content uses `harn-ensemble` in its Foundry paths.
1889
1895
 
1890
1896
  ##### What a role declares
@@ -1987,19 +1993,131 @@ One address resolves its record in this order:
1987
1993
  2. Otherwise the nearest `provenance.yaml`, searching the file's own directory
1988
1994
  and then its ancestors, **stopping at the type root**. A record above the root
1989
1995
  would speak for trees it says nothing about.
1990
- 3. Otherwise nothing, and the provenance fields are blank. A package that
1991
- records no attribution is a fact to state, not a walk to fail.
1996
+ 3. Otherwise nothing, and that is a **finding at the file**: every asset is
1997
+ covered by a record, because the stage decides from the record whether the
1998
+ file ships.
1999
+
2000
+ Resolution is per file. One directory routinely holds files its own
2001
+ `provenance.yaml` covers beside files a sidecar answers for, and each is
2002
+ resolved on its own.
1992
2003
 
1993
2004
  **The nearest record is the whole answer.** It is never merged with the one
1994
2005
  above it, so a record states every key it means to claim — which is what lets a
1995
2006
  reader open one file and know what the tree beneath it carries, instead of
1996
2007
  resolving a chain by hand.
1997
2008
 
1998
- That is also why `attribution` and `license` are **required in any record that
1999
- exists**: a key left out is not inherited from above, it is simply absent, so a
2000
- record stating neither leaves every file it covers with no rights holder and no
2001
- terms. Omitting one is a finding. `source`, `ai`, `notes` and `role` stay optional,
2002
- because a blank is a truthful answer for each of them.
2009
+ That is also why `attribution`, `license` and `ai` are **required in every
2010
+ record**: a key left out is not inherited from above, it is simply absent, so a
2011
+ record stating none leaves every file it covers with no rights holder, no terms,
2012
+ or no statement of whether a machine made it. Omitting one is a finding at the
2013
+ record, which an empty file is held to as well. An unstated `ai` is never read as
2014
+ `false`. `source`, `notes` and `role` stay optional, because a blank is a
2015
+ truthful answer for each of them.
2016
+
2017
+ ##### What a package ships
2018
+
2019
+ The repository keeps every picture; the Foundry package carries a filtered copy.
2020
+ `package-build assets` stages a file from `assets/icons/` or `assets/images/`
2021
+ only when its resolved record says `ai: false` **and** its `license` is one of:
2022
+
2023
+ | Licence | Spelled |
2024
+ | ---------------------------- | ---------------------------- |
2025
+ | Public domain | `LicenseRef-PublicDomain` |
2026
+ | Creative Commons Zero | `CC0-1.0`, or `CC0` |
2027
+ | Creative Commons Attribution | `CC-BY-3.0`, `CC-BY-4.0` |
2028
+ | Attribution-ShareAlike | `CC-BY-SA-4.0` |
2029
+ | Noun Project Pro | `LicenseRef-NounProject-Pro` |
2030
+
2031
+ The spelling is matched exactly; nothing is normalised. A package adds licences
2032
+ to this list with `packageBuild.shippingLicenses`, and ships under the list
2033
+ together with its additions. `ai: true` is never shipped, whatever the
2034
+ additions say. Audio is not filtered
2035
+ and not laid out flat — a package stages it with an ordinary
2036
+ `packageBuild.assets` row.
2037
+
2038
+ **The deployed layout is flat.** A shipped picture sits at the package root as
2039
+ `icons/<basename>` or `images/<basename>`, whatever directories hold it in the
2040
+ repository. The root is the type and the basename is the shortcode with its
2041
+ extension, so the path follows from the address alone, and a root's shortcodes
2042
+ being one namespace is what keeps the flattening collision-free. The same
2043
+ layout serves Foundry (`modules/thalorna/images/thorn.webp`), the website's CDN
2044
+ (`<cdn base>/thalorna/images/thorn.webp`), and every location a world copies
2045
+ the pictures to.
2046
+
2047
+ **The package's asset set**, `asset-sets/default.json` under the package
2048
+ root, lists what it ships. The name is fixed, so a world always finds the
2049
+ package's own set there:
2050
+
2051
+ ```json
2052
+ {
2053
+ "name": "Thalorna — shipped art",
2054
+ "package": "thalorna",
2055
+ "assets": [
2056
+ {
2057
+ "address": "thalorna-none-icon-anvil",
2058
+ "source": "../icons/anvil.svg",
2059
+ "target": "icons/anvil.svg",
2060
+ "size": 412
2061
+ }
2062
+ ]
2063
+ }
2064
+ ```
2065
+
2066
+ | Key | What it says |
2067
+ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2068
+ | `name` | The set's display name: the package's manifest title with ` — shipped art` appended, or the content package without a title. |
2069
+ | `package` | The content package — the addresses' first segment, not the Foundry id. |
2070
+ | `assets` | One entry per file, in `target` order. |
2071
+ | `address` | The file's canonical address. |
2072
+ | `target` | The file's path relative to the location's root, in the deployed layout, extension included. |
2073
+ | `source` | Where the file is fetched from, resolved against the URL of the set file holding it — `new URL(source, setUrl)`. The package's own set sits in `asset-sets/`, so its `source` is `../<target>`. |
2074
+ | `size` | The staged file's size in bytes. |
2075
+
2076
+ `target` is never the record's source `path`. Each release attaches
2077
+ `icons.zip` and `images.zip` beside the package archive: one picture root of
2078
+ the stage each, with `asset-sets/default.json`, for a GM placing the files by
2079
+ hand.
2080
+
2081
+ ##### Every reference states its address
2082
+
2083
+ A compiled document names a picture by its Foundry path, and beside each
2084
+ reference it states the picture's canonical address, so a world can rebuild the
2085
+ path for wherever it keeps the file:
2086
+
2087
+ - **HTML.** Each `<img>` whose `src` names an asset carries
2088
+ `data-asset="<address>"`.
2089
+ - **Path fields.** The address sits in `flags.<system>.assets` on the document
2090
+ holding the field, as nested objects mirroring the field's path — never a
2091
+ dotted key, which Foundry's `expandObject` splits. `<system>` is the system
2092
+ the document is compiled for, its `_stats.systemId`.
2093
+
2094
+ | Document | Flag on | Keys |
2095
+ | ------------------------------------------- | ---------------------- | ------------- |
2096
+ | Actor, Item, ActiveEffect, Macro, RollTable | the document | `img` |
2097
+ | Actor token art | `prototypeToken.flags` | `texture.src` |
2098
+ | Table result | the result | `img` |
2099
+ | Journal image page | the page | `src` |
2100
+
2101
+ ```json
2102
+ "img": "modules/thalorna/icons/anvil.svg",
2103
+ "flags": { "sohl": { "assets": { "img": "thalorna-none-icon-anvil" } } },
2104
+ "prototypeToken": {
2105
+ "texture": { "src": "modules/thalorna/icons/anvil.svg" },
2106
+ "flags": { "sohl": { "assets": { "texture": { "src": "thalorna-none-icon-anvil" } } } }
2107
+ }
2108
+ ```
2109
+
2110
+ Embedded documents — an actor's items and effects, a journal's pages, an
2111
+ Adventure's contents — each carry their own. A path or an `<img>` naming no
2112
+ asset is left as it is.
2113
+
2114
+ **A picture the package does not ship draws a stand-in.** A reference to an
2115
+ excluded picture compiles to its package's own stand-in,
2116
+ `<package>-none-icon-missing` or `<package>-none-image-missing` — a picture
2117
+ under `icons/` or `images/` named `missing`, human-made and licensed to ship —
2118
+ and keeps the excluded picture's address in its flag or `data-asset`. A
2119
+ package that leaves out a picture a note references and ships no stand-in of
2120
+ that type fails the build with an error at the referencing note.
2003
2121
 
2004
2122
  ##### A declared replacement resolves first
2005
2123
 
@@ -4419,18 +4537,19 @@ a propertied or senatorial order supplies nearly everyone who holds them, it is
4419
4537
  **Democracy**.
4420
4538
 
4421
4539
  The distinction is not academic: it is the difference between a ladder whose top
4422
- rungs are a class one is born or bought into, and one whose top rungs are an
4423
- office one is voted into and then vacates. A republic's ladder therefore carries
4424
- **both** — the civic status (Citizen) and the standing in the governing body above
4425
- it (Senator) — because a citizen is not a member of the Senate, and conflating the
4426
- two is the commonest way to get a non-monarchy wrong.
4540
+ rungs are a class one is born or bought into, and a body whose highest posts are
4541
+ offices one is voted into and then vacates. A republic's ladder therefore carries
4542
+ **both** standings — the civic status (Citizen) and the standing in the governing
4543
+ body above it (Senator) — because a citizen is not a member of the Senate, and
4544
+ conflating the two is the commonest way to get a non-monarchy wrong. The
4545
+ magistracies held for a term are offices, not rungs.
4427
4546
 
4428
4547
  **Rank**
4429
4548
  Definition of a level within the organization (e.g., Priest, Layperson, Member, Gang Leader, Master, etc.)
4430
4549
 
4431
4550
  | Property | Required | Values | Description |
4432
4551
  | ------------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------ |
4433
- | `level` | yes | integer | The ranking within the affiliation, increasing values starting with 1, 0 indicates intentional exclusion (expulsion/excommunication) |
4552
+ | `level` | yes | integer | The ranking within the affiliation: 0 those it excludes (expelled, excommunicated), 1 its ordinary members, higher values above them |
4434
4553
  | `title` | yes | string | Title associated with the Rank |
4435
4554
  | `description` | yes | string | Description of the Rank |
4436
4555
  | `lore` | no | `Address` | The standing this rank _is_ — a `lore` note of subType `law`, shared with every other body that confers the same thing |
@@ -4451,11 +4570,37 @@ moving two lines renames the standing. A body that confers two titles at one
4451
4570
  standing writes them as one rung — `title: Knight or Dame` — or gives each its
4452
4571
  own level.
4453
4572
 
4573
+ **Every ladder has rung 0 and rung 1.** Level 1 is the ordinary member, and level
4574
+ 0 names those the body excludes. A body that can name its members can name those
4575
+ it shuts out, whether or not it ever does, so an affiliation whose
4576
+ `governance.ranks` lacks either is an **error** at the `ranks` key, as is one
4577
+ with no ladder at all. The level-0 title is the body's own word for the excluded
4578
+ — Outlaw, Apostate, Níðing — never a generic "Excluded".
4579
+
4580
+ Those two rungs are the minimum. Add rungs above level 1 only where the body
4581
+ truly has them, generally no more than five. **A rung is a standing, what a member
4582
+ is; an office is a post, what a member does.** Leadership is normally an office
4583
+ held by the top rank rather than a rank above it, and a rank never falls when an
4584
+ office is left: a bishop stays a bishop after being Presiding Bishop.
4585
+
4586
+ ```yaml
4587
+ data:
4588
+ governance:
4589
+ ranks:
4590
+ - { level: 0, title: Apostate, description: Put out of the communion. }
4591
+ - { level: 1, title: Layman, description: A baptised member of the faith. }
4592
+ - { level: 2, title: Priest, description: Ordained to serve a parish. }
4593
+ - { level: 3, title: Bishop, description: Consecrated to oversee a diocese. }
4594
+ offices:
4595
+ Presiding Bishop: Chairs the synod of bishops for a term.
4596
+ ```
4597
+
4454
4598
  ```yaml
4455
4599
  data:
4456
4600
  governance:
4457
4601
  ranks:
4458
4602
  - { level: 0, title: Vrystrith, description: Kinless and owed nothing. }
4603
+ - { level: 1, title: Kinsman, description: Born or adopted into a household. }
4459
4604
  - { level: 4, title: Fródrád, description: A householder whose word carries. }
4460
4605
  - level: 5
4461
4606
  title: Hárár
@@ -4491,7 +4636,7 @@ rank names the standing, and the standing says.
4491
4636
  | `symbol` | `string` | Its emblem in words: a feather atop a golden scale, a chisel carving a star |
4492
4637
  | `governance.model` | `GovernanceModel` | Type of government structure, if applicable |
4493
4638
  | `governance.summary` | `string` | summary of the governance situation |
4494
- | `governance.ranks` | `Rank[]` | The ranks available to members of the affiliation |
4639
+ | `governance.ranks` | `Rank[]` | The ranks the affiliation confers; level 0 and level 1 are required |
4495
4640
  | `governance.ranks[].level` | `integer`, required | The rung's position on this body's ladder; a member's `rank` names it |
4496
4641
  | `governance.ranks[].title` | `string`, required | What the standing is called |
4497
4642
  | `governance.ranks[].description` | `string`, required | What the standing is |
@@ -4541,14 +4686,15 @@ the ranks and offices that exist, and a membership names which of them it holds.
4541
4686
  **Rank is the organisation's, not the member's.** A bare `level: 4` on a
4542
4687
  membership says nothing on its own; it means _Knight_ only because the polity
4543
4688
  declared that rung. So the ladder is authored once, on the body that confers it,
4544
- and a member's rank is an index into it. Level 0 is reserved for the excluded —
4545
- outlawed, expelled, excommunicated — which is a standing the organisation still
4546
- recognises, and so still has to define.
4689
+ and a member's rank is an index into it. Level 0 is the excluded — outlawed,
4690
+ expelled, excommunicated — which is a standing the organisation still
4691
+ recognises, and so every ladder defines it beside level 1.
4547
4692
 
4548
4693
  **Offices are named, not ranked.** A Chancellor and a Marshal are both great
4549
4694
  officers and neither is above the other, so an office is a key with a
4550
4695
  description rather than a rung — and a being may hold an office at any rank, or a
4551
- rank with no office at all.
4696
+ rank with no office at all. The head of a body is usually an office of this kind,
4697
+ held by a member of the top rank, who keeps that rank when the term ends.
4552
4698
 
4553
4699
  An office may remain a string description or declare a `description` and a
4554
4700
  `holders` list. Each holder names a `being` Address; optional `start` and `end`
@@ -118,78 +118,78 @@ These fields are accepted by every note type. A field's value can still be irrel
118
118
 
119
119
  **Subtypes:** `guild`, `order`, `polity`, `faithtradition`, `arcanetradition`, `spirittradition`, `lineage`, `venture`, `criminal`, `governmental`, `fellowship`.
120
120
 
121
- | Field | Shape | Meaning |
122
- | ------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
123
- | `data.icon` | an Address | The document's profile art — an `icon` address, resolved into `img`. |
124
- | `data.templatePriority` | number | Template priority; unset means the note is not a template. |
125
- | `data.events` | list of event entries | What happened to or at this subject — each entry one dated, attributed event: a founding, a war, a fall, or an occasion that recurs. |
126
- | `data.demonym` | string | What one member is called — a Vylarian. |
127
- | `data.epithet` | string | The by-name it is known by — a god's, an order's, a company's. |
128
- | `data.symbol` | string | Its emblem in words: a feather atop a golden scale, a chisel carving a star. |
129
- | `data.governance.model` | string | How the affiliation is governed, where it is. |
130
- | `data.governance.summary` | string | A sentence on how the governance actually works. |
131
- | `data.governance.ranks` | list of `{ level, title, description, lore? }` | The ladder of ranks the body confers — level, title, description. |
132
- | `data.governance.offices` | a map of named offices | Named offices, each with a description and optional dated holders. |
133
- | `data.seat` | an Address | Where the affiliation's authority sits. |
134
- | `data.population` | number | How many people it counts. |
135
- | `data.economy` | list of Addresses | What its economic life runs on — currencies, banking bodies, goods. |
136
- | `data.lore` | list of Addresses | Lore concerning it — the peoples it draws on, the god a faith venerates, its law, its calendar. |
137
- | `data.parents` | list of Addresses | Affiliations it is subordinate to. |
138
- | `data.culture` | an Address | The people this affiliation belongs to, as a culture lore note. |
139
- | `data.relations` | a map keyed by Address | Standing with other affiliations — aligned, unaligned, rival, nemesis. |
121
+ | Field | Shape | Meaning |
122
+ | ------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
123
+ | `data.icon` | an Address | The document's profile art — an `icon` address, resolved into `img`. |
124
+ | `data.templatePriority` | number | Template priority; unset means the note is not a template. |
125
+ | `data.events` | list of event entries | What happened to or at this subject — each entry one dated, attributed event: a founding, a war, a fall, or an occasion that recurs. |
126
+ | `data.demonym` | string | What one member is called — a Vylarian. |
127
+ | `data.epithet` | string | The by-name it is known by — a god's, an order's, a company's. |
128
+ | `data.symbol` | string | Its emblem in words: a feather atop a golden scale, a chisel carving a star. |
129
+ | `data.governance.model` | string | How the affiliation is governed, where it is. |
130
+ | `data.governance.summary` | string | A sentence on how the governance actually works. |
131
+ | `data.governance.ranks` | list of `{ level, title, description, lore? }` | The ladder of standings the body confers: level 0 for those it excludes and level 1 for its members, both required, and higher rungs only where the body has them. |
132
+ | `data.governance.offices` | a map of named offices | Named offices, each with a description and optional dated holders. |
133
+ | `data.seat` | an Address | Where the affiliation's authority sits. |
134
+ | `data.population` | number | How many people it counts. |
135
+ | `data.economy` | list of Addresses | What its economic life runs on — currencies, banking bodies, goods. |
136
+ | `data.lore` | list of Addresses | Lore concerning it — the peoples it draws on, the god a faith venerates, its law, its calendar. |
137
+ | `data.parents` | list of Addresses | Affiliations it is subordinate to. |
138
+ | `data.culture` | an Address | The people this affiliation belongs to, as a culture lore note. |
139
+ | `data.relations` | a map keyed by Address | Standing with other affiliations — aligned, unaligned, rival, nemesis. |
140
140
 
141
141
  **Inner keys** — a key a field's value does not declare is an error at its own line and column.
142
142
 
143
- | Key | Shape | Required | Meaning |
144
- | ---------------------------------------------------- | ------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------- |
145
- | `data.events[].id` | an address segment | no | The event's name within its note — lowercase letters and digits. |
146
- | `data.events[].kind` | one `eventKind` value | no | What sort of thing happened. |
147
- | `data.events[].depth` | one `depth` value | no | How far the event's history reaches. |
148
- | `data.events[].when` | a date, or `"0.<day>"` | yes | When it happened, or the anchor and first instance of an event that recurs. |
149
- | `data.events[].until` | a date | no | When a continuous event ended, or where a recurring series stopped. |
150
- | `data.events[].recurs` | `{ every }` or `{ on }` | no | How further occurrences are found; absent for an event that happened once. |
151
- | `data.events[].recurs.every` | a whole number of years, 1 or more | no | A period counted on the canonical axis from `when`. |
152
- | `data.events[].recurs.on` | a list of dates, strictly increasing, each later than `when` | no | Recorded occurrences beyond the first, in place of a period. |
153
- | `data.events[].summary` | a string | yes | What happened, stated plainly. |
154
- | `data.events[].standing` | one `standing` value | no | What the world's evidence supports. |
155
- | `data.events[].names` | `{ name, by, gloss? }[]` | no | The event's names in the world, each with who uses it. |
156
- | `data.events[].names[].name` | a string | yes | One name the event goes by in the world. |
157
- | `data.events[].names[].by` | an Address naming an `affiliation`, `lore`, `place`, `being` or `skill` note | yes | Who uses that name — a people, a polity, a faith, a place, a tongue. |
158
- | `data.events[].names[].gloss` | a string | no | What the name means, or how it is used. |
159
- | `data.events[].where` | `{ locus?, reach? }` | no | Where it happened, and where it was felt. |
160
- | `data.events[].where.locus` | a list of Addresses, each defaulting to `place` | no | Where the event physically happened; each resolves to a place. |
161
- | `data.events[].where.reach` | `{ place, how, knowledge, attributedTo? }[]` | no | Where the event was felt, and whether each place knows why. |
162
- | `data.events[].where.reach[].place` | an Address, defaulting to `place` | yes | A place where the event was felt. |
163
- | `data.events[].where.reach[].how` | a string | yes | One clause: a consequence someone in that place could notice. |
164
- | `data.events[].where.reach[].knowledge` | one `knowledge` value | yes | Whether that place connects what it felt to this event. |
165
- | `data.events[].where.reach[].attributedTo` | an Address, defaulting to `lore`, naming an event or a `lore` note | no | Only beside `knowledge: misattributed` — the cause that place names instead. |
166
- | `data.events[].who` | `{ ref, role }[]` | no | Who took part, and as what. |
167
- | `data.events[].who[].ref` | an Address naming a `being`, `affiliation` or `lore` note | yes | A participant — a being, a people, an affiliation. |
168
- | `data.events[].who[].role` | one `role` value | yes | What the participant was to the event. |
169
- | `data.events[].follows` | `{ event, how, note? }[]` | no | The earlier events this one follows from. |
170
- | `data.events[].follows[].event` | an Address, defaulting to `lore`, anchored to an `event` where its note holds several | yes | The earlier event; it resolves to exactly one event. |
171
- | `data.events[].follows[].how` | one `followsHow` value | yes | How this event follows from it. |
172
- | `data.events[].follows[].note` | a string | no | One clause saying what connects the two. |
173
- | `data.events[].accounts` | `{ by, says, agrees, withholds? }[]` | no | What each people, polity, faith or place says about it. |
174
- | `data.events[].accounts[].by` | an Address naming an `affiliation`, `lore`, `place` or `being` note | yes | Who holds the account. |
175
- | `data.events[].accounts[].says` | a string | yes | What they say happened, in their terms. |
176
- | `data.events[].accounts[].agrees` | one `agrees` value | yes | How far their account agrees with `summary`. |
177
- | `data.events[].accounts[].withholds` | a string | no | What they decline to say. |
178
- | `data.events[].unresolved` | a list of strings | no | What the world itself has not settled. |
179
- | `data.events[].sources` | a list of Addresses | no | The notes that state or support the event; each resolves. |
180
- | `data.events[].stated` | `{ calendar, text }` | no | The date as one tradition writes it in its own reckoning. |
181
- | `data.events[].stated.calendar` | an Address, or a lore shortcode | yes | The calendar that tradition reckons in — a `lore` note with `subType: calendar`. |
182
- | `data.events[].stated.text` | a string | yes | The date as that tradition writes it. |
183
- | `data.governance.ranks[].level` | a whole number — the rung's position on this body's own ladder | yes | The rung's position on the ladder; a member's `rank` names it. |
184
- | `data.governance.ranks[].title` | what the standing is called | yes | What the standing is called. |
185
- | `data.governance.ranks[].description` | what the standing is | yes | What the standing is. |
186
- | `data.governance.ranks[].lore` | a lore Address | no | Lore saying more about the standing than a description holds. |
187
- | `data.governance.offices.<name>.description` | a string | yes | What the office is. |
188
- | `data.governance.offices.<name>.holders` | a list of `{ being, start?, end?, contested? }` | yes | Who has held it, each with the dates of the term. |
189
- | `data.governance.offices.<name>.holders[].being` | a being Address | yes | Who held the office. |
190
- | `data.governance.offices.<name>.holders[].start` | a date | no | When the term began; unstated, it began before anything recorded. |
191
- | `data.governance.offices.<name>.holders[].end` | a date | no | When the term ended; unstated, the being holds the office now. |
192
- | `data.governance.offices.<name>.holders[].contested` | `true` or `false` | no | The term may overlap another holder's. |
143
+ | Key | Shape | Required | Meaning |
144
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
145
+ | `data.events[].id` | an address segment | no | The event's name within its note — lowercase letters and digits. |
146
+ | `data.events[].kind` | one `eventKind` value | no | What sort of thing happened. |
147
+ | `data.events[].depth` | one `depth` value | no | How far the event's history reaches. |
148
+ | `data.events[].when` | a date, or `"0.<day>"` | yes | When it happened, or the anchor and first instance of an event that recurs. |
149
+ | `data.events[].until` | a date | no | When a continuous event ended, or where a recurring series stopped. |
150
+ | `data.events[].recurs` | `{ every }` or `{ on }` | no | How further occurrences are found; absent for an event that happened once. |
151
+ | `data.events[].recurs.every` | a whole number of years, 1 or more | no | A period counted on the canonical axis from `when`. |
152
+ | `data.events[].recurs.on` | a list of dates, strictly increasing, each later than `when` | no | Recorded occurrences beyond the first, in place of a period. |
153
+ | `data.events[].summary` | a string | yes | What happened, stated plainly. |
154
+ | `data.events[].standing` | one `standing` value | no | What the world's evidence supports. |
155
+ | `data.events[].names` | `{ name, by, gloss? }[]` | no | The event's names in the world, each with who uses it. |
156
+ | `data.events[].names[].name` | a string | yes | One name the event goes by in the world. |
157
+ | `data.events[].names[].by` | an Address naming an `affiliation`, `lore`, `place`, `being` or `skill` note | yes | Who uses that name — a people, a polity, a faith, a place, a tongue. |
158
+ | `data.events[].names[].gloss` | a string | no | What the name means, or how it is used. |
159
+ | `data.events[].where` | `{ locus?, reach? }` | no | Where it happened, and where it was felt. |
160
+ | `data.events[].where.locus` | a list of Addresses, each defaulting to `place` | no | Where the event physically happened; each resolves to a place. |
161
+ | `data.events[].where.reach` | `{ place, how, knowledge, attributedTo? }[]` | no | Where the event was felt, and whether each place knows why. |
162
+ | `data.events[].where.reach[].place` | an Address, defaulting to `place` | yes | A place where the event was felt. |
163
+ | `data.events[].where.reach[].how` | a string | yes | One clause: a consequence someone in that place could notice. |
164
+ | `data.events[].where.reach[].knowledge` | one `knowledge` value | yes | Whether that place connects what it felt to this event. |
165
+ | `data.events[].where.reach[].attributedTo` | an Address, defaulting to `lore`, naming an event or a `lore` note | no | Only beside `knowledge: misattributed` — the cause that place names instead. |
166
+ | `data.events[].who` | `{ ref, role }[]` | no | Who took part, and as what. |
167
+ | `data.events[].who[].ref` | an Address naming a `being`, `affiliation` or `lore` note | yes | A participant — a being, a people, an affiliation. |
168
+ | `data.events[].who[].role` | one `role` value | yes | What the participant was to the event. |
169
+ | `data.events[].follows` | `{ event, how, note? }[]` | no | The earlier events this one follows from. |
170
+ | `data.events[].follows[].event` | an Address, defaulting to `lore`, anchored to an `event` where its note holds several | yes | The earlier event; it resolves to exactly one event. |
171
+ | `data.events[].follows[].how` | one `followsHow` value | yes | How this event follows from it. |
172
+ | `data.events[].follows[].note` | a string | no | One clause saying what connects the two. |
173
+ | `data.events[].accounts` | `{ by, says, agrees, withholds? }[]` | no | What each people, polity, faith or place says about it. |
174
+ | `data.events[].accounts[].by` | an Address naming an `affiliation`, `lore`, `place` or `being` note | yes | Who holds the account. |
175
+ | `data.events[].accounts[].says` | a string | yes | What they say happened, in their terms. |
176
+ | `data.events[].accounts[].agrees` | one `agrees` value | yes | How far their account agrees with `summary`. |
177
+ | `data.events[].accounts[].withholds` | a string | no | What they decline to say. |
178
+ | `data.events[].unresolved` | a list of strings | no | What the world itself has not settled. |
179
+ | `data.events[].sources` | a list of Addresses | no | The notes that state or support the event; each resolves. |
180
+ | `data.events[].stated` | `{ calendar, text }` | no | The date as one tradition writes it in its own reckoning. |
181
+ | `data.events[].stated.calendar` | an Address, or a lore shortcode | yes | The calendar that tradition reckons in — a `lore` note with `subType: calendar`. |
182
+ | `data.events[].stated.text` | a string | yes | The date as that tradition writes it. |
183
+ | `data.governance.ranks[].level` | a whole number — the rung's position on this body's own ladder | yes | The rung's position on the ladder — `0` the excluded, `1` the ordinary member; a member's `rank` names it. |
184
+ | `data.governance.ranks[].title` | what the standing is called | yes | What the standing is called. |
185
+ | `data.governance.ranks[].description` | what the standing is | yes | What the standing is. |
186
+ | `data.governance.ranks[].lore` | a lore Address | no | Lore saying more about the standing than a description holds. |
187
+ | `data.governance.offices.<name>.description` | a string | yes | What the office is. |
188
+ | `data.governance.offices.<name>.holders` | a list of `{ being, start?, end?, contested? }` | yes | Who has held it, each with the dates of the term. |
189
+ | `data.governance.offices.<name>.holders[].being` | a being Address | yes | Who held the office. |
190
+ | `data.governance.offices.<name>.holders[].start` | a date | no | When the term began; unstated, it began before anything recorded. |
191
+ | `data.governance.offices.<name>.holders[].end` | a date | no | When the term ended; unstated, the being holds the office now. |
192
+ | `data.governance.offices.<name>.holders[].contested` | `true` or `false` | no | The term may overlap another holder's. |
193
193
 
194
194
  **System mappings**
195
195