@heroiclands/package-build 21.1.0 → 22.0.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 (115) hide show
  1. package/CHANGELOG.md +245 -0
  2. package/CONTENT.md +107 -3
  3. package/assets/fonts/libertinus/LibertinusMono-Regular.otf +0 -0
  4. package/assets/fonts/libertinus/LibertinusSans-Bold.otf +0 -0
  5. package/assets/fonts/libertinus/LibertinusSans-Italic.otf +0 -0
  6. package/assets/fonts/libertinus/LibertinusSans-Regular.otf +0 -0
  7. package/assets/fonts/libertinus/OFL.txt +94 -0
  8. package/assets/fonts/libertinus/provenance.yaml +11 -0
  9. package/assets/images/banners/afflictionbnr.webp +0 -0
  10. package/assets/images/banners/armorclothingbnr.webp +0 -0
  11. package/assets/images/banners/attributebnr.webp +0 -0
  12. package/assets/images/banners/containerbnr.webp +0 -0
  13. package/assets/images/banners/devdocsbnr.webp +0 -0
  14. package/assets/images/banners/miscgearbnr.webp +0 -0
  15. package/assets/images/banners/mysticalabilitybnr.webp +0 -0
  16. package/assets/images/banners/projectilebnr.webp +0 -0
  17. package/assets/images/banners/rulesbnr.webp +0 -0
  18. package/assets/images/banners/skillbnr.webp +0 -0
  19. package/assets/images/banners/traumabnr.webp +0 -0
  20. package/assets/images/banners/userguidebnr.webp +0 -0
  21. package/assets/images/banners/weapongearbnr.webp +0 -0
  22. package/assets/images/provenance.yaml +4 -0
  23. package/bin/content-build.mjs +19 -5
  24. package/bin/package-build.mjs +6 -5
  25. package/ci/emit-asset-index.mjs +32 -0
  26. package/content-config.mjs +30 -1
  27. package/docs/api.md +103 -27
  28. package/docs/commands.md +13 -11
  29. package/docs/configuration.md +16 -7
  30. package/docs/content-format.md +523 -111
  31. package/engine/actor-compiler.mjs +58 -9
  32. package/engine/art-fields.mjs +292 -0
  33. package/engine/asset-index.mjs +397 -0
  34. package/engine/asset-types.mjs +192 -0
  35. package/engine/base-compiler.mjs +69 -1
  36. package/engine/bundles.mjs +5 -5
  37. package/engine/compile-corpus.mjs +4 -0
  38. package/engine/content-address.mjs +14 -2
  39. package/engine/content-charset.mjs +5 -1
  40. package/engine/content-embeds.mjs +314 -0
  41. package/engine/content-html.mjs +5 -1
  42. package/engine/content-icons.mjs +5 -1
  43. package/engine/content-images.mjs +26 -5
  44. package/engine/content-index.mjs +68 -33
  45. package/engine/content-links.mjs +164 -24
  46. package/engine/dependency-bump.mjs +14 -16
  47. package/engine/frontmatter-lint.mjs +36 -23
  48. package/engine/helpers.mjs +83 -31
  49. package/engine/index-records.mjs +63 -4
  50. package/engine/index.mjs +15 -0
  51. package/engine/infobox-registry.mjs +50 -3
  52. package/engine/infobox.mjs +12 -8
  53. package/engine/item-compiler.mjs +10 -10
  54. package/engine/journals.mjs +3 -0
  55. package/engine/macros.mjs +16 -13
  56. package/engine/map-notes.mjs +29 -22
  57. package/engine/metadata-index.mjs +44 -36
  58. package/engine/note-vocabulary.mjs +71 -29
  59. package/engine/pack-router.mjs +14 -0
  60. package/engine/packagebuild-index.mjs +124 -0
  61. package/engine/packages.mjs +83 -0
  62. package/engine/pathnames.mjs +74 -0
  63. package/engine/pdf-build.mjs +133 -8
  64. package/engine/pdf-render.mjs +49 -12
  65. package/engine/scenes.mjs +12 -9
  66. package/engine/site-build.mjs +43 -16
  67. package/engine/site-index.mjs +10 -1
  68. package/engine/subtype-registry.mjs +21 -0
  69. package/engine/web-wikilinks.mjs +46 -12
  70. package/engine/wikilink-syntax.mjs +20 -2
  71. package/engine/wikilinks.mjs +15 -2
  72. package/hm3/actors.mjs +11 -38
  73. package/manifest.mjs +67 -13
  74. package/package.json +5 -2
  75. package/packagebuild-metadata.jsonl +13 -0
  76. package/sohl/actors.mjs +12 -86
  77. package/sohl/note-schemas.mjs +4 -16
  78. package/types/content-config.d.mts +8 -0
  79. package/types/engine/actor-compiler.d.mts +29 -0
  80. package/types/engine/art-fields.d.mts +192 -0
  81. package/types/engine/asset-index.d.mts +92 -0
  82. package/types/engine/asset-types.d.mts +110 -0
  83. package/types/engine/base-compiler.d.mts +42 -1
  84. package/types/engine/compile-corpus.d.mts +4 -1
  85. package/types/engine/content-charset.d.mts +4 -0
  86. package/types/engine/content-embeds.d.mts +218 -0
  87. package/types/engine/content-html.d.mts +4 -0
  88. package/types/engine/content-icons.d.mts +4 -0
  89. package/types/engine/content-images.d.mts +15 -2
  90. package/types/engine/content-index.d.mts +27 -15
  91. package/types/engine/dependency-bump.d.mts +2 -2
  92. package/types/engine/frontmatter-lint.d.mts +11 -14
  93. package/types/engine/helpers.d.mts +7 -2
  94. package/types/engine/index-records.d.mts +44 -3
  95. package/types/engine/index.d.mts +5 -0
  96. package/types/engine/infobox-registry.d.mts +36 -1
  97. package/types/engine/infobox.d.mts +6 -4
  98. package/types/engine/macros.d.mts +9 -5
  99. package/types/engine/map-notes.d.mts +10 -6
  100. package/types/engine/metadata-index.d.mts +2 -17
  101. package/types/engine/note-vocabulary.d.mts +30 -0
  102. package/types/engine/packagebuild-index.d.mts +56 -0
  103. package/types/engine/packages.d.mts +62 -0
  104. package/types/engine/pathnames.d.mts +41 -81
  105. package/types/engine/pdf-build.d.mts +58 -1
  106. package/types/engine/pdf-render.d.mts +3 -1
  107. package/types/engine/scenes.d.mts +2 -1
  108. package/types/engine/site-build.d.mts +5 -1
  109. package/types/engine/site-index.d.mts +4 -1
  110. package/types/engine/subtype-registry.d.mts +20 -0
  111. package/types/engine/web-wikilinks.d.mts +14 -4
  112. package/types/engine/wikilink-syntax.d.mts +13 -1
  113. package/types/engine/wikilinks.d.mts +7 -1
  114. package/types/manifest.d.mts +42 -1
  115. package/types/sohl/actors.d.mts +0 -1
