@heroiclands/package-build 22.17.0 → 22.17.2

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 (37) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/bin/content-commands.mjs +114 -37
  3. package/config.mjs +54 -3
  4. package/docs/api.md +4 -14
  5. package/docs/commands.md +89 -52
  6. package/docs/configuration.md +49 -22
  7. package/docs/reference/format-details.md +27 -45
  8. package/docs/reference/note-types.md +1 -1
  9. package/engine/actor-compiler.mjs +5 -5
  10. package/engine/address-diff.mjs +32 -279
  11. package/engine/art-fields.mjs +8 -5
  12. package/engine/content-format.yaml +4 -4
  13. package/engine/content-index.mjs +28 -3
  14. package/engine/content-lint.mjs +3 -182
  15. package/engine/frontmatter-lint.mjs +112 -59
  16. package/engine/helpers.mjs +15 -3
  17. package/engine/index.mjs +0 -3
  18. package/engine/map-graphviz.mjs +4 -1
  19. package/engine/note-frontmatter.mjs +1 -0
  20. package/engine/note-vocabulary.mjs +1 -13
  21. package/engine/readability-lint.mjs +196 -18
  22. package/engine/subtype-registry.mjs +2 -6
  23. package/engine/systems.mjs +11 -13
  24. package/package.json +5 -1
  25. package/types/config.d.mts +14 -2
  26. package/types/engine/actor-compiler.d.mts +5 -5
  27. package/types/engine/address-diff.d.mts +18 -107
  28. package/types/engine/art-fields.d.mts +4 -5
  29. package/types/engine/frontmatter-lint.d.mts +14 -0
  30. package/types/engine/helpers.d.mts +12 -1
  31. package/types/engine/index.d.mts +0 -1
  32. package/types/engine/note-vocabulary.d.mts +0 -9
  33. package/types/engine/readability-lint.d.mts +11 -5
  34. package/types/engine/subtype-registry.d.mts +2 -6
  35. package/types/engine/systems.d.mts +6 -6
  36. package/engine/note-renames.mjs +0 -134
  37. package/types/engine/note-renames.d.mts +0 -102
package/docs/commands.md CHANGED
@@ -1712,82 +1712,118 @@ $ package-build docs item-fields --out docs/item-fields.md --title "Demo Item Fi
1712
1712
 
1713
1713
  [Configuration](configuration.md).
1714
1714
 
1715
- ### `package-build prose lint [path]`
1715
+ ### `package-build prose lint [path]` and `prose score [path]`
1716
1716
 
1717
1717
  **NAME**
1718
1718
 
1719
- Analyze note prose for readability and simpler wording.
1719
+ Review sentence readability or score each note against a package's prose bands.
1720
1720
 
1721
1721
  **SYNOPSIS**
1722
1722
 
1723
1723
  ```text
1724
- package-build prose lint [path] [--age <years>] [--threshold <count>] [--min-words <count>]
1724
+ package-build prose lint [path] [--age <years>] [--threshold <count>] [--min-words <count>] [--rules readability|simplify|all] [--fail-on-warning]
1725
+ package-build prose score [path] [--min-words <count>] [--fail-outside]
1725
1726
  ```
1726
1727
 
1727
1728
  **DESCRIPTION**
1728
1729
 
1729
- Run this command from the package root to review the readable prose in one
1730
- Markdown note or a content tree. Pass a note path while editing that note;
1731
- omit it to review the configured `paths.content` tree. A directory path scans
1732
- its Markdown files recursively and honors `skipDirectories`. A file path
1733
- analyzes that file alone, even if it sits in an excluded directory. The command
1734
- reads source files and writes nothing. It is an optional editorial check:
1735
- `package-build lint` and the normal lint scripts do not run it.
1730
+ Run the `lint` or `score` action from the package root. Pass a Markdown note while editing;
1731
+ omit the path to read the configured `paths.content` tree. A directory path
1732
+ scans Markdown files recursively and honors `skipDirectories`. A file path
1733
+ analyzes that file alone, even in an excluded directory. Both actions read
1734
+ source files and write nothing. They are optional editorial checks and are
1735
+ outside `package-build lint` and the normal build.
1736
1736
 
