@heroiclands/package-build 22.17.1 → 22.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 22.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Beings use their subtype to choose default art.
8
+
9
+ ### Patch Changes
10
+
11
+ Being profiles identify each subject as Character, NPC, or Creature from its top-level subtype.
12
+
13
+ ## 22.17.2
14
+
15
+ ### Patch Changes
16
+
17
+ Being notes can identify encounterable NPCs, playable characters, and creatures with a checked subtype.
18
+
19
+ **Content notes**
20
+
21
+ - Notes can carry an additional system-specific block in their frontmatter.
22
+
23
+ **Content checks**
24
+
25
+ - Lint and link checks report unresolved references in SoHL system fields.
26
+
3
27
  ## 22.17.1
4
28
 
5
29
  ### Patch Changes
@@ -70,7 +70,12 @@ import { lintContentCharset } from "../engine/content-charset.mjs";
70
70
  import { lintContentHtml } from "../engine/content-html.mjs";
71
71
  import { lintContentIcons } from "../engine/content-icons.mjs";
72
72
  import { lintContentImages } from "../engine/content-images.mjs";
73
- import { declaredSystems, lintFrontmatter, systemBlocksFor } from "../engine/frontmatter-lint.mjs";
73
+ import {
74
+ declaredSystems,
75
+ lintFrontmatter,
76
+ systemAddressFindings,
77
+ systemBlocksFor,
78
+ } from "../engine/frontmatter-lint.mjs";
74
79
  import { loadContentFormat } from "../engine/content-format.mjs";
