@heroiclands/package-build 22.8.0 → 22.9.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.
@@ -52,14 +52,23 @@
52
52
  * and off by one on the other, which is invisible in any list that does not
53
53
  * span it.
54
54
  *
55
- * ## The month structure is the package's, and an absent bound checks nothing
55
+ * ## The month structure is the calendar's, and an absent list bounds nothing
56
56
  *
57
- * An era moves where year 1 sits; it does not divide the year differently. So
58
- * the subdivision is declared once for the package, in `calendar.months` and
59
- * `calendar.monthDays`, and a package that declares neither is bounded below
60
- * and not above — a toolchain that invented twelve Gregorian months would
61
- * refuse `667/2/30`, which is an ordinary date in a year of twelve thirty-day
62
- * months.
57
+ * An era moves where year 1 sits; it does not divide the year differently. The
58
+ * division belongs to the calendar, which is a note declaring an **ordered
59
+ * list** of months, each with a name and a day count. Position in the list is
60
+ * position in the year, so a five-day month sits wherever its people put it and
61
+ * needs no rule of its own.
62
+ *
63
+ * A date is bounded by the calendar it is written in, and a calendar whose list
64
+ * nobody has written down bounds nothing — a toolchain that invented twelve
65
+ * Gregorian months would refuse `667/2/30`, an ordinary date in a year of
66
+ * twelve thirty-day months.
67
+ *
68
+ * ## The lunar phase takes a floored modulo, not JavaScript's
69
+ *
70
+ * `%` takes the dividend's sign, so a day before the epoch yields `-7` where
71
+ * the phase is 23. {@link lunarPhase} is the one place that is written.
63
72
  *
64
73
  * @module
65
74
  */
@@ -118,15 +127,107 @@ export function dateSortKey(year, month, day) {
118
127
  }
119
128
 
120
129
  /**
121
- * The bounds a package states for a month, or `null` where it states none.
130
+ * One month of a calendar, as the note declares it.
122
131
  *
123
- * Separate from the check that reads it so a caller can ask what a package
124
- * declares without phrasing a finding about it.
132
+ * @typedef {object} CalendarMonth
133
+ * @property {string} name - What the month is called.
134
+ * @property {string} [abbreviation] - A short form, where the note gives one.
135
+ * @property {number} days - How many days it holds.
136
+ */
137
+
138
+ /**
139
+ * The months a calendar keeps, or `null` where it declares none.
140
+ *
141
+ * Separate from the check that reads it so a caller can ask what a calendar
142
+ * divides its year into without phrasing a finding about it. An empty list
143
+ * reads as no list: a calendar that declares nothing and one that declares
144
+ * nothing in a pair of brackets divide the year identically.
125
145
  *
126
- * @param {{months?: number|null, monthDays?: number|null}} [calendar] - The
127
- * resolved `calendar:` block.
128
- * @returns {{months: number|null, monthDays: number|null}} What it declares.
146
+ * @param {{months?: readonly CalendarMonth[]|null}} [calendar] - The calendar
147
+ * the date is written in, as its note declares it.
148
+ * @returns {{months: readonly CalendarMonth[]|null}} What it divides the year
149
+ * into.
129
150
  */