1737
1737
  Set defaults in `package-build.config.yaml` under `packageBuild.proseLint`:
1738
1738
 
1739
1739
  ```yaml
1740
1740
  packageBuild:
1741
- proseLint: { age: 21, threshold: 5, minWords: 8 }
1741
+ proseLint: { age: 21, threshold: 5, minWords: 8, rules: readability }
1742
+ proseScore:
1743
+ minWords: 80
1744
+ bands:
1745
+ flesch: { min: 45, max: 80 }
1746
+ syllablesPerWord: { max: 1.65 }
1742
1747
  ```
1743
1748
 
1744
- No configuration is required; the values above are the defaults. `age` is the
1749
+ No configuration is required. `proseLint` uses the defaults shown. The
1750
+ `proseScore` default is `minWords: 80` with no bands; the example bands express
1751
+ one package's editorial preference, not a toolchain standard. `age` is the
1745
1752
  intended reader's age in years. `threshold` is the number of seven readability
1746
1753
  algorithms that must classify a sentence as difficult before it is reported.
1747
1754
  For example, a threshold of 5 reports a sentence if five or more algorithms
1748
- flag it; a threshold of 7 reports only unanimous findings. `minWords` excludes
1749
- shorter sentences from readability analysis. Each setting is a positive integer,
1750
- and `threshold` must be at most 7. CLI options override individual settings:
1751
- `--min-words` overrides `minWords`, while `--age` and `--threshold` override
1752
- their matching keys.
1753
-
1754
- The command analyzes Markdown prose with `retext-english`,
1755
- `retext-readability`, and `retext-simplify`; `retext-stringify` completes the
1756
- text pipeline. YAML frontmatter, code blocks, wikilink addresses, and inline
1757
- expressions are excluded. Each suggestion is a warning with a file, line,
1755
+ flag it; a threshold of 7 reports only unanimous findings. Lint's `minWords`
1756
+ excludes shorter sentences. `rules: readability` reports difficult sentences;
1757
+ `simplify` reports wording suggestions; `all` reports both. The CLI options
1758
+ override individual settings. Lint numeric settings are positive integers;
1759
+ `threshold` is at most 7.
1760
+
1761
+ Lint analyzes Markdown paragraphs, including those in blockquotes, with `retext-english` and the selected
1762
+ readability or simplify rules; `retext-stringify` completes the text pipeline.
1763
+ Frontmatter, headings, lists, GFM tables, code blocks and inline code, image
1764
+ embeds, wikilinks, and inline expressions are excluded. The body of an ordinary
1765
+ directive such as `:::info` remains prose; SQL directive bodies are excluded.
1766
+ Each suggestion is a warning with a file, line,
1758
1767
  column, rule ID, confidence, offending sentence, and `expected` replacement
1759
1768
  list. The source location marks the difficult sentence or the specific phrase
1760
1769
  that could be simplified. Readability confidence is the number of algorithms
1761
1770
  that found the sentence difficult, out of seven. Simplify suggestions have no
1762
1771
  numeric confidence score, so they say `unscored`. `expected` lists suggested
1763
1772
  replacements; `[]` means the rule offers no replacement and may recommend
1764
- deleting a word or rewriting a sentence. Suggestions are advisory, are never
1765
- applied automatically, and do not make the command fail. Unreadable paths and
1766
- invalid options do.
1773
+ deleting a word or rewriting a sentence. Suggestions are advisory and are
1774
+ never applied automatically. `--fail-on-warning` makes a selected rule's
1775
+ suggestions an explicit exit-code gate.
1776
+
1777
+ Score uses the same paragraph selection. It reports Flesch reading ease,
1778
+ syllables per word, mean and longest sentence length, unfamiliar-word percentage
1779
+ against the Dale–Chall list, and nominalizations per thousand words. A
1780
+ nominalization is counted by its word ending (`-tion`, `-sion`, `-ment`,
1781
+ `-ness`, `-ity`, `-ance`, `-ence`, or `-ism`); this is a mechanical count, not a
1782
+ grammar judgment. Coverage is the percentage of readable body words included
1783
+ in the score: headings and lists count toward the body but are not scored.
1784
+ Review low-coverage notes in context. A note with fewer than score's `minWords`
1785
+ is reported as insufficient and is not compared with bands.
1786
+
1787
+ Under `packageBuild.proseScore.bands`, any of `flesch`, `syllablesPerWord`,
1788
+ `meanSentenceWords`, `longestSentenceWords`, `unfamiliarWordPercent`, and
1789
+ `nominalizationsPer1000Words` may set `min`, `max`, or both. Limits are finite
1790
+ numbers and a minimum cannot exceed a maximum. Violations are warnings. The
1791
+ directory summary pools sentence, word, and syllable counts across sufficiently
1792
+ long notes before computing Flesch; it is not an average of note scores.
1793
+ `--fail-outside` makes band violations an explicit gate. No band is enforced
1794
+ until the package declares it.
1767
1795
 