@@ -107,11 +107,11 @@ the key is **machinery**, steering a build or an interface rather than
107
107
  describing the subject, or it is an **image**, which rule 2 keeps out of the
108
108
  box:
109
109
 
110
- | key | why it carries no row |
111
- | ---------------------------- | ------------------------------------------------ |
112
- | `templatePriority` | template machinery, not a fact about the subject |
113
- | `color` | sidebar machinery, not a fact about the subject |
114
- | `portrait`, `img`, `overlay` | an image, which rule 2 keeps out of the box |
110
+ | key | why it carries no row |
111
+ | ------------------------ | ------------------------------------------------ |
112
+ | `templatePriority` | template machinery, not a fact about the subject |
113
+ | `color` | sidebar machinery, not a fact about the subject |
114
+ | the art slots, `overlay` | an image, which rule 2 keeps out of the box |
115
115
 
116
116
  A field whose value is a **mapping** carries no row either — a governance
117
117
  ladder or a wall layout has no summary shape — and neither does one the note
@@ -196,6 +196,15 @@ A system that does not map the note's type gets **no box**. A box reading
196
196
  _Not available_ on every affiliation page would suggest a gap in the note when
197
197
  the truth is about the system's scope.
198
198
 
199
+ Whether a document was produced is the compile's own answer rather than a
200
+ reading of the frontmatter. The note's type must map to the system; the note
201
+ must route to a pack of the document class that map names; and where **that
202
+ pack declares a `system:`**, the note must carry that system's block, which is
203
+ the same rule the pack itself compiles under. A pack declaring **no** system
204
+ compiles every note it claims from `data:` and the field defaults, so a note
205
+ with no block still produces a document and its box still carries content —
206
+ which is the ordinary arrangement in a package built for one system.
207
+
199
208
  #### Four section layouts
200
209
 
201
210
  A section says how it is arranged, and a renderer switches on that and on a
@@ -506,7 +515,7 @@ type.
506
515
  | shared source | → sohl | → hm3 |
507
516
  | ----------------------- | ------------------------- | ---------------------------- |
508
517
  | `name.full` | `name` | `name` |
509
- | `img` | `img` | `img` |
518
+ | `data.icon` | `img` | `img` |
510
519
  | `id` | `_id` | `_id` |
511
520
  | `packFolder` | `folder` | `folder` |
512
521
  | `shortcode` | `system.shortcode` | NA |
@@ -520,23 +529,30 @@ no SoHL form, so its SoHL column is. Nothing else varies — which is why these
520
529
  rows are worth stating once: repeated sixteen times they buried the differences
521
530
  that matter.
522
531
 
523
- **`img` maps onto a document that has one, and not every type's does.** The row
524
- is shared because the key is legal on every note whatever its type, not because
525
- every document carries artwork: `doc`, `place`, `lore` and `scenario` compile
526
- into a JournalEntry, which has no image of any kind, and a `folder` compiles into
527
- a Foundry `Folder`, which has none either. A `homepage` compiles into no
528
- compendium document at all. On any of those the mapping has no destination, so an
529
- authored path is simply dropped — the note validates, the tree compiles, and the
530
- value goes nowhere.
532
+ **`data.icon` maps onto a document that has one, and not every type's does.**
533
+ The row is shared because the key is legal on every note whatever its type, not
534
+ because every document carries artwork: `doc`, `place`, `lore` and `scenario`
535
+ compile into a JournalEntry, which has no image of any kind, and a `folder`
536
+ compiles into a Foundry `Folder`, which has none either. A `homepage` compiles
537
+ into no compendium document at all. On any of those the mapping has no
538
+ destination, so the resolved value is simply dropped — the note validates, the
539
+ tree compiles, and the value goes nowhere.
540
+
541
+ **What a note writes is an address; what a document carries is a path.**
542
+ `data.icon` is a `WikiLink` defaulting to type `icon`, so a being writes
543
+ `icon: anvil` and never a file name. The compiler resolves that address to the
544
+ file the owning package ships and writes the resulting path into `img`. The two
545
+ sides of this row are therefore in two different currencies, which is why the
546
+ row reads `data.icon → img` rather than naming one thing twice.
531
547
 
532
548
  Since a dropped value looks exactly like a value never written, the frontmatter
533
- lint **reports it**: an `img:` (or `portrait:`) authored on a type whose passes
534
- emit neither is a warning naming the note and the key. A warning rather than an
535
- error, because the note still compiles correctly and the key is not certainly
536
- unwanted — a note's top level is the generated page's front matter as well, so a
537
- site template may read there what no document carries. `img: null` is never
538
- reported: that is the blessed way to say "this note names no art", and on a type
539
- with no art it is a true and harmless thing to say.
549
+ lint **reports it**: an art key authored on a type whose passes emit none is a
550
+ warning naming the note and the key. A warning rather than an error, because the
551
+ note still compiles correctly and the key is not certainly unwanted — a note's
552
+ top level is the generated page's front matter as well, so a site template may
553
+ read there what no document carries. `null` is never reported: that is the
554
+ blessed way to say "this note names no art", and on a type with no art it is a
555
+ true and harmless thing to say.
540
556
 
541
557
  Which types those are is **not a list**. It is asked of the passes: a type routes
542
558
  to a document, a document to the pass that compiles it, and each pass declares
@@ -544,29 +560,85 @@ the art it writes (`emitsArt`). A second table of iconless types would be a tabl
544
560
  free to drift from what is actually emitted, which is the defect rather than the
545
561
  check.
546
562
 
