@heroiclands/package-build 22.25.0 → 22.26.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.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -0
- package/docs/api.md +14 -11
- package/docs/authoring/frontmatter.md +21 -0
- package/docs/content-format.md +2 -0
- package/docs/reference/format-details.md +47 -32
- package/docs/reference/note-types.md +27 -26
- package/docs/reference/theme-pages.md +2 -0
- package/engine/calendar-notes.mjs +1 -1
- package/engine/content-format.yaml +1 -0
- package/engine/holdings.mjs +38 -66
- package/engine/index.mjs +1 -1
- package/engine/infobox.mjs +10 -0
- package/engine/metadata-index.mjs +3 -0
- package/engine/note-vocabulary.mjs +16 -4
- package/package.json +1 -1
- package/types/engine/calendar-notes.d.mts +1 -1
- package/types/engine/holdings.d.mts +20 -17
- package/types/engine/infobox.d.mts +1 -0
- package/types/engine/note-vocabulary.d.mts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 22.26.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
Place notes can name their governing affiliation in `data.government`, or state `null` for complete anarchy. A place with positive population and no government now receives an advisory. Places with zero, null or unspecified population no longer receive the old unheld-land warning; holdings remain separate tenure facts. Governments appear in note infoboxes and persist in published metadata.
|
|
8
|
+
|
|
3
9
|
## 22.25.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -46,4 +46,6 @@ The documentation lives in the Markdown files in this repository. Start with [th
|
|
|
46
46
|
| Error locations and severities | [Diagnostics](docs/diagnostics.md) |
|
|
47
47
|
| Architectural rationale | [Design](docs/design.md) |
|
|
48
48
|
|
|
49
|
+
Place notes may declare a governing affiliation with `data.government`, or explicit `null` for complete anarchy. Positive population with an omitted government produces an advisory. See [place governments](docs/authoring/frontmatter.md#place-governments).
|
|
50
|
+
|
|
49
51
|
The package is GPL-3.0-or-later for code and CC-BY-SA-4.0 for original content. See [LICENSE.md](LICENSE.md).
|
package/docs/api.md
CHANGED
|
@@ -348,6 +348,8 @@ The note types the engine itself declares — vocabulary that is a fact about th
|
|
|
348
348
|
|
|
349
349
|
### `engine.noteVocabulary`
|
|
350
350
|
|
|
351
|
+
`DataFieldSpec.nullText` declares the infobox text for an explicitly authored null. The place `government` field uses “Complete anarchy”; omitted fields remain absent. Its Address targets must be affiliations.
|
|
352
|
+
|
|
351
353
|
An addressed content note accepts exactly these top-level keys, in this order: `shortcode`, `name`, `type`, `subType`, `description`, `tags`, `data`, `dnd5e`, `hm3`, `sohl`. A key outside this list is an error at its own line. The system blocks are one per system the address vocabulary recognizes, sorted by name after the fixed keys, so a further system is a change to `SYSTEM_IDS` alone. No dnd5e-specific document types are declared. Within that shape, the closed vocabularies are the `data:` container and each type's `subType`.
|
|
352
354
|
|
|
353
355
|
| Export | Signature | Returns | Use it when |
|
|
@@ -495,17 +497,18 @@ What a place is next to (`data.borders`) and reachable from (`data.routes`): the
|
|
|
495
497
|
|
|
496
498
|
### `engine.holdings`
|
|
497
499
|
|
|
498
|
-
What lies within a place, who holds it, and what an affiliation holds — read off `data.parents` and `data.domains` across this package and every fetched index, inverted into the `contains`, `held_by` and `holdings` lists the site build writes — and the
|
|
499
|
-
|
|
500
|
-
| Export | Signature | Returns | Use it when
|
|
501
|
-
| ---------------------- | ------------------------------------ | ----------------------- |
|
|
502
|
-
| `HOLDINGS_KEYS` | `const HOLDINGS_KEYS` | `readonly string[]` | the three derived keys — `contains`, `held_by`, `holdings` — which a note cannot author
|
|
503
|
-
| `HELD_SUBTYPES` | `const HELD_SUBTYPES` | `readonly string[]` |
|
|
504
|
-
| `holdingsNode` | `holdingsNode(fm, { title, url })` | `HoldingsNode\|null` | reading a local note as a node of the derivation, or `null` for one that is neither a place nor an affiliation
|
|
505
|
-
| `foreignHoldingsNodes` | `foreignHoldingsNodes(foreignIndex)` | `HoldingsNode[]` | reading a fetched index's places and affiliations as nodes
|
|
506
|
-
| `holdingsPages` | `holdingsPages(nodes)` | `Map<string, Holdings>` | inverting `parents` and `domains` into each page's sorted lists, keyed by URL, for every page with at least one entry
|
|
507
|
-
| `shortcodesOf` | `shortcodesOf(value)` | `string[]` | the shortcodes a `parents` or `domains` value names — bare, addressed or wikilinked — empties dropped and repeats collapsed
|
|
508
|
-
| `
|
|
500
|
+
What lies within a place, who holds it, and what an affiliation holds — read off `data.parents` and `data.domains` across this package and every fetched index, inverted into the `contains`, `held_by` and `holdings` lists the site build writes — and the population-based government advisory `package-build lint` runs on a place.
|
|
501
|
+
|
|
502
|
+
| Export | Signature | Returns | Use it when |
|
|
503
|
+
| ---------------------- | ------------------------------------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
504
|
+
| `HOLDINGS_KEYS` | `const HOLDINGS_KEYS` | `readonly string[]` | the three derived keys — `contains`, `held_by`, `holdings` — which a note cannot author |
|
|
505
|
+
| `HELD_SUBTYPES` | `const HELD_SUBTYPES` | `readonly string[]` | deprecated historical tenure subtypes; government checks apply to every place subtype |
|
|
506
|
+
| `holdingsNode` | `holdingsNode(fm, { title, url })` | `HoldingsNode\|null` | reading a local note as a node of the derivation, or `null` for one that is neither a place nor an affiliation |
|
|
507
|
+
| `foreignHoldingsNodes` | `foreignHoldingsNodes(foreignIndex)` | `HoldingsNode[]` | reading a fetched index's places and affiliations as nodes |
|
|
508
|
+
| `holdingsPages` | `holdingsPages(nodes)` | `Map<string, Holdings>` | inverting `parents` and `domains` into each page's sorted lists, keyed by URL, for every page with at least one entry |
|
|
509
|
+
| `shortcodesOf` | `shortcodesOf(value)` | `string[]` | the shortcodes a `parents` or `domains` value names — bare, addressed or wikilinked — empties dropped and repeats collapsed |
|
|
510
|
+
| `checkGovernment` | `checkGovernment(note)` | `object[]` | warning at `data.population` for positive population with no own `data.government` key; explicit null means anarchy |
|
|
511
|
+
| `checkHeld` | `checkHeld(note, { index })` | `object[]` | deprecated compatibility wrapper for `checkGovernment`; holdings do not determine government |
|
|
509
512
|
|
|
510
513
|
### `engine.populations`
|
|
511
514
|
|
|
@@ -39,6 +39,27 @@ A `sohl:`, `hm3:` or `dnd5e:` block holds values for that Foundry system. Its `s
|
|
|
39
39
|
|
|
40
40
|
One body describes the subject for both systems. A being's `{#appearance}` and `{#dossier}` sections feed SoHL Actor `system.appearance` and `system.dossier`, and HM3 Actor `system.description` and `system.biography`. An Item's `{#description}` page becomes a shared JournalEntryPage. Its resolved `@UUID[...]` pointer is stored in SoHL Item `system.docHtml` and HM3 Item `system.description`. An Item with no `{#description}` anchor uses its first page. Other H1 pages remain linkable.
|
|
41
41
|
|
|
42
|
+
## Place governments
|
|
43
|
+
|
|
44
|
+
A place may state its governing affiliation in `data.government`. Use an affiliation Address, either a local shortcode or a fully qualified Address from this package or a fetched dependency:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
data:
|
|
48
|
+
population: 1200
|
|
49
|
+
government: crown
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Write `government: null` for **complete anarchy**. Omitting the key leaves government unspecified. Null is retained in published metadata and displayed as “Complete anarchy” in website, Foundry and book infoboxes; an omitted key has no Government row. A reference displays a link to the governing affiliation.
|
|
53
|
+
|
|
54
|
+
| Population | Government | Governance advisory |
|
|
55
|
+
| --------------------- | ------------------- | -------------------------------- |
|
|
56
|
+
| Positive | Omitted | Warning at `data.population` |
|
|
57
|
+
| Missing, zero or null | Omitted | None |
|
|
58
|
+
| Any | Explicit `null` | None; complete anarchy |
|
|
59
|
+
| Any | Affiliation Address | None; the reference must resolve |
|
|
60
|
+
|
|
61
|
+
These rules apply to every place subtype, including settlements, sites, structures, features, regions and worlds. Empty strings, numbers, lists, maps, references to another note type and unresolved references are errors. They do not declare anarchy. `domains`, `parents`, containment and capitals do not supply a government. Holdings remain tenure facts; the former unconditional “unheld land” warning is retired.
|
|
62
|
+
|
|
42
63
|
## Publication and summaries
|
|
43
64
|
|
|
44
65
|
A note infobox draws from shared `data`. A being profile shows its top-level `subType` as `Type: Character`, `Type: NPC`, or `Type: Creature`. A system infobox draws from the system's fields, and a mapped system with no document says “Not available.” The same authored body appears in Foundry, on the website, and in the book; the book places boxes after the authored body, Foundry appends one page per box, and the website places boxes in its responsive side rail. [Infobox details](../reference/format-details.md#the-infobox) describe field order and empty values.
|
package/docs/content-format.md
CHANGED
|
@@ -85,3 +85,5 @@ A sheltered place where ships load and unload.
|
|
|
85
85
|
The note's readable Address is `<package>-note-lore-harbor`. In prose, link to it with `[[lore-harbor|Harbor]]`. The package segment comes from `package-build.config.yaml`; a regular wikilink defaults to the readable `note` system. An image embed such as `![[icon-harbor|Harbor]]` defaults to `none` instead.
|
|
86
86
|
|
|
87
87
|
The [first-note guide](authoring/first-note.md) shows the build commands and how to add a system document. Use the [command reference](commands.md) for exact flags and the [diagnostics guide](diagnostics.md) for located errors.
|
|
88
|
+
|
|
89
|
+
Place notes may declare a governing affiliation with `data.government`, or explicit `null` for complete anarchy. Positive population with an omitted government produces an advisory. See [place governments](authoring/frontmatter.md#place-governments).
|
|
@@ -4255,32 +4255,33 @@ map's prose.
|
|
|
4255
4255
|
- feature: A place significant by its terrain — forests, rivers, falls, passes, fords.
|
|
4256
4256
|
- celestial: A body observed from a world rather than located on one — a sun, a planet, a comet.
|
|
4257
4257
|
|
|
4258
|
-
| `data` property | Values | Description
|
|
4259
|
-
| --------------------------------- | --------------------------------------------------- |
|
|
4260
|
-
| `calendar` | `Address` | Calendar note used to print this place's dates
|
|
4261
|
-
| `demonym` | `string` | What a person from this place is called — a Vylarian
|
|
4262
|
-
| `purpose` | `placeCharacter` tag | The reason a settlement, site, or structure exists
|
|
4263
|
-
| `lore` | `Address[]` | Lore concerning this place — its peoples, its law, its calendar, its history
|
|
4264
|
-
| `parents` | `Address[]` | Enclosing places within which this place is located
|
|
4265
|
-
| `population` | `number` | Approximate population (precision 2 significant digits)
|
|
4266
|
-
| `
|
|
4267
|
-
| `
|
|
4268
|
-
| `
|
|
4269
|
-
| `
|
|
4270
|
-
| `world.
|
|
4271
|
-
| `world.
|
|
4272
|
-
| `
|
|
4273
|
-
| `year.
|
|
4274
|
-
| `year.
|
|
4275
|
-
| `year.
|
|
4276
|
-
| `
|
|
4277
|
-
| `
|
|
4278
|
-
| `body.
|
|
4279
|
-
| `body.
|
|
4280
|
-
| `body.
|
|
4281
|
-
| `
|
|
4282
|
-
| `moon.
|
|
4283
|
-
| `moon.
|
|
4258
|
+
| `data` property | Values | Description |
|
|
4259
|
+
| --------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
4260
|
+
| `calendar` | `Address` | Calendar note used to print this place's dates |
|
|
4261
|
+
| `demonym` | `string` | What a person from this place is called — a Vylarian |
|
|
4262
|
+
| `purpose` | `placeCharacter` tag | The reason a settlement, site, or structure exists |
|
|
4263
|
+
| `lore` | `Address[]` | Lore concerning this place — its peoples, its law, its calendar, its history |
|
|
4264
|
+
| `parents` | `Address[]` | Enclosing places within which this place is located |
|
|
4265
|
+
| `population` | `number` | Approximate population (precision 2 significant digits) |
|
|
4266
|
+
| `government` | affiliation Address or `null` | Governing affiliation; explicit null means complete anarchy. Omission warns only for positive population. |
|
|
4267
|
+
| `market` | `number` | Market class, 1 to 6 — what trade a settlement supports |
|
|
4268
|
+
| `borders` | `{ to, bearing }[]` | Places sharing a frontier with this one, and where each lies from here |
|
|
4269
|
+
| `routes` | `{ to, bearing, mode, days, terrain?, leagues? }[]` | Journeys from this place's centre to another place |
|
|
4270
|
+
| `world.equatorialCircumferenceKm` | `number` | The distance round the body at its equator, in kilometres |
|
|
4271
|
+
| `world.surfaceGravityG` | `number` | Surface gravity, as a multiple of Earth's |
|
|
4272
|
+
| `world.axialTiltDegrees` | `number` | The tilt of the axis, in degrees |
|
|
4273
|
+
| `year.days` | `number` | How many days the body's year holds |
|
|
4274
|
+
| `year.hoursPerDay` | `number` | How many hours the day divides into |
|
|
4275
|
+
| `year.minutesPerHour` | `number` | How many minutes the hour divides into |
|
|
4276
|
+
| `year.secondsPerMinute` | `number` | How many seconds the minute divides into |
|
|
4277
|
+
| `present` | `date` | The day the setting stops and play begins |
|
|
4278
|
+
| `body.diameterKm` | `number` | The body's diameter, in kilometres |
|
|
4279
|
+
| `body.orbitalRadiusKm` | `number` | How far it orbits from the body it circles, in kilometres |
|
|
4280
|
+
| `body.orbit` | `string` | The orbit's shape — `circular` means the cycle never varies |
|
|
4281
|
+
| `body.inclined` | `boolean` | Whether the orbit is inclined to the plane the world orbits in |
|
|
4282
|
+
| `moon.cycle` | `number` | How many days it takes to return to the same phase |
|
|
4283
|
+
| `moon.newOn` | day-precision `date` | A day it was new, written in the reference calendar |
|
|
4284
|
+
| `moon.eclipses` | `string` | `never`, `rare`, `occasional` or `frequent` |
|
|
4284
4285
|
|
|
4285
4286
|
#### Why a place exists
|
|
4286
4287
|
|
|
@@ -4501,12 +4502,26 @@ never expanded**: a region held by a polity lists the polity in `held_by`, and
|
|
|
4501
4502
|
a settlement within that region lists only the house or order whose `domains`
|
|
4502
4503
|
names it — its lord's lord is reached through `affiliation.parents`.
|
|
4503
4504
|
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4505
|
+
#### Who governs a place
|
|
4506
|
+
|
|
4507
|
+
A place may state its governing affiliation in `data.government`. Use an affiliation Address, either a local shortcode or a fully qualified Address from this package or a fetched dependency:
|
|
4508
|
+
|
|
4509
|
+
```yaml
|
|
4510
|
+
data:
|
|
4511
|
+
population: 1200
|
|
4512
|
+
government: crown
|
|
4513
|
+
```
|
|
4514
|
+
|
|
4515
|
+
Write `government: null` for **complete anarchy**. Omitting the key leaves government unspecified. Null is retained in published metadata and displayed as “Complete anarchy” in website, Foundry and book infoboxes; an omitted key has no Government row. A reference displays a link to the governing affiliation.
|
|
4516
|
+
|
|
4517
|
+
| Population | Government | Governance advisory |
|
|
4518
|
+
| --------------------- | ------------------- | -------------------------------- |
|
|
4519
|
+
| Positive | Omitted | Warning at `data.population` |
|
|
4520
|
+
| Missing, zero or null | Omitted | None |
|
|
4521
|
+
| Any | Explicit `null` | None; complete anarchy |
|
|
4522
|
+
| Any | Affiliation Address | None; the reference must resolve |
|
|
4523
|
+
|
|
4524
|
+
These rules apply to every place subtype, including settlements, sites, structures, features, regions and worlds. Empty strings, numbers, lists, maps, references to another note type and unresolved references are errors. They do not declare anarchy. `domains`, `parents`, containment and capitals do not supply a government. Holdings remain tenure facts; the former unconditional “unheld land” warning is retired.
|
|
4510
4525
|
|
|
4511
4526
|
#### Population figures
|
|
4512
4527
|
|
|
@@ -463,32 +463,33 @@ Lore records in-world knowledge. A `culture` describes a people; a `custom` desc
|
|
|
463
463
|
|
|
464
464
|
**Subtypes:** `world`, `region`, `settlement`, `site`, `structure`, `feature`, `celestial`.
|
|
465
465
|
|
|
466
|
-
| Field | Shape | Meaning
|
|
467
|
-
| -------------------------------------- | ----------------------------------------------------------------- |
|
|
468
|
-
| `data.calendar` | an Address | Calendar used to display this place's dates.
|
|
469
|
-
| `data.demonym` | string | What a person from this place is called — a Vylarian.
|
|
470
|
-
| `data.purpose` | string | The reason this settlement, site, or structure exists, selected from its placeCharacter tags.
|
|
471
|
-
| `data.lore` | list of Addresses | Lore concerning this place — its peoples, its law, its calendar, its history.
|
|
472
|
-
| `data.parents` | list of Addresses | Enclosing places this one sits within.
|
|
473
|
-
| `data.population` | number | Approximate population, to two significant digits.
|
|
474
|
-
| `data.
|
|
475
|
-
| `data.
|
|
476
|
-
| `data.
|
|
477
|
-
| `data.
|
|
478
|
-
| `data.world.
|
|
479
|
-
| `data.world.
|
|
480
|
-
| `data.
|
|
481
|
-
| `data.year.
|
|
482
|
-
| `data.year.
|
|
483
|
-
| `data.year.
|
|
484
|
-
| `data.
|
|
485
|
-
| `data.
|
|
486
|
-
| `data.body.
|
|
487
|
-
| `data.body.
|
|
488
|
-
| `data.body.
|
|
489
|
-
| `data.
|
|
490
|
-
| `data.moon.
|
|
491
|
-
| `data.moon.
|
|
466
|
+
| Field | Shape | Meaning |
|
|
467
|
+
| -------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
468
|
+
| `data.calendar` | an Address | Calendar used to display this place's dates. |
|
|
469
|
+
| `data.demonym` | string | What a person from this place is called — a Vylarian. |
|
|
470
|
+
| `data.purpose` | string | The reason this settlement, site, or structure exists, selected from its placeCharacter tags. |
|
|
471
|
+
| `data.lore` | list of Addresses | Lore concerning this place — its peoples, its law, its calendar, its history. |
|
|
472
|
+
| `data.parents` | list of Addresses | Enclosing places this one sits within. |
|
|
473
|
+
| `data.population` | number | Approximate population, to two significant digits. |
|
|
474
|
+
| `data.government` | an affiliation Address or null | The governing affiliation; explicit null means complete anarchy. A positive population with no government key produces an advisory. |
|
|
475
|
+
| `data.market` | number | What trade the settlement supports, on a scale of six: 1 hamlet, 2 village, 3 town, 4 market town, 5 city, 6 great city. |
|
|
476
|
+
| `data.borders` | list of `{ to, bearing }` entries | Places sharing a frontier with this one — each the other's shortcode and where it lies from here. |
|
|
477
|
+
| `data.routes` | list of `{ to, bearing, mode, days, terrain?, leagues? }` entries | Journeys from this place's centre — where the destination lies, how it is travelled, and about how many days it takes. |
|
|
478
|
+
| `data.world.equatorialCircumferenceKm` | number | The distance round the body at its equator, in kilometres. |
|
|
479
|
+
| `data.world.surfaceGravityG` | number | Surface gravity, as a multiple of Earth's. |
|
|
480
|
+
| `data.world.axialTiltDegrees` | number | The tilt of the axis, in degrees — what makes the body have seasons. |
|
|
481
|
+
| `data.year.days` | number | How many days the body's year holds. Every calendar's months sum to it. |
|
|
482
|
+
| `data.year.hoursPerDay` | number | How many hours the day divides into. |
|
|
483
|
+
| `data.year.minutesPerHour` | number | How many minutes the hour divides into. |
|
|
484
|
+
| `data.year.secondsPerMinute` | number | How many seconds the minute divides into. |
|
|
485
|
+
| `data.present` | a date | The day the setting stops and play begins — what an age is computed against. |
|
|
486
|
+
| `data.body.diameterKm` | number | The body's diameter, in kilometres. |
|
|
487
|
+
| `data.body.orbitalRadiusKm` | number | How far the body orbits from the one it circles, in kilometres. |
|
|
488
|
+
| `data.body.orbit` | string | The orbit's shape — `circular` means the cycle never varies. |
|
|
489
|
+
| `data.body.inclined` | boolean | Whether the orbit is inclined to the plane the world orbits in. |
|
|
490
|
+
| `data.moon.cycle` | number | How many days the body takes to return to the same phase. |
|
|
491
|
+
| `data.moon.newOn` | a day-precision date | A day the body was new, written in the reference calendar. |
|
|
492
|
+
| `data.moon.eclipses` | string | How often the body eclipses or is eclipsed — `never`, `rare`, `occasional` or `frequent`. |
|
|
492
493
|
|
|
493
494
|
### scenario
|
|
494
495
|
|
|
@@ -25,6 +25,8 @@ describes the keys the build writes from it.
|
|
|
25
25
|
|
|
26
26
|
## The infobox
|
|
27
27
|
|
|
28
|
+
A place’s Government row links to its authored governing affiliation. Explicit `data.government: null` produces a text row saying “Complete anarchy”; an omitted government produces no row. The same distinction is retained in the page’s `data` metadata.
|
|
29
|
+
|
|
28
30
|
The toolchain settles what each box holds and in what order — see
|
|
29
31
|
[the infobox](format-details.md#the-infobox) for the field semantics, the
|
|
30
32
|
three states a system box can say, and the four section layouts a box is
|
|
@@ -511,7 +511,7 @@ function checkCalendarDayNames(note, daysPerYear) {
|
|
|
511
511
|
/**
|
|
512
512
|
* Check a `place` note's world facts.
|
|
513
513
|
*
|
|
514
|
-
* Declared on `place` beside its
|
|
514
|
+
* Declared on `place` beside its government advisory, so the lint runs both with the
|
|
515
515
|
* same index. Four rules, and each is a fact about the setting rather than
|
|
516
516
|
* about the file:
|
|
517
517
|
*
|
package/engine/holdings.mjs
CHANGED
|
@@ -35,16 +35,15 @@
|
|
|
35
35
|
* package publishes is listed within a local region, and a house another
|
|
36
36
|
* package publishes is named on the local manor it holds.
|
|
37
37
|
*
|
|
38
|
-
* **
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* through its polity's `domains` and a feature by nobody, so both are exempt.
|
|
38
|
+
* **Government is stated separately from tenure.** A place with positive
|
|
39
|
+
* `data.population` and no authored `data.government` receives an advisory.
|
|
40
|
+
* Explicit null means complete anarchy. Holdings never supply a government.
|
|
42
41
|
*
|
|
43
42
|
* @module
|
|
44
43
|
*/
|
|
45
44
|
|
|
46
45
|
import { isAddressTuple } from "./address.mjs";
|
|
47
|
-
import {
|
|
46
|
+
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
48
47
|
import { parseAddress } from "./address.mjs";
|
|
49
48
|
import { readCanonicalKey } from "./content-address.mjs";
|
|
50
49
|
|
|
@@ -66,9 +65,9 @@ const PLACE_TYPES = Object.freeze(new Set(["place"]));
|
|
|
66
65
|
export const HOLDINGS_KEYS = Object.freeze(["contains", "held_by", "holdings"]);
|
|
67
66
|
|
|
68
67
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
68
|
+
* Legacy subtypes of the retired tenure advisory. Government advisories
|
|
69
|
+
* apply to every place subtype when its authored population is positive.
|
|
70
|
+
* @deprecated This list no longer gates any advisory.
|
|
72
71
|
*
|
|
73
72
|
* @type {readonly string[]}
|
|
74
73
|
*/
|
|
@@ -373,71 +372,44 @@ export function holdingsPages(nodes) {
|
|
|
373
372
|
/* --------------------------------------------------------------------- */
|
|
374
373
|
|
|
375
374
|
/**
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* The shortcodes every affiliation's `domains` names, across the link index.
|
|
387
|
-
*
|
|
388
|
-
* @param {object} index - The link index.
|
|
389
|
-
* @returns {Set<string>} The held shortcodes, lower case.
|
|
375
|
+
* Advise when a place with an authored positive population omits government.
|
|
376
|
+
* Missing, null or zero population establishes no inhabited population.
|
|
377
|
+
* Property presence distinguishes omitted government from explicit null,
|
|
378
|
+
* which means complete anarchy. Non-null values are validated by the declared
|
|
379
|
+
* affiliation reference field. Neither geographic containment nor tenure
|
|
380
|
+
* supplies this fact, and the rule is independent of place subtype.
|
|
381
|
+
*
|
|
382
|
+
* @param {object} note - The note with parsed frontmatter and raw source.
|
|
383
|
+
* @returns {object[]} Located warning findings.
|
|
390
384
|
*/
|
|
391
|
-
function
|
|
392
|
-
let held = HELD.get(index);
|
|
393
|
-
if (held) return held;
|
|
394
|
-
held = new Set();
|
|
395
|
-
for (const note of index.notes ?? []) {
|
|
396
|
-
if (String(note.type ?? note.fm?.type ?? "") !== "affiliation") continue;
|
|
397
|
-
const data = note.fm?.data;
|
|
398
|
-
const domains = data && typeof data === "object" ? data.domains : undefined;
|
|
399
|
-
for (const shortcode of shortcodesOf(domains)) held.add(shortcode);
|
|
400
|
-
}
|
|
401
|
-
for (const entry of index.foreign?.index?.values?.() ?? []) {
|
|
402
|
-
if (String(entry?.type ?? "") !== "affiliation") continue;
|
|
403
|
-
for (const shortcode of shortcodesOf(entry.domains)) held.add(shortcode);
|
|
404
|
-
}
|
|
405
|
-
HELD.set(index, held);
|
|
406
|
-
return held;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Check a place note's tenure: a settlement, a site or a structure that no
|
|
411
|
-
* affiliation's `domains` names is unheld land, reported as a warning at the
|
|
412
|
-
* note's `type:` line.
|
|
413
|
-
*
|
|
414
|
-
* Declared on the `place` vocabulary as its type-level check, so the lint
|
|
415
|
-
* runs it beside the field checks with the same index. Without an index the
|
|
416
|
-
* question cannot be asked and nothing is reported.
|
|
417
|
-
*
|
|
418
|
-
* @param {object} note - The note, as the link index hands it over.
|
|
419
|
-
* @param {object} [opts]
|
|
420
|
-
* @param {object} [opts.index] - The link index, holding every affiliation.
|
|
421
|
-
* @returns {object[]} Findings.
|
|
422
|
-
*/
|
|
423
|
-
export function checkHeld(note, { index } = {}) {
|
|
424
|
-
if (!index) return [];
|
|
385
|
+
export function checkGovernment(note) {
|
|
425
386
|
const fm = note.fm ?? {};
|
|
426
|
-
if (
|
|
427
|
-
const
|
|
428
|
-
if (!
|
|
429
|
-
const
|
|
430
|
-
if (
|
|
431
|
-
|
|
387
|
+
if (fm.type !== "place") return [];
|
|
388
|
+
const data = fm.data;
|
|
389
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return [];
|
|
390
|
+
const population = data.population;
|
|
391
|
+
if (typeof population !== "number" && typeof population !== "string") return [];
|
|
392
|
+
const count = Number(population);
|
|
393
|
+
if (!Number.isFinite(count) || count <= 0 || Object.hasOwn(data, "government")) return [];
|
|
432
394
|
return [
|
|
433
395
|
{
|
|
434
396
|
file: note.file,
|
|
435
|
-
...
|
|
397
|
+
...positionOfFrontmatterPath(note.raw ?? "", ["data", "population"], { key: true }),
|
|
436
398
|
severity: "warning",
|
|
437
399
|
message:
|
|
438
|
-
`
|
|
439
|
-
|
|
440
|
-
`order or polity that does`,
|
|
400
|
+
`missing government: place "${fm.shortcode ?? ""}" has positive population ` +
|
|
401
|
+
"but no `data.government`; name an affiliation or write null for complete anarchy",
|
|
441
402
|
},
|
|
442
403
|
];
|
|
443
404
|
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* The historical API name for the government advisory. Tenure is no longer checked.
|
|
408
|
+
* @deprecated Use checkGovernment.
|
|
409
|
+
* @param {object} note - The place note.
|
|
410
|
+
* @param {object} [_opts] - Historical options, no longer needed.
|
|
411
|
+
* @returns {object[]} Government advisory findings.
|
|
412
|
+
*/
|
|
413
|
+
export function checkHeld(note, _opts = {}) {
|
|
414
|
+
return checkGovernment(note);
|
|
415
|
+
}
|
package/engine/index.mjs
CHANGED
|
@@ -99,7 +99,7 @@ export * as stubLint from "./stub-lint.mjs";
|
|
|
99
99
|
/** What a place is next to and reachable from: the closed sets, and the checks. */
|
|
100
100
|
export * as placeRelations from "./place-relations.mjs";
|
|
101
101
|
|
|
102
|
-
/** What lies within a place, who holds it, and what an affiliation holds — the page lists, and the
|
|
102
|
+
/** What lies within a place, who holds it, and what an affiliation holds — the page lists, and the government advisory. */
|
|
103
103
|
export * as holdings from "./holdings.mjs";
|
|
104
104
|
export * as populations from "./populations.mjs";
|
|
105
105
|
|
package/engine/infobox.mjs
CHANGED
|
@@ -427,6 +427,7 @@ export function isUnsetSentinel(value) {
|
|
|
427
427
|
/**
|
|
428
428
|
* Whether a value is worth a row.
|
|
429
429
|
*
|
|
430
|
+
* A field declaring `nullText` handles explicit null before this general test.
|
|
430
431
|
* Rule 4: an absent field is absent. `null`, `""` and `[]` are how the corpus
|
|
431
432
|
* writes "nobody filled this in" — a note that declares every key of its type
|
|
432
433
|
* and leaves most of them empty is the ordinary shape, not the exception — and
|
|
@@ -943,6 +944,15 @@ function noteBox(
|
|
|
943
944
|
if (structured.length) shown.add(field.name);
|
|
944
945
|
continue;
|
|
945
946
|
}
|
|
947
|
+
if (raw === null && field.nullText) {
|
|
948
|
+
rows.push({
|
|
949
|
+
label: overlay.label ?? humanizeFieldName(field.name),
|
|
950
|
+
kind: "text",
|
|
951
|
+
value: field.nullText,
|
|
952
|
+
});
|
|
953
|
+
shown.add(field.name);
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
946
956
|
if (!hasValue(raw)) continue;
|
|
947
957
|
|
|
948
958
|
if (overlay.group) {
|
|
@@ -435,6 +435,9 @@ export function loadForeignIndexes(config, localPackages, bases = PACKAGE_BASE)
|
|
|
435
435
|
parents: record.data?.parents ?? undefined,
|
|
436
436
|
domains: record.data?.domains ?? undefined,
|
|
437
437
|
population: record.data?.population ?? undefined,
|
|
438
|
+
...(Object.hasOwn(record.data ?? {}, "government") ?
|
|
439
|
+
{ government: record.data.government }
|
|
440
|
+
: {}),
|
|
438
441
|
package: pkg,
|
|
439
442
|
});
|
|
440
443
|
const entry = index.get(key);
|
|
@@ -112,7 +112,7 @@ import { checkCultureChoice } from "./culture-choice.mjs";
|
|
|
112
112
|
import { SOCIAL_TIES, SOCIAL_TIE_TARGET_TYPES } from "./social-tie-terms.mjs";
|
|
113
113
|
import { parseNoteDate } from "./note-dates.mjs";
|
|
114
114
|
import { reckoningContext } from "./reckoning-markers.mjs";
|
|
115
|
-
import {
|
|
115
|
+
import { checkGovernment } from "./holdings.mjs";
|
|
116
116
|
import {
|
|
117
117
|
checkBeingMeasurement,
|
|
118
118
|
parseBeingHeight,
|
|
@@ -153,6 +153,7 @@ import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
|
153
153
|
* so each takes a row labelled and anchored by the post's own name.
|
|
154
154
|
* @property {"subType"} [keySelector] - An alternative `subType:<skill-subtype>` key.
|
|
155
155
|
* @property {readonly string[]} [accepts] - Allowed Address types, separate from `ref`.
|
|
156
|
+
* @property {string} [nullText] - Infobox text for an explicitly authored null with a declared meaning.
|
|
156
157
|
* @property {string} [shape] - Human-readable shape, for a finding and for
|
|
157
158
|
* documentation.
|
|
158
159
|
* @property {string} [entryShape] - For a `scalar-or-map` field, what one
|
|
@@ -512,8 +513,8 @@ const CHARGES = Object.freeze([
|
|
|
512
513
|
/**
|
|
513
514
|
* Everything `place` asks of a whole note.
|
|
514
515
|
*
|
|
515
|
-
* Two questions about one subject, and they are independent: who
|
|
516
|
-
*
|
|
516
|
+
* Two questions about one subject, and they are independent: who governs this
|
|
517
|
+
* inhabited place, and — where the note is a body rather than somewhere within one — what
|
|
517
518
|
* the body states about itself. A type declares one whole-note check, so the
|
|
518
519
|
* two are composed here rather than either being folded into the other.
|
|
519
520
|
*
|
|
@@ -522,7 +523,7 @@ const CHARGES = Object.freeze([
|
|
|
522
523
|
* @returns {object[]} Findings from both.
|
|
523
524
|
*/
|
|
524
525
|
function checkPlace(note, opts) {
|
|
525
|
-
return [...
|
|
526
|
+
return [...checkGovernment(note), ...checkWorldFacts(note, opts)];
|
|
526
527
|
}
|
|
527
528
|
|
|
528
529
|
/** Validate a being's authored date using the shared note-date grammar. */
|
|
@@ -1563,6 +1564,17 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
1563
1564
|
check: checkPopulation,
|
|
1564
1565
|
describe: "Approximate population, to two significant digits.",
|
|
1565
1566
|
},
|
|
1567
|
+
{
|
|
1568
|
+
name: "government",
|
|
1569
|
+
...LINK,
|
|
1570
|
+
shape: "an affiliation Address or null",
|
|
1571
|
+
ref: "affiliation",
|
|
1572
|
+
accepts: ["affiliation"],
|
|
1573
|
+
nullText: "Complete anarchy",
|
|
1574
|
+
describe:
|
|
1575
|
+
"The governing affiliation; explicit null means complete anarchy. " +
|
|
1576
|
+
"A positive population with no government key produces an advisory.",
|
|
1577
|
+
},
|
|
1566
1578
|
{
|
|
1567
1579
|
name: "market",
|
|
1568
1580
|
...NUM,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.26.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@ export function checkCalendarDateFormats(note: any): {
|
|
|
29
29
|
/**
|
|
30
30
|
* Check a `place` note's world facts.
|
|
31
31
|
*
|
|
32
|
-
* Declared on `place` beside its
|
|
32
|
+
* Declared on `place` beside its government advisory, so the lint runs both with the
|
|
33
33
|
* same index. Four rules, and each is a fact about the setting rather than
|
|
34
34
|
* about the file:
|
|
35
35
|
*
|
|
@@ -53,22 +53,25 @@ export function foreignHoldingsNodes(foreignIndex: Map<string, object> | undefin
|
|
|
53
53
|
*/
|
|
54
54
|
export function holdingsPages(nodes: Iterable<HoldingsNode | null>): Map<string, Holdings>;
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* Advise when a place with an authored positive population omits government.
|
|
57
|
+
* Missing, null or zero population establishes no inhabited population.
|
|
58
|
+
* Property presence distinguishes omitted government from explicit null,
|
|
59
|
+
* which means complete anarchy. Non-null values are validated by the declared
|
|
60
|
+
* affiliation reference field. Neither geographic containment nor tenure
|
|
61
|
+
* supplies this fact, and the rule is independent of place subtype.
|
|
59
62
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
*
|
|
66
|
-
* @
|
|
67
|
-
* @
|
|
63
|
+
* @param {object} note - The note with parsed frontmatter and raw source.
|
|
64
|
+
* @returns {object[]} Located warning findings.
|
|
65
|
+
*/
|
|
66
|
+
export function checkGovernment(note: object): object[];
|
|
67
|
+
/**
|
|
68
|
+
* The historical API name for the government advisory. Tenure is no longer checked.
|
|
69
|
+
* @deprecated Use checkGovernment.
|
|
70
|
+
* @param {object} note - The place note.
|
|
71
|
+
* @param {object} [_opts] - Historical options, no longer needed.
|
|
72
|
+
* @returns {object[]} Government advisory findings.
|
|
68
73
|
*/
|
|
69
|
-
export function checkHeld(note: object,
|
|
70
|
-
index?: object | undefined;
|
|
71
|
-
}): object[];
|
|
74
|
+
export function checkHeld(note: object, _opts?: object): object[];
|
|
72
75
|
/**
|
|
73
76
|
* The keys this module writes, and which a note cannot author: `contains`
|
|
74
77
|
* and `held_by` on a place, `holdings` on an affiliation.
|
|
@@ -77,9 +80,9 @@ export function checkHeld(note: object, { index }?: {
|
|
|
77
80
|
*/
|
|
78
81
|
export const HOLDINGS_KEYS: readonly string[];
|
|
79
82
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
+
* Legacy subtypes of the retired tenure advisory. Government advisories
|
|
84
|
+
* apply to every place subtype when its authored population is positive.
|
|
85
|
+
* @deprecated This list no longer gates any advisory.
|
|
83
86
|
*
|
|
84
87
|
* @type {readonly string[]}
|
|
85
88
|
*/
|
|
@@ -27,6 +27,7 @@ export function isUnsetSentinel(value: unknown): boolean;
|
|
|
27
27
|
/**
|
|
28
28
|
* Whether a value is worth a row.
|
|
29
29
|
*
|
|
30
|
+
* A field declaring `nullText` handles explicit null before this general test.
|
|
30
31
|
* Rule 4: an absent field is absent. `null`, `""` and `[]` are how the corpus
|
|
31
32
|
* writes "nobody filled this in" — a note that declares every key of its type
|
|
32
33
|
* and leaves most of them empty is the ordinary shape, not the exception — and
|
|
@@ -380,6 +380,10 @@ export type DataFieldSpec = {
|
|
|
380
380
|
* - Allowed Address types, separate from `ref`.
|
|
381
381
|
*/
|
|
382
382
|
accepts?: readonly string[] | undefined;
|
|
383
|
+
/**
|
|
384
|
+
* - Infobox text for an explicitly authored null with a declared meaning.
|
|
385
|
+
*/
|
|
386
|
+
nullText?: string | undefined;
|
|
383
387
|
/**
|
|
384
388
|
* - Human-readable shape, for a finding and for
|
|
385
389
|
* documentation.
|