1768
1796
  **OPTIONS**
1769
1797
 
1770
- | Input | Default | Meaning |
1771
- | ------------- | -------------------------- | ---------------------------------------- |
1772
- | `path` | configured `paths.content` | Markdown file or content tree |
1773
- | `--age` | 21 | Reader age in years |
1774
- | `--threshold` | 5 | Number of readability algorithms (1–7) |
1775
- | `--min-words` | 8 | Minimum words for readability assessment |
1798
+ | Input | Default | Meaning |
1799
+ | ------------------- | -------------------------- | -------------------------------------------- |
1800
+ | `path` | configured `paths.content` | Markdown file or content tree |
1801
+ | `--age` | 21 | Lint reader age in years |
1802
+ | `--threshold` | 5 | Lint readability algorithms required (1–7) |
1803
+ | `--rules` | `readability` | Lint rules: `readability`, `simplify`, `all` |
1804
+ | `--min-words` | 8 lint; 80 score | Minimum words per sentence or note |
1805
+ | `--fail-on-warning` | false | Fail lint on selected rule findings |
1806
+ | `--fail-outside` | false | Fail score when a note crosses a band |
1776
1807
 
1777
1808
  **EXIT STATUS**
1778
1809
 
1779
- 0 after reporting suggestions; 1 for invalid options or unreadable paths.
1810
+ 0 after an advisory report; 1 for invalid options, unreadable paths, or an
1811
+ explicit failure condition.
1780
1812
 
1781
1813
  **EXAMPLES**
1782
1814
 
1783
1815
  ```bash
1784
1816
  package-build prose lint assets/content/Lore/Harbor.md
1817
+ package-build prose lint assets/content/Lore/Harbor.md --rules simplify
1818
+ package-build prose lint assets/content/Lore/Harbor.md --fail-on-warning
1785
1819
  package-build prose lint
1786
1820
  package-build prose lint assets/content --age 18 --threshold 6 --min-words 10
1821
+ package-build prose score assets/content/Lore/Harbor.md
1822
+ package-build prose score assets/content/Lore --fail-outside
1787
1823
  ```
1788
1824
 
1789
- For a note whose fifth line says `The utilization is very high.`, the command
1790
- can report:
1825
+ For a note whose fifth line says `The utilization is very high.`, lint with
1826
+ `--rules simplify` can report:
1791
1827
 