75
80
  import {
76
81
  checkDeclaredFields,
@@ -124,7 +129,6 @@ import { reportFindings } from "./report.mjs";
124
129
  import {
125
130
  readItemAddresses,
126
131
  diffItemAddresses,
127
- declaredPredecessors,
128
132
  noteFilesById,
129
133
  locateAddressFinding,
130
134
  addressFindingMessage,
@@ -1449,6 +1453,13 @@ function linksCommand() {
1449
1453
  homepageLinks,
1450
1454
  usedManifest,
1451
1455
  } = auditLinks(index);
1456
+ const systemReferences = index.notes.flatMap((note) =>
1457
+ systemAddressFindings(note, {
1458
+ index,
1459
+ schemas: { ...ENGINE_NOTE_SCHEMAS, ...NOTE_SCHEMAS },
1460
+ systems: systemBlocksFor(config, { schemaSystem: "sohl" }),
1461
+ }),
1462
+ );
1452
1463
 
1453
1464
  for (const d of deadAnchors) {
1454
1465
  emitDiagnostic({
@@ -1486,6 +1497,7 @@ function linksCommand() {
1486
1497
  `${f.path} — frontmatter is data and is never resolved`,
1487
1498
  });
1488
1499
  }
1500
+ for (const finding of systemReferences) emitDiagnostic(finding);
1489
1501
 
1490
1502
  // The package homepage. Its addresses are markdown links and
1491
1503
  // `landing:` url/href fields rather than wikilinks — it is
@@ -1506,6 +1518,7 @@ function linksCommand() {
1506
1518
  deadEmbeds.length +
1507
1519
  unlabelledLinks.length +
1508
1520
  frontmatterLinks.length +
1521
+ systemReferences.length +
1509
1522
  homepageLinks.length;
1510
1523
  if (failures) {
1511
1524
  log.error(`${failures} link problem(s) across ${index.notes.length} note(s).`);
@@ -2258,21 +2271,11 @@ function depsCommand() {
2258
2271
  * `addresses diff` — report every published `(type, shortcode)` this build no
2259
2272
  * longer publishes, against a released artifact.
2260
2273
  *
2261
- * The address space is a published interface (see `engine/address-diff.mjs`),
2262
- * and renaming a shortcode used to cost nothing and produce no signal. This is
2263
- * the signal, emitted in the repository doing the renaming while the change is
2264
- * still in front of the author.
2265
- *
2266
- * **Its own command rather than a step of `package compile`.** It reads a
2267
- * *second* artifact that the compile knows nothing about and that has to be
2268
- * obtained separately, and it is a question about a release rather than about a
2269
- * build — a repository between releases has nothing to compare against.
2274
+ * Compare this package's compiled Item addresses with the release artifact
2275
+ * named by `--from`. The command reports departed addresses and locates
2276
+ * findings against the current content tree or baseline artifact.
2270
2277
  *
2271
- * **The baseline is named, never derived, and never downloaded.** `--from`
2272
- * takes the artifact — the `.zip` a release publishes, or a directory built
2273
- * from one — for the same reason `deps fetch --from` does: a command that
2274
- * reaches the network on its own is not reproducible and fails strangely
2275
- * offline. In a release workflow the artifact is one line ahead of it:
2278
+ * `--from` accepts a release `.zip` or a directory built from one:
2276
2279
  *
2277
2280
  * ```sh
2278
2281
  * gh release download v0.8.2 -p system.zip -D build/baseline
@@ -2332,10 +2335,6 @@ async function diffAddresses(config, argv) {
2332
2335
  readItemAddresses(currentDirs),
2333
2336
  {
2334
2337
  baseline: label,
2335
- // Read whether or not anything departed: an id match needs no tree,
2336
- // but the diff decides rename-versus-withdrawal as it walks the
2337
- // baseline, so the declarations have to be in hand before it does.
2338
- predecessors: declaredPredecessors(config.paths.content, corpus),
2339
2338
  },
2340
2339
  );
2341
2340
  if (!findings.length) {
@@ -2343,9 +2342,8 @@ async function diffAddresses(config, argv) {
2343
2342
  return;
2344
2343
  }
2345
2344
 
2346
- // A rename is fixed in the note that made it, so findings are placed
2347
- // against the tree rather than against the compiled output they were read
2348
- // from.
2345
+ // The tree locates a matched document; otherwise the baseline locates the
2346
+ // departed address.
2349
2347
  const noteFiles = noteFilesById(config.paths.content, corpus);
2350
2348
  const severity = argv.strict ? "error" : "warning";
2351
2349
  for (const finding of findings) {
package/docs/api.md CHANGED
@@ -347,7 +347,7 @@ The note types the engine itself declares — vocabulary that is a fact about th
347
347
 
348
348
  ### `engine.noteVocabulary`
349
349
 
350
- The closed half of a note's frontmatter: the `data:` container and each type's `subType` — the only two regions where an unrecognized key is refused rather than passed through to the page.
350
+ An addressed content note accepts exactly these top-level keys, in this order: `shortcode`, `name`, `type`, `subType`, `description`, `tags`, `data`, `hm3`, `sohl`, `dnd5e`. A key outside this list is an error. The address vocabulary recognizes three systems: `sohl`, `hm3`, and `dnd5e`. No dnd5e-specific document types are declared. Within that shape, the closed vocabularies are the `data:` container and each type's `subType`.
351
351
 
352
352
  | Export | Signature | Returns | Use it when |
353
353
  | ------------------------- | -------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------- |
@@ -358,7 +358,7 @@ The closed half of a note's frontmatter: the `data:` container and each type's `
358
358
  | `DECLARED_TAGS` | `const DECLARED_TAGS` | — | looking up the tags a note type may declare, grouped |
359
359
  | `declaredTags` | `declaredTags(type, groups)` | `readonly string[]` | reading the declared tags a note of a type may carry, flattened |
360
360
  | `applicableTagGroups` | `applicableTagGroups(type, groups)` | `object[]` | reading the declared tag groups that apply to a note type |
361
- | `exclusiveTagGroups` | `exclusiveTagGroups(type, groups)` | `Array<{slot, tags}>` | reading the single-valued tag slots a note type has, such as a being's kind |
361
+ | `exclusiveTagGroups` | `exclusiveTagGroups(type, groups)` | `Array<{slot, tags}>` | reading single-valued tag slots declared for a note type |
362
362
  | `hasTag` | `hasTag(fm, tag)` | `boolean` | checking whether a note carries a given tag, whatever scalar-or-list form it was authored in |
363
363
  | `isDraftNote` | `isDraftNote(fm)` | `boolean` | checking whether a note is tagged as an unfinished draft |
364
364
  | `isGmNote` | `isGmNote(fm)` | `boolean` | checking whether a note is restricted to GM-facing output |
@@ -607,13 +607,13 @@ The map from each place, drawn for the site: `package-build site` draws the map
607
607
 
608
608
  ### `engine.systems`
609
609
 
610
- The closed registry of system ids, and the `none` that stands for no system at all. An unknown system value is an error; adding a system is a data change to this registry rather than a hardcoded set scattered through the pipeline.
610
+ The closed registry of system ids, and the `none` that stands for no system at all. An unknown system value is an error; adding a system is a data change to this registry rather than a hardcoded set scattered through the pipeline. The registry recognizes `sohl`, `hm3`, and `dnd5e`; no dnd5e-specific document types are declared.
611
611
 
612
612
  | Export | Signature | Returns | Use it when |
613
613
  | ---------------------- | -------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------- |
614
614
  | `NO_SYSTEM` | `const NO_SYSTEM` | — | naming a systemless asset or Foundry document |
615
615
  | `NOTE_SYSTEM` | `const NOTE_SYSTEM` | — | naming readable note content across web, book and Foundry |
616
- | `SYSTEM_IDS` | `const SYSTEM_IDS` | — | enumerating every game system this toolchain compiles for |
616
+ | `SYSTEM_IDS` | `const SYSTEM_IDS` | — | enumerating game system ids accepted in configuration and addresses |
617
617
  | `SYSTEM_SEGMENTS` | `const SYSTEM_SEGMENTS` | — | enumerating the game systems, `note` and `none` |
618
618
  | `isSystemId` | `isSystemId(value)` | `boolean` | checking whether a value names a game system this toolchain knows (`none` is rejected) |
619
619
  | `isSystemSegment` | `isSystemSegment(value)` | `boolean` | checking whether a value is something the `<system>` address segment may hold |
@@ -809,16 +809,6 @@ The id a note's document is filed under: its pinned `id:`, when a note authors o
809
809
  | `noteDocId` | `function noteDocId(fm,` | {string\|undefined} The document's `_id`, or `undefined` when the note has no address to derive one from. | The document id a note compiles under: its pin, or its address. |
810
810
  | `resolveNoteId` | `function resolveNoteId(fm, opts)` | {object\|null\|undefined} `fm`, for chaining. | Fill a note's `id` in place, so everything downstream reads one value. |
811
811
 
812
- ### `engine.noteRenames`
813
-
814
- The shortcodes a note declares it used to be published under. A package's `(type, shortcode)` addresses are a published interface, and `addresses diff` reports what a build stopped publishing — telling a **rename** from a **withdrawal** by matching document ids across two releases. The property that rested on is gone: an id is derived from the canonical address, which carries the shortcode, so renaming a shortcode moves the id too: both sides of the join move together, the match finds nothing, and a rename is reported as a withdrawal with no successor named.
815
-
816
- | Export | Signature | Returns | Use it when |
817
- | --------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
818
- | `renamedFromEntries` | `function renamedFromEntries(fm)` | {readonly unknown[]} The authored entries, in authored order; empty when the note declares none. | The `renamedFrom:` entries a note authors, exactly as authored. |
819
- | `renamedFrom` | `function renamedFrom(fm)` | {string[]} The declared predecessor shortcodes. | The well-formed shortcodes among a note's `renamedFrom:` entries. |
820
- | `declaresRenamedFrom` | `function declaresRenamedFrom(fm)` | {boolean} `true` when the key is present and not null. | Whether a note declares the key at all, however malformed its value. |
821
-
822
812
  ### `engine.metadataIndex`
823
813
 
824
814
  The published content index — the artifact packages exchange addresses through. **A package publishes its own index; a consumer fetches the ones it depends on.** That is the whole mechanism, and it replaces a vendored link manifest that each repository committed a copy of every other repository's file into. Vendoring failed three ways, and only the last is about staleness:
@@ -21,7 +21,7 @@ data: {}
21
21
  ---
22
22
  ```
23
23
 
24
- `name.full` is the displayed name; `name.aliases` helps search. `description` is the short page summary. `tags` hold draft state and classification. `data` holds facts about the subject shared by systems. A YAML comment is the place for an author-only note: commented values remain valid YAML but do not enter the content index or generated documents.
24
+ `name.full` is the displayed name; `name.aliases` helps search. `description` is the short page summary. `tags` hold draft state and descriptive labels. The note type's classification belongs in `subType` where it is declared. `data` holds facts about the subject shared by systems. A YAML comment is the place for an author-only note: commented values remain valid YAML but do not enter the content index or generated documents.
25
25
 
26
26
  Use `tags: [gm]` for a note intended only for the GM. It is absent from the public website and book. Foundry includes it only when its document routes to a pack with `private: true`. A note with `gm` that also creates a prose JournalEntry needs a private JournalEntry pack. Links from untagged notes to GM notes are errors; GM notes may link to one another.
27
27
 
@@ -37,4 +37,4 @@ One body describes the subject for both systems. A being's `{#appearance}` and `
37
37
 
38
38
  ## Publication and summaries
39
39
 
40
- A note infobox draws from shared `data`. A system infobox draws from the system's fields, and a mapped system with no document says “Not available.” The same authored body appears in Foundry, on the website, and in the book; presentation adapts to each medium. [Infobox details](../reference/format-details.md#the-infobox) describe field order and empty values.
40
+ A note infobox draws from shared `data`. A being profile shows its top-level `subType` as `Type: Character`, `Type: NPC`, or `Type: Creature`. A system infobox draws from the system's fields, and a mapped system with no document says “Not available.” The same authored body appears in Foundry, on the website, and in the book; presentation adapts to each medium. [Infobox details](../reference/format-details.md#the-infobox) describe field order and empty values.
package/docs/commands.md CHANGED
@@ -1859,8 +1859,11 @@ checks frontmatter against the declared vocabulary and each system's `sohl:`
1859
1859
  it), that an icon a note writes is one the registry declares, and that
1860
1860
  markup inside a note's body does not smuggle in a character the charset
1861
1861
  check cannot see. `--no-references` turns off the check that a frontmatter
1862
- shortcode reference lands, for a tree whose cross-package references it
1863
- cannot see. Reads the content tree named by `root`, defaulting to
1862
+ shortcode reference lands, including references in schema-declared system
1863
+ fields, for a tree whose cross-package references it cannot see. Address
1864
+ references in system fields resolve against this package and its declared
1865
+ dependencies; exact Item UUID validation remains part of compilation. Reads
1866
+ the content tree named by `root`, defaulting to
1864
1867
  `paths.content`; writes nothing.
1865
1868
 
1866
1869
  **OPTIONS**
@@ -2073,7 +2076,9 @@ retired — every link is an address), a wikilink authored in frontmatter
2073
2076
  (which is data and is never resolved), and a package homepage's markdown
2074
2077
  links and `landing:` addresses, which are published verbatim and use no
2075
2078
  wikilink at all. Also reports a vendored foreign-package content index that
2076
- has drifted out of reach, naming `package-build deps fetch` as the fix.
2079
+ has drifted out of reach, naming `package-build deps fetch` as the fix. Resolves
2080
+ address-valued SoHL system fields declared by the installed schema against this
2081
+ package and its declared dependencies.
2077
2082
  Reads the content tree named by `root`, defaulting to `paths.content`, and
2078
2083
  the cached content indexes of any declared dependency; writes nothing.
2079
2084
 
@@ -2683,19 +2688,15 @@ package-build addresses diff --from <zip|dir> [--strict]
2683
2688
  **DESCRIPTION**
2684
2689
 
2685
2690
  Reports every published `(type, shortcode)` address this build no longer
2686
- publishes, against a released artifact — the signal that a shortcode
2687
- rename or a withdrawn note used to cost nothing and now does, emitted in
2688
- the repository doing the renaming while the change is still in front of
2689
- its author. Its own command rather than a step of `package compile`,
2690
- because it reads a _second_ artifact the compile knows nothing about and
2691
- asks a question about a release, not about a build — a repository between
2692
- releases has nothing to compare against. `--from` names the baseline
2693
- explicitly — a release's `.zip`, or the directory built from one — never
2694
- derived and never downloaded, for the same reason `deps fetch --from` is
2695
- explicit: a command that reaches the network on its own is not
2696
- reproducible. A finding is placed against the tree, at the note that made
2697
- the rename, not against the compiled output it was read from. Reads the
2698
- baseline artifact and the current content tree; writes nothing.
2691
+ publishes, against a released artifact. A departed address is identified as
2692
+ renamed when the same document id appears under another address; otherwise it
2693
+ is reported as withdrawn. An id derived from the canonical address changes
2694
+ with its shortcode. The old address is reported as withdrawn; the new address
2695
+ appears in the current package and produces no finding. Pair those addresses
2696
+ by hand. A pinned id lets the command identify the same document at its new
2697
+ address. `--from` names the baseline explicitly: a release's `.zip`, or the
2698
+ directory built from one. The command reads the baseline artifact and current
2699
+ content tree and writes diagnostics only.
2699
2700
 
2700
2701
  **OPTIONS**
2701
2702
 
@@ -2710,8 +2711,8 @@ baseline artifact and the current content tree; writes nothing.
2710
2711
 
2711
2712
  **EXIT STATUS**
2712
2713
 
2713
- 1 if `diff` is named with no `--from`. Without `--strict`, a renamed or
2714
- withdrawn address is reported as a warning and does not fail the run. With
2714
+ 1 if `diff` is named with no `--from`. Without `--strict`, a departed address
2715
+ is reported as a warning and does not fail the run. With
2715
2716
  `--strict`, 1 if any address is no longer published. 1 on any other thrown
2716
2717
  error (the baseline declaring an Item pack this build does not have, or
2717
2718
  this repository declaring no Item pack at all to diff). Otherwise 0.
@@ -58,6 +58,9 @@ it does not set a document's profile art. The
58
58
  [art-slot reference](../reference/format-details.md#the-four-art-slots) gives each
59
59
  slot's type and document destination.
60
60
 
61
+ A being's `subType` selects its compiled default art: `character` and `npc`
62
+ use person art, and `creature` uses creature art.
63
+
61
64
  ## Write an inline icon in prose
62
65
 
63
66
  Declare the glyph vocabulary in `package-build.config.yaml` or a separate YAML
@@ -101,6 +101,10 @@ column regardless.
101
101
  6. **Order is the toolchain's.** A medium renders boxes, sections and rows in
102
102
  the order given.
103
103
 
104
+ A note profile begins with `Name`. For a being with a top-level `subType`, it
105
+ follows with `Type: Character`, `Type: NPC`, or `Type: Creature`. A being with
106
+ no subtype has no `Type` row.
107
+
104
108
  **Embedded models link to documentation in an infobox.** The model Address
105
109
  continues to identify the native Item used for compilation. Its displayed link
106
110
  uses that same package, type and Shortcode under `note`, which names the
@@ -303,10 +307,16 @@ has three regions:
303
307
  | **Top level** | The note's identity, name, description, and publication | An error |
304
308
  | **`data:`** | The subject itself, shared by systems | An error |
305
309
  | **`hm3:` / `sohl:`** | The subject as one system's documents | An error |
310
+ | **`dnd5e:`** | A declared system block with no document field schema | Unchecked |
306
311
 
307
312
  The complete top-level allowlist, in its required order, is `shortcode`, `name`,
308
- `type`, `subType`, `description`, `tags`, `data`, `hm3`, `sohl`. Omit fields that
309
- do not apply. The first fields of a typical note look like this:
313
+ `type`, `subType`, `description`, `tags`, `data`, `hm3`, `sohl`, `dnd5e`. The
314
+ address vocabulary recognizes three systems: `sohl`, `hm3`, and `dnd5e`. Omit
315
+ fields that do not apply. The first fields of a typical note look like this:
316
+
317
+ The `dnd5e` system id is accepted in configuration and addresses. No
318
+ dnd5e-specific document types are declared, so the block has no field
319
+ vocabulary for lint or a compiler to check.
310
320
 
311
321
  ```yaml
312
322
  ---
@@ -410,7 +420,6 @@ is declared, so a near miss is a finding that names what you probably meant.
410
420
  | **place character** | `place` | `fortified`, `temple`, `market`, `trading`, `merchant`, `mining`, `fishing`, `naval`, `military`, `imperial`, `provincial`, `coastal`, `river`, `lakeside`, `hill`, `mountain`, `valley`, `forest`, `woodland`, `inland`, `island`, `frontier`, `border`, `craft`, `caravan`, `pilgrimage`, `holy`, `sacred`, `free`, `ford`, `portage`, `pass`, `well` |
411
421
  | **place scale** | `place` / `region` | `continent` |
412
422
  | **being station** | `being` | `tradesfolk`, `common-folk`, `soldiery`, `administration`, `clergy`, `mages`, `underworld`, `dependents`, `guilded`, `unguilded` |
413
- | **being kind** | `being` | `character`, `creature` |
414
423
  | **state** | any | `draft` |
415
424
  | **audience** | any | `gm` |
416
425
 
@@ -484,48 +493,25 @@ first because a person may be several at once and because nothing ranks
484
493
  **A continent is a region carrying a tag, not a subtype**, because structurally it
485
494
  is a region: the same fields, the same parent chain, everything but scale.
486
495
 
487
- #### A being's kind is the one tag group that is a slot
488
-
489
- A person and a beast carry very different amounts of data, and a being note says
490
- which it is with a tag rather than leaving it to be guessed from how much the
491
- note holds.
492
-
493
- | `beingKind` value | the subject is |
494
- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
495
- | `character` | a person — someone with a name, a station, a history, and the attributes, skills and carried gear that go with living a life |
496
- | `creature` | a beast, a monster or a made thing — statted for what it does rather than for who it is, and usually carrying no equipment and no affiliation |
497
-
498
- **The slot is filled once, or not at all.** The two are alternative answers to
499
- one question, so a note carrying both has answered it twice and every reader of
500
- the tag gets to pick; that is refused as an error naming the note. A being
501
- carrying **neither** is not a finding. The kind is authored deliberately, and
502
- nothing can tell a being nobody has classified from one whose author means to
503
- leave it unclassified — a tree part-way through tagging is a tree with untagged
504
- beings in it, and failing its build would be a rule about the schedule rather
505
- than about the content.
506
-
507
- **Nothing else fills the slot, and nothing else is refused for failing to.** A
508
- being tagged `undead` or `beast-of-burden` is describing the subject in the
509
- author's own words, in a region that is open, and this build has no standing to
510
- take that back. What is refused is a near miss of one of the two — `charcter`
511
- drops the note out of every query for characters while the list still renders —
512
- and the two of them together. Those are the only two refusals a closed tag
513
- vocabulary can make here, and both are errors, so a build carrying one fails.
514
-
515
- **The same two words name two other things, and neither is this.** `hm3.type`
516
- takes `character` or `creature` as the HM3 _document_ a being compiles to, in a
517
- system block rather than in `tags:`. The note itself uses `type: being`; `type: character` is refused.
518
- Three namespaces, one pair of words; the tag is the system-agnostic one, and it
519
- is the only one that describes the subject rather than a document.
520
-
521
- **Neither the book nor the website branches on the kind.** One flow serves both
522
- — the authored image, then the infobox, then the prose — and a creature's box is
523
- simply shorter, because it has less in it. A section with nothing to put in it is
524
- not emitted at all, so a creature with no equipment has no `EQUIPMENT` heading
525
- rather than an empty one, exactly as rule 4 of [the infobox](#the-infobox) drops
526
- a row with no value. The tag classifies the subject, which is what makes a list
527
- of the creatures in a setting a thing a query can ask for; it is not an
528
- instruction to a renderer.
496
+ #### A being's subtype describes the subject
497
+
498
+ `being` accepts the optional top-level values `npc`, `character`, and `creature`.
499
+ The subtype classifies the note's subject; it does not select a Foundry Actor
500
+ document type.
501
+
502
+ | `subType` | the subject is |
503
+ | ----------- | --------------------------------------------------------------------------------------------------------------- |
504
+ | `npc` | A character meant to be encountered. It may describe a reusable template and need not be a singular individual. |
505
+ | `character` | A detailed, playable, singular individual. |
506
+ | `creature` | Any being that is neither a character nor an NPC. |
507
+
508
+ `hm3.type` remains the HM3 document selector and must be `character` or
509
+ `creature` when an HM3 being block is present. SoHL continues to compile beings
510
+ as `Actor.being`. The note subtype and each system's document type answer
511
+ different questions.
512
+
513
+ Tags remain open descriptive terms. A being may carry tags such as `undead`,
514
+ `soldiery`, or `draft`; those tags do not replace the `subType` classification.
529
515
 
530
516
  The mapping tables below describe the **document** destinations. A key that
531
517
  appears in no table still reaches the web page; it simply reaches no Foundry
@@ -28,7 +28,7 @@ These fields are accepted by every note type. A field's value can still be irrel
28
28
 
29
29
  ### being
30
30
 
31
- **Subtypes:** Open vocabulary.
31
+ **Subtypes:** `npc`, `character`, `creature`.
32
32
 
33
33
  | Field | Shape | Meaning |
34
34
  | -------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -448,11 +448,10 @@ export class SystemActorCompiler extends BasePackCompiler {
448
448
  * The actor's two pieces of art, resolved, with the being default beneath
449
449
  * them.
450
450
  *
451
- * **The default is chosen from the note's tags**, which only a compiler can
452
- * read: a `character` falls back to one file and a `creature` to another,
453
- * and both are addresses in the package that ships them. A tree whose index
454
- * cannot answer that address — a dependency not yet fetched — falls through
455
- * to the subtype's own default, so the document is never left with no art.
451
+ * **The default follows the note's `subType`**. Both addresses belong to
452
+ * the package that ships them. A tree whose index cannot answer an
453
+ * address — a dependency not yet fetched — falls through to the document
454
+ * subtype's own default, so the document is never left with no art.
456
455
  *
457
456
  * `tokenIcon` unset follows `icon`, and the fallback is applied after
458
457
  * resolution rather than before: a note naming an icon and no token icon