130
151
  export function calendarStructure(calendar) {
131
- return { months: calendar?.months ?? null, monthDays: calendar?.monthDays ?? null };
152
+ const written = calendar?.months;
153
+ if (!Array.isArray(written) || written.length === 0) return { months: null };
154
+ return { months: written };
155
+ }
156
+
157
+ /**
158
+ * How long one month of a calendar is, or `null` where the calendar is silent.
159
+ *
160
+ * @param {{months?: readonly CalendarMonth[]|null}} [calendar] - The calendar.
161
+ * @param {number} month - The month, numbered from 1.
162
+ * @returns {number|null} Its length in days, or `null`.
163
+ */
164
+ export function daysInMonth(calendar, month) {
165
+ const { months } = calendarStructure(calendar);
166
+ if (!months || month < 1 || month > months.length) return null;
167
+ const days = months[month - 1]?.days;
168
+ return typeof days === "number" ? days : null;
169
+ }
170
+
171
+ /**
172
+ * How many days the months add up to.
173
+ *
174
+ * The number a calendar's list claims the year is. Whether that is the year the
175
+ * world keeps is a question about the world, and is asked elsewhere.
176
+ *
177
+ * @param {readonly CalendarMonth[]} months - The ordered month list.
178
+ * @returns {number} The sum of their day counts.
179
+ */
180
+ export function daysInYear(months) {
181
+ return months.reduce((sum, month) => sum + (Number(month?.days) || 0), 0);
182
+ }
183
+
184
+ /**
185
+ * The day of the year each month opens on, in order, numbered from 1.
186
+ *
187
+ * The sum read as a sequence rather than as a total. Two lists holding the same
188
+ * numbers in a different order sum alike and open their months on different
189
+ * days, which is the error a total cannot see.
190
+ *
191
+ * @param {readonly CalendarMonth[]} months - The ordered month list.
192
+ * @returns {number[]} One entry per month: the day of the year it begins on.
193
+ */
194
+ export function monthStarts(months) {
195
+ const starts = [];
196
+ let day = 1;
197
+ for (const month of months) {
198
+ starts.push(day);
199
+ day += Number(month?.days) || 0;
200
+ }
201
+ return starts;
202
+ }
203
+
204
+ /**
205
+ * Which day of the year a written month and day land on, numbered from 1.
206
+ *
207
+ * @param {readonly CalendarMonth[]} months - The ordered month list.
208
+ * @param {number} month - The month, numbered from 1.
209
+ * @param {number} day - The day within it, numbered from 1.
210
+ * @returns {number} The day of the year.
211
+ */
212
+ export function dayOfYear(months, month, day) {
213
+ return monthStarts(months)[month - 1] + day - 1;
214
+ }
215
+
216
+ /**
217
+ * How far into its cycle a moon is on a given day.
218
+ *
219
+ * **Floored, not truncated.** JavaScript's `%` takes the dividend's sign, so a
220
+ * day seven before the epoch yields `-7` where the phase is 23 — a value that
221
+ * is right on one side of the epoch and wrong on the other, which is invisible
222
+ * in any list that does not span it.
223
+ *
224
+ * @param {number} dayNumber - The day, on whatever continuous day count the
225
+ * caller keeps.
226
+ * @param {number} epochDay - The day of that same count on which the moon was
227
+ * new.
228
+ * @param {number} cycle - The cycle's length in days.
229
+ * @returns {number} Days into the cycle, `0` to `cycle - 1`.
230
+ */
231
+ export function lunarPhase(dayNumber, epochDay, cycle) {
232
+ return (((dayNumber - epochDay) % cycle) + cycle) % cycle;
132
233
  }
package/engine/index.mjs CHANGED
@@ -130,6 +130,9 @@ export * as calendars from "./calendars.mjs";
130
130
  /** A note's dates: one authored string, one grammar, one record shape. */
131
131
  export * as noteDates from "./note-dates.mjs";
132
132
 
133
+ /** Calendars as notes: what one may declare, and the definition emitted from it. */
134
+ export * as calendarNotes from "./calendar-notes.mjs";
135
+
133
136
  /** The one normalisation this build makes: prose to a URL-safe token. */
134
137
  export * as contentSlug from "./content-slug.mjs";
135
138
 
@@ -27,9 +27,9 @@
27
27
  * `-1` sits immediately before `1`.
28
28
  * - `YYYY` — required.
29
29
  * - `MM`, `DD` — optional, in that order.
30
- * - `ERA` — an affiliation's address plus `.<era shortcode>`, in the four
31
- * address forms {@link ERA_QUALIFIER_PATTERN} admits. Omitted, the date sits
32
- * on the canonical axis and is attributed to nobody.
30
+ * - `ERA` — a calendar's address plus `.<era shortcode>`, in the four address
31
+ * forms {@link ERA_QUALIFIER_PATTERN} admits. Omitted, the date sits on the
32
+ * canonical axis and is attributed to nobody.
33
33
  *
34
34
  * **Precision falls out of how much is written.** `-984` is a year, `689/6` a
35
35
  * month, `689/6/19` a day. There is no precision vocabulary, so there is
@@ -98,7 +98,7 @@
98
98
  */
99
99
 
100
100
  import { ADDRESS_SEGMENT_PATTERN } from "./address-charset.mjs";
101
- import { calendarStructure, canonicalYear, dateSortKey } from "./calendars.mjs";
101
+ import { calendarStructure, canonicalYear, dateSortKey, daysInMonth } from "./calendars.mjs";
102
102
  import { positionOfFrontmatterPath } from "./diagnostics.mjs";
103
103
 
