@heroiclands/package-build 22.14.4 → 22.15.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 +26 -0
- package/CONTENT.md +25 -1
- package/bin/content-build.mjs +3 -7
- package/bin/package-build.mjs +25 -0
- package/docs/api.md +61 -34
- package/docs/commands.md +65 -9
- package/docs/configuration.md +6 -3
- package/docs/content-format.md +165 -121
- package/docs/diagnostics.md +1 -1
- package/docs/getting-started.md +23 -0
- package/engine/base-compiler.mjs +3 -1
- package/engine/being-age.mjs +16 -24
- package/engine/calendar-artifacts.mjs +153 -0
- package/engine/calendar-notes.mjs +64 -16
- package/engine/calendars.mjs +129 -0
- package/engine/content-index.mjs +3 -1
- package/engine/exported-scene.mjs +230 -0
- package/engine/generate.mjs +1 -0
- package/engine/ids.mjs +1 -1
- package/engine/index.mjs +6 -0
- package/engine/infobox.mjs +4 -0
- package/engine/itinerary-scenes.mjs +170 -0
- package/engine/map-build.mjs +10 -45
- package/engine/map-dot.mjs +6 -3
- package/engine/map-graphviz.mjs +50 -131
- package/engine/map-notes.mjs +26 -3
- package/engine/map-places.mjs +12 -6
- package/engine/map-raster.mjs +49 -0
- package/engine/note-dates.mjs +125 -85
- package/engine/note-vocabulary.mjs +22 -6
- package/engine/pdf-build.mjs +57 -1
- package/engine/pdf-render.mjs +18 -0
- package/engine/reckoning-markers.mjs +207 -0
- package/engine/scenes.mjs +113 -26
- package/engine/site-build.mjs +11 -13
- package/engine/site-maps.mjs +5 -35
- package/engine/site-preview.mjs +6 -1
- package/engine/social-tie-terms.mjs +3 -0
- package/engine/social-ties.mjs +30 -19
- package/package.json +3 -1
- package/types/engine/being-age.d.mts +2 -2
- package/types/engine/calendar-artifacts.d.mts +16 -0
- package/types/engine/calendar-notes.d.mts +1 -1
- package/types/engine/calendars.d.mts +65 -0
- package/types/engine/exported-scene.d.mts +12 -0
- package/types/engine/index.d.mts +2 -0
- package/types/engine/itinerary-scenes.d.mts +29 -0
- package/types/engine/map-build.d.mts +4 -13
- package/types/engine/map-graphviz.d.mts +17 -59
- package/types/engine/map-places.d.mts +5 -1
- package/types/engine/map-raster.d.mts +8 -0
- package/types/engine/note-dates.d.mts +6 -0
- package/types/engine/note-vocabulary.d.mts +3 -3
- package/types/engine/pdf-render.d.mts +7 -1
- package/types/engine/reckoning-markers.d.mts +27 -0
- package/types/engine/scenes.d.mts +9 -2
- package/types/engine/site-build.d.mts +1 -5
- package/types/engine/site-maps.d.mts +2 -7
- package/types/engine/social-tie-terms.d.mts +2 -0
- package/types/engine/social-ties.d.mts +2 -1
package/docs/content-format.md
CHANGED
|
@@ -326,6 +326,42 @@ flow collection is formatted into one of those forms; folded collections are
|
|
|
326
326
|
not used. `content-build format --write` applies this rule and the top-level
|
|
327
327
|
order without changing the Markdown body.
|
|
328
328
|
|
|
329
|
+
#### Dates and reckoning markers
|
|
330
|
+
|
|
331
|
+
A calendar names and orders the days within a year. An **era** states where its
|
|
332
|
+
year count begins. Several eras may use one calendar; each has its own origin.
|
|
333
|
+
An era may declare a short, uppercase `marker` for authored dates. The marker
|
|
334
|
+
selects that era and the calendar whose months interpret the date inside it:
|
|
335
|
+
|
|
336
|
+
```yaml
|
|
337
|
+
data:
|
|
338
|
+
born: VR(676/12/5)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
`VR` is a marker declared on an era row of the Common Calendar. `VR(720/5/14)`
|
|
342
|
+
means its year 720, fifth month, fourteenth day. `VR(720/5)` states month
|
|
343
|
+
precision, and `VR(720)` states year precision. The parser retains that
|
|
344
|
+
precision; it does not invent a first day when the author only knows a year.
|
|
345
|
+
`VR(~-480)` keeps an approximate year. A date whose occurrence is known but
|
|
346
|
+
whose value is not recorded is `unknown`, with no marker.
|
|
347
|
+
|
|
348
|
+
The calendar-neutral spelling is `<year>.<day>[:HHMMSS]`: `720.136` names day
|
|
349
|
+
136 of canonical year 720, and `720.136:143005` adds 14:30:05. The dot means
|
|
350
|
+
**day of year**, not a month. Its day must fit the world's `data.year.days`,
|
|
351
|
+
and its time must fit a 24-hour day. A bare year is also accepted. Unmarked
|
|
352
|
+
slash dates are accepted as calendar-neutral numeric month/day values; their
|
|
353
|
+
month division has no named calendar, so use a marker when the month is part
|
|
354
|
+
of a people's reckoning.
|
|
355
|
+
|
|
356
|
+
An era's `start` locates its first year on the canonical timeline. The marker's
|
|
357
|
+
own start is written canonically, such as `start: 1`, so its definition has no
|
|
358
|
+
circular reference. Another era may place its start using a marker already
|
|
359
|
+
defined in the corpus. A year-precision start anchors the reckoning at the
|
|
360
|
+
first day of that canonical year for conversion; it does not date the event
|
|
361
|
+
that gave the era its name. A marker must be unique across the corpus and contain
|
|
362
|
+
uppercase letters or digits, beginning with a letter. An optional `end` bounds
|
|
363
|
+
dates in that era; a year-precision end includes the whole named year.
|
|
364
|
+
|
|
329
365
|
**`data:` is closed.** It holds the type-specific facts about the
|
|
330
366
|
subject — a weapon's weight, an affliction's transmission, a being's species —
|
|
331
367
|
and every note type declares which keys it may carry. A misspelled key is a
|
|
@@ -2452,6 +2488,19 @@ an acquaintance knows them without a strong disposition; a rival opposes them
|
|
|
2452
2488
|
without implacable hostility; a nemesis opposes them personally and
|
|
2453
2489
|
implacably. The infobox groups targets by term and links to their notes.
|
|
2454
2490
|
|
|
2491
|
+
```yaml
|
|
2492
|
+
data:
|
|
2493
|
+
socialTies:
|
|
2494
|
+
being-kaldor: patron
|
|
2495
|
+
being-alys: dependent
|
|
2496
|
+
affiliation-silverguild: nemesis
|
|
2497
|
+
kethira-note-being-kaldas: rival
|
|
2498
|
+
```
|
|
2499
|
+
|
|
2500
|
+
The full Address names a target in another package. Each target has one tie:
|
|
2501
|
+
writing its short and full Address in the same map still names one target and is
|
|
2502
|
+
an error.
|
|
2503
|
+
|
|
2455
2504
|
Each tie is one-sided. The target need not name the subject back, even when
|
|
2456
2505
|
`patron` and `dependent` would describe opposite ends of the same support.
|
|
2457
2506
|
Kinship, household membership, organizational membership, rank, and office are
|
|
@@ -3425,15 +3474,15 @@ In-world information about people, places, or concepts.
|
|
|
3425
3474
|
tournament is not a matter of time-reckoning at all — and from `culture`, which is a grouping
|
|
3426
3475
|
of people rather than an occasion they attend.
|
|
3427
3476
|
|
|
3428
|
-
| `data` property | Values
|
|
3429
|
-
| --------------- |
|
|
3430
|
-
| `epoch` | `date`
|
|
3431
|
-
| `months` | `{ name, abbreviation?, days }[]`
|
|
3432
|
-
| `weekdays` | `{ name, abbreviation? }[]`
|
|
3433
|
-
| `seasons` | `{ name, abbreviation?, monthStart?, monthEnd?, dayStart?, dayEnd? }[]`
|
|
3434
|
-
| `eras` | `{ shortcode, name, abbreviation?, proclaimedBy?, start, end? }[]`
|
|
3435
|
-
| `dateFormats` | `Map<slot, format>`
|
|
3436
|
-
| `event` | `Map<field, value>`
|
|
3477
|
+
| `data` property | Values | Description |
|
|
3478
|
+
| --------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
3479
|
+
| `epoch` | `date` | Which in-world day the world's clock reads zero on |
|
|
3480
|
+
| `months` | `{ name, abbreviation?, days }[]` | The months this calendar keeps, in order — the list sums to the world's year |
|
|
3481
|
+
| `weekdays` | `{ name, abbreviation? }[]` | The days of the week it names, in order; a calendar with no week writes none |
|
|
3482
|
+
| `seasons` | `{ name, abbreviation?, monthStart?, monthEnd?, dayStart?, dayEnd? }[]` | The seasons it marks, bounded by month or by day of year |
|
|
3483
|
+
| `eras` | `{ shortcode, name, marker?, abbreviation?, proclaimedBy?, start, end? }[]` | The year-counts kept in it; a marker names one era and uses this calendar's months |
|
|
3484
|
+
| `dateFormats` | `Map<slot, format>` | Calendaria display formats for this calendar |
|
|
3485
|
+
| `event` | `Map<field, value>` | A dated occurrence, with its sources, locations, reach, and relationships |
|
|
3437
3486
|
|
|
3438
3487
|
`data.event` is available on every `lore` subType. It holds structured
|
|
3439
3488
|
chronology metadata, including the event's kind, date, sources, and the places
|
|
@@ -3450,13 +3499,13 @@ the year and says where the count begins, and one that does neither is prose
|
|
|
3450
3499
|
about time rather than a calendar.
|
|
3451
3500
|
|
|
3452
3501
|
```yaml
|
|
3453
|
-
type: lore
|
|
3454
|
-
subType: calendar
|
|
3455
3502
|
shortcode: commoncal
|
|
3456
3503
|
name:
|
|
3457
3504
|
full: The Common Calendar
|
|
3505
|
+
type: lore
|
|
3506
|
+
subType: calendar
|
|
3458
3507
|
data:
|
|
3459
|
-
epoch: 720/1/1
|
|
3508
|
+
epoch: VR(720/1/1)
|
|
3460
3509
|
months:
|
|
3461
3510
|
- { name: Floralis, abbreviation: Flor, days: 30 }
|
|
3462
3511
|
- { name: Lusenar, abbreviation: Luse, days: 31 }
|
|
@@ -3466,6 +3515,7 @@ data:
|
|
|
3466
3515
|
eras:
|
|
3467
3516
|
- shortcode: founding
|
|
3468
3517
|
name: After the Founding
|
|
3518
|
+
marker: VR
|
|
3469
3519
|
abbreviation: AF
|
|
3470
3520
|
proclaimedBy: vylarinmpr
|
|
3471
3521
|
start: 1
|
|
@@ -3492,8 +3542,10 @@ that: `months` must sum to `year.days`, and a list that does not describes a
|
|
|
3492
3542
|
different world rather than a different calendar.
|
|
3493
3543
|
|
|
3494
3544
|
**An era is a way of counting years within a calendar**, so its rows sit beside
|
|
3495
|
-
the months. Each states its own `shortcode`, unique within the note
|
|
3496
|
-
names
|
|
3545
|
+
the months. Each states its own `shortcode`, unique within the note. A date
|
|
3546
|
+
names an era with its marker, such as `VR(720/5/14)`. A calendar may contain
|
|
3547
|
+
several era rows, each with its own marker and year origin. The marker is an
|
|
3548
|
+
authoring identifier; the `abbreviation` is its printed label.
|
|
3497
3549
|
`proclaimedBy` is optional: a reckoning whose proclaiming body is unknown, or
|
|
3498
3550
|
whose body has no note, says so by leaving it out.
|
|
3499
3551
|
|
|
@@ -3503,115 +3555,96 @@ that names none.
|
|
|
3503
3555
|
|
|
3504
3556
|
### type: map
|
|
3505
3557
|
|
|
3506
|
-
A map
|
|
3507
|
-
locations described in its own body. Produces a Foundry **Scene**, and a web page carrying the map image
|
|
3508
|
-
and its description.
|
|
3558
|
+
A map note has its own prose and a Foundry Scene. A map can depict a place or an encounter. Several maps can depict the same place; the map names that place with `sohl.place`, while the place note does not list its maps.
|
|
3509
3559
|
|
|
3510
|
-
|
|
3511
|
-
reasons. A place commonly has **several** maps — a floor per level of a keep, or
|
|
3512
|
-
a feature's detail map alongside its local-area map — and a scene nested in a
|
|
3513
|
-
place could only ever be one. A Scene is a substantial document in itself: walls,
|
|
3514
|
-
doors, lights, tiles, sounds, region shapes and pins, authored in two deliberate
|
|
3515
|
-
unit conventions. And a map has prose and named locations of its own, which makes
|
|
3516
|
-
it a document rather than a field.
|
|
3560
|
+
**subType**:
|
|
3517
3561
|
|
|
3518
|
-
|
|
3519
|
-
|
|
3562
|
+
- `battlemap`: A tactical Scene built in Foundry.
|
|
3563
|
+
- `localmap`: A settlement or local-area Scene built in Foundry.
|
|
3564
|
+
- `regionalmap`: A large-scale chart with an authored image and scale.
|
|
3565
|
+
- `totm`: A gridless image Scene for theatre-of-the-mind play.
|
|
3566
|
+
|
|
3567
|
+
| `data` property | Values | Description |
|
|
3568
|
+
| ----------------- | ------------ | ---------------------------------------------------------- |
|
|
3569
|
+
| `scene` | object | Exported Foundry Scene for battle and local maps |
|
|
3570
|
+
| `fixup` | object[] | Asset address replacements in an exported Scene |
|
|
3571
|
+
| `bgImage` | Address | Background image for regional and theatre-of-the-mind maps |
|
|
3572
|
+
| `scale` | object | Regional distance and unit per grid unit |
|
|
3573
|
+
| `dimensions` | `[int, int]` | Optional shared dimensions metadata |
|
|
3574
|
+
| `pxPerGrid` | number | Optional shared grid size metadata |
|
|
3575
|
+
| `navName` | string | Short Scene navigation name |
|
|
3576
|
+
| `levelName` | string | Embedded Level name |
|
|
3577
|
+
| `backgroundColor` | string | Colour outside the art |
|
|
3578
|
+
| `overlay` | string | Foreground art path |
|
|
3579
|
+
| `walls` | object | Regional wall geometry |
|
|
3580
|
+
| `doors` | object | Regional door geometry |
|
|
3581
|
+
| `lights` | object | Regional light geometry |
|
|
3582
|
+
| `tiles` | object | Regional tile geometry |
|
|
3583
|
+
| `sounds` | object | Regional sound geometry |
|
|
3584
|
+
| `regions` | object | Regional region geometry |
|
|
3585
|
+
| `notes` | object | Regional pin geometry |
|
|
3586
|
+
| `place` | Address | Depicted place, when one exists |
|
|
3587
|
+
|
|
3588
|
+
#### Battle and local maps
|
|
3589
|
+
|
|
3590
|
+
Author the Scene in Foundry, export it, convert the export to YAML, and put the resulting object at `data.scene`. Preserve its walls, lights, tiles, regions, levels, environment, flags, and canvas settings. The export can name art from another Foundry package; that path is used as authored in Foundry. The Scene needs positive whole-number `width` and `height`, at least one Level, a valid `initialLevel` when present, and a unique 16-character `_id` on each embedded document. The build derives the top-level Scene `_id` from the map note, adds compendium keys and build metadata, and otherwise preserves the exported fields.
|
|
3591
|
+
|
|
3592
|
+
A pin's `text` can contain `#anchor`, where the map note has a heading such as `# Big Bad Wolf {#myanchor1}`. With `text: '#myanchor1'`, the compiled pin opens the JournalEntry and page generated from this map note and displays **Big Bad Wolf**. Its position, icon, other settings, and embedded `_id` remain as exported. A missing anchor is a build error. Pins whose text does not use `#anchor` retain their exported text and references.
|
|
3593
|
+
|
|
3594
|
+
`data.fixup` replaces exported asset paths without editing the Scene object by
|
|
3595
|
+
hand. Each entry has `path`, `type: address`, and `value`, an asset Address in
|
|
3596
|
+
the local or a fetched content index. Paths start at `data.scene` and use a
|
|
3597
|
+
property path with array indices or stable embedded `_id` selectors. A path
|
|
3598
|
+
must name one existing string or null field, and the address must resolve; any
|
|
3599
|
+
mistake fails the build. An `_id` selector remains stable when Foundry reorders
|
|
3600
|
+
an array on export.
|
|
3520
3601
|
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3602
|
+
```yaml
|
|
3603
|
+
data:
|
|
3604
|
+
fixup:
|
|
3605
|
+
- { path: ".notes[NNNNNNNNNNNNNNNN].texture.src", type: address, value: sohl-none-icon-book }
|
|
3606
|
+
- { path: ".sounds[0].path", type: address, value: sohl-none-audio-forest }
|
|
3607
|
+
```
|
|
3526
3608
|
|
|
3527
|
-
|
|
3609
|
+
Fixups change the compiled copy only. The exported Scene in the note stays
|
|
3610
|
+
intact, and pin anchor binding runs after fixups. A package name such as
|
|
3611
|
+
`fvtt` is an ordinary address package name: it needs published asset records
|
|
3612
|
+
before its addresses can resolve.
|
|
3528
3613
|
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
**`levels:` is never authored, and must never be.** A Scene has to ship at least
|
|
3565
|
-
one Level or it has no map at all, and nothing supplies one after the fact: the
|
|
3566
|
-
client-side `_preCreate` that would create it does not run during offline pack
|
|
3567
|
-
compilation, and the server-side migration shim is version-gated on
|
|
3568
|
-
`_stats.coreVersion`, so a pack stamped 14.x or later skips it entirely. The
|
|
3569
|
-
single Level is synthesised from `bgImage`, `overlay`, `levelName` and
|
|
3570
|
-
`backgroundColor`.
|
|
3571
|
-
|
|
3572
|
-
> **`bgImage` is an `Address` under `data:`, as every other type's artwork is.**
|
|
3573
|
-
> Art is not system-specific — a Scene is a core Foundry document, and a second
|
|
3574
|
-
> system would want the identical art — so the field sits under `data:` rather
|
|
3575
|
-
> than inside a system block.
|
|
3576
|
-
>
|
|
3577
|
-
> **A map has no `img`.** `bgImage` is the only key naming a map's background,
|
|
3578
|
-
> and a note writing `img:` or `data.img:` is refused. The two are not
|
|
3579
|
-
> alternative spellings: `img` is a Foundry destination and no Scene has one.
|
|
3580
|
-
|
|
3581
|
-
**Every art key inside a map is an address, and each is named for the type it
|
|
3582
|
-
reaches.** A map's geometry is authored in Foundry's own units, and its art is
|
|
3583
|
-
not authored in Foundry's own paths.
|
|
3584
|
-
|
|
3585
|
-
| authored | default type | emitted as |
|
|
3586
|
-
| -------------------- | ------------ | ---------------------------- |
|
|
3587
|
-
| `data.bgImage` | `image` | the level's `background.src` |
|
|
3588
|
-
| `tiles.<key>.image` | `image` | the tile's `texture.src` |
|
|
3589
|
-
| `sounds.<key>.audio` | `audio` | the `AmbientSound`'s `path` |
|
|
3590
|
-
|
|
3591
|
-
Naming each key for the type it references is what makes the default worth
|
|
3592
|
-
having: `audio:` reaches an `audio` address without a note ever writing the
|
|
3593
|
-
type segment. Where a tile places a glyph rather than artwork it qualifies —
|
|
3594
|
-
`image: sohl-none-icon-chest` — which is the ordinary short-form ladder rather
|
|
3595
|
-
than an exception to it.
|
|
3596
|
-
|
|
3597
|
-
**The authored key and the emitted field differ where Foundry's name differs.**
|
|
3598
|
-
An `AmbientSound` stores its clip in `path`, and that is what the compiled
|
|
3599
|
-
document carries; the note writes `audio` because the note is naming an
|
|
3600
|
-
address, not filling in a Foundry field.
|
|
3601
|
-
|
|
3602
|
-
**Two unit conventions, deliberately.** Geometry — walls, doors, lights, tiles,
|
|
3603
|
-
sounds, region shapes — is authored in **pixels**, Foundry's native storage,
|
|
3604
|
-
because traced walls often fall between grid intersections. Map pins are
|
|
3605
|
-
authored in **grid squares**, commonly
|
|
3606
|
-
half-integers, because that is how a person reads a position off a map. The two
|
|
3607
|
-
are told apart by their key: `position:` and segment or shape coordinates are
|
|
3608
|
-
pixels, `at:` is grid squares. Mixing them fails silently and visually in
|
|
3609
|
-
Foundry, so the build refuses rather than resolving.
|
|
3610
|
-
|
|
3611
|
-
**Map pins stay within the map.** `notes:` names anchors in this map note's own
|
|
3612
|
-
body, so a pin opens the journal page describing that spot. `data.place` names
|
|
3613
|
-
the place depicted by the map when one exists. A place's map listing is derived
|
|
3614
|
-
from those map notes, so each map states the relationship once.
|
|
3614
|
+
```yaml
|
|
3615
|
+
shortcode: wolfden
|
|
3616
|
+
name: { full: The Wolf's Den }
|
|
3617
|
+
type: map
|
|
3618
|
+
subType: battlemap
|
|
3619
|
+
data:
|
|
3620
|
+
scene:
|
|
3621
|
+
name: Wolf Den Scene
|
|
3622
|
+
width: 1900
|
|
3623
|
+
height: 2600
|
|
3624
|
+
grid: { type: 1, size: 100, distance: 5, units: ft }
|
|
3625
|
+
initialLevel: defaultLevel0000
|
|
3626
|
+
levels:
|
|
3627
|
+
- { _id: defaultLevel0000, background: { src: modules/maps/den.webp } }
|
|
3628
|
+
notes:
|
|
3629
|
+
- { _id: NNNNNNNNNNNNNNNN, text: "#myanchor1", x: 950, y: 2350 }
|
|
3630
|
+
---
|
|
3631
|
+
# Big Bad Wolf {#myanchor1}
|
|
3632
|
+
```
|
|
3633
|
+
|
|
3634
|
+
When the Scene has pins, its Adventure bundle carries the Scene and the JournalEntry generated from its Markdown. Importing that bundle preserves the IDs the pins address.
|
|
3635
|
+
|
|
3636
|
+
#### Regional maps
|
|
3637
|
+
|
|
3638
|
+
A `regionalmap` uses `data.bgImage`, an image Address, and `sohl.dimensions: [width, height]` plus `sohl.pxPerGrid` to build a gridless Scene. `data.scale: {distance: positive number, unit: nonempty string}` sets the distance per grid unit. Its shared `data` fields can also name `overlay`, `backgroundColor`, `levelName`, `navName`, `walls`, `doors`, `lights`, `tiles`, `sounds`, `regions`, and `notes`; the latter geometry uses the map-note conventions described in the SoHL map authoring guide. `data.scene` is not used for a regional map.
|
|
3639
|
+
|
|
3640
|
+
An SVG `data.bgImage` remains vector art on the site and in the book. The Foundry build stages a PNG at the note's stated dimensions with the installed SVG renderer; the source SVG is unchanged. Its aspect ratio must match the stated dimensions. A selected regional SVG map takes a full page in the PDF.
|
|
3641
|
+
|
|
3642
|
+
#### Theatre-of-the-mind maps
|
|
3643
|
+
|
|
3644
|
+
A `totm` map names its background with `data.bgImage` and its canvas size with
|
|
3645
|
+
`sohl.dimensions: [width, height]`. It builds a gridless Scene with token vision
|
|
3646
|
+
off. It needs no `sohl.pxPerGrid` or `data.scale`. The Markdown body remains the
|
|
3647
|
+
map's prose.
|
|
3615
3648
|
|
|
3616
3649
|
### type: place
|
|
3617
3650
|
|
|
@@ -3764,9 +3797,10 @@ data:
|
|
|
3764
3797
|
on a land route — or a road on a ship route — is an error.
|
|
3765
3798
|
- `leagues` — optional on a route; a distance, only where the note states one.
|
|
3766
3799
|
|
|
3767
|
-
**
|
|
3768
|
-
|
|
3769
|
-
|
|
3800
|
+
**A border is a shared frontier between areas.** Regions commonly state one;
|
|
3801
|
+
a settlement does so only when it actually shares a frontier with another
|
|
3802
|
+
place. Most settlements use `parents` for where they sit and `routes` for
|
|
3803
|
+
journeys between them. A pair appears in `routes` once per `mode` — a port and its
|
|
3770
3804
|
neighbour may be three days by land and one by ship — and in `borders` once.
|
|
3771
3805
|
**A place never borders its own parent or child**: containment is `parents`,
|
|
3772
3806
|
and restating it as adjacency makes the two contradict each other.
|
|
@@ -3781,6 +3815,16 @@ neighbour that states a different bearing, a different `days` or a different
|
|
|
3781
3815
|
tell which. Every `to` must resolve to a place, every value must come from its
|
|
3782
3816
|
closed set, and each finding is located at the entry that states it.
|
|
3783
3817
|
|
|
3818
|
+
The terrain names form one shared registry in package-build. A route crossing
|
|
3819
|
+
a package boundary uses the same terrain and mode rules at both ends.
|
|
3820
|
+
|
|
3821
|
+
For every place that participates in a border or route, the map builder draws
|
|
3822
|
+
an itinerary from that place. A site page carries it as a linked SVG. A PDF
|
|
3823
|
+
book that includes the place gives its map a separate full page after the
|
|
3824
|
+
entry. A configured Foundry Scene pack carries a gridless map backed by a
|
|
3825
|
+
generated PNG, with Notes linked to importable place journals. Places with no
|
|
3826
|
+
relations create no itinerary output.
|
|
3827
|
+
|
|
3784
3828
|
**A relation that crosses a package boundary is written in full at both ends.**
|
|
3785
3829
|
An omitted `<package>` segment is the package being built, in whatever note the
|
|
3786
3830
|
value is read from — a note of this package's own, or a record a consumer fetched
|
package/docs/diagnostics.md
CHANGED
|
@@ -286,7 +286,7 @@ over one would teach people to stop reading the report.
|
|
|
286
286
|
| `content-build markdown [paths..]` | it reports any markdownlint finding |
|
|
287
287
|
| `content-build site` | any gate finding (no homepage, a wikilink in frontmatter, an address that cannot be derived, an unusable or unaddressable foreign manifest, a conflicting address) or any table or wikilink error while writing pages — the run stops at the first gate that fires |
|
|
288
288
|
| `content-build pdf` | it could not build the book for a reason other than a deliberate no-op — never on a reported finding by itself |
|
|
289
|
-
| `content-build map` | `--tree` reports an `error`-severity finding (a place with no parent, a parent that does not resolve, a cycle), a named place is not one, the
|
|
289
|
+
| `content-build map` | `--tree` reports an `error`-severity finding (a place with no parent, a parent that does not resolve, a cycle), a named place is not one, the renderer fails, or the corpus cannot be indexed — never on a warning |
|
|
290
290
|
| `content-build docs item-fields --check` | the generated page differs from what is committed |
|
|
291
291
|
| `content-build package compile` | pack JSON generation reported any `error`-severity finding (checked, and the compile refused, before any pack is written), or a compiled pack fails its Scene/Level integrity check |
|
|
292
292
|
| `content-build deps fetch` | the fetch itself throws — it emits no diagnostics |
|
package/docs/getting-started.md
CHANGED
|
@@ -739,6 +739,29 @@ compiles to a page, so there is no document to address.
|
|
|
739
739
|
The index is derived and disposable; it is written under `build/` and rebuilt
|
|
740
740
|
from the tree every time.
|
|
741
741
|
|
|
742
|
+
### Calendar files for a setting module
|
|
743
|
+
|
|
744
|
+
A content package with `lore` notes of `subType: calendar` can emit Foundry
|
|
745
|
+
calendar definitions after the content index is built:
|
|
746
|
+
|
|
747
|
+
```console
|
|
748
|
+
$ npx package-build calendars --calendaria-version 1.4.2
|
|
749
|
+
✅ 5 calendars emitted (10 files).
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
The command writes `build/calendars/<shortcode>.json` and
|
|
753
|
+
`build/calendars/<shortcode>.calendaria.json` for each calendar. The first is a
|
|
754
|
+
calendar definition; the second wraps the same definition for import through
|
|
755
|
+
Calendaria's settings. The version argument names the Calendaria import format
|
|
756
|
+
the package targets. The envelope uses a fixed `exportedAt` sentinel because
|
|
757
|
+
these files are reproducible build artifacts. A package with no calendars gets
|
|
758
|
+
an empty `build/calendars/` directory.
|
|
759
|
+
|
|
760
|
+
To ship the files in a Foundry package, add
|
|
761
|
+
`{ from: build/calendars, to: calendars }` to `packageBuild.assets` and run the
|
|
762
|
+
calendar command before `package-build assets`. Running the command requires
|
|
763
|
+
the current content index; regenerate it after changing calendar notes.
|
|
764
|
+
|
|
742
765
|
## Step 11 — the release archive
|
|
743
766
|
|
|
744
767
|
```console
|
package/engine/base-compiler.mjs
CHANGED
|
@@ -100,6 +100,7 @@ import { locateFrontmatterKey } from "./retired-fields.mjs";
|
|
|
100
100
|
// present before this pass reads `data:` for anything else — see
|
|
101
101
|
// `engine/being-age.mjs`.
|
|
102
102
|
import { applyComputedBeingAge, presentAmongRecords } from "./being-age.mjs";
|
|
103
|
+
import { reckoningContext } from "./reckoning-markers.mjs";
|
|
103
104
|
|
|
104
105
|
/**
|
|
105
106
|
* The tallies one pass accumulates while walking the tree.
|
|
@@ -1078,6 +1079,7 @@ export class BasePackCompiler {
|
|
|
1078
1079
|
// to have the body anyway. So the read is one this pass was already
|
|
1079
1080
|
// making; what it no longer does is decide for itself which files to
|
|
1080
1081
|
// make it over.
|
|
1082
|
+
const dates = reckoningContext({ notes: this.corpus.records });
|
|
1081
1083
|
for (const record of this.corpus.records) {
|
|
1082
1084
|
if (!isNoteRecord(record)) continue;
|
|
1083
1085
|
const absPath = noteFile(this.contentBase, record);
|
|
@@ -1091,7 +1093,7 @@ export class BasePackCompiler {
|
|
|
1091
1093
|
stats.skippedOther++;
|
|
1092
1094
|
continue;
|
|
1093
1095
|
}
|
|
1094
|
-
applyComputedBeingAge(fm, this.worldPresent);
|
|
1096
|
+
applyComputedBeingAge(fm, this.worldPresent, dates);
|
|
1095
1097
|
// The retired frontmatter fields, refused before `selects` so a
|
|
1096
1098
|
// note is answered whichever pass would have claimed it — and
|
|
1097
1099
|
// whatever the declared value says.
|
package/engine/being-age.mjs
CHANGED
|
@@ -36,17 +36,10 @@
|
|
|
36
36
|
*
|
|
37
37
|
* A year subtracted from a year is wrong whenever the birthday has not yet
|
|
38
38
|
* happened in the present's year — the common shape, not the exception. So
|
|
39
|
-
* {@link computeAge} compares
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* calendar
|
|
43
|
-
* through the calendar's month lengths or simply reads `(month, day)` as a
|
|
44
|
-
* pair, because a calendar's months keep the same lengths every year — this
|
|
45
|
-
* package's calendars carry no leap rule (`calendar-notes.mjs`'s
|
|
46
|
-
* `years.leapYear` is always `null`). So the comparison is calendar-agnostic
|
|
47
|
-
* by construction, and correct for a twelve-month calendar of thirty-day
|
|
48
|
-
* months, a ten-month calendar, or none at all — a bare `born` with no era
|
|
49
|
-
* names no calendar to consult in the first place.
|
|
39
|
+
* {@link computeAge} compares the two dates' canonical day ordinals where an
|
|
40
|
+
* era marker supplies them. The birthday that still lies ahead in the current
|
|
41
|
+
* year does not count as a completed year. An unmarked slash date has no
|
|
42
|
+
* calendar to convert, so it uses its written month and day as its ordinal.
|
|
50
43
|
*
|
|
51
44
|
* ## `~` follows the convention {@link module:engine/note-dates} sets
|
|
52
45
|
*
|
|
@@ -65,6 +58,7 @@
|
|
|
65
58
|
import { worldInvariants } from "./calendar-notes.mjs";
|
|
66
59
|
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
67
60
|
import { parseNoteDate } from "./note-dates.mjs";
|
|
61
|
+
import { reckoningContext } from "./reckoning-markers.mjs";
|
|
68
62
|
// `born`'s retired spelling is still read during its retirement window — see
|
|
69
63
|
// `docs/content-format.md`'s "`birthday:` is the retired spelling of `born:`"
|
|
70
64
|
// — so a being on the old spelling still computes an age rather than reading
|
|
@@ -124,23 +118,21 @@ export function parseAgeMagnitude(raw) {
|
|
|
124
118
|
* @returns {number|null} The age, or `null` where `born` is `unknown` or
|
|
125
119
|
* absent, where no present is declared, or where either date fails to parse.
|
|
126
120
|
*/
|
|
127
|
-
export function computeAge(bornRaw, presentRaw) {
|
|
121
|
+
export function computeAge(bornRaw, presentRaw, dateContext = {}) {
|
|
128
122
|
if (bornRaw === undefined || bornRaw === null) return null;
|
|
129
123
|
if (presentRaw === undefined || presentRaw === null) return null;
|
|
130
124
|
|
|
131
|
-
const born = parseNoteDate(bornRaw, { allowUnknown: true }).date;
|
|
125
|
+
const born = parseNoteDate(bornRaw, { ...dateContext, allowUnknown: true }).date;
|
|
132
126
|
if (!born || !born.known || born.canonicalYear == null) return null;
|
|
133
127
|
|
|
134
|
-
const present = parseNoteDate(presentRaw, { allowUnknown: false }).date;
|
|
128
|
+
const present = parseNoteDate(presentRaw, { ...dateContext, allowUnknown: false }).date;
|
|
135
129
|
if (!present || !present.known || present.canonicalYear == null) return null;
|
|
136
130
|
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const bornOrdinal = (born.month ?? 1) * 100 + (born.day ?? 1);
|
|
143
|
-
const presentOrdinal = (present.month ?? 1) * 100 + (present.day ?? 1);
|
|
131
|
+
// A resolved marker supplies a canonical day. An unmarked slash date uses
|
|
132
|
+
// its written month and day. Missing parts read as the first day that the
|
|
133
|
+
// value can name.
|
|
134
|
+
const bornOrdinal = born.canonicalDay ?? (born.month ?? 1) * 100 + (born.day ?? 1);
|
|
135
|
+
const presentOrdinal = present.canonicalDay ?? (present.month ?? 1) * 100 + (present.day ?? 1);
|
|
144
136
|
const notYetHappened = bornOrdinal > presentOrdinal;
|
|
145
137
|
|
|
146
138
|
return present.canonicalYear - born.canonicalYear - (notYetHappened ? 1 : 0);
|
|
@@ -163,7 +155,7 @@ export function computeAge(bornRaw, presentRaw) {
|
|
|
163
155
|
* `undefined`/`null` where it states none.
|
|
164
156
|
* @returns {void}
|
|
165
157
|
*/
|
|
166
|
-
export function applyComputedBeingAge(fm, presentRaw) {
|
|
158
|
+
export function applyComputedBeingAge(fm, presentRaw, dateContext = {}) {
|
|
167
159
|
if (!isBeingType(fm?.type)) return;
|
|
168
160
|
const data = dataOf(fm);
|
|
169
161
|
if (!data) return;
|
|
@@ -179,7 +171,7 @@ export function applyComputedBeingAge(fm, presentRaw) {
|
|
|
179
171
|
}
|
|
180
172
|
|
|
181
173
|
const born = readAliasedField(fm, "born", { inData: true });
|
|
182
|
-
const computed = computeAge(born, presentRaw);
|
|
174
|
+
const computed = computeAge(born, presentRaw, dateContext);
|
|
183
175
|
if (computed !== null) data.age = computed;
|
|
184
176
|
}
|
|
185
177
|
|
|
@@ -251,7 +243,7 @@ export function checkBeingAge(note, { index } = {}) {
|
|
|
251
243
|
if (!index) return [];
|
|
252
244
|
|
|
253
245
|
const present = worldInvariants(index).present;
|
|
254
|
-
const computed = computeAge(bornRaw, present);
|
|
246
|
+
const computed = computeAge(bornRaw, present, reckoningContext(index));
|
|
255
247
|
if (computed === null) return [];
|
|
256
248
|
|
|
257
249
|
const authored = parseAgeMagnitude(ageRaw);
|