1792
1828
  ```text
1793
1829
  assets/content/Lore/Harbor.md:5:5: warning: retext-simplify/utilization: confidence=unscored; sentence="The utilization is very high."; expected=["use"]; Unexpected `utilization`, use `use` instead
@@ -1823,8 +1859,11 @@ checks frontmatter against the declared vocabulary and each system's `sohl:`
1823
1859
  it), that an icon a note writes is one the registry declares, and that
1824
1860
  markup inside a note's body does not smuggle in a character the charset
1825
1861
  check cannot see. `--no-references` turns off the check that a frontmatter
1826
- shortcode reference lands, for a tree whose cross-package references it
1827
- 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
1828
1867
  `paths.content`; writes nothing.
1829
1868
 
1830
1869
  **OPTIONS**
@@ -2037,7 +2076,9 @@ retired — every link is an address), a wikilink authored in frontmatter
2037
2076
  (which is data and is never resolved), and a package homepage's markdown
2038
2077
  links and `landing:` addresses, which are published verbatim and use no
2039
2078
  wikilink at all. Also reports a vendored foreign-package content index that
2040
- 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.
2041
2082
  Reads the content tree named by `root`, defaulting to `paths.content`, and
2042
2083
  the cached content indexes of any declared dependency; writes nothing.
2043
2084
 
@@ -2647,19 +2688,15 @@ package-build addresses diff --from <zip|dir> [--strict]
2647
2688
  **DESCRIPTION**
2648
2689
 
2649
2690
  Reports every published `(type, shortcode)` address this build no longer
2650
- publishes, against a released artifact — the signal that a shortcode
2651
- rename or a withdrawn note used to cost nothing and now does, emitted in
2652
- the repository doing the renaming while the change is still in front of
2653
- its author. Its own command rather than a step of `package compile`,
2654
- because it reads a _second_ artifact the compile knows nothing about and
2655
- asks a question about a release, not about a build — a repository between
2656
- releases has nothing to compare against. `--from` names the baseline
2657
- explicitly — a release's `.zip`, or the directory built from one — never
2658
- derived and never downloaded, for the same reason `deps fetch --from` is
2659
- explicit: a command that reaches the network on its own is not
2660
- reproducible. A finding is placed against the tree, at the note that made
2661
- the rename, not against the compiled output it was read from. Reads the
2662
- 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.
2663
2700
 
2664
2701
  **OPTIONS**
2665
2702
 
@@ -2674,8 +2711,8 @@ baseline artifact and the current content tree; writes nothing.
2674
2711
 
2675
2712
  **EXIT STATUS**
2676
2713
 
2677
- 1 if `diff` is named with no `--from`. Without `--strict`, a renamed or
2678
- 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
2679
2716
  `--strict`, 1 if any address is no longer published. 1 on any other thrown
2680
2717
  error (the baseline declaring an Item pack this build does not have, or
2681
2718
  this repository declaring no Item pack at all to diff). Otherwise 0.
@@ -1457,38 +1457,65 @@ packageBuild:
1457
1457
  envPrefix: SOHL
1458
1458
  ```
1459
1459
 