104
104
  /**
@@ -120,7 +120,7 @@ const ERA = `(?:${SEGMENT}-){0,3}${SEGMENT}\\.${SEGMENT}`;
120
120
  /**
121
121
  * What a date's trailing token may look like.
122
122
  *
123
- * An affiliation's address followed by `.<era shortcode>`. The `{0,3}`
123
+ * A calendar note's address followed by `.<era shortcode>`. The `{0,3}`
124
124
  * repetition is the four address forms exactly — bare `shortcode`,
125
125
  * `type-shortcode`, `system-type-shortcode` and
126
126
  * `package-system-type-shortcode` — so the hyphen separates an address's
@@ -182,9 +182,10 @@ function unknownRecord() {
182
182
  * @param {unknown} value - The authored value. A number is what YAML hands
183
183
  * back for a bare year, so it is accepted and stringified.
184
184
  * @param {object} [options]
185
- * @param {{months?: number|null, monthDays?: number|null}} [options.calendar]
186
- * The resolved `calendar:` block, whose `months` and `monthDays` bound a
187
- * written month and day. An absent bound is not applied.
185
+ * @param {{months?: readonly object[]|null}} [options.calendar] - The calendar
186
+ * the value is written in, as its note declares it. Its ordered month list
187
+ * bounds a written month and a written day, the day against the length of
188
+ * the month it names. A calendar that declares no list bounds nothing.
188
189
  * @param {string} [options.field] - The key that carried it, named in every
189
190
  * message. Omitted, a message names the value alone.
190
191
  * @param {boolean} [options.allowUnknown=true] - Whether `"unknown"` is a
@@ -246,7 +247,7 @@ export function parseNoteDate(value, options) {
246
247
  if (MALFORMED_ERA_PATTERN.test(trimmed)) {
247
248
  return refuse(
248
249
  `${subject} — an era is written ` +
249
- `\`<affiliation shortcode>.<era shortcode>\`, lowercase letters ` +
250
+ `\`<calendar shortcode>.<era shortcode>\`, lowercase letters ` +
250
251
  `and digits only`,
251
252
  );
252
253
  }
@@ -256,14 +257,14 @@ export function parseNoteDate(value, options) {
256
257
  // the rule rather than guessing a concrete replacement.
257
258
  return refuse(
258
259
  `${subject} names no era — a reckoning is written ` +
259
- `\`<affiliation shortcode>.<era shortcode>\`: a year before ` +
260
+ `\`<calendar shortcode>.<era shortcode>\`: a year before ` +
260
261
  `an era's epoch is written negative, and a year after it ` +
261
262
  `simply drops the token`,
262
263
  );
263
264
  }
264
265
  return refuse(
265
266
  `${subject} is not a date — write ` +
266
- `\`[~][-]YYYY[/MM[/DD]] [<affiliation shortcode>.<era shortcode>]\`: ` +
267
+ `\`[~][-]YYYY[/MM[/DD]] [<calendar shortcode>.<era shortcode>]\`: ` +
267
268
  `a year, negative where it falls before the epoch it is counted ` +
268
269
  `from, optionally a month and a day, and an era where the date is a ` +
269
270
  `reckoning somebody kept`,
@@ -285,26 +286,31 @@ export function parseNoteDate(value, options) {
285
286
  );
286
287
  }
287
288
 
288
- const { months, monthDays } = calendarStructure(calendar);
289
+ const { months } = calendarStructure(calendar);
289
290
  if (month !== null && month < 1) {
290
291
  return refuse(`${subject} writes month ${month}, and a month is numbered from 1`);
291
292
  }
292
- // An upper bound is checked only where the package states one. Twelve
293
- // Gregorian months assumed here would refuse `667/2/30`, which is a correct
294
- // date in a year of twelve thirty-day months.
295
- if (month !== null && months !== null && month > months) {
293
+ // An upper bound is checked only where the calendar states its months.
294
+ // Twelve Gregorian months assumed here would refuse `667/2/30`, which is a
295
+ // correct date in a year of twelve thirty-day months.
296
+ if (month !== null && months !== null && month > months.length) {
296
297
  return refuse(
297
- `${subject} writes month ${month}, and \`calendar.months\` declares a ` +
298
- `year of ${months}`,
298
+ `${subject} writes month ${month}, and the calendar it is written in ` +
299
+ `keeps ${months.length} months`,
299
300
  );
300
301
  }
301
302
  if (day !== null && day < 1) {
302
303
  return refuse(`${subject} writes day ${day}, and a day is numbered from 1`);
303
304
  }
304
- if (day !== null && monthDays !== null && day > monthDays) {
305
+ // Per month, because the months differ: a five-day month sits in the list
306
+ // wherever its people put it, and a bound taken from the longest month
307
+ // would accept a date that calendar has no day for.
308
+ const length = month !== null ? daysInMonth(calendar, month) : null;
309
+ if (day !== null && length !== null && day > length) {
310
+ const named = months?.[month - 1]?.name;
305
311
  return refuse(
306
- `${subject} writes day ${day}, and \`calendar.monthDays\` declares a ` +
307
- `month of ${monthDays}`,
312
+ `${subject} writes day ${day}, and ${named ? `${named}` : `month ${month}`} ` +
313
+ `is ${length} days long`,
308
314
  );
309
315
  }
310
316
 
@@ -85,6 +85,12 @@ import { currentType } from "./ids.mjs";
85
85
  // What a place is next to and reachable from — the two relation lists and
86
86
  // the checks that hold them to their closed sets and to each other.
87
87
  import { checkBorders, checkRoutes } from "./place-relations.mjs";
88
+ import {
89
+ CALENDAR_FIELDS,
90
+ INVARIANT_FIELDS,
91
+ checkCalendarNote,
92
+ checkWorldFacts,
93
+ } from "./calendar-notes.mjs";
88
94
  import { checkHeld } from "./holdings.mjs";
89
95
  import { checkCitedPopulations, checkPopulation } from "./populations.mjs";
90
96
  // What trade a settlement supports — the scale and the check that holds a
@@ -306,6 +312,22 @@ const CHARGES = Object.freeze([
306
312
  { name: "charges.max", ...NUM, describe: "Most charges it can hold; unset means no maximum." },
307
313
  ]);
308
314
 
315
+ /**
316
+ * Everything `place` asks of a whole note.
317
+ *
318
+ * Two questions about one subject, and they are independent: who holds this
319
+ * land, and — where the note is a body rather than somewhere within one — what
320
+ * the body states about itself. A type declares one whole-note check, so the
321
+ * two are composed here rather than either being folded into the other.
322
+ *
323
+ * @param {object} note - The note.
324
+ * @param {object} [opts] - The lint's options, passed through unchanged.
325
+ * @returns {object[]} Findings from both.
326
+ */
327
+ function checkPlace(note, opts) {
328
+ return [...checkHeld(note, opts), ...checkWorldFacts(note, opts)];
329
+ }
330
+
309
331
  /* --------------------------------------------------------------------- */
