@heroiclands/package-build 22.22.0 → 22.23.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/content-commands.mjs +51 -14
  3. package/ci/content-format-reference.mjs +1 -1
  4. package/content-config.mjs +80 -2
  5. package/docs/api.md +85 -72
  6. package/docs/authoring/assets.md +14 -0
  7. package/docs/authoring/dates-and-calendars.md +37 -1
  8. package/docs/authoring/links-and-markup.md +112 -31
  9. package/docs/commands.md +12 -10
  10. package/docs/configuration.md +90 -13
  11. package/docs/content-format.md +1 -1
  12. package/docs/guides/book.md +5 -4
  13. package/docs/reference/format-details.md +323 -60
  14. package/docs/reference/note-types.md +9 -9
  15. package/engine/anchors.mjs +16 -10
  16. package/engine/art-fields.mjs +118 -0
  17. package/engine/art-slots.mjs +7 -1
  18. package/engine/asset-index.mjs +160 -10
  19. package/engine/asset-replacement.mjs +93 -0
  20. package/engine/asset-types.mjs +240 -0
  21. package/engine/base-compiler.mjs +54 -2
  22. package/engine/bundles.mjs +13 -1
  23. package/engine/content-blocks.mjs +4 -4
  24. package/engine/content-embeds.mjs +19 -0
  25. package/engine/content-figures.mjs +468 -0
  26. package/engine/content-footnotes.mjs +10 -2
  27. package/engine/content-format.yaml +1 -1
  28. package/engine/content-images.mjs +126 -25
  29. package/engine/content-index.mjs +36 -10
  30. package/engine/field-reference.mjs +31 -10
  31. package/engine/field-spec.mjs +10 -8
  32. package/engine/foreign-catalog.mjs +143 -18
  33. package/engine/frontmatter-lint.mjs +73 -1
  34. package/engine/heading-attributes.mjs +31 -18
  35. package/engine/helpers.mjs +97 -30
  36. package/engine/infobox-registry.mjs +4 -1
  37. package/engine/infobox.mjs +47 -8
  38. package/engine/itinerary-scenes.mjs +4 -10
  39. package/engine/journals.mjs +264 -39
  40. package/engine/lore-events.mjs +197 -0
  41. package/engine/markdown-expressions.mjs +257 -7
  42. package/engine/note-dates.mjs +305 -12
  43. package/engine/note-vocabulary.mjs +11 -5
  44. package/engine/pathnames.mjs +36 -0
  45. package/engine/pdf-build.mjs +357 -34
  46. package/engine/pdf-images.mjs +64 -0
  47. package/engine/pdf-render.mjs +160 -36
  48. package/engine/scenes.mjs +16 -4
  49. package/engine/site-build.mjs +127 -13
  50. package/engine/site-index.mjs +72 -1
  51. package/engine/site-preview.mjs +2 -0
  52. package/engine/system-block.mjs +44 -16
  53. package/hm3/actors.mjs +2 -2
  54. package/manifest.mjs +30 -24
  55. package/package.json +1 -1
  56. package/packagebuild-metadata.jsonl +13 -13
  57. package/sohl/item-fields.mjs +4 -3
  58. package/types/content-config.d.mts +48 -1
  59. package/types/engine/anchors.d.mts +9 -4
  60. package/types/engine/art-fields.d.mts +64 -0
  61. package/types/engine/asset-index.d.mts +43 -0
  62. package/types/engine/asset-replacement.d.mts +22 -0
  63. package/types/engine/asset-types.d.mts +18 -0
  64. package/types/engine/base-compiler.d.mts +0 -17
  65. package/types/engine/content-embeds.d.mts +16 -0
  66. package/types/engine/content-figures.d.mts +101 -0
  67. package/types/engine/content-footnotes.d.mts +7 -0
  68. package/types/engine/content-images.d.mts +78 -12
  69. package/types/engine/content-index.d.mts +8 -2
  70. package/types/engine/field-reference.d.mts +21 -0
  71. package/types/engine/field-spec.d.mts +20 -16
  72. package/types/engine/foreign-catalog.d.mts +39 -0
  73. package/types/engine/heading-attributes.d.mts +13 -8
  74. package/types/engine/helpers.d.mts +23 -7
  75. package/types/engine/infobox-registry.d.mts +4 -1
  76. package/types/engine/infobox.d.mts +6 -0
  77. package/types/engine/journals.d.mts +31 -9
  78. package/types/engine/lore-events.d.mts +14 -0
  79. package/types/engine/markdown-expressions.d.mts +23 -2
  80. package/types/engine/note-dates.d.mts +64 -1
  81. package/types/engine/pathnames.d.mts +26 -0
  82. package/types/engine/pdf-build.d.mts +28 -6
  83. package/types/engine/pdf-images.d.mts +52 -0
  84. package/types/engine/pdf-render.d.mts +12 -0
  85. package/types/engine/site-build.d.mts +14 -3
  86. package/types/engine/site-index.d.mts +54 -0
  87. package/types/engine/system-block.d.mts +26 -2
  88. package/types/hm3/actors.d.mts +18 -0
  89. package/types/manifest.d.mts +19 -23
  90. package/engine/content-captions.mjs +0 -230
  91. package/types/engine/content-captions.d.mts +0 -58
