@heroiclands/package-build 23.0.0 → 23.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +1 -1
- package/bin/content-commands.mjs +1 -1
- package/ci/content-format-reference.mjs +46 -0
- package/docs/api.md +124 -141
- package/docs/authoring/assets.md +1 -1
- package/docs/authoring/dates-and-calendars.md +12 -6
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +145 -29
- package/docs/commands.md +8 -10
- package/docs/configuration.md +13 -11
- package/docs/content-format.md +2 -2
- package/docs/guides/book.md +59 -4
- package/docs/guides/icons.md +4 -5
- package/docs/guides/packs.md +6 -0
- package/docs/guides/reference-pages.md +21 -1
- package/docs/guides/release-and-deploy.md +39 -7
- package/docs/guides/site.md +17 -18
- package/docs/reference/format-details.md +877 -276
- package/docs/reference/note-types.md +278 -91
- package/docs/reference/theme-pages.md +58 -97
- package/engine/address-values.mjs +124 -4
- package/engine/address.mjs +44 -3
- package/engine/anchors.mjs +103 -3
- package/engine/art-slots.mjs +2 -9
- package/engine/base-compiler.mjs +25 -6
- package/engine/calendar-artifacts.mjs +4 -1
- package/engine/calendar-notes.mjs +166 -102
- package/engine/content-format-check.mjs +2 -3
- package/engine/content-format.mjs +44 -1
- package/engine/content-format.yaml +79 -16
- package/engine/content-images.mjs +10 -1
- package/engine/content-index.mjs +37 -9
- package/engine/content-links.mjs +24 -5
- package/engine/content-tables.mjs +24 -3
- package/engine/data-keys.mjs +347 -0
- package/engine/derived-sections.mjs +322 -0
- package/engine/diagnostics.mjs +28 -0
- package/engine/event-fields.mjs +187 -0
- package/engine/event-rows.mjs +126 -0
- package/engine/event-views.mjs +305 -0
- package/engine/exported-scene.mjs +29 -53
- package/engine/frontmatter-lint.mjs +103 -77
- package/engine/frontmatter.mjs +7 -2
- package/engine/generate.mjs +53 -0
- package/engine/generated-sections.mjs +223 -0
- package/engine/heading-attributes.mjs +4 -2
- package/engine/helpers.mjs +12 -1
- package/engine/holdings.mjs +12 -33
- package/engine/ids.mjs +17 -11
- package/engine/index.mjs +4 -4
- package/engine/infobox-render.mjs +21 -6
- package/engine/infobox.mjs +217 -99
- package/engine/journals.mjs +104 -47
- package/engine/literature-notes.mjs +4 -1
- package/engine/literature-works.mjs +21 -13
- package/engine/map-graphviz.mjs +4 -18
- package/engine/markdown-expressions.mjs +116 -11
- package/engine/metadata-index.mjs +26 -0
- package/engine/near-miss.mjs +90 -0
- package/engine/note-addresses.mjs +75 -19
- package/engine/note-dates.mjs +5 -3
- package/engine/note-event-terms.mjs +364 -0
- package/engine/note-events.mjs +823 -0
- package/engine/note-vocabulary.mjs +184 -88
- package/engine/office-holders.mjs +20 -54
- package/engine/packagebuild-index.mjs +16 -1
- package/engine/pdf-build.mjs +97 -60
- package/engine/pdf-index.mjs +263 -0
- package/engine/pdf-render.mjs +47 -5
- package/engine/place-relation-terms.mjs +174 -0
- package/engine/place-relations.mjs +72 -160
- package/engine/rank-ladder.mjs +14 -41
- package/engine/reckoning-markers.mjs +2 -0
- package/engine/region-events.mjs +6 -12
- package/engine/scenes.mjs +73 -468
- package/engine/site-build.mjs +75 -101
- package/engine/site-index.mjs +3 -1
- package/engine/site-maps.mjs +43 -29
- package/engine/site-preview.mjs +40 -31
- package/engine/social-ties.mjs +2 -1
- package/engine/sql-tables.mjs +212 -9
- package/engine/standing-terms.mjs +145 -6
- package/engine/standings.mjs +37 -49
- package/engine/web-wikilinks.mjs +21 -1
- package/engine/wikilink-syntax.mjs +34 -4
- package/engine/wikilinks.mjs +27 -2
- package/engine/yaml-lint.mjs +7 -4
- package/hugo-theme/layouts/partials/infobox/entry.html +4 -1
- package/hugo-theme/layouts/partials/page-body.html +0 -3
- package/hugo-theme/static/css/style.css +25 -55
- package/package.json +1 -2
- package/packagebuild-metadata.jsonl +13 -13
- package/release.mjs +62 -4
- package/sohl/note-schemas.mjs +8 -73
- package/types/engine/address-values.d.mts +50 -3
- package/types/engine/address.d.mts +21 -0
- package/types/engine/anchors.d.mts +49 -2
- package/types/engine/art-slots.d.mts +1 -1
- package/types/engine/base-compiler.d.mts +13 -4
- package/types/engine/calendar-notes.d.mts +7 -0
- package/types/engine/content-format.d.mts +52 -0
- package/types/engine/content-images.d.mts +5 -1
- package/types/engine/content-tables.d.mts +9 -1
- package/types/engine/data-keys.d.mts +145 -0
- package/types/engine/derived-sections.d.mts +73 -0
- package/types/engine/diagnostics.d.mts +15 -0
- package/types/engine/event-fields.d.mts +99 -0
- package/types/engine/event-rows.d.mts +12 -0
- package/types/engine/event-views.d.mts +67 -0
- package/types/engine/exported-scene.d.mts +5 -2
- package/types/engine/frontmatter-lint.d.mts +3 -3
- package/types/engine/generated-sections.d.mts +143 -0
- package/types/engine/holdings.d.mts +6 -20
- package/types/engine/ids.d.mts +15 -12
- package/types/engine/index.d.mts +1 -1
- package/types/engine/infobox.d.mts +13 -3
- package/types/engine/journals.d.mts +24 -11
- package/types/engine/literature-works.d.mts +1 -2
- package/types/engine/map-graphviz.d.mts +2 -10
- package/types/engine/markdown-expressions.d.mts +12 -3
- package/types/engine/near-miss.d.mts +42 -0
- package/types/engine/note-addresses.d.mts +7 -1
- package/types/engine/note-event-terms.d.mts +68 -0
- package/types/engine/note-events.d.mts +71 -0
- package/types/engine/note-vocabulary.d.mts +37 -8
- package/types/engine/office-holders.d.mts +3 -0
- package/types/engine/pdf-index.d.mts +35 -0
- package/types/engine/pdf-render.d.mts +8 -2
- package/types/engine/place-relation-terms.d.mts +83 -0
- package/types/engine/place-relations.d.mts +11 -66
- package/types/engine/rank-ladder.d.mts +2 -0
- package/types/engine/region-events.d.mts +6 -12
- package/types/engine/scenes.d.mts +6 -32
- package/types/engine/site-build.d.mts +8 -22
- package/types/engine/site-maps.d.mts +15 -7
- package/types/engine/sql-tables.d.mts +77 -2
- package/types/engine/standing-terms.d.mts +37 -4
- package/types/engine/standings.d.mts +14 -9
- package/types/engine/wikilink-syntax.d.mts +20 -0
- package/types/engine/wikilinks.d.mts +3 -2
- package/types/engine/yaml-lint.d.mts +5 -4
- package/types/release.d.mts +4 -0
- package/docs/guides/government-migration.md +0 -52
- package/engine/itinerary-scenes.mjs +0 -166
- package/engine/lore-events.mjs +0 -197
- package/engine/map-notes.mjs +0 -1309
- package/engine/map-raster.mjs +0 -51
- package/hugo-theme/layouts/partials/holdings/entry.html +0 -21
- package/hugo-theme/layouts/partials/holdings/kind-groups.html +0 -71
- package/hugo-theme/layouts/partials/holdings.html +0 -61
- package/hugo-theme/layouts/partials/literature-works.html +0 -29
- package/hugo-theme/layouts/partials/place-map.html +0 -9
- package/types/engine/itinerary-scenes.d.mts +0 -29
- package/types/engine/lore-events.d.mts +0 -14
- package/types/engine/map-notes.d.mts +0 -297
- package/types/engine/map-raster.d.mts +0 -8
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The closed sets a place's `borders` and `routes` are held to, and the keys
|
|
16
|
+
* one entry of each carries.
|
|
17
|
+
*
|
|
18
|
+
* A leaf module, importing nothing: the note vocabulary reads the entry
|
|
19
|
+
* declarations as it loads, and {@link module:engine/place-relations}, which
|
|
20
|
+
* checks what the values mean, reaches the vocabulary through the Address
|
|
21
|
+
* reader.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/* --------------------------------------------------------------------- */
|
|
27
|
+
/* The closed sets */
|
|
28
|
+
/* --------------------------------------------------------------------- */
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The type a border's or a route's `to` names.
|
|
32
|
+
*
|
|
33
|
+
* It is both the default its `<type>` segment takes when an author omits it and
|
|
34
|
+
* the whole of the set the position accepts, which is what makes `vylar` and
|
|
35
|
+
* `place-vylar` the same Address and a `to` naming a `lore` note an error rather
|
|
36
|
+
* than a lookup that quietly finds nothing.
|
|
37
|
+
*
|
|
38
|
+
* @type {string}
|
|
39
|
+
*/
|
|
40
|
+
export const RELATION_TYPE = "place";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The eight compass bearings, clockwise from north.
|
|
44
|
+
*
|
|
45
|
+
* Where a neighbour or a destination lies from the place stating it. The
|
|
46
|
+
* order is load-bearing: {@link oppositeBearing} reads four steps around it.
|
|
47
|
+
*
|
|
48
|
+
* @type {readonly string[]}
|
|
49
|
+
*/
|
|
50
|
+
export const BEARINGS = Object.freeze(["N", "NE", "E", "SE", "S", "SW", "W", "NW"]);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The modes a route is travelled by: `land`, `boat` on a river or a lake,
|
|
54
|
+
* `ship` on open sea.
|
|
55
|
+
*
|
|
56
|
+
* @type {readonly string[]}
|
|
57
|
+
*/
|
|
58
|
+
export const ROUTE_MODES = Object.freeze(["land", "boat", "ship"]);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The days markers a route may state.
|
|
62
|
+
*
|
|
63
|
+
* Each is "about this, under normal conditions": `10`, `20` and `30` are one,
|
|
64
|
+
* two and three ten-day weeks, `180` is many months, `360` is "who knows".
|
|
65
|
+
*
|
|
66
|
+
* @type {readonly number[]}
|
|
67
|
+
*/
|
|
68
|
+
export const TRAVEL_DAYS = Object.freeze([1, 2, 3, 5, 10, 20, 30, 45, 60, 90, 180, 360]);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The terrain registry, each terrain naming the modes that cross it.
|
|
72
|
+
*
|
|
73
|
+
* A land terrain is crossed by land alone; open sea by ship; a river or a lake
|
|
74
|
+
* by boat; a coast by all three, because a coast road and a coasting voyage
|
|
75
|
+
* are both journeys along it.
|
|
76
|
+
*
|
|
77
|
+
* @type {Readonly<Record<string, readonly string[]>>}
|
|
78
|
+
*/
|
|
79
|
+
export const TERRAIN_MODES = Object.freeze({
|
|
80
|
+
road: Object.freeze(["land"]),
|
|
81
|
+
track: Object.freeze(["land"]),
|
|
82
|
+
plain: Object.freeze(["land"]),
|
|
83
|
+
steppe: Object.freeze(["land"]),
|
|
84
|
+
hills: Object.freeze(["land"]),
|
|
85
|
+
mountains: Object.freeze(["land"]),
|
|
86
|
+
forest: Object.freeze(["land"]),
|
|
87
|
+
jungle: Object.freeze(["land"]),
|
|
88
|
+
marsh: Object.freeze(["land"]),
|
|
89
|
+
dunes: Object.freeze(["land"]),
|
|
90
|
+
desert: Object.freeze(["land"]),
|
|
91
|
+
ice: Object.freeze(["land"]),
|
|
92
|
+
coast: Object.freeze(["land", "boat", "ship"]),
|
|
93
|
+
"open-sea": Object.freeze(["ship"]),
|
|
94
|
+
river: Object.freeze(["boat"]),
|
|
95
|
+
lake: Object.freeze(["boat"]),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The terrain names, in registry order — the closed set a `terrain` list
|
|
100
|
+
* draws from.
|
|
101
|
+
*
|
|
102
|
+
* @type {readonly string[]}
|
|
103
|
+
*/
|
|
104
|
+
export const TERRAINS = Object.freeze(Object.keys(TERRAIN_MODES));
|
|
105
|
+
|
|
106
|
+
/** A list entry's `to` and `bearing`, which both relations carry. */
|
|
107
|
+
const RELATION_ENDS = Object.freeze([
|
|
108
|
+
Object.freeze({
|
|
109
|
+
name: "to",
|
|
110
|
+
kind: "address",
|
|
111
|
+
required: true,
|
|
112
|
+
shape: "the other place — its shortcode, or its Address",
|
|
113
|
+
describe: "The other place, by Address; the type defaults to `place`.",
|
|
114
|
+
}),
|
|
115
|
+
Object.freeze({
|
|
116
|
+
name: "bearing",
|
|
117
|
+
kind: "string",
|
|
118
|
+
required: true,
|
|
119
|
+
shape: `one of ${BEARINGS.join(", ")}`,
|
|
120
|
+
describe: "Where the other place lies from here.",
|
|
121
|
+
}),
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The keys a `borders` entry carries, as inner-key declarations. Both are
|
|
126
|
+
* required.
|
|
127
|
+
*
|
|
128
|
+
* @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
|
|
129
|
+
*/
|
|
130
|
+
export const BORDER_FIELDS = RELATION_ENDS;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The keys a `routes` entry carries, as inner-key declarations. `to`,
|
|
134
|
+
* `bearing`, `mode` and `days` are required; `terrain` and `leagues` are
|
|
135
|
+
* optional.
|
|
136
|
+
*
|
|
137
|
+
* @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
|
|
138
|
+
*/
|
|
139
|
+
export const ROUTE_FIELDS = Object.freeze([
|
|
140
|
+
...RELATION_ENDS,
|
|
141
|
+
Object.freeze({
|
|
142
|
+
name: "mode",
|
|
143
|
+
kind: "string",
|
|
144
|
+
required: true,
|
|
145
|
+
shape: `one of ${ROUTE_MODES.join(", ")}`,
|
|
146
|
+
describe: "How the journey is travelled.",
|
|
147
|
+
}),
|
|
148
|
+
Object.freeze({
|
|
149
|
+
name: "days",
|
|
150
|
+
kind: "number",
|
|
151
|
+
required: true,
|
|
152
|
+
shape: `one of ${TRAVEL_DAYS.join(", ")}`,
|
|
153
|
+
describe: "About how many days the journey takes, under normal conditions.",
|
|
154
|
+
}),
|
|
155
|
+
Object.freeze({
|
|
156
|
+
name: "terrain",
|
|
157
|
+
kind: "list",
|
|
158
|
+
shape: "a list of terrains in travel order",
|
|
159
|
+
entries: Object.freeze({ kind: "string", shape: "a terrain" }),
|
|
160
|
+
describe: "The terrains crossed, in travel order, each crossable by the mode.",
|
|
161
|
+
}),
|
|
162
|
+
Object.freeze({
|
|
163
|
+
name: "leagues",
|
|
164
|
+
kind: "number",
|
|
165
|
+
shape: "a distance in leagues",
|
|
166
|
+
describe: "The distance, in leagues.",
|
|
167
|
+
}),
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
/** The keys a `borders` entry carries. @type {readonly string[]} */
|
|
171
|
+
export const BORDER_KEYS = Object.freeze(BORDER_FIELDS.map((field) => field.name));
|
|
172
|
+
|
|
173
|
+
/** The keys a `routes` entry carries. @type {readonly string[]} */
|
|
174
|
+
export const ROUTE_KEYS = Object.freeze(ROUTE_FIELDS.map((field) => field.name));
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
* shortcode in different packages never answer for each other.
|
|
35
35
|
*
|
|
36
36
|
* **The value sets are closed and stated once.** The eight bearings, the three
|
|
37
|
-
* travel modes, the days markers and the terrain registry are the constants
|
|
38
|
-
*
|
|
37
|
+
* travel modes, the days markers and the terrain registry are the constants of
|
|
38
|
+
* {@link module:engine/place-relation-terms}, re-exported here, read by the lint, compared against the specification's tables by the
|
|
39
39
|
* test suite, and exported for a consumer drawing a map. A second copy of any
|
|
40
40
|
* of them is one more list free to disagree.
|
|
41
41
|
*
|
|
@@ -69,100 +69,31 @@ import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
|
69
69
|
import { acceptsType, parseAddress, renderAddress } from "./address.mjs";
|
|
70
70
|
import { NOTE_SYSTEM } from "./systems.mjs";
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* The modes a route is travelled by: `land`, `boat` on a river or a lake,
|
|
100
|
-
* `ship` on open sea.
|
|
101
|
-
*
|
|
102
|
-
* @type {readonly string[]}
|
|
103
|
-
*/
|
|
104
|
-
export const ROUTE_MODES = Object.freeze(["land", "boat", "ship"]);
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* The days markers a route may state.
|
|
108
|
-
*
|
|
109
|
-
* Each is "about this, under normal conditions": `10`, `20` and `30` are one,
|
|
110
|
-
* two and three ten-day weeks, `180` is many months, `360` is "who knows".
|
|
111
|
-
*
|
|
112
|
-
* @type {readonly number[]}
|
|
113
|
-
*/
|
|
114
|
-
export const TRAVEL_DAYS = Object.freeze([1, 2, 3, 5, 10, 20, 30, 45, 60, 90, 180, 360]);
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* The terrain registry, each terrain naming the modes that cross it.
|
|
118
|
-
*
|
|
119
|
-
* A land terrain is crossed by land alone; open sea by ship; a river or a lake
|
|
120
|
-
* by boat; a coast by all three, because a coast road and a coasting voyage
|
|
121
|
-
* are both journeys along it.
|
|
122
|
-
*
|
|
123
|
-
* @type {Readonly<Record<string, readonly string[]>>}
|
|
124
|
-
*/
|
|
125
|
-
export const TERRAIN_MODES = Object.freeze({
|
|
126
|
-
road: Object.freeze(["land"]),
|
|
127
|
-
track: Object.freeze(["land"]),
|
|
128
|
-
plain: Object.freeze(["land"]),
|
|
129
|
-
steppe: Object.freeze(["land"]),
|
|
130
|
-
hills: Object.freeze(["land"]),
|
|
131
|
-
mountains: Object.freeze(["land"]),
|
|
132
|
-
forest: Object.freeze(["land"]),
|
|
133
|
-
jungle: Object.freeze(["land"]),
|
|
134
|
-
marsh: Object.freeze(["land"]),
|
|
135
|
-
dunes: Object.freeze(["land"]),
|
|
136
|
-
desert: Object.freeze(["land"]),
|
|
137
|
-
ice: Object.freeze(["land"]),
|
|
138
|
-
coast: Object.freeze(["land", "boat", "ship"]),
|
|
139
|
-
"open-sea": Object.freeze(["ship"]),
|
|
140
|
-
river: Object.freeze(["boat"]),
|
|
141
|
-
lake: Object.freeze(["boat"]),
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* The terrain names, in registry order — the closed set a `terrain` list
|
|
146
|
-
* draws from.
|
|
147
|
-
*
|
|
148
|
-
* @type {readonly string[]}
|
|
149
|
-
*/
|
|
150
|
-
export const TERRAINS = Object.freeze(Object.keys(TERRAIN_MODES));
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* The keys a `borders` entry may carry. Both are required.
|
|
154
|
-
*
|
|
155
|
-
* @type {readonly string[]}
|
|
156
|
-
*/
|
|
157
|
-
export const BORDER_KEYS = Object.freeze(["to", "bearing"]);
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* The keys a `routes` entry may carry. `to`, `bearing`, `mode` and `days` are
|
|
161
|
-
* required; `terrain` and `leagues` are optional.
|
|
162
|
-
*
|
|
163
|
-
* @type {readonly string[]}
|
|
164
|
-
*/
|
|
165
|
-
export const ROUTE_KEYS = Object.freeze(["to", "bearing", "mode", "days", "terrain", "leagues"]);
|
|
72
|
+
import {
|
|
73
|
+
RELATION_TYPE,
|
|
74
|
+
BEARINGS,
|
|
75
|
+
ROUTE_MODES,
|
|
76
|
+
TRAVEL_DAYS,
|
|
77
|
+
TERRAIN_MODES,
|
|
78
|
+
TERRAINS,
|
|
79
|
+
BORDER_FIELDS,
|
|
80
|
+
ROUTE_FIELDS,
|
|
81
|
+
BORDER_KEYS,
|
|
82
|
+
ROUTE_KEYS,
|
|
83
|
+
} from "./place-relation-terms.mjs";
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
RELATION_TYPE,
|
|
87
|
+
BEARINGS,
|
|
88
|
+
ROUTE_MODES,
|
|
89
|
+
TRAVEL_DAYS,
|
|
90
|
+
TERRAIN_MODES,
|
|
91
|
+
TERRAINS,
|
|
92
|
+
BORDER_FIELDS,
|
|
93
|
+
ROUTE_FIELDS,
|
|
94
|
+
BORDER_KEYS,
|
|
95
|
+
ROUTE_KEYS,
|
|
96
|
+
};
|
|
166
97
|
|
|
167
98
|
/**
|
|
168
99
|
* The bearing the other end of a relation states, or `undefined` for a value
|
|
@@ -362,7 +293,6 @@ function checkRelation(note, { field, index }) {
|
|
|
362
293
|
})
|
|
363
294
|
: undefined;
|
|
364
295
|
const isRoute = field === "routes";
|
|
365
|
-
const keys = isRoute ? ROUTE_KEYS : BORDER_KEYS;
|
|
366
296
|
const at = (i, key) =>
|
|
367
297
|
positionOfFrontmatterPath(raw, ["data", field, i, ...(key ? [key] : [])]);
|
|
368
298
|
const label = (i, key) => `\`data.${field}[${i}]${key ? `.${key}` : ""}\``;
|
|
@@ -373,31 +303,20 @@ function checkRelation(note, { field, index }) {
|
|
|
373
303
|
message,
|
|
374
304
|
});
|
|
375
305
|
const error = (i, key, message) => findings.push(finding(i, key, "error", message));
|
|
306
|
+
// Absent, or not one value: the inner-key check's finding, made from the
|
|
307
|
+
// declaration, so nothing here repeats it.
|
|
308
|
+
const unread = (value) =>
|
|
309
|
+
value === undefined || value === null || value === "" || typeof value === "object";
|
|
376
310
|
|
|
377
311
|
/** `to` → the modes it has been listed with, for the once-per-pair rule. */
|
|
378
312
|
const seen = new Map();
|
|
379
313
|
|
|
380
314
|
list.forEach((entry, i) => {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
`${isRoute ? ", mode: <mode>, days: <days>" : ""} }\` — but reads ` +
|
|
387
|
-
JSON.stringify(entry),
|
|
388
|
-
);
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
for (const key of Object.keys(entry)) {
|
|
393
|
-
if (keys.includes(key)) continue;
|
|
394
|
-
error(
|
|
395
|
-
i,
|
|
396
|
-
key,
|
|
397
|
-
`"${key}" is not a key of a ${field} entry; an entry carries ` +
|
|
398
|
-
`${oneOf(keys.map((k) => `\`${k}\``))} and nothing else`,
|
|
399
|
-
);
|
|
400
|
-
}
|
|
315
|
+
// An entry that is not a map, an undeclared key, a required key left
|
|
316
|
+
// out and a value of the wrong kind are the inner-key check's
|
|
317
|
+
// findings, made from `BORDER_FIELDS` and `ROUTE_FIELDS`. What is
|
|
318
|
+
// checked here is what the values mean.
|
|
319
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return;
|
|
401
320
|
|
|
402
321
|
// `to`: an Address naming a place, and one a place declares.
|
|
403
322
|
const to = entry.to;
|
|
@@ -407,7 +326,7 @@ function checkRelation(note, { field, index }) {
|
|
|
407
326
|
let toAddress;
|
|
408
327
|
const read = parseAddress(to, here);
|
|
409
328
|
if (to === undefined || to === null || to === "") {
|
|
410
|
-
|
|
329
|
+
toIsValid = false;
|
|
411
330
|
} else if (
|
|
412
331
|
read.reason === "no-content-index" ||
|
|
413
332
|
(!read.reason && here.noIndexPackages?.has(read.package))
|
|
@@ -468,8 +387,8 @@ function checkRelation(note, { field, index }) {
|
|
|
468
387
|
// `bearing`.
|
|
469
388
|
const bearing = entry.bearing;
|
|
470
389
|
let bearingIsValid = false;
|
|
471
|
-
if (bearing
|
|
472
|
-
|
|
390
|
+
if (unread(bearing)) {
|
|
391
|
+
bearingIsValid = false;
|
|
473
392
|
} else if (!BEARINGS.includes(String(bearing))) {
|
|
474
393
|
error(
|
|
475
394
|
i,
|
|
@@ -485,8 +404,8 @@ function checkRelation(note, { field, index }) {
|
|
|
485
404
|
let mode;
|
|
486
405
|
let days;
|
|
487
406
|
if (isRoute) {
|
|
488
|
-
if (entry.mode
|
|
489
|
-
|
|
407
|
+
if (unread(entry.mode)) {
|
|
408
|
+
mode = undefined;
|
|
490
409
|
} else if (!ROUTE_MODES.includes(String(entry.mode))) {
|
|
491
410
|
error(
|
|
492
411
|
i,
|
|
@@ -498,9 +417,7 @@ function checkRelation(note, { field, index }) {
|
|
|
498
417
|
mode = String(entry.mode);
|
|
499
418
|
}
|
|
500
419
|
|
|
501
|
-
if (entry.days
|
|
502
|
-
error(i, undefined, `${label(i)} must state \`days\` — ${oneOf(TRAVEL_DAYS)}`);
|
|
503
|
-
} else {
|
|
420
|
+
if (!unread(entry.days)) {
|
|
504
421
|
const n = daysOf(entry.days);
|
|
505
422
|
if (n === undefined || !TRAVEL_DAYS.includes(n)) {
|
|
506
423
|
error(
|
|
@@ -515,44 +432,39 @@ function checkRelation(note, { field, index }) {
|
|
|
515
432
|
}
|
|
516
433
|
}
|
|
517
434
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
message:
|
|
545
|
-
`${label(i, "terrain")} names "${name}", which is crossed ` +
|
|
546
|
-
`by ${oneOf(modes)}, but the route is by ${mode}`,
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
}
|
|
435
|
+
// A `terrain` that is not a list is the inner-key check's finding.
|
|
436
|
+
if (Array.isArray(entry.terrain)) {
|
|
437
|
+
entry.terrain.forEach((terrain, j) => {
|
|
438
|
+
if (unread(terrain)) return;
|
|
439
|
+
const name = String(terrain);
|
|
440
|
+
const modes = TERRAIN_MODES[name];
|
|
441
|
+
if (!modes) {
|
|
442
|
+
findings.push({
|
|
443
|
+
file: note.file,
|
|
444
|
+
...positionOfFrontmatterPath(raw, ["data", field, i, "terrain", j]),
|
|
445
|
+
severity: "error",
|
|
446
|
+
message:
|
|
447
|
+
`${label(i, "terrain")} names "${name}", which is not a ` +
|
|
448
|
+
`terrain; the registry is ${oneOf(TERRAINS)}`,
|
|
449
|
+
});
|
|
450
|
+
} else if (mode && !modes.includes(mode)) {
|
|
451
|
+
findings.push({
|
|
452
|
+
file: note.file,
|
|
453
|
+
...positionOfFrontmatterPath(raw, ["data", field, i, "terrain", j]),
|
|
454
|
+
severity: "error",
|
|
455
|
+
message:
|
|
456
|
+
`${label(i, "terrain")} names "${name}", which is crossed ` +
|
|
457
|
+
`by ${oneOf(modes)}, but the route is by ${mode}`,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
});
|
|
551
461
|
}
|
|
552
462
|
|
|
553
|
-
|
|
463
|
+
// Not a number at all is the inner-key check's finding; a number
|
|
464
|
+
// that is no distance is this one's.
|
|
465
|
+
if (!unread(entry.leagues)) {
|
|
554
466
|
const n = daysOf(entry.leagues);
|
|
555
|
-
if (n
|
|
467
|
+
if (n !== undefined && n <= 0) {
|
|
556
468
|
error(
|
|
557
469
|
i,
|
|
558
470
|
"leagues",
|
package/engine/rank-ladder.mjs
CHANGED
|
@@ -30,25 +30,22 @@
|
|
|
30
30
|
* nothing, and one missing its `description` ships a rung that says nothing on
|
|
31
31
|
* a sheet or a page.
|
|
32
32
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* note, this one included.
|
|
33
|
+
* What a rung *holds* is declared in {@link RUNG_FIELDS} and checked by the
|
|
34
|
+
* inner-key check: that `ranks` is a list of maps, that a rung carries no key
|
|
35
|
+
* beyond the four, that `level`, `title` and `description` are stated, and that
|
|
36
|
+
* each is the kind it is declared — a `level` a whole number. What is checked
|
|
37
|
+
* here is what only a ladder can say — that a level is held by one rung — and nothing is
|
|
38
|
+
* reported twice, because a finding reported twice is a finding read once and
|
|
39
|
+
* fixed neither time. That `lore` resolves is the reference checker's.
|
|
41
40
|
*
|
|
42
41
|
* @module
|
|
43
42
|
*/
|
|
44
43
|
|
|
45
44
|
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
const RUNG_KEYS = Object.freeze(["level", "title", "description", "lore"]);
|
|
46
|
+
import { RUNG_FIELDS } from "./standing-terms.mjs";
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
const REQUIRED_TEXT = Object.freeze(["title", "description"]);
|
|
48
|
+
export { RUNG_FIELDS };
|
|
52
49
|
|
|
53
50
|
/** Every affiliation has an ordinary standing that a being can name as rank 1. */
|
|
54
51
|
export function checkAffiliationRankFloor(note) {
|
|
@@ -176,27 +173,12 @@ export function checkRankLadder(note) {
|
|
|
176
173
|
const which =
|
|
177
174
|
integer(rung?.level) ? `rank ${rung.level}` : `rank ${index + 1} of the ladder`;
|
|
178
175
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
for (const key of Object.keys(rung)) {
|
|
185
|
-
if (RUNG_KEYS.includes(key)) continue;
|
|
186
|
-
at(
|
|
187
|
-
[[...rungPath, key], rungPath],
|
|
188
|
-
`${which} has unknown key ${key}; a rung takes ${RUNG_KEYS.join(", ")}`,
|
|
189
|
-
);
|
|
190
|
-
}
|
|
176
|
+
// Not a map, an undeclared key, and an absent or mistyped `level`,
|
|
177
|
+
// `title` or `description` are the inner-key check's findings.
|
|
178
|
+
if (!mapping(rung)) return;
|
|
191
179
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
[[...rungPath, "level"], rungPath],
|
|
195
|
-
rung.level === undefined || rung.level === null ?
|
|
196
|
-
`${which} needs a level — the rung's position on this body's own ladder`
|
|
197
|
-
: `${which} has a level that is not a whole number`,
|
|
198
|
-
);
|
|
199
|
-
} else {
|
|
180
|
+
// A level that is not a whole number is the inner-key check's finding.
|
|
181
|
+
if (integer(rung.level)) {
|
|
200
182
|
// A level belongs to one rung. Two rungs claiming it leave a
|
|
201
183
|
// member's `rank` answering to the one written first, so the
|
|
202
184
|
// ladder's own order decides what the standing is called and
|
|
@@ -215,15 +197,6 @@ export function checkRankLadder(note) {
|
|
|
215
197
|
`each rung states its own`,
|
|
216
198
|
);
|
|
217
199
|
}
|
|
218
|
-
|
|
219
|
-
for (const key of REQUIRED_TEXT) {
|
|
220
|
-
if (stated(rung[key])) continue;
|
|
221
|
-
at(
|
|
222
|
-
[[...rungPath, key], rungPath],
|
|
223
|
-
`${which} needs a ${key}: ` +
|
|
224
|
-
(key === "title" ? "what the standing is called" : "what the standing is"),
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
200
|
});
|
|
228
201
|
|
|
229
202
|
return findings;
|
|
@@ -131,6 +131,8 @@ export function resolveReckoningMarkers(index, daysPerYear) {
|
|
|
131
131
|
);
|
|
132
132
|
const byStart = new Map();
|
|
133
133
|
for (const row of declared) {
|
|
134
|
+
// A fractional start is the inner-key check's one finding.
|
|
135
|
+
if (typeof row.start === "number" && !Number.isInteger(row.start)) continue;
|
|
134
136
|
if (row.start !== null && (!Number.isSafeInteger(row.start) || row.start < 1)) {
|
|
135
137
|
error(
|
|
136
138
|
note,
|
package/engine/region-events.mjs
CHANGED
|
@@ -15,19 +15,13 @@
|
|
|
15
15
|
* The curated Foundry region-event vocabulary, as plain data.
|
|
16
16
|
*
|
|
17
17
|
* Deliberately **plain ESM** — no TypeScript, no `@src` aliases, no Foundry —
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* strips types, and it must reject an authored region event that the runtime
|
|
21
|
-
* would silently drop. One list here is what keeps the build-time lint and the
|
|
22
|
-
* runtime bridge from drifting apart.
|
|
18
|
+
* so the Foundry runtime and any build-time consumer read one list, and the
|
|
19
|
+
* events a region trigger forwards cannot drift between them.
|
|
23
20
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* `./engine/region-events` entry point. It is engine-side, not
|
|
29
|
-
* SoHL-side, because any content module that authors a scene region — an
|
|
30
|
-
* adventure module included — needs this vocabulary.
|
|
21
|
+
* The runtime reaches it through the package's `./engine/region-events` entry
|
|
22
|
+
* point. It is engine-side, not SoHL-side, because a Scene region exported
|
|
23
|
+
* from any content module — an adventure module included — names its events
|
|
24
|
+
* from this vocabulary.
|
|
31
25
|
*
|
|
32
26
|
* The prose explaining *why* each event is curated or excluded lives with the
|
|
33
27
|
* typed re-exports in `src/entity/event/region-triggers.ts`, which is the
|