@heroiclands/package-build 22.14.3 → 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 +32 -0
- package/CONTENT.md +25 -1
- package/bin/content-build.mjs +3 -7
- package/bin/package-build.mjs +25 -0
- package/docs/api.md +64 -36
- package/docs/commands.md +65 -9
- package/docs/configuration.md +6 -3
- package/docs/content-format.md +187 -140
- package/docs/diagnostics.md +1 -1
- package/docs/getting-started.md +27 -3
- 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-images.mjs +20 -10
- 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 +48 -7
- 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/content-images.d.mts +7 -4
- 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 +16 -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
|
|
@@ -2066,10 +2102,10 @@ place for those words and this is it.
|
|
|
2066
2102
|
|
|
2067
2103
|
##### Width is a class, and the ordinary width carries no marker
|
|
2068
2104
|
|
|
2069
|
-
| `class` value | book
|
|
2070
|
-
| ------------- |
|
|
2071
|
-
| _(none)_ |
|
|
2072
|
-
| `.full-width` |
|
|
2105
|
+
| `class` value | book | website | Foundry journal |
|
|
2106
|
+
| ------------- | ----------------------------- | ------------------ | --------------- |
|
|
2107
|
+
| _(none)_ | natural width within a column | natural width | natural width |
|
|
2108
|
+
| `.full-width` | page measure | full content width | full page width |
|
|
2073
2109
|
|
|
2074
2110
|
The simple case needs no spelling, which is why the ordinary width has no name.
|
|
2075
2111
|
|
|
@@ -2080,21 +2116,24 @@ refused.
|
|
|
2080
2116
|
|
|
2081
2117
|
##### Named size is `size:`
|
|
2082
2118
|
|
|
2083
|
-
| `size` value |
|
|
2084
|
-
| ------------ |
|
|
2085
|
-
| `auto` |
|
|
2086
|
-
| `small` |
|
|
2087
|
-
| `medium` |
|
|
2088
|
-
| `large` |
|
|
2089
|
-
| `xlarge` |
|
|
2090
|
-
| `full-width` |
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2119
|
+
| `size` value | website and Foundry | book |
|
|
2120
|
+
| ------------ | ------------------- | ------------ |
|
|
2121
|
+
| `auto` | natural size | natural size |
|
|
2122
|
+
| `small` | 64 CSS pixels | 1.6 cm |
|
|
2123
|
+
| `medium` | 128 CSS pixels | 3.2 cm |
|
|
2124
|
+
| `large` | 256 CSS pixels | 5.6 cm |
|
|
2125
|
+
| `xlarge` | 512 CSS pixels | 8 cm |
|
|
2126
|
+
| `full-width` | content measure | page measure |
|
|
2127
|
+
|
|
2128
|
+
These are maximum display widths for Markdown images and embedded assets;
|
|
2129
|
+
height follows the file's aspect ratio. The available column, page, or browser
|
|
2130
|
+
measure caps every size, and print also caps image height within the page.
|
|
2131
|
+
Omitting `size:` means `auto`, which uses the file's natural dimensions within
|
|
2132
|
+
those bounds. `full-width` spans the book's page rather than one column. The
|
|
2133
|
+
`.full-width` class also grants page scope in print; when it accompanies a
|
|
2134
|
+
bounded `size:`, the image keeps that bounded width inside the page figure.
|
|
2135
|
+
A `size:` may sit before or after `float:` in the same directive, and neither
|
|
2136
|
+
changes the other's behavior.
|
|
2098
2137
|
|
|
2099
2138
|
##### Position is `float:`
|
|
2100
2139
|
|
|
@@ -2449,6 +2488,19 @@ an acquaintance knows them without a strong disposition; a rival opposes them
|
|
|
2449
2488
|
without implacable hostility; a nemesis opposes them personally and
|
|
2450
2489
|
implacably. The infobox groups targets by term and links to their notes.
|
|
2451
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
|
+
|
|
2452
2504
|
Each tie is one-sided. The target need not name the subject back, even when
|
|
2453
2505
|
`patron` and `dependent` would describe opposite ends of the same support.
|
|
2454
2506
|
Kinship, household membership, organizational membership, rank, and office are
|
|
@@ -3422,15 +3474,15 @@ In-world information about people, places, or concepts.
|
|
|
3422
3474
|
tournament is not a matter of time-reckoning at all — and from `culture`, which is a grouping
|
|
3423
3475
|
of people rather than an occasion they attend.
|
|
3424
3476
|
|
|
3425
|
-
| `data` property | Values
|
|
3426
|
-
| --------------- |
|
|
3427
|
-
| `epoch` | `date`
|
|
3428
|
-
| `months` | `{ name, abbreviation?, days }[]`
|
|
3429
|
-
| `weekdays` | `{ name, abbreviation? }[]`
|
|
3430
|
-
| `seasons` | `{ name, abbreviation?, monthStart?, monthEnd?, dayStart?, dayEnd? }[]`
|
|
3431
|
-
| `eras` | `{ shortcode, name, abbreviation?, proclaimedBy?, start, end? }[]`
|
|
3432
|
-
| `dateFormats` | `Map<slot, format>`
|
|
3433
|
-
| `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 |
|
|
3434
3486
|
|
|
3435
3487
|
`data.event` is available on every `lore` subType. It holds structured
|
|
3436
3488
|
chronology metadata, including the event's kind, date, sources, and the places
|
|
@@ -3447,13 +3499,13 @@ the year and says where the count begins, and one that does neither is prose
|
|
|
3447
3499
|
about time rather than a calendar.
|
|
3448
3500
|
|
|
3449
3501
|
```yaml
|
|
3450
|
-
type: lore
|
|
3451
|
-
subType: calendar
|
|
3452
3502
|
shortcode: commoncal
|
|
3453
3503
|
name:
|
|
3454
3504
|
full: The Common Calendar
|
|
3505
|
+
type: lore
|
|
3506
|
+
subType: calendar
|
|
3455
3507
|
data:
|
|
3456
|
-
epoch: 720/1/1
|
|
3508
|
+
epoch: VR(720/1/1)
|
|
3457
3509
|
months:
|
|
3458
3510
|
- { name: Floralis, abbreviation: Flor, days: 30 }
|
|
3459
3511
|
- { name: Lusenar, abbreviation: Luse, days: 31 }
|
|
@@ -3463,6 +3515,7 @@ data:
|
|
|
3463
3515
|
eras:
|
|
3464
3516
|
- shortcode: founding
|
|
3465
3517
|
name: After the Founding
|
|
3518
|
+
marker: VR
|
|
3466
3519
|
abbreviation: AF
|
|
3467
3520
|
proclaimedBy: vylarinmpr
|
|
3468
3521
|
start: 1
|
|
@@ -3489,8 +3542,10 @@ that: `months` must sum to `year.days`, and a list that does not describes a
|
|
|
3489
3542
|
different world rather than a different calendar.
|
|
3490
3543
|
|
|
3491
3544
|
**An era is a way of counting years within a calendar**, so its rows sit beside
|
|
3492
|
-
the months. Each states its own `shortcode`, unique within the note
|
|
3493
|
-
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.
|
|
3494
3549
|
`proclaimedBy` is optional: a reckoning whose proclaiming body is unknown, or
|
|
3495
3550
|
whose body has no note, says so by leaving it out.
|
|
3496
3551
|
|
|
@@ -3500,115 +3555,96 @@ that names none.
|
|
|
3500
3555
|
|
|
3501
3556
|
### type: map
|
|
3502
3557
|
|
|
3503
|
-
A map
|
|
3504
|
-
locations described in its own body. Produces a Foundry **Scene**, and a web page carrying the map image
|
|
3505
|
-
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.
|
|
3506
3559
|
|
|
3507
|
-
|
|
3508
|
-
reasons. A place commonly has **several** maps — a floor per level of a keep, or
|
|
3509
|
-
a feature's detail map alongside its local-area map — and a scene nested in a
|
|
3510
|
-
place could only ever be one. A Scene is a substantial document in itself: walls,
|
|
3511
|
-
doors, lights, tiles, sounds, region shapes and pins, authored in two deliberate
|
|
3512
|
-
unit conventions. And a map has prose and named locations of its own, which makes
|
|
3513
|
-
it a document rather than a field.
|
|
3560
|
+
**subType**:
|
|
3514
3561
|
|
|
3515
|
-
|
|
3516
|
-
|
|
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.
|
|
3517
3601
|
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
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
|
+
```
|
|
3523
3608
|
|
|
3524
|
-
|
|
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.
|
|
3525
3613
|
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
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
|
-
**`levels:` is never authored, and must never be.** A Scene has to ship at least
|
|
3562
|
-
one Level or it has no map at all, and nothing supplies one after the fact: the
|
|
3563
|
-
client-side `_preCreate` that would create it does not run during offline pack
|
|
3564
|
-
compilation, and the server-side migration shim is version-gated on
|
|
3565
|
-
`_stats.coreVersion`, so a pack stamped 14.x or later skips it entirely. The
|
|
3566
|
-
single Level is synthesised from `bgImage`, `overlay`, `levelName` and
|
|
3567
|
-
`backgroundColor`.
|
|
3568
|
-
|
|
3569
|
-
> **`bgImage` is an `Address` under `data:`, as every other type's artwork is.**
|
|
3570
|
-
> Art is not system-specific — a Scene is a core Foundry document, and a second
|
|
3571
|
-
> system would want the identical art — so the field sits under `data:` rather
|
|
3572
|
-
> than inside a system block.
|
|
3573
|
-
>
|
|
3574
|
-
> **A map has no `img`.** `bgImage` is the only key naming a map's background,
|
|
3575
|
-
> and a note writing `img:` or `data.img:` is refused. The two are not
|
|
3576
|
-
> alternative spellings: `img` is a Foundry destination and no Scene has one.
|
|
3577
|
-
|
|
3578
|
-
**Every art key inside a map is an address, and each is named for the type it
|
|
3579
|
-
reaches.** A map's geometry is authored in Foundry's own units, and its art is
|
|
3580
|
-
not authored in Foundry's own paths.
|
|
3581
|
-
|
|
3582
|
-
| authored | default type | emitted as |
|
|
3583
|
-
| -------------------- | ------------ | ---------------------------- |
|
|
3584
|
-
| `data.bgImage` | `image` | the level's `background.src` |
|
|
3585
|
-
| `tiles.<key>.image` | `image` | the tile's `texture.src` |
|
|
3586
|
-
| `sounds.<key>.audio` | `audio` | the `AmbientSound`'s `path` |
|
|
3587
|
-
|
|
3588
|
-
Naming each key for the type it references is what makes the default worth
|
|
3589
|
-
having: `audio:` reaches an `audio` address without a note ever writing the
|
|
3590
|
-
type segment. Where a tile places a glyph rather than artwork it qualifies —
|
|
3591
|
-
`image: sohl-none-icon-chest` — which is the ordinary short-form ladder rather
|
|
3592
|
-
than an exception to it.
|
|
3593
|
-
|
|
3594
|
-
**The authored key and the emitted field differ where Foundry's name differs.**
|
|
3595
|
-
An `AmbientSound` stores its clip in `path`, and that is what the compiled
|
|
3596
|
-
document carries; the note writes `audio` because the note is naming an
|
|
3597
|
-
address, not filling in a Foundry field.
|
|
3598
|
-
|
|
3599
|
-
**Two unit conventions, deliberately.** Geometry — walls, doors, lights, tiles,
|
|
3600
|
-
sounds, region shapes — is authored in **pixels**, Foundry's native storage,
|
|
3601
|
-
because traced walls often fall between grid intersections. Map pins are
|
|
3602
|
-
authored in **grid squares**, commonly
|
|
3603
|
-
half-integers, because that is how a person reads a position off a map. The two
|
|
3604
|
-
are told apart by their key: `position:` and segment or shape coordinates are
|
|
3605
|
-
pixels, `at:` is grid squares. Mixing them fails silently and visually in
|
|
3606
|
-
Foundry, so the build refuses rather than resolving.
|
|
3607
|
-
|
|
3608
|
-
**Map pins stay within the map.** `notes:` names anchors in this map note's own
|
|
3609
|
-
body, so a pin opens the journal page describing that spot. `data.place` names
|
|
3610
|
-
the place depicted by the map when one exists. A place's map listing is derived
|
|
3611
|
-
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.
|
|
3612
3648
|
|
|
3613
3649
|
### type: place
|
|
3614
3650
|
|
|
@@ -3761,9 +3797,10 @@ data:
|
|
|
3761
3797
|
on a land route — or a road on a ship route — is an error.
|
|
3762
3798
|
- `leagues` — optional on a route; a distance, only where the note states one.
|
|
3763
3799
|
|
|
3764
|
-
**
|
|
3765
|
-
|
|
3766
|
-
|
|
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
|
|
3767
3804
|
neighbour may be three days by land and one by ship — and in `borders` once.
|
|
3768
3805
|
**A place never borders its own parent or child**: containment is `parents`,
|
|
3769
3806
|
and restating it as adjacency makes the two contradict each other.
|
|
@@ -3778,6 +3815,16 @@ neighbour that states a different bearing, a different `days` or a different
|
|
|
3778
3815
|
tell which. Every `to` must resolve to a place, every value must come from its
|
|
3779
3816
|
closed set, and each finding is located at the entry that states it.
|
|
3780
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
|
+
|
|
3781
3828
|
**A relation that crosses a package boundary is written in full at both ends.**
|
|
3782
3829
|
An omitted `<package>` segment is the package being built, in whatever note the
|
|
3783
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
|
@@ -370,9 +370,10 @@ position together:
|
|
|
370
370
|
```
|
|
371
371
|
|
|
372
372
|
The accepted sizes are `auto`, `small`, `medium`, `large`, `xlarge`, and
|
|
373
|
-
`full-width`. Omitting `size:` means `auto
|
|
374
|
-
|
|
375
|
-
|
|
373
|
+
`full-width`. Omitting `size:` means `auto`, which uses the image's natural
|
|
374
|
+
dimensions within the available measure. The other names set maximum display
|
|
375
|
+
widths for the website, Foundry, and book. See
|
|
376
|
+
[Images](content-format.md#images) for the surface sizes and layout rules.
|
|
376
377
|
|
|
377
378
|
## Step 6 — check the tree
|
|
378
379
|
|
|
@@ -738,6 +739,29 @@ compiles to a page, so there is no document to address.
|
|
|
738
739
|
The index is derived and disposable; it is written under `build/` and rebuilt
|
|
739
740
|
from the tree every time.
|
|
740
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
|
+
|
|
741
765
|
## Step 11 — the release archive
|
|
742
766
|
|
|
743
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);
|