547
- Actor types (`being`, `vehicle`) add one more:
563
+ #### The four art slots
564
+
565
+ A note declares the art its document needs, and every other image in it is
566
+ inline. All four slots are ordinary `WikiLink` fields declaring a default type,
567
+ exactly as `seat` declares `place` — a bare shortcode takes its type from the
568
+ declaration, and qualification climbs the same short-form ladder every other
569
+ link uses.
570
+
571
+ | field | default type | resolves into | on |
572
+ | ----------- | ------------ | ---------------------------- | ----------------------------------------- |
573
+ | `icon` | `icon` | `document.img` | every Actor/Item type, and embedded items |
574
+ | `tokenIcon` | `icon` | `prototypeToken.texture.src` | Actor types |
575
+ | `bgImage` | `image` | `background.src` | map types |
576
+ | `banner` | `image` | the site hero image | any note; not a Foundry field |
577
+
578
+ All four are authored under `data:`:
579
+
580
+ ```yaml
581
+ data:
582
+ icon: anvil
583
+ banner: packagebuild-none-image-skillbnr
584
+ ```
585
+
586
+ **Two of them are legal on _every_ note**, whatever its type. `data:` is a
587
+ closed container and the per-type vocabularies below are the only lists there
588
+ are, so a key legal everywhere is stated once here rather than repeated in
589
+ twenty-five tables where the one that was mistyped would be the one nobody
590
+ noticed:
591
+
592
+ | shared `data` property | Values | Description |
593
+ | ---------------------- | ---------- | -------------------------------------------------------------------- |
594
+ | `icon` | `WikiLink` | The document's profile art — an `icon` address, resolved into `img`. |
595
+ | `banner` | `WikiLink` | The page's hero image — an `image` address. |
548
596
 
549
- | shared source | sohl | hm3 |
550
- | --------------- | ----------------- | ----------------- |
551
- | `data.portrait` | `system.portrait` | `system.bioImage` |
597
+ `icon` is legal everywhere because most types compile into a document that
598
+ carries one; where a type's passes emit none, the lint says so and the value is
599
+ left alone, because a page template may still read it. `banner` reaches no
600
+ compiled document at all, and a page is what every note publishes.
552
601
 
