@heroiclands/package-build 17.2.0 → 18.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.
- package/CHANGELOG.md +1711 -0
- package/CONTENT.md +288 -72
- package/README.md +1 -1
- package/bin/content-build.mjs +218 -137
- package/bin/package-build.mjs +43 -0
- package/content-config.mjs +62 -67
- package/docs/content-format.md +768 -58
- package/engine/actor-compiler.mjs +586 -0
- package/engine/address-charset.mjs +11 -8
- package/engine/address-diff.mjs +266 -22
- package/engine/anchored-sections.mjs +83 -0
- package/engine/anchors.mjs +83 -0
- package/engine/base-compiler.mjs +148 -18
- package/engine/bundle-notes.mjs +276 -0
- package/engine/bundles.mjs +307 -0
- package/engine/code-fences.mjs +103 -0
- package/engine/compile-corpus.mjs +89 -0
- package/engine/content-address.mjs +267 -5
- package/engine/content-format-check.mjs +54 -11
- package/engine/content-format.mjs +37 -5
- package/engine/content-index.mjs +225 -127
- package/engine/content-links.mjs +185 -77
- package/engine/content-lint.mjs +232 -14
- package/engine/content-package.mjs +2 -1
- package/engine/content-tables.mjs +99 -3
- package/engine/document-subtypes.mjs +49 -6
- package/engine/field-reference.mjs +1 -1
- package/engine/field-spec.mjs +56 -6
- package/engine/folder-notes.mjs +470 -0
- package/engine/foreign-catalog.mjs +195 -4
- package/engine/{manifest-emit.mjs → foundry-entries.mjs} +55 -83
- package/engine/frontmatter-lint.mjs +245 -19
- package/engine/frontmatter.mjs +88 -8
- package/engine/generate.mjs +304 -13
- package/engine/helpers.mjs +293 -64
- package/engine/ids.mjs +137 -2
- package/engine/index-records.mjs +126 -0
- package/engine/index.mjs +23 -5
- package/engine/item-compiler.mjs +349 -0
- package/engine/item-docs.mjs +7 -1
- package/engine/item-registry.mjs +6 -0
- package/engine/journals.mjs +93 -27
- package/engine/macros.mjs +4 -2
- package/engine/metadata-index.mjs +495 -0
- package/engine/note-claims.mjs +146 -25
- package/engine/note-ids.mjs +112 -0
- package/engine/note-renames.mjs +134 -0
- package/engine/note-schemas.mjs +25 -0
- package/engine/note-vocabulary.mjs +110 -27
- package/engine/pack-config.mjs +4 -0
- package/engine/pack-router.mjs +23 -0
- package/engine/retired-fields.mjs +137 -3
- package/engine/scenes.mjs +66 -34
- package/engine/schema-check.mjs +58 -17
- package/engine/site-build.mjs +71 -12
- package/engine/site-index.mjs +20 -7
- package/engine/sql-tables.mjs +485 -0
- package/engine/subtype-registry.mjs +102 -0
- package/engine/system-block.mjs +96 -12
- package/engine/systems.mjs +201 -0
- package/engine/web-wikilinks.mjs +50 -20
- package/engine/wikilink-syntax.mjs +17 -8
- package/engine/wikilinks.mjs +124 -51
- package/engine/yaml-lint.mjs +282 -0
- package/hm3/actors.mjs +352 -0
- package/hm3/default-item-art.mjs +75 -0
- package/hm3/document-subtypes.mjs +134 -0
- package/hm3/index.mjs +56 -0
- package/hm3/item-builders.mjs +84 -0
- package/hm3/item-fields.mjs +180 -0
- package/hm3/items.mjs +96 -0
- package/hm3/template-priority.mjs +85 -0
- package/manifest.mjs +43 -2
- package/package.json +18 -4
- package/release.mjs +62 -7
- package/sohl/actors.mjs +33 -487
- package/sohl/being-info.mjs +16 -7
- package/sohl/default-item-art.mjs +14 -3
- package/sohl/document-subtypes.mjs +16 -10
- package/sohl/item-builders.mjs +14 -5
- package/sohl/item-fields.mjs +68 -7
- package/sohl/items.mjs +44 -258
- package/sohl/note-schemas.mjs +2 -2
- package/types/content-config.d.mts +21 -42
- package/types/engine/actor-compiler.d.mts +204 -0
- package/types/engine/address-charset.d.mts +11 -8
- package/types/engine/address-diff.d.mts +53 -5
- package/types/engine/anchored-sections.d.mts +21 -0
- package/types/engine/anchors.d.mts +20 -0
- package/types/engine/base-compiler.d.mts +17 -17
- package/types/engine/bundle-notes.d.mts +173 -0
- package/types/engine/bundles.d.mts +60 -0
- package/types/engine/code-fences.d.mts +43 -0
- package/types/engine/compile-corpus.d.mts +32 -0
- package/types/engine/content-address.d.mts +205 -5
- package/types/engine/content-format-check.d.mts +6 -2
- package/types/engine/content-format.d.mts +57 -1
- package/types/engine/content-index.d.mts +54 -49
- package/types/engine/content-links.d.mts +52 -7
- package/types/engine/content-lint.d.mts +10 -1
- package/types/engine/content-package.d.mts +2 -1
- package/types/engine/content-tables.d.mts +21 -39
- package/types/engine/document-subtypes.d.mts +37 -3
- package/types/engine/field-spec.d.mts +76 -5
- package/types/engine/folder-notes.d.mts +159 -0
- package/types/engine/foreign-catalog.d.mts +53 -0
- package/types/engine/{manifest-emit.d.mts → foundry-entries.d.mts} +2 -43
- package/types/engine/frontmatter-lint.d.mts +10 -2
- package/types/engine/frontmatter.d.mts +64 -0
- package/types/engine/generate.d.mts +38 -0
- package/types/engine/helpers.d.mts +94 -30
- package/types/engine/ids.d.mts +96 -0
- package/types/engine/index-records.d.mts +68 -0
- package/types/engine/index.d.mts +9 -3
- package/types/engine/item-compiler.d.mts +131 -0
- package/types/engine/journals.d.mts +47 -9
- package/types/engine/metadata-index.d.mts +226 -0
- package/types/engine/note-claims.d.mts +57 -11
- package/types/engine/note-ids.d.mts +38 -0
- package/types/engine/note-renames.d.mts +102 -0
- package/types/engine/note-vocabulary.d.mts +44 -8
- package/types/engine/retired-fields.d.mts +75 -0
- package/types/engine/scenes.d.mts +3 -2
- package/types/engine/schema-check.d.mts +25 -4
- package/types/engine/site-build.d.mts +4 -4
- package/types/engine/site-index.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +185 -0
- package/types/engine/subtype-registry.d.mts +49 -0
- package/types/engine/system-block.d.mts +40 -1
- package/types/engine/systems.d.mts +106 -0
- package/types/engine/web-wikilinks.d.mts +4 -2
- package/types/engine/wikilink-syntax.d.mts +10 -3
- package/types/engine/wikilinks.d.mts +41 -13
- package/types/engine/yaml-lint.d.mts +107 -0
- package/types/hm3/actors.d.mts +48 -0
- package/types/hm3/default-item-art.d.mts +42 -0
- package/types/hm3/document-subtypes.d.mts +24 -0
- package/types/hm3/index.d.mts +7 -0
- package/types/hm3/item-builders.d.mts +11 -0
- package/types/hm3/item-fields.d.mts +12 -0
- package/types/hm3/items.d.mts +23 -0
- package/types/hm3/template-priority.d.mts +21 -0
- package/types/manifest.d.mts +22 -1
- package/types/release.d.mts +9 -4
- package/types/sohl/actors.d.mts +7 -74
- package/types/sohl/being-info.d.mts +8 -27
- package/types/sohl/default-item-art.d.mts +5 -3
- package/types/sohl/items.d.mts +17 -32
- package/engine/foreign-manifests.mjs +0 -126
- package/engine/kb-manifest.mjs +0 -490
- package/types/engine/foreign-manifests.d.mts +0 -43
- package/types/engine/kb-manifest.d.mts +0 -241
package/docs/content-format.md
CHANGED
|
@@ -138,6 +138,18 @@ it _there_, and a system that disagrees is not in error. A weapon weighs what
|
|
|
138
138
|
This is the same rule as `hm3.type` overriding a derived document type, applied
|
|
139
139
|
to fields: derive from the shared source, and let the system state the exception.
|
|
140
140
|
|
|
141
|
+
**A shared source and the key a system block still carries are two
|
|
142
|
+
declarations.** The mapping tables name the shared source — `data.species` — and
|
|
143
|
+
the corpus writes the same fact inside the block it has always written it in —
|
|
144
|
+
`hm3.species`. Those are two positions for one field, and both are read while
|
|
145
|
+
the corpus moves, with the block winning. A field says so by naming each: its
|
|
146
|
+
shared source, and the legacy in-block key it is being swept off. Reading it
|
|
147
|
+
from the legacy position is _reported_, so the sweep has a progress signal, and
|
|
148
|
+
the note compiles to the identical document either way — the same read-both,
|
|
149
|
+
report-one shape every other retirement in this format uses. Until #305 the two
|
|
150
|
+
were one declaration, so a field could name only one of them, and a row this
|
|
151
|
+
table stated was reachable only by a note that had already moved.
|
|
152
|
+
|
|
141
153
|
**A field whose spelling means something else at the note level has no shared
|
|
142
154
|
source.** The fallback assumes the two vocabularies agree about what a name
|
|
143
155
|
means, and they do not always: a note's top-level `title` is the heading its page
|
|
@@ -166,7 +178,7 @@ at all in any particular system.
|
|
|
166
178
|
|
|
167
179
|
Each system then declares a map from the note's `(type, subType)` onto its own
|
|
168
180
|
document type — and, for SoHL, its own `system.subType`. The map is **declared**,
|
|
169
|
-
never inferred from a coincidence of names: `skill`, `weapongear`, `
|
|
181
|
+
never inferred from a coincidence of names: `skill`, `weapongear`, `armor`,
|
|
170
182
|
`containergear` and `miscgear` exist in both systems with _different_ data
|
|
171
183
|
models, so name-matching there would not fail, it would succeed wrongly.
|
|
172
184
|
|
|
@@ -174,6 +186,22 @@ Because the map derives the document type, `hm3.type` and `sohl.system.subType`
|
|
|
174
186
|
are **overrides**, not required declarations. A note states them only when the
|
|
175
187
|
map cannot decide, or decides wrongly.
|
|
176
188
|
|
|
189
|
+
**Four rows cannot decide, and there `hm3.type` is required.** HM3 splits four
|
|
190
|
+
of the note vocabulary's types across several documents — `mysticalability` into
|
|
191
|
+
a `psionic`, a `spell` or an `invocation`; `trauma` into an `injury` or a
|
|
192
|
+
`trait`; `weapongear` into a `weapongear` or a `missilegear`; `being` into a
|
|
193
|
+
`character` or a `creature`. Nothing in the note's own vocabulary partitions
|
|
194
|
+
cleanly onto any of those splits, so the note says which, in its own block, and
|
|
195
|
+
a note that says nothing is **an error naming the note and listing the permitted
|
|
196
|
+
values**. It is never defaulted: a default would pick one and be right about
|
|
197
|
+
half the time.
|
|
198
|
+
|
|
199
|
+
A consequence worth knowing before you author a being's embedded items: a
|
|
200
|
+
`(type, shortcode)` reference has no block of its own to read a discriminator
|
|
201
|
+
from, so it **cannot address a one-to-many type**. `[[weapongear-spear]]` names
|
|
202
|
+
no single HM3 document, and the reference is refused rather than resolved to
|
|
203
|
+
whichever came first.
|
|
204
|
+
|
|
177
205
|
A note whose `(type, subType)` has no mapping for a system it carries a block
|
|
178
206
|
for is an **error naming the note**, never a silent skip and never a guess at the
|
|
179
207
|
first matching value. Where a mapping is missing for a whole class of note, that
|
|
@@ -186,8 +214,16 @@ Eight rows were identical in all sixteen tables below, so they are stated once
|
|
|
186
214
|
here and omitted there. Each per-type table shows only what is particular to that
|
|
187
215
|
type.
|
|
188
216
|
|
|
189
|
-
| shared source
|
|
190
|
-
|
|
|
217
|
+
| shared source | → sohl | → hm3 |
|
|
218
|
+
| ----------------------- | ------------------------- | ---------------------------- |
|
|
219
|
+
| `name.full` | `name` | `name` |
|
|
220
|
+
| `img` | `img` | `img` |
|
|
221
|
+
| `id` | `_id` | `_id` |
|
|
222
|
+
| `packFolder` / `folder` | `folder` | `folder` |
|
|
223
|
+
| `shortcode` | `system.shortcode` | NA |
|
|
224
|
+
| `data.templatePriority` | `system.templatePriority` | `flags.hm3.templatePriority` |
|
|
225
|
+
| `actionDefs` | `system.actionDefs` | NA |
|
|
226
|
+
| `notes` | `system.notes` | `system.notes` |
|
|
191
227
|
|
|
192
228
|
**A column reads NA wherever the type produces no document in that system.** An
|
|
193
229
|
`affiliation` has no HM3 form, so its whole HM3 column is NA; `armorlocation` has
|
|
@@ -197,13 +233,377 @@ that matter.
|
|
|
197
233
|
|
|
198
234
|
Actor types (`being`, `vehicle`) add one more:
|
|
199
235
|
|
|
200
|
-
| shared source
|
|
201
|
-
|
|
|
236
|
+
| shared source | → sohl | → hm3 |
|
|
237
|
+
| --------------- | ----------------- | ----------------- |
|
|
238
|
+
| `data.portrait` | `system.portrait` | `system.bioImage` |
|
|
239
|
+
|
|
240
|
+
An actor carries `img` (its token art) and `portrait` (its sheet portrait)
|
|
241
|
+
independently, which is why this is a row of its own rather than a second
|
|
242
|
+
spelling of the one above. An Item has no second image, so the row applies to
|
|
243
|
+
actor types alone.
|
|
244
|
+
|
|
245
|
+
**Two of the eight are Item-only in SoHL.** `actionDefs` and `notes` are declared
|
|
246
|
+
on every SoHL Item subtype and on no SoHL Actor, so on a `being` or a `vehicle`
|
|
247
|
+
the SoHL column of both reads NA.
|
|
248
|
+
|
|
249
|
+
`notes` is also the one row that is emitted rather than mapped: SoHL writes
|
|
250
|
+
`system.notes` empty on every Item, and no note-level key fills it yet. The row
|
|
251
|
+
states where such a key would land, which is what makes `armorlocation`'s
|
|
252
|
+
exception below sayable at all.
|
|
253
|
+
|
|
254
|
+
**A third SoHL Item mapping has no shared source, so it is not a row.** SoHL
|
|
255
|
+
writes `system.docHtml` on every Item from the note's own prose — the UUID of the
|
|
256
|
+
JournalEntry that prose compiled into, which is derived rather than authored.
|
|
257
|
+
HM3's data model has nowhere to put such a pointer, so the same prose reaches an
|
|
258
|
+
HM3 item only as its own journal.
|
|
202
259
|
|
|
203
260
|
**One exception.** HM3's `armorlocation` declares no `notes` — it is the one
|
|
204
261
|
subtype that extends the Foundry base directly with no templates — so `notes` is
|
|
205
262
|
NA on both sides for that type, and its table says so.
|
|
206
263
|
|
|
264
|
+
**And one divergence, tracked rather than specified away.** HM3 records a
|
|
265
|
+
template priority on an Actor and not on an Item: `hm3/actors.mjs` writes
|
|
266
|
+
`flags.hm3.templatePriority`, and HM3's Item pass writes no equivalent, so an HM3
|
|
267
|
+
item compiled from a template note loses the fact that it is one
|
|
268
|
+
(`HeroicLands/package-build#283`). The row states the mapping the format makes;
|
|
269
|
+
the gap is in the pass, not in the table.
|
|
270
|
+
|
|
271
|
+
#### The pack a note compiles into
|
|
272
|
+
|
|
273
|
+
`pack` names which configured compendium receives the note's document.
|
|
274
|
+
|
|
275
|
+
```yaml
|
|
276
|
+
pack: items-hm3
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
It is deliberately close to the retired `package:` and deliberately not the same
|
|
280
|
+
word: `package:` said which _distribution_ owned a note — now the repository's
|
|
281
|
+
`contentPackage`, and no longer authorable — while `pack:` says which
|
|
282
|
+
_compendium_ receives its document.
|
|
283
|
+
|
|
284
|
+
**It names the pack for the note's _own_ document.** A document derived from it
|
|
285
|
+
— an item's prose compiling into a `JournalEntry` of its own — is not what the
|
|
286
|
+
author was addressing, and is routed by the pass that produces it.
|
|
287
|
+
|
|
288
|
+
**`<system>.pack` overrides it for one system.** A note that compiles into two
|
|
289
|
+
systems can send each document to its own pack:
|
|
290
|
+
|
|
291
|
+
```yaml
|
|
292
|
+
pack: items-sohl
|
|
293
|
+
hm3:
|
|
294
|
+
pack: items-hm3
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Unstated, the document goes to the pack of its type marked `default: true`.**
|
|
298
|
+
Where no pack of that type is the default, the build refuses rather than
|
|
299
|
+
guessing, and names the candidates.
|
|
300
|
+
|
|
301
|
+
Three declarations are refused, each with the reason:
|
|
302
|
+
|
|
303
|
+
| written | why it is refused |
|
|
304
|
+
| ----------------------------------- | --------------------------------------------------------------------------------- |
|
|
305
|
+
| a **companion** pack | A companion is written by another pack's pass, so no note may be routed into one. |
|
|
306
|
+
| a pack **nothing answers to** | The message lists the configured packs of that document type. |
|
|
307
|
+
| a pack of **another document type** | A note's `pack:` names a pack of its own document type. |
|
|
308
|
+
|
|
309
|
+
#### Template priority: which template wins
|
|
310
|
+
|
|
311
|
+
A note can mark its document as a **starting template** the Create dialog offers
|
|
312
|
+
to clone from, so a new being or item is born populated rather than blank. The
|
|
313
|
+
value is a **priority**, and the priority is the whole mechanism — it decides
|
|
314
|
+
which of several competing templates a player is actually offered.
|
|
315
|
+
|
|
316
|
+
The shared mapping table above names it `data.templatePriority`, targeting
|
|
317
|
+
`system.templatePriority` in SoHL and `flags.hm3.templatePriority` in HM3.
|
|
318
|
+
|
|
319
|
+
> **`archetype` is the retiring spelling.** It is still read, in the `sohl:`
|
|
320
|
+
> block and at the top level, so a tree sweeps on its own schedule
|
|
321
|
+
> (`HeroicLands/package-build#266`) — but the frontmatter linter refuses it, and
|
|
322
|
+
> what is compiled and emitted is `templatePriority` on all three sides.
|
|
323
|
+
>
|
|
324
|
+
> It is more than a rename, and the collision is **already live** rather than
|
|
325
|
+
> pending: `archetypes` is specified above as the _sort_ a character is, and a
|
|
326
|
+
> being's row declares it. So a number deciding which template wins and a list of
|
|
327
|
+
> what sort of character this is would otherwise be distinguished **only by a
|
|
328
|
+
> plural `s`**.
|
|
329
|
+
>
|
|
330
|
+
> A note carrying both spellings with **different** values is refused rather than
|
|
331
|
+
> resolved quietly — `templatePriority: null` and `archetype: 0` say opposite
|
|
332
|
+
> things, and picking either silently would decide it on the author's behalf.
|
|
333
|
+
|
|
334
|
+
```yaml
|
|
335
|
+
data:
|
|
336
|
+
templatePriority: 0 # a template, at the priority SoHL's own ship at
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
```yaml
|
|
340
|
+
data:
|
|
341
|
+
templatePriority: null # not a template
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Every note SoHL compiles into an Item or an Actor must state it.** Absent, the
|
|
345
|
+
build refuses: "not a template" has to be _said_, not left out, or an omission
|
|
346
|
+
and a decision look identical. The value is a number or `null`, and **`0` is a
|
|
347
|
+
real priority** — the one SoHL's own templates ship at — not an absence.
|
|
348
|
+
|
|
349
|
+
**Where it lands differs by system, because HM3's data model has no field for
|
|
350
|
+
it.** SoHL records it in `system`; HM3 keeps it under its own flag scope,
|
|
351
|
+
`flags.hm3`, and a note that is not a template writes nothing there rather than a
|
|
352
|
+
`null` nothing reads. Both of HM3's passes write it — an Item's flag was missing
|
|
353
|
+
until `HeroicLands/package-build#283`, which made an item note's priority reach
|
|
354
|
+
SoHL and stop at HM3, with nothing said on either side.
|
|
355
|
+
|
|
356
|
+
**How a winner is chosen.** Opening a Create dialog gathers every candidate
|
|
357
|
+
across the world and every matching compendium, _including other modules'_. Those
|
|
358
|
+
are filtered to the `(type, subType)` being created, deduped by **`shortcode`** —
|
|
359
|
+
a template's stable identity, where the name is only presentation — and one
|
|
360
|
+
winner is taken per shortcode:
|
|
361
|
+
|
|
362
|
+
1. the highest priority;
|
|
363
|
+
2. then the nearest source — **world**, then **system**, then **module** — so a
|
|
364
|
+
GM's own copy shadows a shipped one at equal priority;
|
|
365
|
+
3. then a stable UUID, so the answer never depends on load order.
|
|
366
|
+
|
|
367
|
+
**The reserved ranges make a collision predictable.** Two packages can easily
|
|
368
|
+
ship a template under one shortcode, and the number says which yields:
|
|
369
|
+
|
|
370
|
+
| priority | reserved for |
|
|
371
|
+
| ---------- | -------------------------- |
|
|
372
|
+
| `0`–`98` | SoHL and HM3 themselves |
|
|
373
|
+
| `99`–`999` | other HeroicLands packages |
|
|
374
|
+
| `1000`+ | everyone else |
|
|
375
|
+
|
|
376
|
+
HeroicLands reserves everything below `1000`. Since the highest priority wins,
|
|
377
|
+
**anyone else's template always beats content shipped from here** — which is the
|
|
378
|
+
point: a module author can override a standard template without coordinating with
|
|
379
|
+
anybody, and be certain it takes effect.
|
|
380
|
+
|
|
381
|
+
#### The document id
|
|
382
|
+
|
|
383
|
+
A note's Foundry `_id` is **derived from its canonical address**, and no note
|
|
384
|
+
needs to write one:
|
|
385
|
+
|
|
386
|
+
```yaml
|
|
387
|
+
type: miscgear
|
|
388
|
+
shortcode: bowlcer
|
|
389
|
+
# no `id:` — the document is filed under makeId("document", "sohl-none-miscgear-bowlcer")
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
The derivation is exactly:
|
|
393
|
+
|
|
394
|
+
```
|
|
395
|
+
_id = makeId("document", "<package>-<system>-<type>-<shortcode>")
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
— the note's canonical address, hashed with SHA-1 and truncated to the 16 hex
|
|
399
|
+
characters a Foundry id is. Nothing else feeds it. A consumer holding a
|
|
400
|
+
content-index entry can therefore recompute a document's id, and so its
|
|
401
|
+
compendium UUID, from the `canonical` key alone; it is not a value the index has
|
|
402
|
+
to transport.
|
|
403
|
+
|
|
404
|
+
**Why the address and not an authored string.** A note used to declare an
|
|
405
|
+
opaque 16-character `id` — 6,343 of them across the four content trees — which
|
|
406
|
+
said nothing its address did not, could not be read or reviewed, and was
|
|
407
|
+
guaranteed by nothing: `content-lint` refuses a **duplicate address** across
|
|
408
|
+
every pack of a document type, which is exactly the scope a primary document's
|
|
409
|
+
id must be unique within, and it said nothing at all about a duplicate `id`. So
|
|
410
|
+
the derived id inherits a guard that already exists, where the authored one had
|
|
411
|
+
none. It is the same principle that turned `folder: ONXsqZAIZr2qzxTb` into
|
|
412
|
+
`packFolder: <path>` above: an opaque derived identity does not belong in
|
|
413
|
+
authored content.
|
|
414
|
+
|
|
415
|
+
**An authored `id` still wins**, and that is how a document's identity is
|
|
416
|
+
**pinned**:
|
|
417
|
+
|
|
418
|
+
```yaml
|
|
419
|
+
type: miscgear
|
|
420
|
+
shortcode: bowlcer
|
|
421
|
+
id: plaiQQm2T5zVK5mO # pinned: this document keeps this id
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
A blank `id:` is not a pin — it is a deleted value with the key left behind, and
|
|
425
|
+
is treated as absent.
|
|
426
|
+
|
|
427
|
+
**A rename moves the id, and that is the trade.** The address carries the
|
|
428
|
+
shortcode, so renaming a shortcode gives the document a new `_id`. Two things
|
|
429
|
+
make that acceptable: a shortcode rename already breaks every wikilink to the
|
|
430
|
+
note, so it is a breaking change either way; and a note that must keep its
|
|
431
|
+
identity across one pins its `id`, which is what the pin is for. One diagnostic
|
|
432
|
+
narrows — `content-build` tells a **rename** from a **withdrawal** by matching
|
|
433
|
+
document ids across releases, and for an unpinned note both sides now move
|
|
434
|
+
together. It never reports a _wrong_ successor, and it stays exact for a pinned
|
|
435
|
+
note; for the rest, the note **declares** the rename (below).
|
|
436
|
+
|
|
437
|
+
**A note with no address gets no id, and no document.** `type` and `shortcode`
|
|
438
|
+
are what a note is addressed by, so a note missing either cannot be filed and
|
|
439
|
+
the build refuses it by that name rather than by a missing `id:`.
|
|
440
|
+
|
|
441
|
+
#### Declaring a rename
|
|
442
|
+
|
|
443
|
+
A note names the shortcode it used to be published under:
|
|
444
|
+
|
|
445
|
+
```yaml
|
|
446
|
+
type: weapongear
|
|
447
|
+
shortcode: Taburi
|
|
448
|
+
renamedFrom: Tabri
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
`(type, shortcode)` is a **published interface** — every satellite that declares
|
|
452
|
+
`itemCatalog: true` assembles its beings out of those addresses — so renaming a
|
|
453
|
+
shortcode breaks other repositories, and `content-build addresses diff` exists to
|
|
454
|
+
say so before a release does. To be useful it has to name where the address
|
|
455
|
+
_went_, and since ids are derived it can no longer work that out for an unpinned
|
|
456
|
+
note: both sides of the match move together, and the rename reads as a
|
|
457
|
+
withdrawal.
|
|
458
|
+
|
|
459
|
+
**Pinning an `id` needs foresight; a declaration needs only hindsight.** A pin
|
|
460
|
+
has to be written _before_ the rename, by an author who does not yet know they
|
|
461
|
+
will make one. An author who has just renamed a shortcode knows exactly what the
|
|
462
|
+
old one was, and that is the only moment anyone does — so this is the key to
|
|
463
|
+
reach for, and `id:` stays what it is for: keeping a document's identity across
|
|
464
|
+
the rename, which is a different question from explaining it.
|
|
465
|
+
|
|
466
|
+
**It takes one shortcode or a list**, because renames chain: the diff runs
|
|
467
|
+
against a released baseline, and a shortcode may have been renamed more than once
|
|
468
|
+
since. List every name the baseline might still know it by.
|
|
469
|
+
|
|
470
|
+
```yaml
|
|
471
|
+
shortcode: Taburin
|
|
472
|
+
renamedFrom:
|
|
473
|
+
- Tabri
|
|
474
|
+
- Taburi
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
**It is transient.** Once every baseline a build is compared against post-dates
|
|
478
|
+
the rename, the declaration has nothing left to say and should be deleted. That
|
|
479
|
+
is what separates it from an `id:` pin, which is permanent.
|
|
480
|
+
|
|
481
|
+
**One key per note, at the top level**, however many systems the note compiles
|
|
482
|
+
into: a shortcode is the note's rather than a system block's, so a note carrying
|
|
483
|
+
`sohl:` and `hm3:` blocks compiles two documents that share one shortcode, and
|
|
484
|
+
one declaration covers both.
|
|
485
|
+
|
|
486
|
+
**What a declaration changes is what the diagnostic can say, not what it says
|
|
487
|
+
about you.** A finding reports which of the two joins it had, because they are
|
|
488
|
+
not equally checkable — a matched id is a fact a reader can verify in both
|
|
489
|
+
artefacts, while a declaration is the author's word:
|
|
490
|
+
|
|
491
|
+
```text
|
|
492
|
+
since sohl@0.8.2, weapongear:Tabri is no longer published; the note now
|
|
493
|
+
published as weapongear:Taburi declares it was renamed from Tabri. Every
|
|
494
|
+
package that resolves weapongear:Tabri breaks when it moves past sohl@0.8.2
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
A rename that is neither pinned nor declared is still reported as a
|
|
498
|
+
**withdrawal**. Nothing infers a successor from a similar-looking string: a wrong
|
|
499
|
+
one sends the reader to the wrong fix, which is worse than saying nothing.
|
|
500
|
+
|
|
501
|
+
`content-lint` holds a declaration to the same rules a current address is held
|
|
502
|
+
to. An entry must be a well-formed shortcode, must not be the note's own, and
|
|
503
|
+
must name an address the package actually **vacated** — an entry naming an
|
|
504
|
+
address some note still publishes is refused, as are two notes claiming one
|
|
505
|
+
predecessor, since an address had one holder and so has one successor. A repeat
|
|
506
|
+
of the same entry is a warning; the declaration still works.
|
|
507
|
+
|
|
508
|
+
#### The compendium folder
|
|
509
|
+
|
|
510
|
+
A note says which folder of its pack it lands in. Two spellings are read, and
|
|
511
|
+
`packFolder` wins where both are present:
|
|
512
|
+
|
|
513
|
+
```yaml
|
|
514
|
+
packFolder: poisonsandtoxins # a folder note's address
|
|
515
|
+
folder: ONXsqZAIZr2qzxTb # a Foundry id
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**`packFolder` is a folder note's address** — an ordinary address, resolved the
|
|
519
|
+
way every other reference is, and written in any form [the grammar
|
|
520
|
+
admits](#shorter-forms). The field supplies the type, so a bare shortcode is a
|
|
521
|
+
complete address here; `folder-poisonsandtoxins` and the fully qualified
|
|
522
|
+
`sohl-none-folder-poisonsandtoxins` name the same folder. An address no folder
|
|
523
|
+
note answers to is a build error naming the folders the package does declare.
|
|
524
|
+
|
|
525
|
+
**`folder` is a Foundry id**, and is unchanged: a note that names one is read,
|
|
526
|
+
resolved and emitted exactly as before.
|
|
527
|
+
|
|
528
|
+
**Which one a value is comes from the field it was written in, never from the
|
|
529
|
+
string.** Both are alphanumeric, so there is nothing in the value to tell them
|
|
530
|
+
apart.
|
|
531
|
+
|
|
532
|
+
Note this is the _pack_ folder, not the note's directory. The directory is
|
|
533
|
+
`file.path` / `file.folder`, which a content table reads separately.
|
|
534
|
+
|
|
535
|
+
**Where a folder materialises is derived from what references it.** Every pack
|
|
536
|
+
holding a document that names a folder gets that folder, and its ancestors with
|
|
537
|
+
it — so a documentation journal is filed beside the item it describes without
|
|
538
|
+
the journals pack having to declare anything. A folder nothing references
|
|
539
|
+
materialises nowhere.
|
|
540
|
+
|
|
541
|
+
That derivation is what makes a whole class of defect unrepresentable. The
|
|
542
|
+
folder used to be declared twice, once per pack, in two files free to disagree:
|
|
543
|
+
`sohl-thalorna` was missing 57 of its item folders from its journal folder file
|
|
544
|
+
and `sohl-kethira-basic` had no journal folder file at all, so both emitted
|
|
545
|
+
documentation journals into folders their own pack never declared — silently.
|
|
546
|
+
With one folder note and one address there is no second file to disagree with
|
|
547
|
+
the first.
|
|
548
|
+
|
|
549
|
+
> **`packFolder` was a path** for one release (`Possessions/Misc_Gear/Cooking`).
|
|
550
|
+
> A path encoded the hierarchy in the value, so reparenting a folder made every
|
|
551
|
+
> note naming it wrong — a structural edit became a corpus-wide rewrite. The
|
|
552
|
+
> path form is **removed**, not deprecated: nothing authored it yet, which is
|
|
553
|
+
> the whole reason the change was cheap enough to make.
|
|
554
|
+
|
|
555
|
+
#### The knowledgebase category
|
|
556
|
+
|
|
557
|
+
`kbcat` names the group a note is listed under on the knowledgebase and the
|
|
558
|
+
website. It is written in the system block:
|
|
559
|
+
|
|
560
|
+
```yaml
|
|
561
|
+
sohl:
|
|
562
|
+
kbcat: poisontoxin
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**It compiles into no document.** No pack compiler reads it and no `system`
|
|
566
|
+
field receives it. It reaches a published page because a note's frontmatter is
|
|
567
|
+
copied onto that page, where a list layout groups by `sohl.kbcat` — so `kbcat`
|
|
568
|
+
is the one key in this section that answers _where does this appear_ for the
|
|
569
|
+
web rather than for Foundry. `pack` and `packFolder` place a document in a
|
|
570
|
+
compendium; `kbcat` places a page in a list.
|
|
571
|
+
|
|
572
|
+
That is also why it is specified here rather than in a type's table. A type's
|
|
573
|
+
fields say what the **builder** compiles, and `kbcat` is never compiled — but
|
|
574
|
+
what a note **may write** is broader than what any one consumer reads, and a
|
|
575
|
+
check that equated the two reported thousands of correctly authored properties
|
|
576
|
+
as unknown.
|
|
577
|
+
|
|
578
|
+
**It is editorial, and deliberately independent of `subType`.** The two are not
|
|
579
|
+
alternative spellings of one classification and neither is derived from the
|
|
580
|
+
other. `kbcat` both _subdivides_ a subtype — `trauma`/`physcond` is listed as
|
|
581
|
+
`physdisability`, `physfeature` or `physprivations` — and _renames_ one for
|
|
582
|
+
display, as `trauma`/`fear` listed under `phobias`. Most notes that carry a
|
|
583
|
+
`kbcat` declare no `subType` at all. So the two are stated separately where both
|
|
584
|
+
apply, and a reviewer should not read a disagreement between them as an error.
|
|
585
|
+
|
|
586
|
+
**The value is free-form, and nothing validates it.** There is no configured
|
|
587
|
+
list of categories. The frontmatter check knows `kbcat` is a key every type may
|
|
588
|
+
write and says nothing whatever about its value. A layout supplies display
|
|
589
|
+
titles and an explicit order for the values it knows about, and appends any
|
|
590
|
+
other value as its own group, titled by humanizing it.
|
|
591
|
+
|
|
592
|
+
The consequence is worth stating plainly, because it is the failure mode this
|
|
593
|
+
key has: **a misspelled category is not a build error and is not dropped — it
|
|
594
|
+
silently becomes a group of one**, sorted in after the known ones.
|
|
595
|
+
|
|
596
|
+
**A note that writes none is dropped from the list entirely.** Grouping is by
|
|
597
|
+
the key, so a page carrying no value falls in no group and is absent from the
|
|
598
|
+
list page — not listed last, not listed under a fallback heading, absent, with
|
|
599
|
+
nothing reported at either build. Every note of a listed type in SoHL's tree
|
|
600
|
+
carries one today, and nothing in this package enforces that; the content index
|
|
601
|
+
is where the question _which notes carry no `kbcat`?_ is answered.
|
|
602
|
+
|
|
603
|
+
**It is also what a content table sections on.** `sohl.kbcat AS _section` in a
|
|
604
|
+
`sql` fence is the ordinary case of _Content tables_ below, and the same
|
|
605
|
+
free-form value decides the headings there.
|
|
606
|
+
|
|
207
607
|
### WikiLinks
|
|
208
608
|
|
|
209
609
|
Twenty-seven fields in the tables below take a `WikiLink`, and a link is written
|
|
@@ -477,11 +877,11 @@ objects with a SoHL counterpart — `Arrow (Longbow)`, `Bolt (Crossbow)`,
|
|
|
477
877
|
both blocks. Only four are companions: `Javelin (thrown)`, `Shorkana (thrown)`,
|
|
478
878
|
`Spear (thrown)`, `Taburi (thrown)`.
|
|
479
879
|
|
|
480
|
-
**
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
880
|
+
**Every note carrying an `hm3:` block states its type.** Nothing can derive
|
|
881
|
+
whether `Spear (thrown)` is a `weapongear` or a `missilegear` — there is no other
|
|
882
|
+
system to infer from and no subType to read — and the same is true of the note
|
|
883
|
+
carrying both blocks, whose SoHL strike modes describe every usage at once. So
|
|
884
|
+
`hm3.type` is _required_ on both, not only on the companion.
|
|
485
885
|
|
|
486
886
|
The following special markdown sequences are recognized:
|
|
487
887
|
|
|
@@ -495,6 +895,133 @@ Any header can include curly braces. Inside the curly braces:
|
|
|
495
895
|
- `.class1` represents a CSS class named `class1` (any number of classes allowed)
|
|
496
896
|
- `attr="value"` represents an HTML attribute named `attr` whose value is `value` (any number of attr/value pairs allowed)
|
|
497
897
|
|
|
898
|
+
#### Content tables
|
|
899
|
+
|
|
900
|
+
A fenced `dataview` block is replaced by the table its query selects:
|
|
901
|
+
|
|
902
|
+
````markdown
|
|
903
|
+
```dataview
|
|
904
|
+
TABLE WITHOUT ID name.full AS "Name", shortcode AS "Code"
|
|
905
|
+
WHERE type = "armor"
|
|
906
|
+
```
|
|
907
|
+
````
|
|
908
|
+
|
|
909
|
+
**A query that selects nothing is a build error.** A zero-row table publishes as
|
|
910
|
+
a bare header and a rule, and a stale query — a renamed type, a retired
|
|
911
|
+
category, a typo'd path — is then indistinguishable from a category that is
|
|
912
|
+
legitimately empty. Eight tables in one note published that way for months after
|
|
913
|
+
a type rename, and no build said a word.
|
|
914
|
+
|
|
915
|
+
Where a table is _meant_ to be empty, say so on the fence:
|
|
916
|
+
|
|
917
|
+
````markdown
|
|
918
|
+
```dataview allow-empty
|
|
919
|
+
TABLE WITHOUT ID name.full AS "Name"
|
|
920
|
+
WHERE type = "affliction" AND sohl.kbcat = "not-written-yet"
|
|
921
|
+
```
|
|
922
|
+
````
|
|
923
|
+
|
|
924
|
+
The opt-in is on the fence rather than in the query because it is a statement
|
|
925
|
+
about this directive, not part of the query language. Either way the table is
|
|
926
|
+
still rendered — the finding is the point, not withholding the output.
|
|
927
|
+
|
|
928
|
+
##### In SQL, over the content index
|
|
929
|
+
|
|
930
|
+
`dataview` is being replaced by **SQL**, queried over the content index, and both
|
|
931
|
+
fences work while the corpus is converted (#246). The query is real SQL, run by
|
|
932
|
+
DuckDB — not a dialect maintained by this package.
|
|
933
|
+
|
|
934
|
+
````markdown
|
|
935
|
+
```sql
|
|
936
|
+
SELECT address.slug AS _ref,
|
|
937
|
+
sohl.kbcat AS _section,
|
|
938
|
+
name.full AS "Name",
|
|
939
|
+
sohl.weight AS "Weight"
|
|
940
|
+
FROM notes
|
|
941
|
+
WHERE type = 'miscgear'
|
|
942
|
+
ORDER BY sohl.kbcat, name.full
|
|
943
|
+
```
|
|
944
|
+
````
|
|
945
|
+
|
|
946
|
+
**`FROM notes`** is the content index: one row per note, plus one per
|
|
947
|
+
documentation entry, so `type = 'miscgear'` selects the items and never their
|
|
948
|
+
journals. A nested field is addressed exactly as a note authors it —
|
|
949
|
+
`sohl.weight`, `name.full`, `file.path` — because the index is read as JSON and
|
|
950
|
+
every nested object is inferred as a struct. A field a note type does not carry
|
|
951
|
+
reads `NULL` rather than failing.
|
|
952
|
+
|
|
953
|
+
**Two aliases are read by the renderer rather than printed**, because which
|
|
954
|
+
column links and where a section breaks are decisions about output, not
|
|
955
|
+
relational operations:
|
|
956
|
+
|
|
957
|
+
| Alias | What it does |
|
|
958
|
+
| ---------- | ---------------------------------------------------------------------- |
|
|
959
|
+
| `_ref` | Makes the row's **first** rendered column a wikilink to that address. |
|
|
960
|
+
| `_section` | Emits a headed table per distinct value, in the order the rows arrive. |
|
|
961
|
+
|
|
962
|
+
`_section` is why one query replaces the forty near-identical blocks a grouped
|
|
963
|
+
table used to need: the authored `ORDER BY` decides the section order too.
|
|
964
|
+
|
|
965
|
+
**Beware `folder`.** It is a note's _pack_ folder, not its directory — the
|
|
966
|
+
directory is `file.folder`.
|
|
967
|
+
|
|
968
|
+
###### Reading another package's notes
|
|
969
|
+
|
|
970
|
+
Each package this one **depends on** is attached as a schema named after it, so
|
|
971
|
+
a satellite can tabulate what it builds on:
|
|
972
|
+
|
|
973
|
+
````markdown
|
|
974
|
+
```sql
|
|
975
|
+
SELECT name.full AS "Name", sohl.skillBase AS "Base"
|
|
976
|
+
FROM sohl.notes
|
|
977
|
+
WHERE type = 'skill'
|
|
978
|
+
ORDER BY name.full
|
|
979
|
+
```
|
|
980
|
+
````
|
|
981
|
+
|
|
982
|
+
This package's own notes stay at the unqualified `notes`, and a query may read
|
|
983
|
+
both at once — joining your beings against the skills they cite is one `FROM`
|
|
984
|
+
clause. It needs no fetch and no configuration: a dependency's published index
|
|
985
|
+
is already cached when a compile starts, because resolving addresses across
|
|
986
|
+
packages needs it.
|
|
987
|
+
|
|
988
|
+
Which dataset a query reads is `FROM`'s job rather than a fence property. A
|
|
989
|
+
fence naming a file would write a build artifact's path into the corpus, so
|
|
990
|
+
renaming the artifact would mean sweeping every note that cited it.
|
|
991
|
+
|
|
992
|
+
###### Header arguments
|
|
993
|
+
|
|
994
|
+
Statements _about the directive_ — as opposed to the query — are written after
|
|
995
|
+
the language as **org-babel header arguments**:
|
|
996
|
+
|
|
997
|
+
````markdown
|
|
998
|
+
```sql :section-level 3 :allow-empty
|
|
999
|
+
SELECT name.full AS "Name", sohl.kbcat AS _section
|
|
1000
|
+
FROM notes WHERE type = 'affliction'
|
|
1001
|
+
```
|
|
1002
|
+
````
|
|
1003
|
+
|
|
1004
|
+
The language word stays first and stays plain, so GitHub, Prettier and every
|
|
1005
|
+
other markdown reader still highlight the block as SQL and simply ignore what
|
|
1006
|
+
follows.
|
|
1007
|
+
|
|
1008
|
+
| Argument | What it does |
|
|
1009
|
+
| ---------------------- | ---------------------------------------------------------------------------------------- |
|
|
1010
|
+
| `:allow-empty` | A table selecting nothing is intended, not a stale query. Without it, empty is an error. |
|
|
1011
|
+
| `:section-level <1-6>` | The heading level `_section` emits. Default `2`. |
|
|
1012
|
+
|
|
1013
|
+
The grammar is org's, so it extends without inventing a spelling per property:
|
|
1014
|
+
|
|
1015
|
+
- a key is `:name` **starting a word**, so a colon inside or ending one is text
|
|
1016
|
+
— `:caption Gear: the tables` is a single argument;
|
|
1017
|
+
- a value runs to the next key or the end of the line, spaces included;
|
|
1018
|
+
- a key with no value means `true`, which is what `:allow-empty` is;
|
|
1019
|
+
- a value may be `"quoted"` to hold a word that would otherwise read as a key;
|
|
1020
|
+
- a repeated key takes its last value.
|
|
1021
|
+
|
|
1022
|
+
`dataview` keeps its own bare `allow-empty`; it is the retiring language and its
|
|
1023
|
+
grammar is frozen.
|
|
1024
|
+
|
|
498
1025
|
```
|
|
499
1026
|
:::secret
|
|
500
1027
|
This is secret text
|
|
@@ -562,42 +1089,84 @@ The following H1 headers are treated specially:
|
|
|
562
1089
|
|
|
563
1090
|
Generates a living (or undead, or spirit) being.
|
|
564
1091
|
|
|
565
|
-
| `data` property | Values | Description
|
|
566
|
-
| --------------------------- | ---------------------------------------------- |
|
|
567
|
-
| `portrait` | `string` | File path to the portrait image
|
|
568
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template
|
|
569
|
-
| `archetypes` | `Archetype[]` |
|
|
570
|
-
| `occupation` | `string` | Name of the character's occupation
|
|
571
|
-
| `stations` | `WikiLink[]` | Name of the stations the character belongs to
|
|
572
|
-
| `lore` | `WikiLink[]` | Lore concerning this being — the people it is of, the standing it holds, the law it lives under
|
|
573
|
-
| `homes` | `WikiLink[]` | Place the being calls home
|
|
574
|
-
| `affiliations` | `WikiLink[]` | Affilliations (e.g., arcane/divine traditions, polities, etc)
|
|
575
|
-
| `gender` | `male \| female \| other` | Gender of the character
|
|
576
|
-
| `species` | `WikiLink` | Being's species (lore)
|
|
577
|
-
| `age` | `number` | Age of the character
|
|
578
|
-
| `birthday` | `YYYY/MM/DD` | Date of birth of the character
|
|
579
|
-
| `height` | `number` | Height in meters
|
|
580
|
-
| `weight` | `number` | Weight in kilograms
|
|
581
|
-
| `frame` | `scant \| light \| medium \| large \| massive` | Relative frame size
|
|
582
|
-
| `appearance.eye_color` | `string` | Eye color
|
|
583
|
-
| `appearance.hair_color` | `string` | Hair color
|
|
584
|
-
| `appearance.skin_color` | `string` | Skin color
|
|
585
|
-
| `appearance.complexion` | `string` | Complexion
|
|
586
|
-
| `appearance.extra_features` | `string[]` | Extra features
|
|
1092
|
+
| `data` property | Values | Description |
|
|
1093
|
+
| --------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
1094
|
+
| `portrait` | `string` | File path to the portrait image |
|
|
1095
|
+
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
1096
|
+
| `archetypes` | `Archetype[]` | What sort of character this is. **Always an array** — `[]` where none apply; `null` is an error. |
|
|
1097
|
+
| `occupation` | `string` | Name of the character's occupation |
|
|
1098
|
+
| `stations` | `WikiLink[]` | Name of the stations the character belongs to |
|
|
1099
|
+
| `lore` | `WikiLink[]` | Lore concerning this being — the people it is of, the standing it holds, the law it lives under |
|
|
1100
|
+
| `homes` | `WikiLink[]` | Place the being calls home |
|
|
1101
|
+
| `affiliations` | `WikiLink[]` | Affilliations (e.g., arcane/divine traditions, polities, etc) |
|
|
1102
|
+
| `gender` | `male \| female \| other` | Gender of the character |
|
|
1103
|
+
| `species` | `WikiLink` | Being's species (lore) |
|
|
1104
|
+
| `age` | `number` | Age of the character |
|
|
1105
|
+
| `birthday` | `YYYY/MM/DD` | Date of birth of the character |
|
|
1106
|
+
| `height` | `number` | Height in meters |
|
|
1107
|
+
| `weight` | `number` | Weight in kilograms |
|
|
1108
|
+
| `frame` | `scant \| light \| medium \| large \| massive` | Relative frame size |
|
|
1109
|
+
| `appearance.eye_color` | `string` | Eye color |
|
|
1110
|
+
| `appearance.hair_color` | `string` | Hair color |
|
|
1111
|
+
| `appearance.skin_color` | `string` | Skin color |
|
|
1112
|
+
| `appearance.complexion` | `string` | Complexion |
|
|
1113
|
+
| `appearance.extra_features` | `string[]` | Extra features |
|
|
1114
|
+
|
|
1115
|
+
#### Identifying a being's embedded items
|
|
1116
|
+
|
|
1117
|
+
Each entry in `sohl.items` compiles into one embedded Item, and its `_id` is
|
|
1118
|
+
derived from **what the entry is**, never from where it sits in the list:
|
|
1119
|
+
|
|
1120
|
+
```
|
|
1121
|
+
_id = makeId(<the actor's id>, "<subType>:<system.shortcode>")
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
An entry's identity is its **own `system.shortcode`**. The entry's _top-level_
|
|
1125
|
+
`shortcode` is a **selector** — it names the catalogue template the entry is
|
|
1126
|
+
written from and is never written to the document — so two entries may share
|
|
1127
|
+
one:
|
|
1128
|
+
|
|
1129
|
+
```yaml
|
|
1130
|
+
sohl:
|
|
1131
|
+
items:
|
|
1132
|
+
- shortcode: dgr # selects the catalogue's dagger
|
|
1133
|
+
type: weapongear
|
|
1134
|
+
name: Dagger 1
|
|
1135
|
+
system:
|
|
1136
|
+
shortcode: dgr1 # this dagger's own identity
|
|
1137
|
+
- shortcode: dgr
|
|
1138
|
+
type: weapongear
|
|
1139
|
+
name: Dagger 2
|
|
1140
|
+
system:
|
|
1141
|
+
shortcode: dgr2
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
**Two entries resolving to one identity are a build error naming both.** Without
|
|
1145
|
+
their own `system.shortcode`, both daggers above carry the catalogue's `dgr`,
|
|
1146
|
+
which makes them the same entity to everything that resolves by `(type,
|
|
1147
|
+
shortcode)` — compendium/world reconciliation, template shadowing, cohort
|
|
1148
|
+
membership, effect and expression references. A `name` cannot stand in: it is
|
|
1149
|
+
presentation, free to be localized or to diverge.
|
|
1150
|
+
|
|
1151
|
+
**Reordering the list moves no id.** The key used to carry the entry's position,
|
|
1152
|
+
so inserting an item renumbered every id after it and a re-import created new
|
|
1153
|
+
documents beside the old ones — while nothing about those documents had changed,
|
|
1154
|
+
only their neighbours. The same is now true of a note's journal pages: an
|
|
1155
|
+
unanchored page is keyed on its heading, so inserting a heading leaves every
|
|
1156
|
+
other page's id where it was. Two sibling pages sharing a heading is likewise a
|
|
1157
|
+
build error, matching the `MD024` lint rule that already refuses it.
|
|
587
1158
|
|
|
588
1159
|
If a `sohl` property is present, a SoHL actor of type "being" will be created.
|
|
589
1160
|
|
|
590
|
-
If an `hm3` property is present, an HM3 actor is created. Its document type is derived
|
|
1161
|
+
If an `hm3` property is present, an HM3 actor is created. Its document type is **not** derived: `hm3.type` states it, and must be `character` or `creature`. A note that omits it is an error naming the note — see _The note vocabulary, and how it maps_.
|
|
591
1162
|
|
|
592
1163
|
A SoHL "being" document will be created, as will an "HM3" document.
|
|
593
1164
|
|
|
594
|
-
| shared source
|
|
595
|
-
|
|
|
596
|
-
| `data.
|
|
597
|
-
| `data.
|
|
598
|
-
| `data.
|
|
599
|
-
| `data.gender` | NA | `system.gender` |
|
|
600
|
-
| `data.occupation` | NA | `system.occupation` |
|
|
1165
|
+
| shared source | → sohl | → hm3 |
|
|
1166
|
+
| ----------------- | ------ | ------------------- |
|
|
1167
|
+
| `data.species` | NA | `system.species` |
|
|
1168
|
+
| `data.gender` | NA | `system.gender` |
|
|
1169
|
+
| `data.occupation` | NA | `system.occupation` |
|
|
601
1170
|
|
|
602
1171
|
### type: homepage
|
|
603
1172
|
|
|
@@ -618,10 +1187,8 @@ Represents a conveyance able to hold goods and people moving from one place to a
|
|
|
618
1187
|
|
|
619
1188
|
If `sohl` is present, this becomes a `vehicle` actor.
|
|
620
1189
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
| `data.portrait` | `system.portrait` | NA |
|
|
624
|
-
| `data.templatePriority` | `system.template` | NA |
|
|
1190
|
+
It maps nothing beyond the shared rows above, actor row included: a vehicle
|
|
1191
|
+
carries a portrait and a template priority and no field of its own.
|
|
625
1192
|
|
|
626
1193
|
### type: affiliation
|
|
627
1194
|
|
|
@@ -856,7 +1423,7 @@ If `sohl` is present, this becomes an `affliction` item.
|
|
|
856
1423
|
| `data.healingCheckDurationFormula` | `system.healingCheckDurationFormula` | NA |
|
|
857
1424
|
| `data.resolutionDurationFormula` | `system.resolutionDurationFormula` | NA |
|
|
858
1425
|
|
|
859
|
-
### type:
|
|
1426
|
+
### type: armor
|
|
860
1427
|
|
|
861
1428
|
Note: `data.quantity` may not be specified. Quantity is always 1.
|
|
862
1429
|
|
|
@@ -870,7 +1437,9 @@ Note: `data.quantity` may not be specified. Quantity is always 1.
|
|
|
870
1437
|
|
|
871
1438
|
If a `sohl` property is present, a SoHL item of type "armorgear" will be created.
|
|
872
1439
|
|
|
873
|
-
if a `hm3` property is present, an HM3 item of type "armorgear" will be created.
|
|
1440
|
+
if a `hm3` property is present, an HM3 item of type "armorgear" will be created. The
|
|
1441
|
+
note type is `armor` in both cases: the `gear` suffix named a document subtype rather
|
|
1442
|
+
than the thing the note is about.
|
|
874
1443
|
|
|
875
1444
|
| shared source | → sohl | → hm3 |
|
|
876
1445
|
| ----------------- | ----------------------- | --------------- |
|
|
@@ -902,7 +1471,7 @@ if a `sohl` property is present, a SoHL item of type "attribute" will be created
|
|
|
902
1471
|
| shared source | → sohl | → hm3 |
|
|
903
1472
|
| ------------- | ------ | ----- |
|
|
904
1473
|
|
|
905
|
-
### type:
|
|
1474
|
+
### type: concoction
|
|
906
1475
|
|
|
907
1476
|
**subType**:
|
|
908
1477
|
|
|
@@ -1043,13 +1612,7 @@ if a `sohl` property is present, a SoHL item of type "mystery" will be created.
|
|
|
1043
1612
|
|
|
1044
1613
|
if a `sohl` property is present, a SoHL item of type "mysticalability" will be created.
|
|
1045
1614
|
|
|
1046
|
-
If an `hm3` property is present, an HM3 item is created.
|
|
1047
|
-
|
|
1048
|
-
- if `subType` === `arcaneinvocation`, `hm3.type` = `spell`
|
|
1049
|
-
- if `subType` === `divineinvocation`, `hm3.type` = `invocation`
|
|
1050
|
-
- if `subType` === `arcanetalent`, `hm3.type` = `psionic`
|
|
1051
|
-
|
|
1052
|
-
If `hm3.type` is specified, it must be `psionic`, `spell` or `invocation`.
|
|
1615
|
+
If an `hm3` property is present, an HM3 item is created, and `hm3.type` states which — `psionic`, `spell` or `invocation`. It is **authored, not derived from `subType`**: the ten mystical-ability subtypes do not partition onto HM3's three documents (a `spiritrite`, an `alchemy` and a `divination` each answer to none of them), so a derivation would be a guess with a plausible shape. A note that omits it is an error naming the note.
|
|
1053
1616
|
|
|
1054
1617
|
| shared source | → sohl | → hm3 |
|
|
1055
1618
|
| ----------------------- | ----------------------------- | ------------- |
|
|
@@ -1061,7 +1624,7 @@ If `hm3.type` is specified, it must be `psionic`, `spell` or `invocation`.
|
|
|
1061
1624
|
| `data.charges.value` | `system.charges.value` | NA |
|
|
1062
1625
|
| `data.charges.max` | `system.charges.max` | NA |
|
|
1063
1626
|
|
|
1064
|
-
### type:
|
|
1627
|
+
### type: projectile
|
|
1065
1628
|
|
|
1066
1629
|
**subTypes**:
|
|
1067
1630
|
|
|
@@ -1175,7 +1738,7 @@ if a `sohl` property is present, a SoHL item of type "weapongear" will be create
|
|
|
1175
1738
|
carrying every strike mode the weapon has — melee and missile alike — on
|
|
1176
1739
|
`system.strikeModes`.
|
|
1177
1740
|
|
|
1178
|
-
If an `hm3` property is present, an HM3 item is created. **`weapon` has no `subType`**: SoHL distinguishes a weapon's uses with strike modes rather than by kind, and HM3
|
|
1741
|
+
If an `hm3` property is present, an HM3 item is created, and `hm3.type` states whether it is a `weapongear` or a `missilegear`. **`weapon` has no `subType`**: SoHL distinguishes a weapon's uses with strike modes rather than by kind, and HM3 has one document per usage, so nothing but the note can say which usage it describes. Every note carrying an `hm3:` block states it — the one carrying both blocks as well as the companion carrying only `hm3:`, which is usually a `missilegear` — see _One note is at most one document per system_.
|
|
1179
1742
|
|
|
1180
1743
|
| shared source | → sohl | → hm3 |
|
|
1181
1744
|
| ----------------- | ----------------------- | --------------- |
|
|
@@ -1310,8 +1873,8 @@ Foundry, so the build refuses rather than resolving.
|
|
|
1310
1873
|
> system-specific, and HM3 would want the identical Scene. Authoring it under
|
|
1311
1874
|
> `sohl:` means a map produces nothing for a system-agnostic build and carries a
|
|
1312
1875
|
> SoHL infobox implying a specificity it does not have. It is the same class of
|
|
1313
|
-
> mistake as storing
|
|
1314
|
-
> container was, rather than where it belongs. Three notes carry it today.
|
|
1876
|
+
> mistake as storing the template priority in flags: the data went where the only
|
|
1877
|
+
> available container was, rather than where it belongs. Three notes carry it today.
|
|
1315
1878
|
|
|
1316
1879
|
**A map is always a leaf.** Its frontmatter references nothing outside itself.
|
|
1317
1880
|
`notes:` is a list of `[anchor, GridLocation]`, and each anchor names a heading in
|
|
@@ -1489,3 +2052,150 @@ need an authoring convention of their own.
|
|
|
1489
2052
|
|
|
1490
2053
|
The note's `img` is a content-relative path resolved the way every other note's
|
|
1491
2054
|
is; a note that authors none takes Foundry's own `icons/svg/dice-target.svg`.
|
|
2055
|
+
|
|
2056
|
+
### type: bundle
|
|
2057
|
+
|
|
2058
|
+
A bundle of notes to be taken as a single unit — an `Adventure` in Foundry VTT.
|
|
2059
|
+
|
|
2060
|
+
| `data` property | Values | Description |
|
|
2061
|
+
| --------------- | ------------ | ------------------------------------------------------ |
|
|
2062
|
+
| `contents` | `WikiLink[]` | The documents the Adventure holds; `[]` when unstated. |
|
|
2063
|
+
|
|
2064
|
+
```yaml
|
|
2065
|
+
---
|
|
2066
|
+
type: bundle
|
|
2067
|
+
shortcode: hegovynvale
|
|
2068
|
+
name:
|
|
2069
|
+
full: The Hegóvyn Vale
|
|
2070
|
+
data:
|
|
2071
|
+
contents:
|
|
2072
|
+
- map-hegovynvale
|
|
2073
|
+
- miscgear-bowlcer
|
|
2074
|
+
- being-aurochs
|
|
2075
|
+
---
|
|
2076
|
+
Prose describing what the bundle is for.
|
|
2077
|
+
```
|
|
2078
|
+
|
|
2079
|
+
An `Adventure` carries **copies** of what it holds, not references: importing one
|
|
2080
|
+
creates or updates each document in the world, after which they live
|
|
2081
|
+
independently. So a bundle is not a folder — a folder is a live grouping that
|
|
2082
|
+
persists in the pack.
|
|
2083
|
+
|
|
2084
|
+
**Each address names the note's own document.** That is the same rule `pack:`
|
|
2085
|
+
follows, so there is one answer and not two. A note that compiles into _two_
|
|
2086
|
+
documents — an item and the JournalEntry its prose became — puts the second in a
|
|
2087
|
+
bundle only when the bundle names it by its own `doc…` address:
|
|
2088
|
+
`miscgear-bowlcer` is the item, `docmiscgear-bowlcer` its description page.
|
|
2089
|
+
|
|
2090
|
+
**An address that resolves to nothing fails the build.** A `folder` address is
|
|
2091
|
+
refused with a message of its own: a folder materialises in every pack holding
|
|
2092
|
+
something filed in it, so it belongs to no one pack and there is no single copy
|
|
2093
|
+
to take.
|
|
2094
|
+
|
|
2095
|
+
**The note's prose becomes the Adventure's `description`**, which is what
|
|
2096
|
+
Foundry renders on the import card. A bundle is something you hand someone, so
|
|
2097
|
+
its prose belongs on the document itself — which is why, unlike an item, a
|
|
2098
|
+
bundle earns no separate documentation journal.
|
|
2099
|
+
|
|
2100
|
+
Each Adventure is written to the pack the note's `pack` names — the shared
|
|
2101
|
+
routing field every type uses, not one of the bundle's own — defaulting to the
|
|
2102
|
+
configured `Adventure` pack, conventionally `adventures`. `<system>.pack`
|
|
2103
|
+
overrides it for that system, as it does everywhere else.
|
|
2104
|
+
|
|
2105
|
+
**It cannot be the `adventures` companion**, though, where a repository also
|
|
2106
|
+
compiles map notes: that pack is written by the scenes pass, and a companion is
|
|
2107
|
+
written by its parent pass rather than routed to. A repository that authors
|
|
2108
|
+
bundles declares an Adventure pack of its own, and one that declares none is
|
|
2109
|
+
told so by name.
|
|
2110
|
+
|
|
2111
|
+
**A pack's `system:` constrains what its Adventures may hold.** An `Adventure`
|
|
2112
|
+
has no `system` field, so a bundle spanning two systems cannot be one document
|
|
2113
|
+
that knows it spans them: it is one Adventure per system, and the pack each is
|
|
2114
|
+
written to is what carries the system. A pack declaring `system: hm3` sees the
|
|
2115
|
+
HM3 packs and the system-neutral ones, so a member that publishes no HM3
|
|
2116
|
+
document is **left out rather than failing** — and named, because an installer
|
|
2117
|
+
that quietly ships half its contents is worse than one that fails. A pack
|
|
2118
|
+
declaring no system scopes nothing away, and a member it cannot find is a dead
|
|
2119
|
+
address.
|
|
2120
|
+
|
|
2121
|
+
**The bundles pass runs last**, after every pass producing what a bundle can
|
|
2122
|
+
hold — Item, Actor, JournalEntry, Macro and Scene. That ordering is derived from
|
|
2123
|
+
what the pass declares it reads, not from the order `packs:` happens to list, so
|
|
2124
|
+
an Adventure pack declared first still compiles last.
|
|
2125
|
+
|
|
2126
|
+
### type: folder
|
|
2127
|
+
|
|
2128
|
+
Foundry's `Folder` — the grouping documents are filed in, and the last document
|
|
2129
|
+
this package compiled from bespoke configuration (`*-folders.yaml`, five files
|
|
2130
|
+
per tree) rather than from a note.
|
|
2131
|
+
|
|
2132
|
+
```yaml
|
|
2133
|
+
---
|
|
2134
|
+
type: folder
|
|
2135
|
+
shortcode: possessionscooking
|
|
2136
|
+
name:
|
|
2137
|
+
full: Cooking
|
|
2138
|
+
data:
|
|
2139
|
+
parent: possessionsmiscgear
|
|
2140
|
+
color: "#7a4b2a"
|
|
2141
|
+
---
|
|
2142
|
+
```
|
|
2143
|
+
|
|
2144
|
+
| `data` property | Values | Description |
|
|
2145
|
+
| --------------- | ------------------------------------------ | ------------------------------------------------------------------------------ |
|
|
2146
|
+
| `parent` | `WikiLink`, or a map of them keyed by pack | The folder this one sits in — one address, or one per pack. Unset at the root. |
|
|
2147
|
+
| `color` | `"#RRGGBB"`, a string | The folder's colour. Unset for Foundry's default. |
|
|
2148
|
+
|
|
2149
|
+
A folder is addressed `<package>-none-folder-<shortcode>` — **`none`**, because a
|
|
2150
|
+
`Folder` is a core Foundry document like a `JournalEntry` or a `Scene`, not a
|
|
2151
|
+
system's. Its shortcode is [an address segment](#the-canonical-address) like
|
|
2152
|
+
every other, so it is strictly alphanumeric: `possessionscooking`, never
|
|
2153
|
+
`possessions-cooking`, which would read as two segments and resolve to nothing.
|
|
2154
|
+
|
|
2155
|
+
**`color` must be quoted**, and YAML gives no third option: `color: #7a4b2a`
|
|
2156
|
+
parses as `null` (a `#` after a space opens a comment) and `color: 000000` parses
|
|
2157
|
+
as the number `0`. All 639 colour values across the five trees are already
|
|
2158
|
+
written `"#RRGGBB"`.
|
|
2159
|
+
|
|
2160
|
+
`parent` is an address, so a dangling one is an ordinary dead-address finding
|
|
2161
|
+
rather than a special-cased `Unknown folder id`, and a cycle is refused. Both are
|
|
2162
|
+
reported when the tree is read, not when something happens to reference the
|
|
2163
|
+
folder that carries them.
|
|
2164
|
+
|
|
2165
|
+
**`parent` may be a map keyed by pack**, because a folder's _identity_ is one
|
|
2166
|
+
thing and its _hierarchy_ is another. The same folder is deliberately filed
|
|
2167
|
+
under different parents in different packs: an item compendium is browsed by
|
|
2168
|
+
kind, a journal compendium is read by subject.
|
|
2169
|
+
|
|
2170
|
+
```yaml
|
|
2171
|
+
data:
|
|
2172
|
+
parent:
|
|
2173
|
+
default: ~ # at the root of the items pack
|
|
2174
|
+
journals: descriptions # under Rules/Descriptions in the journals pack
|
|
2175
|
+
```
|
|
2176
|
+
|
|
2177
|
+
`default` is every pack that is not named; an explicit `~` under a pack key means
|
|
2178
|
+
_at the root there_, which is a different statement from saying nothing. A plain
|
|
2179
|
+
scalar — the everyday spelling, and the right one wherever the hierarchies agree
|
|
2180
|
+
— is exactly `{ default: <value> }`. The folder keeps **one id** across every
|
|
2181
|
+
pack it materialises in, which is what files a documentation journal beside the
|
|
2182
|
+
item it describes; only its parent differs.
|
|
2183
|
+
|
|
2184
|
+
Every key but `default` names a **pack the package declares**, and one that names
|
|
2185
|
+
none is a finding of its own. It cannot be a harmless surplus: the compile asks
|
|
2186
|
+
the map for the pack it is writing and falls back to `default` when there is no
|
|
2187
|
+
such key, so a mistyped `journal:` files the folder wherever the default puts it
|
|
2188
|
+
— exactly the hierarchy the key was written to override, and silently.
|
|
2189
|
+
|
|
2190
|
+
**A folder note carries no prose.** It is structure, not content, so it produces
|
|
2191
|
+
no documentation journal and takes no part in `docEntryTypes`.
|
|
2192
|
+
|
|
2193
|
+
**It declares no pack.** Which packs a folder materialises in is derived from
|
|
2194
|
+
[what references it](#the-compendium-folder), and its ancestors materialise with
|
|
2195
|
+
it; a folder nothing references materialises nowhere.
|
|
2196
|
+
|
|
2197
|
+
**Its Foundry `_id` is derived from its address**, stable across runs, so a new
|
|
2198
|
+
folder needs no invented id. An authored `id` is kept where one is present —
|
|
2199
|
+
which is what lets a tree sweep its folder YAML into notes without a world that
|
|
2200
|
+
already holds those folders losing them. Two folders claiming one id is a build
|
|
2201
|
+
error.
|