310
332
  /* The vocabulary */
311
333
  /* --------------------------------------------------------------------- */
@@ -1044,16 +1066,27 @@ export const NOTE_VOCABULARY = Object.freeze({
1044
1066
  "bestiary",
1045
1067
  "gathering",
1046
1068
  ]),
1047
- // Nothing of its own: a lore note is prose, and what it *is* about is
1048
- // its subType. The specification declares an empty table for it, and
1049
- // the authored corpus writes no `data:` key on any of the 180.
1050
- data: Object.freeze([]),
1069
+ // A lore note is prose, and what it *is* about is its subType — with
1070
+ // one exception. A calendar is a division of the year, and a division
1071
+ // is data; the check scopes the family to the subType that means it,
1072
+ // because `DataFieldSpec` declares the keys a type accepts and not the
1073
+ // subType that may write them.
1074
+ check: checkCalendarNote,
1075
+ data: CALENDAR_FIELDS,
1051
1076
  }),
1052
1077
 
1053
1078
  place: Object.freeze({
1054
1079
  stubbable: true,
1055
- subTypes: Object.freeze(["world", "region", "settlement", "site", "structure", "feature"]),
1056
- check: checkHeld,
1080
+ subTypes: Object.freeze([
1081
+ "world",
1082
+ "region",
1083
+ "settlement",
1084
+ "site",
1085
+ "structure",
1086
+ "feature",
1087
+ "celestial",
1088
+ ]),
1089
+ check: checkPlace,
1057
1090
  data: Object.freeze([
1058
1091
  {
1059
1092
  name: "demonym",
@@ -1104,6 +1137,10 @@ export const NOTE_VOCABULARY = Object.freeze({
1104
1137
  "Journeys from this place's centre — where the destination lies, how " +
1105
1138
  "it is travelled, and about how many days it takes.",
1106
1139
  },
1140
+ // What a body states about itself: its size, its year, its moon.
1141
+ // Legal on a `world` or a `celestial` and nowhere else, which the
1142
+ // type's own check is what enforces.
1143
+ ...INVARIANT_FIELDS,
1107
1144
  ]),
1108
1145
  }),
1109
1146
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "22.8.0",
3
+ "version": "22.9.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",
@@ -844,16 +844,6 @@ export type ContentBuildConfig = {
844
844
  * each pack followed by its companions.
845
845
  */
846
846
  packDirectories: readonly string[];
847
- /**
848
- * How this package's year is divided, and
849
- * the date it calls the present. Every
850
- * value `null` when none is declared.
851
- */
852
- calendar: Readonly<{
853
- months: number | null;
854
- monthDays: number | null;
855
- present: string | null;
856
- }>;
857
847
  /**
858
848
  * Passed through
859
849
  * frozen, uninterpreted. `{}` when absent.
@@ -0,0 +1,173 @@
1
+ /**
2
+ * The world facts a package declares, read from its own notes.
3
+ *
4
+ * Nothing here is configured and nothing is assumed: a package with no world
5
+ * note has no year, and every question that depends on one goes unasked.
6
+ *
7
+ * @param {object} [index] - The link index, holding every note in the package.
8
+ * @returns {{year: object|null, present: unknown, moon: object|null,
9
+ * body: object|null, moonName: string|null, yearNote: object|null,
10
+ * moonNote: object|null}} What the package's notes state.
11
+ */
12
+ export function worldInvariants(index?: object): {
13
+ year: object | null;
14
+ present: unknown;
15
+ moon: object | null;
16
+ body: object | null;
17
+ moonName: string | null;
18
+ yearNote: object | null;
19
+ moonNote: object | null;
20
+ };
21
+ /**
22
+ * Check a `place` note's world facts.
23
+ *
24
+ * Declared on `place` beside its tenure check, so the lint runs both with the
25
+ * same index. Four rules, and each is a fact about the setting rather than
26
+ * about the file:
27
+ *
28
+ * - a world fact is written on a body — a `world` or a `celestial` — and
29
+ * nowhere else;
30
+ * - a package states **one** year, because two years is two settings and
31
+ * nothing can choose between them;
32
+ * - a package states **one** moon, for the same reason;
33
+ * - a moon with no year is refused, because a cycle measured in days has
34
+ * nothing to be measured against.
35
+ *
36
+ * `data.present` is read through the parser a note's dates go through, with the
37
+ * corpus in hand, so a present written in an era resolves where it is written.
38
+ *
39
+ * @param {object} note - The note, as the link index hands it over.
40
+ * @param {object} [opts]
41
+ * @param {object} [opts.index] - The link index, holding every note.
42
+ * @returns {object[]} Findings.
43
+ */
44
+ export function checkWorldFacts(note: object, { index }?: {
45
+ index?: object | undefined;
46
+ }): object[];
47
+ /**
48
+ * Check a `lore` note's calendar declaration.
49
+ *
50
+ * Both halves of one sentence, and the second is the half that keeps the family
51
+ * from leaking: a calendar note must carry its months and its epoch, and a lore
52
+ * note that is not a calendar must carry none of the family at all.
53
+ *
54
+ * The sum is the only arithmetic. It runs where the package states a year and
55
+ * the note states months, and nowhere else — neither fires on a package that
56
+ * declared nothing.
57
+ *
58
+ * A calendar note **cannot** restate a world fact, and it needs no rule of its
59
+ * own to stop it: `data.year`, `data.moon`, `data.world` and `data.body` are
60
+ * declared on `place` and not on `lore`, so the closed `data:` container
61
+ * already refuses them by the rule it refuses every undeclared key by.
62
+ *
63
+ * @param {object} note - The note, as the link index hands it over.
64
+ * @param {object} [opts]
65
+ * @param {object} [opts.index] - The link index, for the world's year.
66
+ * @returns {object[]} Findings.
67
+ */
68
+ export function checkCalendarNote(note: object, { index }?: {
69
+ index?: object | undefined;
70
+ }): object[];
71
+ /**
72
+ * The four days the year turns on, by day of year.
73
+ *
74
+ * The year opens on the first of the first month, and the year's length sets
75
+ * the spacing; nothing else is consulted. A body's tilt is what makes seasons
76
+ * exist at all, and it is read by nobody — a quarter day is where the calendar
77
+ * opens plus a quarter of the year, to the nearest whole day, because a day is
78
+ * the smallest thing a calendar can name.
79
+ *
80
+ * @param {number} daysPerYear - The world's year.
81
+ * @returns {number[]} Four day-of-year numbers, in order from the year's start.
82
+ */
83
+ export function quarterDays(daysPerYear: number): number[];
84
+ /**
85
+ * Compile one calendar note and the package's world facts into one definition.
86
+ *
87
+ * The object both consumers read: array-shaped for core's `ArrayField`
88
+ * collections, carrying the keys core prunes and Calendaria keeps.
89
+ *
90
+ * @param {object} opts
91
+ * @param {object} opts.note - The calendar note.
92
+ * @param {object} opts.invariants - What {@link worldInvariants} read.
93
+ * @param {string} [opts.contentPackage] - The package shipping the note, which
94
+ * is a fact about the tree rather than about the note: a note never declares
95
+ * its own package.
96
+ * @returns {object} The definition.
97
+ */
98
+ export function compileCalendar({ note, invariants, contentPackage }: {
99
+ note: object;
100
+ invariants: object;
101
+ contentPackage?: string | undefined;
102
+ }): object;
103
+ /**
104
+ * Wrap a definition in the envelope a Calendaria settings import reads.
105
+ *
106
+ * `calendarData` is the definition unchanged — the importer derives an id from
107
+ * its `name` and creates a custom calendar from it — so the envelope adds a
108
+ * header and nothing else. That is what makes the two artefacts assertable
109
+ * against each other rather than merely similar.
110
+ *
111
+ * @param {object} definition - What {@link compileCalendar} produced.
112
+ * @param {object} opts
113
+ * @param {string} opts.version - The Calendaria version this was generated
114
+ * against, which the importer records.
115
+ * @param {string} opts.exportedAt - When, as an ISO 8601 instant.
116
+ * @returns {object} The envelope.
117
+ */
118
+ export function calendariaEnvelope(definition: object, { version, exportedAt }: {
119
+ version: string;
120
+ exportedAt: string;
121
+ }): object;
122
+ /**
123
+ * Every calendar a package declares, compiled.
124
+ *
125
+ * A package whose notes state no year declares no calendars and is asked
126
+ * nothing about one, which is the right answer for a package that builds a
127
+ * setting somebody else wrote down.
128
+ *
129
+ * @param {object} [index] - The link index, holding every note.
130
+ * @param {object} [opts]
131
+ * @param {string} [opts.contentPackage] - The package shipping the tree.
132
+ * @returns {{calendars: object[], invariants: object}} One compiled definition
133
+ * per calendar note, and the facts they were compiled against.
134
+ */
135
+ export function compileCalendars(index?: object, { contentPackage }?: {
136
+ contentPackage?: string | undefined;
137
+ }): {
138
+ calendars: object[];
139
+ invariants: object;
140
+ };
141
+ /** The `lore` subType whose notes are calendars. */
142
+ export const CALENDAR_SUBTYPE: "calendar";
143
+ /**
144
+ * The `place` subTypes a world fact may be written on.
145
+ *
146
+ * `world` is a body people stand on; `celestial` is one they only look at. A
147
+ * region, a settlement, a site, a structure or a feature is somewhere *in* a
148
+ * world and states nothing about the world's year.
149
+ *
150
+ * @type {readonly string[]}
151
+ */
152
+ export const INVARIANT_SUBTYPES: readonly string[];
153
+ /**
154
+ * The `data:` keys a calendar note may write — the closed list, in authored
155
+ * order.
156
+ *
157
+ * Every key in either target schema that is not here is either a world fact,
158
+ * read from a `place` note and written by the emitters, or omitted because no
159
+ * calendar has declared one.
160
+ *
161
+ * @type {readonly object[]}
162
+ */
163
+ export const CALENDAR_FIELDS: readonly object[];
164
+ /**
165
+ * The `data:` keys a world or a celestial body may write.
166
+ *
167
+ * Four groups, and which note carries which is a fact about the body rather
168
+ * than a convention: a planet states its own size, tilt, year and present; a
169
+ * moon states its own size, orbit and cycle.
170
+ *
171
+ * @type {readonly object[]}
172
+ */
173
+ export const INVARIANT_FIELDS: readonly object[];