@@ -37,7 +37,9 @@ The harbor faces west.
37
37
  Travelers present their papers here. See [[#harbor|the harbor]].
38
38
  ```
39
39
 
40
- A `:::caption` block starts a JournalEntryPage too, named for the caption's own label, in Foundry alone — the web and the book keep the caption in the flow of the page around it. The prose introducing a captioned block and the block itself land on two different Foundry pages, so a reference such as `[[#trade|]]` written just above a caption sends a Foundry reader to a different page than the one carrying the sentence. Give the introducing prose its own heading, or accept that following the reference leaves the page, before relying on a caption to sit inside a longer page's flow.
40
+ A `:::figure` fence starts a JournalEntryPage too, in Foundry alone — the web and the book keep the fence in the flow of the page around it. A fence holding exactly one picture, captioned with plain text and nothing else, becomes a page of type `image`: the picture is its `src`, the caption is `image.caption`, and the page is named for the figure's own number, such as "Figure 3". A fence carrying no caption still takes the `image` type when it holds one picture, named the same way. Every other fence takes a page of type `text` instead — a grouped fence holding more than one picture, an audio embed, a fence whose caption carries emphasis, an icon, or a link, and a fence that shares its page with trailing prose before the next heading or fence.
41
+
42
+ The prose introducing a fence and the fence itself land on two different Foundry pages, so a reference such as `{{ref "#trade"}}` written just above a fence sends a Foundry reader to a different page than the one carrying the sentence. Give the introducing prose its own heading, or accept that following the reference leaves the page, before relying on a fence to sit inside a longer page's flow.
41
43
 
42
44
  A heading's braces hold an attribute block, not an anchor alone: `#slug` names the anchor, `.class` adds a CSS class, and `key="value"` writes an attribute. The website and a Foundry journal carry all three onto the heading; a book keeps the anchor and drops the rest, because Typst has no stylesheet to name a class in. Braces holding anything but an attribute block — `# The set {a, b}` — are reported at the heading's line and set as written.
43
45
 
@@ -61,23 +63,98 @@ A `:::secret` block inside the section is legal and renders as it does anywhere
61
63
 
62
64
  The special Actor anchors `{#appearance}` and `{#dossier}` feed the same authored sections to each system's Actor fields, and take an H1 only — not the lower-level heading a journal page may otherwise start from. The section runs to the next H1, nested headings included. An anchor declared on a lower heading is still found, and is a build error rather than a quietly empty field: move the heading to the top level, or drop the anchor if the section is meant to stay unaddressed prose.
63
65
 
64
- ## Captions and numbered references
66
+ ## Figures and numbered references
65
67
 
66
- A caption fence labels the next Markdown block. Give it an ID so prose can refer to the block without hard-coding its number:
68
+ A `:::figure` fence numbers whatever it holds and, written with an id, makes it a target `{{ref}}` can point to:
67
69
 
68
70
  ````markdown
69
- Refer to [[#trade|]] for the market routes.
70
-
71
- :::caption {#trade}
72
- Regional trade routes
73
- :::
71
+ :::figure {#trade}
74
72
 
75
73
  ```sql
76
74
  SELECT name.full AS "Market" FROM notes WHERE type = 'place'
77
75
  ```
76
+
77
+ ///
78
+ Regional trade routes
79
+ :::
80
+
81
+ Refer to {{ref "#trade"}} for the market routes.
78
82
  ````
79
83
 
80
- The table displays **Table 1: Regional trade routes**, and the reference displays **Table 1**. The number is assigned by the output. An explicit link label, such as `[[#trade|the trade table]]`, displays the written label. A caption can precede a code fence, table, image, or any prose block. An ordinary code fence is code; an SQL result is a table; an image is a figure; every other block is prose. Web pages and Foundry journals number each kind within the note. Books number each kind across the book in reading order. A caption needs a unique ID, nonempty text, a closing `:::`, and a following block.
84
+ The fence displays **Table 1: Regional trade routes**, and `{{ref "#trade"}}` displays **Table 1** as a link. **A figure is numbered because it is a fence, not because it carries caption text** — an ordinary table or picture written outside any fence is numbered not at all.
85
+
86
+ **The construct is `:::figure`**, with an optional `{#id .border}` attribute block, closed by `:::`. Everything between the opening line and the closing `:::` is the captioned thing.
87
+
88
+ **The first `///` at the top level of the fence separates the captioned thing from its caption.** A second `///` at the top level is an error — a figure holds one caption. A `///` written inside a nested code fence is content, not a separator, so a captioned code listing can carry `///` as a comment of its own.
89
+
90
+ **The caption is optional.** A fence with no `///` draws its label alone, with nothing under it. So the counter and the label come from the fence itself, whether or not an author writes a caption.
91
+
92
+ **`{#id}` is optional too.** A fence that declares none is still numbered and drawn; it carries no anchor, so nothing can refer to it. Give a figure an id only when prose needs to point at it.
93
+
94
+ **`.border` draws the figure and its label inside a border.** It is the only class the construct accepts — any other class, such as `.wide`, is an error naming the class. `key="value"` is refused outright: a figure takes an id and classes, nothing else.
95
+
96
+ **The counter is derived from the fence's contents, never authored.** A table is a table; an `sql` fence is a table, because its rendered output is one; any other fence or code block is code; pictures alone, one or several, are a figure; everything else is prose. A fence around prose with `.border` is a numbered, referable boxed aside — the `Prose` counter's whole purpose. The counters are `Code`, `Table`, `Figure`, `Map`, and `Prose`. The website and a Foundry journal number each counter within the note; a book numbers each counter across the book in reading order.
97
+
98
+ **A picture counts as `Map` instead of `Figure` when the asset it draws declares `role: map`** in its provenance — see [the asset provenance reference](../reference/format-details.md). The fence's own markup cannot say so; a picture of a map looks like any other picture, so the fact rides entirely on the asset's own record. A fence grouping several pictures counts as `Map` only when every one of them declares the role; one picture without it keeps the whole fence a `Figure`.
99
+
100
+ **A reference is written `{{ref "#thorn"}}`**, rendering as a link reading "Figure 13". `form="full"` renders the number and the caption; `form="title"` renders the caption alone; `form="number"` is the default, and may be written explicitly. A link written inside the caption contributes only its label text to a `full` or `title` reference, because the reference is itself a link and an anchor cannot nest inside another.
101
+
102
+ **A reference may name another note's figure**, writing the note's address before the anchor: `{{ref "place-thornford#trade"}}` points at the figure `#trade` in `place-thornford`. It renders the number that figure carries **on the surface doing the rendering** — its own count within `place-thornford` on the website and in a Foundry journal, and its place in the book's reading order in the book, which may differ from the number it carries on its own page. An anchor matching no figure in the addressed note is refused, and so is an address naming no note at all.
103
+
104
+ In a Foundry journal, a reference resolves to the figure's own JournalEntryPage — the `image` page a solitary, plainly captioned picture becomes, or the `text` page any other fence becomes — addressed exactly as an authored `[[#anchor|Text]]` link to the same anchor would be. A reference naming another note's figure is not resolved there: a journal addresses a page by UUID, and has none for a note outside the one compiling.
105
+
106
+ **A figure needs contents and a closing `:::`.** Its id, when one is given, must be unique in the note; its caption, when one is given, is the one `///` section the fence carries.
107
+
108
+ A few worked examples. A captioned picture:
109
+
110
+ ```markdown
111
+ :::figure {#thorn}
112
+ ![[being-foobar|The Great Beast]]
113
+ ///
114
+ The great beast, as drawn by [[person-havard|Havard]].
115
+ :::
116
+ ```
117
+
118
+ This draws **Figure 1**, labelled "Figure 1: The great beast, as drawn by Havard." beneath the picture.
119
+
120
+ A grouped fence holding two pictures as one figure — a plate rather than two separate plates:
121
+
122
+ ```markdown
123
+ :::figure {#passes}
124
+ ![[image-northpass|Northern approach]]
125
+ ![[image-southpass|Southern approach]]
126
+ ///
127
+ The two approaches to the pass.
128
+ :::
129
+ ```
130
+
131
+ Both pictures draw together under one label, **Figure 2**, because the fence is the unit the counter numbers — not the pictures inside it.
132
+
133
+ A fence with no caption:
134
+
135
+ ```markdown
136
+ :::figure {#anvil}
137
+ ![[icon-anvil|A worn anvil]]
138
+ :::
139
+ ```
140
+
141
+ This draws **Figure 3**, with no caption line beneath it, and still carries the anchor `#anvil`.
142
+
143
+ A reference in running prose:
144
+
145
+ ```markdown
146
+ See {{ref "#thorn"}} for the plate, or {{ref "#thorn" form="full"}} for the full description.
147
+ ```
148
+
149
+ This displays as "See [Figure 1](#thorn) for the plate, or [Figure 1: The great beast, as drawn by Havard.](#thorn) for the full description," each bracketed phrase a link to the figure.
150
+
151
+ A reference to a figure in another note:
152
+
153
+ ```markdown
154
+ The market routes are drawn in {{ref "place-thornford#trade"}}.
155
+ ```
156
+
157
+ On the website this links into `place-thornford`'s own page, reading "Table 1" — the number that fence carries there, whatever number it carries in the book or in a note of its own.
81
158
 
82
159
  ## Images and protected content
83
160
 
@@ -100,8 +177,10 @@ a closing `:::`. A name that is not one of the three is reported.
100
177
  **A GM-only section holds a box.** A `:::secret` is a container for whatever the
101
178
  GM reads, boxes included, and a box written inside one stays inside it. A box
102
179
  holds no named block of its own, so a second box inside a box is reported. A
103
- `:::caption` belongs to the caption construct and may be written inside any of
104
- them.
180
+ `:::figure` fence may be written inside any of them. The two draw a box for
181
+ different reasons: a named block says what kind of aside a passage is, while a
182
+ `:::figure` fence makes a passage numbered and referable — nesting one inside
183
+ the other gives a passage both.
105
184
 
106
185
  **An attribute block follows the name.** It is the same braced grammar the other
107
186
  body extensions use, so it carries an id, classes, and any attribute the element
@@ -147,11 +226,11 @@ not parse is reported at its own line and left as written; every well-formed
147
226
  block in the same note still renders.
148
227
 
149
228
  **A heading that would start its own journal page cannot be written inside a
150
- block, or inside a caption.** An H1, or a heading of any level carrying an
151
- `{#anchor}`, is reported at its own line there — starting a page would tear
152
- the block in two, and a reader could open the second half with none of the
153
- box around it. A lower heading with no anchor is unaffected and is the
154
- ordinary way to structure a long box.
229
+ block, or inside a `:::figure` fence.** An H1, or a heading of any level
230
+ carrying an `{#anchor}`, is reported at its own line there — starting a page
231
+ would tear the block or the figure in two, and a reader could open the second
232
+ half with none of the box around it. A lower heading with no anchor is
233
+ unaffected and is the ordinary way to structure a long box.
155
234
 
156
235
  ## Footnotes and definition lists
157
236
 
@@ -170,7 +249,8 @@ The road is passable in summer.[^season] The bridge has a toll.[^2]
170
249
  ```
171
250
 
172
251
  A reference may be written anywhere prose can be written — a word in a
173
- quotation, a bullet, a table cell, a heading, a named block, a caption. A
252
+ quotation, a bullet, a table cell, a heading, a named block, a figure's
253
+ caption. A
174
254
  definition belongs at the top level of the note: indent a following paragraph
175
255
  or code block by four spaces to include it in the same footnote, but never
176
256
  inside a list, a block quote, or a table. A definition written there, or a
@@ -324,20 +404,21 @@ There are {{words (sql "SELECT COUNT(*) FROM notes WHERE type = 'being'")}} bein
324
404
 
325
405
  <!-- expression-helpers:start -->
326
406
 
327
- | Helper | Parameters | What it gives you |
328
- | ------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
329
- | `eq` | `a b` | True when both are the same value. Compares exactly, so 1 and "1" differ. |
330
- | `gt` | `a b` | True when a is greater than b. Both must be finite numbers. |
331
- | `gte` | `a b` | True when a is greater than or equal to b. Both must be finite numbers. |
332
- | `lt` | `a b` | True when a is less than b. Both must be finite numbers. |
333
- | `lte` | `a b` | True when a is less than or equal to b. Both must be finite numbers. |
334
- | `not` | `value` | True when the value is false, zero, empty or absent. |
335
- | `and` | `value …` | True when every value given is true. Takes two or more. |
336
- | `or` | `value …` | True when any value given is true. Takes two or more. |
337
- | `words` | `number` | The number spelled out for running prose — `1200` becomes one thousand two hundred. Whole numbers only; a decimal is a finding, and `digits` takes one. |
338
- | `digits` | `number` | The number as a grouped numeral — `1200` becomes 1,200, and a fractional part is kept. |
339
- | `sql` | `"query"` | The single value a query returns, for use inside a sentence. The query is a quoted string, the same SQL a table fence takes. |
340
- | `dateformat` | `calendar date ["format"]` | A date written in a calendar. The calendar is an Address or its shortcode, the date is the value a note carries, and the optional third argument names an output pattern. |
407
+ | Helper | Parameters | What it gives you |
408
+ | ------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
409
+ | `eq` | `a b` | True when both are the same value. Compares exactly, so 1 and "1" differ. |
410
+ | `gt` | `a b` | True when a is greater than b. Both must be finite numbers. |
411
+ | `gte` | `a b` | True when a is greater than or equal to b. Both must be finite numbers. |
412
+ | `lt` | `a b` | True when a is less than b. Both must be finite numbers. |
413
+ | `lte` | `a b` | True when a is less than or equal to b. Both must be finite numbers. |
414
+ | `not` | `value` | True when the value is false, zero, empty or absent. |
415
+ | `and` | `value …` | True when every value given is true. Takes two or more. |
416
+ | `or` | `value …` | True when any value given is true. Takes two or more. |
417
+ | `words` | `number` | The number spelled out for running prose — `1200` becomes one thousand two hundred. Whole numbers only; a decimal is a finding, and `digits` takes one. |
418
+ | `digits` | `number` | The number as a grouped numeral — `1200` becomes 1,200, and a fractional part is kept. |
419
+ | `sql` | `"query"` | The single value a query returns, for use inside a sentence. The query is a quoted string, the same SQL a table fence takes. |
420
+ | `dateformat` | `calendar date ["format"]` | A date written in a calendar. The calendar is an Address or its shortcode, the date is the value a note carries, and the optional third argument names an output pattern. |
421
+ | `ref` | `address [form="number"\|"full"\|"title"]` | A link to a `:::figure` fence, by its anchor — `"#thorn"` on this note, `"note-address#thorn"` on another. `form` is `number` (the default, and admitted explicitly), rendering "Figure 13"; `full`, the number and the caption; or `title`, the caption alone. Always renders as a link to the figure, and a link inside the caption contributes only its label text. The number rendered is the one the figure carries on the surface doing the rendering, whichever note names it. |
341
422
 
342
423
  <!-- expression-helpers:end -->
343
424
 
package/docs/commands.md CHANGED
@@ -1455,15 +1455,17 @@ package-build deps fetch [--from <zip|dir>] [--id <id>]
1455
1455
  **DESCRIPTION**
1456
1456
 
1457
1457
  The only action is `fetch`, which fills three caches under `build/cache/`:
1458
- the **content index** of every declared dependency, the **item catalogue**
1459
- of those that additionally declare `itemCatalog: true`, and the **site
1460
- navigation** — `https://www.heroiclands.org/nav.json`, the header menu
1461
- every package site renders, fetched for every package because every package
1462
- publishes a site. Its own command rather than a step of `package compile`
1463
- or `site`, so neither reaches the network — a build that downloads silently
1464
- is not reproducible and hides a dependency's version change behind a
1465
- passing run. Each cache is stamped complete only once its fetch finishes,
1466
- so a half-finished one reads as cold.
1458
+ the **content index** of every declared dependency, the **item catalogue or
1459
+ asset archive** of those that additionally declare `itemCatalog: true` or
1460
+ `assetArchive: true`, and the **site navigation** —
1461
+ `https://www.heroiclands.org/nav.json`, the header menu every package site
1462
+ renders, fetched for every package because every package publishes a site.
1463
+ A relationship declaring both `itemCatalog: true` and `assetArchive: true`
1464
+ is fetched once. Its own command rather than a step of `package compile` or
1465
+ `site`, so neither reaches the network — a build that downloads silently is
1466
+ not reproducible and hides a dependency's version change behind a passing
1467
+ run. Each cache is stamped complete only once its fetch finishes, so a
1468
+ half-finished one reads as cold.
1467
1469
 
1468
1470
  `lint`, `links`, `site`, `pdf`, `map`, `reachability`, and `package compile`
1469
1471
  check the declared dependencies' content-index cache before reading notes or
@@ -1501,7 +1503,7 @@ including when the repository declares no dependencies at all.
1501
1503
  ```
1502
1504
  $ package-build deps fetch
1503
1505
  […] Fetched the site navigation to build/cache/navigation/nav.json.
1504
- […] No relationship declares `itemCatalog: true`; nothing to fetch.
1506
+ […] No relationship declares `itemCatalog: true` or `assetArchive: true`; nothing to fetch.
1505
1507
  […] This package declares no dependencies.
1506
1508
 
1507
1509
  $ package-build deps fetch --from build/dist/module.zip
@@ -55,7 +55,7 @@ around its own evaluation.
55
55
 
56
56
  ### Quick reference
57
57
 
58
- 22 top-level keys. `rootDir` is not one of them — a data configuration never
58
+ 23 top-level keys. `rootDir` is not one of them — a data configuration never
59
59
  writes it — and is documented under [Derived values](#derived-values) instead,
60
60
  alongside `foundryPackage`, `homepage`, `author` and `itemBuilders`, whose
61
61
  data-configuration behaviour is also derivation rather than ordinary
@@ -72,6 +72,7 @@ authoring.
72
72
  | [`itemBuilders`](#itembuilders) | object, or list of `{system, builders}` (or a name/list of names, in YAML) | no; refused in a `documentation` package | `{}` |
73
73
  | [`paths`](#paths) | object | no | see [`paths`](#paths) |
74
74
  | [`skipDirectories`](#skipdirectories) | string[] | no | `[]` |
75
+ | [`forbidGeneratedArt`](#forbidgeneratedart) | boolean | no | `false` |
75
76
  | [`icons`](#icons) | object, or a path to a file holding one | no | empty registry |
76
77
  | [`packs`](#packs) | array | yes, at least one entry, in a `systems` or `modules` package; refused in a `documentation` package | — |
77
78
  | [`docs`](#docs) | object | no; refused in a `documentation` package | `{}` |
@@ -87,7 +88,7 @@ authoring.
87
88
 
88
89
  Any key outside this list is refused:
89
90
 
90
- > `` `<key>` is not a recognized option (expected one of: rootDir, contentPackage, foundryPackage, homepage, author, packageKind, stats, itemBuilders, paths, skipDirectories, icons, packs, docs, site, pdf, compatibility, relationships, systems, requiresSystem, packageBuild, publish, changelog). ``
91
+ > `` `<key>` is not a recognized option (expected one of: rootDir, contentPackage, foundryPackage, homepage, author, packageKind, stats, itemBuilders, paths, skipDirectories, forbidGeneratedArt, icons, packs, docs, site, pdf, compatibility, relationships, systems, requiresSystem, packageBuild, publish, changelog). ``
91
92
 
92
93
  (`rootDir` appears in that list because it is a key `defineConfig` itself
93
94
  accepts — an `.mjs` configuration authors it directly. A YAML configuration
@@ -515,7 +516,7 @@ HeroicLands layout, resolved against `rootDir`:
515
516
  | `paths.packJson` | `build/packs-json` | Build-only per-entry JSON intermediate. |
516
517
  | `paths.stage` | `build/stage/packs` | Compiled LevelDB packs. |
517
518
  | `paths.unpack` | `build/tmp/packs` | Where `unpack` extracts JSON back to. |
518
- | `paths.foreignCache` | `build/cache/foreign` | Where a dependency declaring `itemCatalog: true` is unpacked. |
519
+ | `paths.foreignCache` | `build/cache/foreign` | Where a dependency declaring `itemCatalog: true` or `assetArchive: true` is unpacked. |
519
520
  | `paths.metadataCache` | `build/cache/metadata` | Where a dependency's published content index is fetched to, for every declared dependency. |
520
521
  | `paths.navigationCache` | `build/cache/navigation` | Where the site navigation heroiclands.org publishes is fetched to, for the generated Hugo menu. |
521
522
 
@@ -547,6 +548,26 @@ skipDirectories: [Templates]
547
548
 
548
549
  > ``package-build config: `skipDirectories[<index>]` must be a non-empty string.``
549
550
 
551
+ ### `forbidGeneratedArt`
552
+
553
+ **Type:** boolean · **Optional** · default `false`.
554
+
555
+ With it `true`, `package-build lint` refuses every asset record of this
556
+ package whose `ai` field states `true` — a licensing stance, not a build
557
+ detail: this package carries no machine-generated art. With it absent or
558
+ `false`, `lint` reports nothing extra about an `ai: true` record.
559
+
560
+ ```yaml
561
+ forbidGeneratedArt: true
562
+ ```
563
+
564
+ `lint` reports a located error naming the record and its file for each one it
565
+ finds:
566
+
567
+ > ``<file>: error: `<address>` is machine-generated art (`ai: true`), and `forbidGeneratedArt` refuses it in this package``
568
+
569
+ > ``package-build config: `forbidGeneratedArt` must be a boolean.``
570
+
550
571
  ### `icons`
551
572
 
552
573
  **Type:** object (`{families, icons, defaultFamily?}`), or a string naming a
@@ -1205,15 +1226,17 @@ system relationship it declares — see [`stats.systemVersion`](#statssystemvers
1205
1226
 
1206
1227
  Each entry, in any of the four lists:
1207
1228
 
1208
- | Key (under `relationships.<kind>[]`) | Type | Required | Default |
1209
- | ---------------------------------------- | ------------------------------- | -------- | ------- |
1210
- | `relationships.systems[].id` | string | yes | — |
1211
- | `relationships.systems[].contentPackage` | string | no | the id |
1212
- | `relationships.systems[].type` | string | no | none |
1213
- | `relationships.systems[].manifest` | string | no | none |
1214
- | `relationships.systems[].compatibility` | object, `{minimum?, verified?}` | no | none |
1215
- | `relationships.systems[].itemCatalog` | boolean | no | `false` |
1216
- | `relationships.systems[].contentIndex` | boolean | no | `true` |
1229
+ | Key (under `relationships.<kind>[]`) | Type | Required | Default |
1230
+ | ------------------------------------------ | ------------------------------- | -------- | ------- |
1231
+ | `relationships.systems[].id` | string | yes | — |
1232
+ | `relationships.systems[].contentPackage` | string | no | the id |
1233
+ | `relationships.systems[].type` | string | no | none |
1234
+ | `relationships.systems[].manifest` | string | no | none |
1235
+ | `relationships.systems[].compatibility` | object, `{minimum?, verified?}` | no | none |
1236
+ | `relationships.systems[].itemCatalog` | boolean | no | `false` |
1237
+ | `relationships.systems[].assetArchive` | boolean | no | `false` |
1238
+ | `relationships.systems[].contentIndex` | boolean | no | `true` |
1239
+ | `relationships.systems[].assetReplacement` | boolean | no | `false` |
1217
1240
 
1218
1241
  (the same keys apply under `requires[]`, `recommends[]` and
1219
1242
  `conflicts[]`.)
@@ -1222,7 +1245,7 @@ Each entry, in any of the four lists:
1222
1245
 
1223
1246
  > ``package-build config: `relationships.<kind>[<index>].id` must be a non-empty string.``
1224
1247
 
1225
- > ``package-build config: `relationships.<kind>[<index>].<key>` is not a recognized option (expected one of: id, contentPackage, type, manifest, compatibility, itemCatalog, contentIndex).``
1248
+ > ``package-build config: `relationships.<kind>[<index>].<key>` is not a recognized option (expected one of: id, contentPackage, type, manifest, compatibility, itemCatalog, assetArchive, contentIndex, assetReplacement).``
1226
1249
 
1227
1250
  `contentPackage` names what the other package's _content_ is called, where
1228
1251
  that differs from its Foundry id. A note addresses a file by the content
@@ -1241,6 +1264,31 @@ item catalogue at build time. It requires a `manifest`:
1241
1264
 
1242
1265
  > ``package-build config: `relationships.<kind>[<index>].itemCatalog` needs a `manifest` naming the package to fetch.``
1243
1266
 
1267
+ `assetArchive` opts into unpacking the named package's release archive for
1268
+ its asset bytes alone — the sibling of `itemCatalog`, for a dependency whose
1269
+ bytes are wanted and whose items are not. It builds no item catalogue from
1270
+ the archive, which is what distinguishes it from `itemCatalog: true`. It
1271
+ requires a `manifest`:
1272
+
1273
+ > ``package-build config: `relationships.<kind>[<index>].assetArchive` must be true or false.``
1274
+
1275
+ > ``package-build config: `relationships.<kind>[<index>].assetArchive` needs a `manifest` naming the package to fetch.``
1276
+
1277
+ A relationship with no items of its own to extract, such as an asset
1278
+ replacement module, declares only `assetArchive: true`:
1279
+
1280
+ ```yaml
1281
+ relationships:
1282
+ requires:
1283
+ - id: thalornaaltart
1284
+ manifest: https://…/releases/latest/download/module.json
1285
+ compatibility: { verified: "1.0.0" }
1286
+ assetArchive: true
1287
+ ```
1288
+
1289
+ A relationship declaring both `itemCatalog: true` and `assetArchive: true` is
1290
+ fetched once: `deps fetch` does not download the archive twice for one entry.
1291
+
1244
1292
  `contentIndex` and `itemCatalog` are the two edges a relationship may declare,
1245
1293
  and a package may have either without the other. `itemCatalog` says a
1246
1294
  dependency supplies _items_; `contentIndex`, `true` by default, says
@@ -1260,6 +1308,35 @@ demand.
1260
1308
 
1261
1309
  > ``package-build config: `relationships.<kind>[<index>].contentIndex` cannot be false together with `itemCatalog: true` — a catalogue is fetched from the same index.``
1262
1310
 
1311
+ `assetReplacement` opts a relationship into serving this package's own asset
1312
+ addresses from the named dependency's tree instead of its local one —
1313
+ `engine/asset-replacement.mjs`'s `resolveAssetReplacement` is what reads the
1314
+ declaration. Off by default: declaring a dependency is not the same as
1315
+ wanting its pictures in place of the ones this package ships itself. It
1316
+ requires a fetched index to check the replacement against, so it cannot pair
1317
+ with `contentIndex: false`:
1318
+
1319
+ ```yaml
1320
+ relationships:
1321
+ requires:
1322
+ - id: thalornaaltart
1323
+ manifest: https://…/releases/latest/download/module.json
1324
+ assetReplacement: true
1325
+ contentIndex: true
1326
+ ```
1327
+
1328
+ With that declared, an address `thalorna` would otherwise answer itself —
1329
+ `thalorna-none-image-thorn` — resolves against
1330
+ `thalornaaltart-none-image-thorn` first, falling back to `thalorna`'s own
1331
+ record when `thalornaaltart` carries no file at that address. A package
1332
+ declaring more than one `assetReplacement: true` relationship is tried in
1333
+ the order the relationships are declared, and the first one carrying the
1334
+ address wins.
1335
+
1336
+ > ``package-build config: `relationships.<kind>[<index>].assetReplacement` must be true or false.``
1337
+
1338
+ > ``package-build config: `relationships.<kind>[<index>].assetReplacement` cannot be true together with `contentIndex: false` — a replacement with no fetched index to check against resolves nothing.``
1339
+
1263
1340
  ### `systems`
1264
1341
 
1265
1342
  **Type:** object (`{id: spec}`) · **Optional** · default `{}`. Refused in a
@@ -25,7 +25,7 @@ Start with [your first note](authoring/first-note.md). It explains the smallest
25
25
 
26
26
  A nonempty note body creates one system-agnostic Foundry JournalEntry. Content before the first H1 becomes an introduction page; every H1 starts another JournalEntryPage. A lower-level heading with an explicit `{#anchor}` starts a page too, so a link can address it. The same prose becomes a web page and can be selected for a book. The content index records the note and its frontmatter for queries and navigation.
27
27
 
28
- A `:::caption {#anchor}` fence labels the next prose, code, table, or image block. Its anchor makes the block addressable; `[[#anchor|]]` displays the generated kind and number, such as **Figure 12**. Foundry gives the captioned block a JournalEntryPage. See [captions and numbered references](authoring/links-and-markup.md#captions-and-numbered-references) for the syntax.
28
+ A `:::figure {#anchor}` fence numbers whatever it holds — prose, code, a table, or an image. Its anchor makes the fence a target `{{ref "#anchor"}}` can point to, displaying the generated kind and number, such as **Figure 12**. Foundry gives the fence its own JournalEntryPage. See [figures and numbered references](authoring/links-and-markup.md#figures-and-numbered-references) for the syntax.
29
29
 
30
30
  Body extensions use braces with space-separated `key=value` attributes:
31
31
  `![[icon-harbor|Harbor]]{float=top-left size=medium}` and an SQL fence with
@@ -88,10 +88,11 @@ finding.
88
88
  Each section and each selected entry starts on its own page. The PDF has a
89
89
  contents page and an outline for section and entry navigation. A note's
90
90
  `description` appears as an epigraph when present. Wider tables span both
91
- columns; an uncaptioned wide table starts on a new page so it stays after its
92
- introduction. A captioned wide table may float to the page top, and its numbered
93
- caption stays with it. A table too tall for one page takes pages of its own.
94
- Caption numbers run through the whole book in reading order. Maps attached
91
+ columns; a wide table outside a figure fence starts on a new page so it stays
92
+ after its introduction. A wide table inside one may float to the page top, and
93
+ its label stays with it. A table too tall for one page takes pages of its own.
94
+ A figure's number runs through the whole book in reading order, counted within
95
+ its own kind. Maps attached
95
96
  to selected places use landscape pages after those entries. Regional, theatre
96
97
  of the mind, battle, and local map backgrounds also print on landscape pages.
97
98
  Battle and local maps print each Scene level's background on its own page.