1460
- | Key | Type | Required | Default |
1461
- | ------------------------------------------------------------------------------------------ | ------------------------------------------- | -------- | -------------------------------------- |
1462
- | [`packageBuild.stageDir`](#packagebuildstagedir-and-packagebuildassets) | string | no | `build/stage` |
1463
- | [`packageBuild.assets`](#packagebuildstagedir-and-packagebuildassets) | array | no | `[]` |
1464
- | [`packageBuild.assetTransform`](#packagebuildassettransform-and-packagebuildmanifestflags) | string (path to a module) | no | `null` |
1465
- | [`packageBuild.manifest`](#packagebuildmanifest) | object, pass-through | no | `{}` |
1466
- | [`packageBuild.manifestFlags`](#packagebuildassettransform-and-packagebuildmanifestflags) | string (path to a module) | no | `null` |
1467
- | [`packageBuild.schema`](#packagebuildschema) | object (`{documentType: {from, registry}}`) | no | `[]` |
1468
- | [`packageBuild.clean`](#packagebuildclean) | object | no | `{extra: []}` |
1469
- | [`packageBuild.lang`](#packagebuildlang) | object | no | see below |
1470
- | [`packageBuild.deploy`](#packagebuilddeploy) | object | no | `{envPrefix: "SOHL"}` |
1471
- | [`packageBuild.release`](#packagebuildrelease) | object | no | `{artifact: <from packageKind>}` |
1472
- | [`packageBuild.bundle`](#packagebuildbundle) | object | no | `{entry: "<foundryPackage>.mjs"}` |
1473
- | [`packageBuild.container`](#packagebuildcontainer) | object | no | see below |
1474
- | [`packageBuild.e2e`](#packagebuilde2e) | object | no | see below |
1475
- | [`packageBuild.proseLint`](#packagebuildproselint) | object | no | `{age: 21, threshold: 5, minWords: 8}` |
1476
-
1477
- > ``package-build config: `packageBuild.<key>` is not a recognised key (expected one of: stageDir, assets, assetTransform, manifest, manifestFlags, schema, clean, lang, deploy, release, bundle, container, e2e, proseLint).``
1460
+ | Key | Type | Required | Default |
1461
+ | ------------------------------------------------------------------------------------------ | ------------------------------------------- | -------- | ---------------------------------------------------------- |
1462
+ | [`packageBuild.stageDir`](#packagebuildstagedir-and-packagebuildassets) | string | no | `build/stage` |
1463
+ | [`packageBuild.assets`](#packagebuildstagedir-and-packagebuildassets) | array | no | `[]` |
1464
+ | [`packageBuild.assetTransform`](#packagebuildassettransform-and-packagebuildmanifestflags) | string (path to a module) | no | `null` |
1465
+ | [`packageBuild.manifest`](#packagebuildmanifest) | object, pass-through | no | `{}` |
1466
+ | [`packageBuild.manifestFlags`](#packagebuildassettransform-and-packagebuildmanifestflags) | string (path to a module) | no | `null` |
1467
+ | [`packageBuild.schema`](#packagebuildschema) | object (`{documentType: {from, registry}}`) | no | `[]` |
1468
+ | [`packageBuild.clean`](#packagebuildclean) | object | no | `{extra: []}` |
1469
+ | [`packageBuild.lang`](#packagebuildlang) | object | no | see below |
1470
+ | [`packageBuild.deploy`](#packagebuilddeploy) | object | no | `{envPrefix: "SOHL"}` |
1471
+ | [`packageBuild.release`](#packagebuildrelease) | object | no | `{artifact: <from packageKind>}` |
1472
+ | [`packageBuild.bundle`](#packagebuildbundle) | object | no | `{entry: "<foundryPackage>.mjs"}` |
1473
+ | [`packageBuild.container`](#packagebuildcontainer) | object | no | see below |
1474
+ | [`packageBuild.e2e`](#packagebuilde2e) | object | no | see below |
1475
+ | [`packageBuild.proseLint`](#packagebuildproselint) | object | no | `{age: 21, threshold: 5, minWords: 8, rules: readability}` |
1476
+ | [`packageBuild.proseScore`](#packagebuildprosescore) | object | no | `{minWords: 80, bands: {}}` |
1477
+
1478
+ > ``package-build config: `packageBuild.<key>` is not a recognised key (expected one of: stageDir, assets, assetTransform, manifest, manifestFlags, schema, clean, lang, deploy, release, bundle, container, e2e, proseLint, proseScore).``
1478
1479
 
1479
1480
  ### `packageBuild.proseLint`
1480
1481
 
1481
- Optional defaults for [`package-build prose lint`](commands.md#package-build-prose-lint-path).
1482
+ Optional defaults for [`package-build prose lint`](commands.md#package-build-prose-lint-path-and-prose-score-path).
1482
1483
  `age` is the reader age, `threshold` is the number of readability algorithms
1483
1484
  required to flag a sentence (1–7), and `minWords` is the minimum sentence
1484
- length. Each setting is a positive integer; command-line options can override
1485
- them for one analysis.
1485
+ length. `rules` is `readability`, `simplify`, or `all`. Each numeric setting is
1486
+ a positive integer; command-line options can override them for one analysis.
1486
1487
 
1487
1488
  ```yaml
1488
1489
  packageBuild:
1489
- proseLint: { age: 21, threshold: 5, minWords: 8 }
1490
+ proseLint: { age: 21, threshold: 5, minWords: 8, rules: readability }
1490
1491
  ```
1491
1492
 
1493
+ ### `packageBuild.proseScore`
1494
+
1495
+ Optional defaults for [`package-build prose score`](commands.md#package-build-prose-lint-path-and-prose-score-path).
1496
+ `minWords` is the minimum number of analyzed words for a note to receive a
1497
+ score. `bands` states the package's acceptable range for any reported metric;
1498
+ there are no default bands. Each band has `min`, `max`, or both, as finite
1499
+ numbers. An omitted bound is open. The score report stays advisory unless
1500
+ `--fail-outside` is requested.
1501
+
1502
+ ```yaml
1503
+ packageBuild:
1504
+ proseScore:
1505
+ minWords: 80
1506
+ bands:
1507
+ flesch: { min: 45, max: 80 }
1508
+ syllablesPerWord: { max: 1.65 }
1509
+ meanSentenceWords: { max: 28 }
1510
+ longestSentenceWords: { max: 55 }
1511
+ unfamiliarWordPercent: { max: 35 }
1512
+ nominalizationsPer1000Words: { max: 30 }
1513
+ ```
1514
+
1515
+ Flesch is a reading-ease score, so a value below its minimum is harder prose
1516
+ and one above its maximum is simpler prose. These sample bounds are editorial
1517
+ choices for illustration. `--min-words` overrides `minWords` for one score run.
1518
+
1492
1519
  ### `packageBuild.stageDir` and `packageBuild.assets`
1493
1520
 
1494
1521
  `stageDir` is where the package is assembled before it is zipped or
@@ -303,10 +303,16 @@ has three regions:
303
303
  | **Top level** | The note's identity, name, description, and publication | An error |
304
304
  | **`data:`** | The subject itself, shared by systems | An error |
305
305
  | **`hm3:` / `sohl:`** | The subject as one system's documents | An error |
306
+ | **`dnd5e:`** | A declared system block with no document field schema | Unchecked |
306
307
 
307
308
  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:
309
+ `type`, `subType`, `description`, `tags`, `data`, `hm3`, `sohl`, `dnd5e`. The
310
+ address vocabulary recognizes three systems: `sohl`, `hm3`, and `dnd5e`. Omit
311
+ fields that do not apply. The first fields of a typical note look like this:
312
+
313
+ The `dnd5e` system id is accepted in configuration and addresses. No
314
+ dnd5e-specific document types are declared, so the block has no field
315
+ vocabulary for lint or a compiler to check.
310
316
 
311
317
  ```yaml
312
318
  ---
@@ -410,7 +416,6 @@ is declared, so a near miss is a finding that names what you probably meant.
410
416
  | **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
417
  | **place scale** | `place` / `region` | `continent` |
412
418
  | **being station** | `being` | `tradesfolk`, `common-folk`, `soldiery`, `administration`, `clergy`, `mages`, `underworld`, `dependents`, `guilded`, `unguilded` |
413
- | **being kind** | `being` | `character`, `creature` |
414
419
  | **state** | any | `draft` |
415
420
  | **audience** | any | `gm` |
416
421
 
@@ -484,48 +489,25 @@ first because a person may be several at once and because nothing ranks
484
489
  **A continent is a region carrying a tag, not a subtype**, because structurally it
485
490
  is a region: the same fields, the same parent chain, everything but scale.
486
491
 
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.
492
+ #### A being's subtype describes the subject
493
+
494
+ `being` accepts the optional top-level values `npc`, `character`, and `creature`.
495
+ The subtype classifies the note's subject; it does not select a Foundry Actor
496
+ document type.
497
+
498
+ | `subType` | the subject is |
499
+ | ----------- | --------------------------------------------------------------------------------------------------------------- |
500
+ | `npc` | A character meant to be encountered. It may describe a reusable template and need not be a singular individual. |
501
+ | `character` | A detailed, playable, singular individual. |
502
+ | `creature` | Any being that is neither a character nor an NPC. |
503
+
504
+ `hm3.type` remains the HM3 document selector and must be `character` or
505
+ `creature` when an HM3 being block is present. SoHL continues to compile beings
506
+ as `Actor.being`. The note subtype and each system's document type answer
507
+ different questions.
508
+
509
+ Tags remain open descriptive terms. A being may carry tags such as `undead`,
510
+ `soldiery`, or `draft`; those tags do not replace the `subType` classification.
529
511
 
530
512
  The mapping tables below describe the **document** destinations. A key that
531
513
  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,11 @@ 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`**, with tags as a compatibility
452
+ * fallback for content trees that have not migrated. Both addresses belong
453
+ * to the package that ships them. A tree whose index cannot answer an
454
+ * address — a dependency not yet fetched — falls through to the document
455
+ * subtype's own default, so the document is never left with no art.
456
456
  *
457
457
  * `tokenIcon` unset follows `icon`, and the fallback is applied after
458
458
  * resolution rather than before: a note naming an icon and no token icon