553
- An actor carries `img` (its token art) and `portrait` (its sheet portrait)
554
- independently, which is why this is a row of its own rather than a second
555
- spelling of the one above. An Item has no second image, so the row applies to
556
- actor types alone. Note the asymmetry in where the two are authored: `img` stays
557
- at the note's top level and `portrait` moved under `data:`, because a note's
558
- token art is a fact about the _note as a published artefact_ while the portrait
559
- is a fact about the _subject_.
602
+ The other two are narrower, and each type's own table names them: `tokenIcon`
603
+ on the Actor types, `bgImage` on a map.
604
+
605
+ **A portrait is not one of them.** A picture of the subject is a picture, so it
606
+ is authored in the prose that describes the subject, as an ordinary embedded
607
+ image see [the lead image](#the-lead-image). A slot exists for art a
608
+ _document field_ needs; nothing else earns one.
609
+
610
+ **A reference carries no slashes and no extension.** That is what keeps it from
611
+ being read as a file name, and it is checked: a value shaped like a pathname is
612
+ an error naming the note and the key.
613
+
614
+ `tokenIcon` unset defaults to `icon`. A token has to read at grid scale and when
615
+ a map is zoomed out, so it is an `icon` rather than an `image`, and its fallback
616
+ has to be `icon`-typed too.
617
+
618
+ Actor types (`being`, `vehicle`) therefore add one row:
619
+
620
+ | shared source | → sohl | → hm3 |
621
+ | ---------------- | ---------------------------- | ---------------------------- |
622
+ | `data.tokenIcon` | `prototypeToken.texture.src` | `prototypeToken.texture.src` |
623
+
624
+ An actor's two pieces of art are two different questions. `data.icon` is the
625
+ profile art — what a directory listing shows beside the name and what the sheet
626
+ header carries. `data.tokenIcon` is what a token on the canvas wears. Both are
627
+ wikilinks and both resolve the same way; only the destination differs. An Item
628
+ has one piece of art, so this row applies to actor types alone.
560
629
 
561
630
  **A `data:` source is still read at the top level, for now.** `data:` did
562
- not invent the facts it holds — it gathered them out of the top level, where
563
- `portrait:` sat beside `img:` — so every key it collected has a **pre-`data:`
564
- spelling** that is read after the declared one and reported as retiring. Write
565
- `data.portrait`; a tree still on `portrait:` compiles to the identical document
566
- and gets a warning naming the line, until a later release removes the position.
567
- This is the shared level's counterpart to the in-block `<system>.<key>`
568
- retirement, and the two are separate: a note may have moved one and not the
569
- other.
631
+ not invent the facts it holds — it gathered them out of the top level — so every
632
+ key it collected has a **pre-`data:` spelling** that is read after the declared
633
+ one and reported as retiring. This is the shared level's counterpart to the
634
+ in-block `<system>.<key>` retirement, and the two are separate: a note may have
635
+ moved one and not the other.
636
+
637
+ **No image key is one of those.** A top-level `img:` is not authored at all:
638
+ `data.icon` is a different key holding a different kind of value, and a note
639
+ writing `img:` is naming a file where an address belongs. `portrait:` is not
640
+ relocated either, at any level — a portrait is an embed in the body, and the
641
+ key that used to declare one is not a key.
570
642
 
571
643
  **Two of the eight are Item-only in SoHL.** `actionDefs` and `notes` are declared
572
644
  on every SoHL Item subtype and on no SoHL Actor, so on a `being` or a `vehicle`
@@ -607,9 +679,9 @@ shipped tree, and the one piece every derived form is built from. A pathname
607
679
  that does not open `<package>/assets/` belongs to the package being compiled,
608
680
  and the whole of it is the suffix.
609
681
 
610
- ```yaml
611
- img: images/beings/athlwvthrnd-portrait.webp # this package's
612
- img: sohl/assets/icons/noun/shield.svg # the sohl package's
682
+ ```markdown
683
+ ![A map of the Vale](images/maps/valeofthorns.webp) <!-- this package's -->
684
+ ![A shield](sohl/assets/icons/noun/shield.svg) <!-- the sohl package's -->
613
685
  ```
614
686
 
615
687
  The four forms, for a `thalorna` note (the `thalorna` package ships as the
@@ -676,39 +748,28 @@ whole `assets/` tree and the directory names inside it are its own business:
676
748
  `sohl-kethira-basic` keeps art under `assets/artwork/`, and `artwork/deity.webp`
677
749
  is that package's by the same rule that claims `icons/…` and `images/…`.
678
750
 
679
- **Every pathname a note carries follows it.** `img:` and `data.portrait:`; a
680
- map note's background, overlay, tile textures and ambient sounds; and the
681
- address of every image in a note's body.
682
-
683
- #### `banner:` addresses the CDN, not the Foundry install
751
+ **Where a pathname is still authored, it follows this rule.** That is the
752
+ address of an image in a note's body, and a map note's `overlay`. The art
753
+ fields do not: `icon`, `tokenIcon`, `bgImage` and `banner` name
754
+ addresses, and the path comes from the record the resolved address points at —
755
+ which already carries the owning package, so there is nothing for a first
756
+ segment to state.
684
757
 
685
- **`banner:` is a path, and it does not follow the rule above.** It is worth
686
- stating plainly, because the two fields look alike and a value written for one
687
- resolves somewhere else entirely under the other.
758
+ #### `banner:` reaches no compiled document
688
759
 
689
- `banner:` never reaches a compiled document searching a built `packs-json`
690
- tree for it turns up nothing. It is a top-level key, so it passes through to the
691
- generated page, and its only consumer is the Hugo theme, whose
692
- `partials/banner-url.html` applies its own rule: an absolute URL passes through,
693
- and **anything else is prefixed with `images/`** and joined onto
694
- `params.cdnBaseURL`. A `banner:` written to the package rule therefore resolves
695
- to a doubled path:
760
+ `data.banner` is the page's hero image, and it is the one art slot with no
761
+ Foundry destination: searching a built `packs-json` tree for it turns up
762
+ nothing. It reaches the generated page and the book's section plates, and
763
+ nothing else.
696
764
 
697
- ```text
698
- banner: sohl/assets/images/banners/lore.webp
699
- <cdnBaseURL>/images/sohl/assets/images/banners/lore.webp
700
- ```
701
-
702
- That can be made to work by mirroring the path on the CDN, and one consumer
703
- does exactly that — but it is not what the author meant.
765
+ It is a `WikiLink` all the same, defaulting to type `image`, so a section note
766
+ writes `banner: skillbnr` and a note borrowing another package's plate writes
767
+ `banner: packagebuild-none-image-skillbnr`. The resolved record carries the
768
+ owning package, and each surface joins the suffix onto its own root — the site
769
+ onto `site.assets`, the book onto the directory it stages banners into.
704
770
 
705
- **The two are not reconciled, because they are not two spellings of one thing.**
706
- A pathname names a file a package ships, and four surfaces derive an address
707
- from it. `banner:` names a hero image on the site's own asset host, reaches no
708
- compiled document and no book, and is resolved by the theme rather than by this
709
- toolchain — `banner: none` is not even a target. Write a `banner:` relative to
710
- the CDN's `images/` root — `banners/lore.webp`, not `images/banners/lore.webp`
711
- and not a package-qualified pathname.
771
+ **`banner: null` is how a note says it wants no plate.** It is a true statement
772
+ on any type, and the frontmatter lint never reports it.
712
773
 
713
774
  #### The pack a note compiles into
714
775
 
@@ -1060,7 +1121,7 @@ free-form value decides the headings there.
1060
1121
 
1061
1122
  ### WikiLinks
1062
1123
 
1063
- Twenty-seven fields in the tables below take a `WikiLink`, and a link is written
1124
+ Many fields in the tables below take a `WikiLink`, and a link is written
1064
1125
  `[[target]]` or `[[target|label]]`. The target is an **address**.
1065
1126
 
1066
1127
  #### The canonical address
@@ -1311,6 +1372,249 @@ Brackets belong in prose, where a link sits inside a sentence and needs marking
1311
1372
  off from the words around it. A frontmatter value has nothing to be marked off
1312
1373
  from.
1313
1374
 
1375
+ ### Assets are types
1376
+
1377
+ `icon`, `image` and `audio` are types in the type vocabulary, and an address
1378
+ reaches one exactly as it reaches a being or a skill. Their trees are
1379
+ the one thing about them that differs: they sit beside `content/` rather than
1380
+ inside it, because the thing a note is addressing is a file rather than a note.
1381
+
1382
+ **Three roots, one per type.** The directory is named for what it holds and the
1383
+ type is named for what an address reaches, so the two differ by a letter and the
1384
+ mapping is declared rather than derived from the name:
1385
+
1386
+ | root | type |
1387
+ | --------------- | ------- |
1388
+ | `assets/icons` | `icon` |
1389
+ | `assets/images` | `image` |
1390
+ | `assets/audio` | `audio` |
1391
+
1392
+ **A file's extension decides whether it is an asset at all**, and its root
1393
+ decides which type. Every image-extensioned file anywhere under `assets/icons`
1394
+ is an icon, and every audio-extensioned file under `assets/audio` is an audio
1395
+ asset, however deep it sits.
1396
+
1397
+ That filter is load-bearing rather than tidy-minded: `provenance.yaml` files
1398
+ live **inside** these roots, at any level, and a walk that took every file would
1399
+ read attribution records as assets. A directory under `assets/` that is not one
1400
+ of the four holds no addressable asset — `assets/ui` is a package's own
1401
+ furniture, and nothing addresses it.
1402
+
1403
+ **An asset type carries no system.** Its addresses keep the fourth segment, and
1404
+ it is always `none` — `sohl-none-icon-anvil`, never `sohl-icon-anvil` — so key
1405
+ parsing stays uniform across every type. The path shape is a property of the
1406
+ type rather than a test on a value.
1407
+
1408
+ #### The filename is the shortcode
1409
+
1410
+ ```text
1411
+ assets/icons/game-icons/lorc/anvil.svg → <package>-none-icon-anvil
1412
+ assets/images/beings/creatures/drake.webp → <package>-none-image-drake
1413
+ ```
1414
+
1415
+ **The extension is not part of the address**, and neither are the directories
1416
+ above the file. A root's shortcodes are one flat namespace however deeply the
1417
+ tree nests, so two files under one root sharing a basename are two claims on one
1418
+ address and a build error naming both. Across roots they are no such thing:
1419
+ `icon-anvil` and `image-anvil` are different addresses.
1420
+
1421
+ Leaving the extension out is what makes a format change free. Changing an icon
1422
+ from SVG to WebP is dropping a different file in place, and no note changes;
1423
+ `abysdrksvg` and `abysdrkwebp` would be two addresses for one drake.
1424
+
1425
+ **Beneath the root the structure is arbitrary, exactly as it is under
1426
+ `assets/content`.** A note's address does not depend on where its `.md` sits,
1427
+ and an asset's address does not depend on where its file sits within its root.
1428
+ Both trees are arranged for the people who maintain them —
1429
+ `icons/game-icons/<contributor>/`, `images/beings/creatures/` — and either can be
1430
+ rearranged wholesale without a single reference changing.
1431
+
1432
+ **That freedom is the point.** Whoever maintains a tree organises it however
1433
+ makes sense to them — by contributor, by subject, by the source a set came from,
1434
+ by whatever the next thousand files want — and the build has no opinion. A
1435
+ layout that carried meaning would spend that freedom to buy nothing: moving a
1436
+ file would become a breaking change, and the arrangement would answer to the
1437
+ walk rather than to the person reading it.
1438
+
1439
+ **The root itself is the exception, and only because an asset root is
1440
+ homogeneous.** `assets/content` is mixed: it holds beings, places, skills and
1441
+ the rest side by side, and each note declares its own `type:`, so no directory
1442
+ needs to. An asset root holds one type and nothing else — everything under
1443
+ `assets/icons` is an icon — which is what lets the root supply the type. It has
1444
+ to: a `.webp` carries no frontmatter and has nowhere to say what it is.
1445
+
1446
+ So the two trees follow one rule about layout and differ on exactly one point:
1447
+ a note's type comes from the note, an asset's type comes from its root. That is
1448
+ also why the three roots are a closed list rather than a convention — a fourth
1449
+ directory under `assets/` declares nothing, so nothing in it is addressable.
1450
+ `assets/ui` and `assets/fonts` fall out of that rule rather than needing an
1451
+ exemption.
1452
+
1453
+ **A shortcode is lowercase alphanumerics.** A filename that is not — a version
1454
+ string, a hyphen, a date stamp — cannot be addressed, and the build says so
1455
+ rather than inventing a shortcode for it.
1456
+
1457
+ #### The asset record
1458
+
1459
+ An asset reaches the content index as a record of its own — one line per
1460
+ addressable file, in the same JSON Lines index the notes publish into. It
1461
+ carries no frontmatter, no anchors and no `foundry` block: a file declares
1462
+ nothing about itself, compiles into no document, and publishes no page, so the
1463
+ `address` holds the canonical key and no page slug.
1464
+
1465
+ | `asset` field | Source | What it says |
1466
+ | ------------- | ---------- | ------------------------------------------------------------------ |
1467
+ | `path` | the walk | Where the file sits inside the emitting package's asset directory. |
1468
+ | `attribution` | provenance | Who made the file, or what tool generated it. |
1469
+ | `source` | provenance | Where it came from — a URL, or a sentence. |
1470
+ | `license` | provenance | The licence it is used under — an SPDX identifier, or terms. |
1471
+ | `notes` | provenance | Anything else a person reading the attribution needs. |
1472
+
1473
+ Every field is present on every record, blank where nothing states one. A fixed
1474
+ shape is what lets a consumer read `asset.license` without first asking whether
1475
+ the package happened to record one.
1476
+
1477
+ **`path` is what makes resolution one step.** The record is emitted by the
1478
+ package holding the bytes, so the path is that package's path and each consumer
1479
+ joins its own root onto it:
1480
+
1481
+ | Consumer | `thalorna-none-image-thorn` resolves to |
1482
+ | -------- | ------------------------------------------------------------------ |
1483
+ | Web | `<cdn base>/thalorna/images/beings/characters/thorn.webp` |
1484
+ | Book | `<asset base>/thalorna/images/beings/characters/thorn.webp` |
1485
+ | Foundry | `modules/sohl-thalorna/assets/images/beings/characters/thorn.webp` |
1486
+
1487
+ Foundry names a **Foundry package id** rather than a content package, because
1488
+ the two are distinct — they are equal in the system only by coincidence, and in
1489
+ `sohl-thalorna` they differ.
1490
+
1491
+ ##### Where provenance comes from
1492
+
1493
+ A `provenance.yaml` states `attribution`, `source`, `license` and `notes`, and
1494
+ nothing else: an unknown key is a finding rather than a silent drop, because
1495
+ `licence` beside `license` is otherwise an attribution record that looks
1496
+ complete and carries nothing.
1497
+
1498
+ One address resolves its record in this order:
1499
+
1500
+ 1. A sibling `<filename.ext>.yaml`, which **replaces** an inherited record
1501
+ wholesale rather than merging over it. A sidecar exists precisely because the
1502
+ inherited answer is wrong for this one file, so carrying half of it forward
1503
+ would make the record's meaning depend on a directory two levels up.
1504
+ 2. Otherwise the nearest `provenance.yaml`, searching the file's own directory
1505
+ and then its ancestors, **stopping at the type root**. A record above the root
1506
+ would speak for trees it says nothing about.
1507
+ 3. Otherwise nothing, and the provenance fields are blank. A package that
1508
+ records no attribution is a fact to state, not a walk to fail.
1509
+
1510
+ #### A font is not an asset
1511
+
1512
+ `assets/fonts` is not a root, and a font has no address.
1513
+
1514
+ **An asset type exists so a note can name a file and a package can substitute
1515
+ it.** Nothing about a font answers to either half. No note names a typeface —
1516
+ the art slots are `icon`, `tokenIcon`, `bgImage` and `banner` — and substituting
1517
+ one package's font for another's is not something anyone wants.
1518
+
1519
+ **Neither consumer of a font can use an address.** A stylesheet names a file
1520
+ with `url()`, resolved relative to the stylesheet and never through the content
1521
+ index. The book names a _family_ — `#set text(font: "Libertinus Serif")` — and
1522
+ the typesetter matches that against a directory it is pointed at. One wants a
1523
+ relative path, the other a family name; an address serves neither.
1524
+
1525
+ **And the model fits badly.** An address holds one file whose format is free to
1526
+ change, which suits a picture. A font family is a matrix of family, weight and
1527
+ style, and a filename already says which cell a file is — a shortcode would have
1528
+ to re-encode that, worse.
1529
+
1530
+ So fonts stay ordinary files a package ships and points a tool at, and their
1531
+ licences are recorded where a person reads them rather than in an index.
1532
+
1533
+ #### `icon` and `image` are two types, not one
1534
+
1535
+ An icon has to stay coherent drawn into a 32×32 slot, which is why icons are
1536
+ SVG. An image is unbounded, expected to be large, and unreadable at that size.
1537
+ That is a fitness property of the asset itself, and one merged type would leave
1538
+ every consumer inferring the handling from the extension; two types let a guard
1539
+ assert it instead.
1540
+
1541
+ So the two have **separate shortcode namespaces**. `icon-anvil` and
1542
+ `image-anvil` are different addresses for different purposes, and a rewrite rule
1543
+ reaches one without touching the other.
1544
+
1545
+ **An asset address is its own name.** `image-thorn` is an image called `thorn`;
1546
+ any resemblance to `being-thorn` is coincidence, and nothing derives one from
1547
+ the other.
1548
+
1549
+ #### `packagebuild` is a reserved package name
1550
+
1551
+ package-build is an npm package rather than a system or a module, and it ships
1552
+ a set of images — section banners chiefly — that many packages draw on.
1553
+ Addressing them as `packagebuild-none-image-<shortcode>` lets a note reach one
1554
+ without declaring a dependency on some parent system or module it otherwise has
1555
+ no relationship with.
1556
+
1557
+ **Its files reach the website and the book and no Foundry document.** Foundry
1558
+ installs no package of that name and no relationship can be declared with one,
1559
+ so `banner:` — the one art slot with no Foundry destination — is where a
1560
+ `packagebuild` address belongs. A body image naming one, written either way, is
1561
+ refused with the line and column it sits on.
1562
+
1563
+ `packagebuild` is therefore **reserved** in the package registry: nothing may
1564
+ create a real package that collides with it, and its resolution is special-cased,
1565
+ because no installed directory sits behind the name.
1566
+
1567
+ #### An embedded image is a wikilink
1568
+
1569
+ ```text
1570
+ ![[address|label]]
1571
+ ```
1572
+
1573
+ There is no image grammar. An embed is the wikilink above, with `!` meaning
1574
+ _render it here_ rather than _link to it_, and the syntax supplying the default
1575
+ type the way a field declaration does — `image`. The short-form ladder, the
1576
+ package and system defaults, the lowercase rule, the ambiguity reporting and the
1577
+ findings vocabulary all apply unchanged.
1578
+
1579
+ | written | means |
1580
+ | ------------------------------ | ---------------------------------------- |
1581
+ | `![[anvil\|]]` | decorative — the common case |
1582
+ | `![[anvil\|An anvil]]` | alt text where it carries meaning |
1583
+ | `![[anvil]]` | unlabelled, and a finding like any other |
1584
+ | `![[sohl-image-anvil\|Anvil]]` | qualified, reaching another package |
1585
+
1586
+ The parser distinguishes a missing label from an empty one, so _deliberately
1587
+ decorative_ and _not written_ differ without an exemption.
1588
+
1589
+ **The label is the alt text, and it stays with the referrer.** One image serves
1590
+ many documents, and only the referrer knows what it means where it sits.
1591
+
1592
+ **An embed reaches asset types only.** The syntax invites the broader reading —
1593
+ transclusion of an arbitrary note — so the restriction is a guard rather than a
1594
+ convention.
1595
+
1596
+ **An embed takes the same directive an image does**, in the same place —
1597
+ immediately after the closing `]]`, with no space:
1598
+
1599
+ ```markdown
1600
+ ![[branwldrgr|Brànwâal Dôrgaar]]{float: top-left}
1601
+ ![[thalornamap|Map of Thalorna]]{.full-width}
1602
+ ![[thalornamap|Map of Thalorna]]{.full-width, float: top-left}
1603
+ ![[thalornamap|Map of Thalorna]]{float: top-left, .full-width}
1604
+ ```
1605
+
1606
+ **The same parser reads both**, so an embed takes a width class, a `float:`, or
1607
+ both together in either order, comma-separated — everything
1608
+ [an image's directive](#width-is-a-class-and-the-ordinary-width-carries-no-marker)
1609
+ takes and nothing beyond it. The width and position vocabularies are the ones
1610
+ above, and they are closed here for the same reason: a directive that is quietly
1611
+ ignored looks exactly like one that worked, and a directive holding a problem is
1612
+ not honoured in part either.
1613
+
1614
+ **A being's portrait is one of these**, opening its `{#appearance}` section by
1615
+ convention rather than declared in a field — see
1616
+ [the lead image](#the-lead-image).
1617
+
1314
1618
  ### What a note produces
1315
1619
 
1316
1620
  Note types fall into two groups, and only the first has a mapping table.
@@ -1402,7 +1706,7 @@ authored statement, three renderers, which is the single-sourcing every other
1402
1706
  part of this format follows.
1403
1707
 
1404
1708
  ```markdown
1405
- ![Brànwâal Dôrgaar](images/beings/branwldrgr-portrait.webp){float: top-left}
1709
+ ![[branwldrgr|Brànwâal Dôrgaar]]{float: top-left}
1406
1710
 
1407
1711
  ![Map of Thalorna](images/maps/thalorna.webp){.full-width}
1408
1712
  ```
@@ -1450,8 +1754,17 @@ not. The website and a Foundry journal get true CSS wrap from the same
1450
1754
  directive. Expect the same statement, not the same page.
1451
1755
 
1452
1756
  An image with no `float:` is an ordinary block in the flow, where it was
1453
- written. A `.full-width` image is placed at the top of the page it falls on,
1454
- since a block cannot leave the column it is set in.
1757
+ written. A `.full-width` image is set at the **top of a new page**: the book is
1758
+ set in two columns, only a float spans them both, and a float is placed where
1759
+ the page has room rather than where it was written — so the page breaks before
1760
+ the picture, and the prose that follows it runs below it on the same page. The
1761
+ picture can therefore never print above the prose that introduces it or after
1762
+ the prose that follows it, at the cost of the page before it ending short. A
1763
+ picture taller than the page takes a page of its own.
1764
+
1765
+ A `.full-width` image that **also states a `float:`** is asking for a float and
1766
+ gets one, with no break before it. A float is placed where it fits, which may be
1767
+ the next page.
1455
1768
 
1456
1769
  ##### Both vocabularies are closed
1457
1770
 
@@ -1472,12 +1785,18 @@ a scheme of `http:` or `https:`, or none at all.
1472
1785
 
1473
1786
  ##### Where an address resolves
1474
1787
 
1475
- An image's address follows the rule [`img:` follows](#an-asset-paths-first-segment-says-which-package-owns-it)
1788
+ An image's address follows [the pathname rule](#a-pathname-names-the-package-that-owns-the-file)
1476
1789
  — its first segment says which package owns the file — and each surface
1477
1790
  resolves it to what that surface serves:
1478
1791
 
1479
1792
  - **Foundry** is handed the path inside the install, so `images/map.webp`
1480
- reaches a journal page as `modules/<package>/assets/images/map.webp`.
1793
+ reaches a journal page as `modules/<package>/assets/images/map.webp`. An
1794
+ address with no such path — a package this build declares no relationship
1795
+ with, or `packagebuild`, which Foundry installs nothing of — is **refused,
1796
+ located by file, line and column**, and the build fails. Foundry is the one
1797
+ surface a correctly written pathname can be dead on while the other two
1798
+ resolve it, and a `src` that resolves against nothing looks exactly like an
1799
+ address that worked.
1481
1800
  - **The book** is handed a copy of the file, staged out of this package's own
1482
1801
  asset tree into the build directory before the compiler runs. An address
1483
1802
  naming a file this package does not ship — another package's, or a URL —
@@ -1651,7 +1970,6 @@ The following H1 headers are treated specially:
1651
1970
  For JournalEntries, the following rules apply:
1652
1971
 
1653
1972
  - `# ... {#spoilers}`: The contents of this header go into a page which is viewable only by the GM (`CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE`).
1654
- - The `portrait` frontmatter property will be added as a new JournalPage of type image with the name "Portrait" and anchor "portrait".
1655
1973
 
1656
1974
  **Archetype** is often used to describe a character in broad terms. These are often useful when determining whether a character matches a particular adventure. The list of character archetypes are:
1657
1975
 
@@ -1684,7 +2002,7 @@ Generates a living (or undead, or spirit) being.
1684
2002
 
1685
2003
  | `data` property | Values | Description |
1686
2004
  | --------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------ |
1687
- | `portrait` | `string` | File path to the portrait image |
2005
+ | `tokenIcon` | `WikiLink` | What a token on the canvas wears — an `icon` address; defaults to `icon` |
1688
2006
  | `templatePriority` | `number` | Template priority, _null_ = not a template |
1689
2007
  | `archetypes` | `Archetype[]` | What sort of character this is. **Always an array** — `[]` where none apply; `null` is an error. |
1690
2008
  | `occupation` | `string` | Name of the character's occupation |
@@ -1705,6 +2023,45 @@ Generates a living (or undead, or spirit) being.
1705
2023
  | `appearance.complexion` | `string` | Complexion |
1706
2024
  | `appearance.extra_features` | `string[]` | Extra features |
1707
2025
 
2026
+ #### The lead image
2027
+
2028
+ **A being's portrait is the first thing inside its `{#appearance}` section,
2029
+ always.**
2030
+
2031
+ ```markdown
2032
+ # Appearance {#appearance}
2033
+
2034
+ ![[<address>|<the being's full name>]]{float: top-left}
2035
+ ```
2036
+
2037
+ **Nothing about that embed is special.** It is an ordinary embedded image with
2038
+ an ordinary directive, and no pass treats it differently for sitting where it
2039
+ sits. What is strict is the convention: the portrait opens the appearance
2040
+ section, so a reader opening any being note finds the picture in the same place,
2041
+ and an author writing one never has to decide where it goes.
2042
+
2043
+ **The section is the part that is not a convention.** `{#appearance}` is what
2044
+ becomes an actor's `system.appearance`, so a picture written above that heading
2045
+ is outside the section and reaches no document. The heading carries the anchor
2046
+ at **H1** — a deeper heading is not matched, and its whole section is extracted
2047
+ as empty rather than reported.
2048
+
2049
+ The label is the being's full name. That is what a reader needs when the picture
2050
+ does not load, and what a screen reader announces in its place.
2051
+
2052
+ **There is no `portrait` field**, at any level. A portrait is a picture of the
2053
+ subject, and a picture of the subject belongs in the prose that describes it —
2054
+ where an author can see it, move it, caption it or remove it, as with any other
2055
+ image. A field puts it somewhere only a template can reach, and every template
2056
+ then has to agree about where that is.
2057
+
2058
+ Later images are ordinary images too — a second picture of the being, a coat of
2059
+ arms, a map of its holding — and they sit wherever the prose wants them,
2060
+ including in `{#dossier}`.
2061
+
2062
+ **A being with no portrait writes no lead image.** There is no empty form and no
2063
+ placeholder.
2064
+
1708
2065
  #### A being's embedded items
1709
2066
 
1710
2067
  `<system>.items` is a list, and each entry compiles into one embedded Item. An
@@ -1727,6 +2084,7 @@ sohl:
1727
2084
  | `model` | yes | The address of the item this entry is a copy of |
1728
2085
  | `system` | no | Values that override the model's |
1729
2086
  | `name` | no | A name of this entry's own, where it differs from the model's |
2087
+ | `data` | no | Art this entry names — see _An entry's art_ below |
1730
2088
 
1731
2089
  **`type:` is not written beside a `model`.** The address already names the type,
1732
2090
  so a second statement of it is a place to be wrong, and it is refused.
@@ -1761,6 +2119,38 @@ things — and it could not say which package a template came from, so an addres
1761
2119
  resolved into a dependency only because no local pack claimed it and would have
1762
2120
  retargeted silently the day one did.
1763
2121
 
2122
+ #### An entry's art
2123
+
2124
+ An entry is an item in every respect but where it is written, so it carries art
2125
+ by the two rules [an item note](#the-four-art-slots) does. It names an `icon`
2126
+ address under its own `data:`, and that resolves into the embedded document's
2127
+ `img`:
2128
+
2129
+ ```yaml
2130
+ sohl:
2131
+ items:
2132
+ - name: Quiver (leather)
2133
+ type: miscgear
2134
+ data:
2135
+ icon: quiver
2136
+ system:
2137
+ shortcode: quiver
2138
+ ```
2139
+
2140
+ `icon` is the only slot an entry has. The other three belong to a note: two
2141
+ describe an Actor or a map, and `banner` is a page's hero image, which an
2142
+ embedded document has no page for.
2143
+
2144
+ **Naming none is the common case, and it takes a default.** An entry copying a
2145
+ `model:` carries the catalogue item's art, and an entry copying nothing takes
2146
+ the default its type pairs in `itemBuilders` — the same default an item note of
2147
+ that type compiles with. An entry naming an address of its own overrides either.
2148
+ Writing `icon: ""` ships the document blank on purpose, and no default replaces
2149
+ it.
2150
+
2151
+ **`data:` reaches no compiled document.** It is where a note names art, and a
2152
+ compiled Item has `img` instead; the container is read and goes no further.
2153
+
1764
2154
  #### Identifying a being's embedded items
1765
2155
 
1766
2156
  Each entry in `sohl.items` compiles into one embedded Item, and its `_id` is
@@ -1827,15 +2217,15 @@ It will also generate a single JournalEntry located at the top level of the "jou
1827
2217
 
1828
2218
  Represents a conveyance able to hold goods and people moving from one place to another.
1829
2219
 
1830
- | `data` property | Values | Description |
1831
- | ------------------ | -------- | ------------------------------------------ |
1832
- | `portrait` | `string` | File path to the portrait image |
1833
- | `templatePriority` | `number` | Template priority, _null_ = not a template |
2220
+ | `data` property | Values | Description |
2221
+ | ------------------ | ---------- | ------------------------------------------------------------------------ |
2222
+ | `tokenIcon` | `WikiLink` | What a token on the canvas wears — an `icon` address; defaults to `icon` |
2223
+ | `templatePriority` | `number` | Template priority, _null_ = not a template |
1834
2224
 
1835
2225
  If `sohl` is present, this becomes a `vehicle` actor.
1836
2226
 
1837
2227
  It maps nothing beyond the shared rows above, actor row included: a vehicle
1838
- carries a portrait and a template priority and no field of its own.
2228
+ carries a template priority and no field of its own.
1839
2229
 
1840
2230
  ### type: affiliation
1841
2231
 
@@ -2525,23 +2915,23 @@ are subTypes of one type rather than three types.
2525
2915
 
2526
2916
  The `data:` fields, of which three are required:
2527
2917
 
2528
- | `data` property | Values | Description |
2529
- | ----------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2530
- | `img` | `string` | **Required.** The path to the map art. Becomes the level's `background.src` what tokens stand on. Authored at the note's **top level**, not here — see below |
2531
- | `dimensions` | `[int, int]` | **Required.** `[width, height]` in whole pixels, the art's own size |
2532
- | `pxPerGrid` | `integer` | **Required.** Whole pixels per grid square; must match the art |
2533
- | `navName` | `string` | Short name for Foundry's scene navigation bar |
2534
- | `levelName` | `string` | The name of the embedded level. Defaults to `Ground` |
2535
- | `backgroundColor` | `ColorHexValue` | Shown where the art does not reach. Defaults to `#999999` |
2536
- | `overlay` | `string` | Path to **foreground** art |
2537
- | `walls` | `WallSegment[]` | List of wall segments |
2538
- | `doors` | `Door[]` | List of doors |
2539
- | `lights` | `Light[]` | List of lights |
2540
- | `tiles` | `Tile[]` | List of tiles |
2541
- | `sounds` | `Sound[]` | List of sounds |
2542
- | `regions` | `SceneRegion[]` | List of scene regions |
2543
- | `place` | `WikiLink` | The place this map depicts. Optional, because an encounter map depicts no named place — but that is the exception, and a map without one is a map of nowhere |
2544
- | `notes` | `NoteLocation[]` | grid coordinates of note markers mapped to anchors in this document |
2918
+ | `data` property | Values | Description |
2919
+ | ----------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
2920
+ | `bgImage` | `WikiLink` | **Required.** The map art an `image` address. Becomes the level's `background.src`, what tokens stand on |
2921
+ | `dimensions` | `[int, int]` | **Required.** `[width, height]` in whole pixels, the art's own size |
2922
+ | `pxPerGrid` | `integer` | **Required.** Whole pixels per grid square; must match the art |
2923
+ | `navName` | `string` | Short name for Foundry's scene navigation bar |
2924
+ | `levelName` | `string` | The name of the embedded level. Defaults to `Ground` |
2925
+ | `backgroundColor` | `ColorHexValue` | Shown where the art does not reach. Defaults to `#999999` |
2926
+ | `overlay` | `string` | Path to **foreground** art |
2927
+ | `walls` | `WallSegment[]` | List of wall segments |
2928
+ | `doors` | `Door[]` | List of doors |
2929
+ | `lights` | `Light[]` | List of lights |
2930
+ | `tiles` | `Tile[]` | List of tiles, each naming its art in `image` |
2931
+ | `sounds` | `Sound[]` | List of sounds, each naming its clip in `audio` |
2932
+ | `regions` | `SceneRegion[]` | List of scene regions |
2933
+ | `place` | `WikiLink` | The place this map depicts. Optional, because an encounter map depicts no named place — but that is the exception, and a map without one is a map of nowhere |
2934
+ | `notes` | `NoteLocation[]` | grid coordinates of note markers mapped to anchors in this document |
2545
2935
 
2546
2936
  Everything else a Scene holds is **derived**, not authored: padding, grid type,
2547
2937
  grid distance and units, token vision and fog mode all come from the subType, and
@@ -2554,16 +2944,38 @@ one Level or it has no map at all, and nothing supplies one after the fact: the
2554
2944
  client-side `_preCreate` that would create it does not run during offline pack
2555
2945
  compilation, and the server-side migration shim is version-gated on
2556
2946
  `_stats.coreVersion`, so a pack stamped 14.x or later skips it entirely. The
2557
- single Level is synthesised from `img`, `overlay`, `levelName` and
2947
+ single Level is synthesised from `bgImage`, `overlay`, `levelName` and
2558
2948
  `backgroundColor`.
2559
2949
 
2560
- > **`img`, at the note's top level, as every other type's artwork is.** Art is
2561
- > not system-specific — a Scene is a core Foundry document, and a second system
2562
- > would want the identical art — so the field sits beside every other note's
2563
- > `img` rather than inside a system block.
2950
+ > **`bgImage` is a `WikiLink` under `data:`, as every other type's artwork is.**
2951
+ > Art is not system-specific — a Scene is a core Foundry document, and a second
2952
+ > system would want the identical art — so the field sits under `data:` rather
2953
+ > than inside a system block.
2564
2954
  >
2565
- > `image` is **not a key a map has**: in a `sohl:` block it is reported as
2566
- > unknown, and either way the note is refused for the `img` it never declared.
2955
+ > **A map has no `img`.** `bgImage` is the only key naming a map's background,
2956
+ > and a note writing `img:` or `data.img:` is refused. The two are not
2957
+ > alternative spellings: `img` is a Foundry destination and no Scene has one.
2958
+
2959
+ **Every art key inside a map is an address, and each is named for the type it
2960
+ reaches.** A map's geometry is authored in Foundry's own units, and its art is
2961
+ not authored in Foundry's own paths.
2962
+
2963
+ | authored | default type | emitted as |
2964
+ | -------------------- | ------------ | ---------------------------- |
2965
+ | `data.bgImage` | `image` | the level's `background.src` |
2966
+ | `tiles.<key>.image` | `image` | the tile's `texture.src` |
2967
+ | `sounds.<key>.audio` | `audio` | the `AmbientSound`'s `path` |
2968
+
2969
+ Naming each key for the type it references is what makes the default worth
2970
+ having: `audio:` reaches an `audio` address without a note ever writing the
2971
+ type segment. Where a tile places a glyph rather than artwork it qualifies —
2972
+ `image: sohl-none-icon-chest` — which is the ordinary short-form ladder rather
2973
+ than an exception to it.
2974
+
2975
+ **The authored key and the emitted field differ where Foundry's name differs.**
2976
+ An `AmbientSound` stores its clip in `path`, and that is what the compiled
2977
+ document carries; the note writes `audio` because the note is naming an
2978
+ address, not filling in a Foundry field.
2567
2979
 
2568
2980
  **Two unit conventions, deliberately.** Geometry — walls, doors, lights, tiles,
2569
2981
  sounds, region shapes — is authored in **pixels**, Foundry's native storage,
@@ -2762,8 +3174,8 @@ rules describe it, and compiling one through this path would ship a macro whose
2762
3174
  body was a code block posted verbatim into chat. Chat macros as content would
2763
3175
  need an authoring convention of their own.
2764
3176
 
2765
- The note's `img` is a content-relative path resolved the way every other note's
2766
- is; a note that authors none takes Foundry's own `icons/svg/dice-target.svg`.
3177
+ The note's `data.icon` resolves the way every other note's does; a note that
3178
+ authors none takes Foundry's own `icons/svg/dice-target.svg`.
2767
3179
 
2768
3180
  ### type: bundle
2769
3181