@heroiclands/package-build 22.15.0 → 22.16.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 +103 -0
- package/README.md +39 -678
- package/bin/{content-build.mjs → content-commands.mjs} +197 -118
- package/bin/package-build.mjs +303 -42
- package/bin/report.mjs +1 -1
- package/ci/ci-docker.mjs +6 -3
- package/ci/ci-steps.mjs +6 -4
- package/ci/content-format-reference.mjs +106 -0
- package/config.mjs +22 -2
- package/content-config.mjs +74 -134
- package/docs/api.md +294 -234
- package/docs/authoring/assets.md +31 -0
- package/docs/authoring/dates-and-calendars.md +33 -0
- package/docs/authoring/first-note.md +48 -0
- package/docs/authoring/frontmatter.md +40 -0
- package/docs/authoring/links-and-markup.md +137 -0
- package/docs/commands.md +631 -162
- package/docs/configuration.md +176 -140
- package/docs/content-format.md +50 -4229
- package/docs/design.md +32 -0
- package/docs/diagnostics.md +29 -22
- package/docs/getting-started.md +21 -877
- package/docs/guides/book.md +153 -0
- package/docs/guides/e2e.md +143 -0
- package/docs/guides/icons.md +126 -0
- package/docs/guides/localization.md +120 -0
- package/docs/guides/module-project.md +915 -0
- package/docs/guides/packs.md +167 -0
- package/docs/guides/reference-pages.md +148 -0
- package/docs/guides/release-and-deploy.md +114 -0
- package/docs/guides/site.md +152 -0
- package/docs/guides/system-project.md +45 -0
- package/docs/index.md +16 -0
- package/docs/project-setup.md +54 -35
- package/docs/reference/format-details.md +4420 -0
- package/docs/reference/note-types.md +531 -0
- package/engine/address-render.mjs +26 -0
- package/engine/address.mjs +3 -20
- package/engine/anchors.mjs +15 -6
- package/engine/asset-bindings.mjs +6 -2
- package/engine/base-compiler.mjs +47 -11
- package/engine/being-measurements.mjs +70 -0
- package/engine/calendar-artifacts.mjs +1 -1
- package/engine/calendar-choice.mjs +32 -0
- package/engine/calendar-human.mjs +71 -0
- package/engine/calendar-notes.mjs +41 -3
- package/engine/calendars.mjs +8 -1
- package/engine/changelog-lint.mjs +2 -2
- package/engine/code-fences.mjs +23 -97
- package/engine/compile-corpus.mjs +6 -7
- package/engine/content-admonitions.mjs +114 -0
- package/engine/content-captions.mjs +181 -0
- package/engine/content-embeds.mjs +2 -2
- package/engine/content-footnotes.mjs +34 -0
- package/engine/content-format-check.mjs +2 -2
- package/engine/content-format.mjs +127 -73
- package/engine/content-format.yaml +745 -0
- package/engine/content-html.mjs +1 -1
- package/engine/content-icons.mjs +45 -92
- package/engine/content-images.mjs +22 -74
- package/engine/content-index.mjs +12 -3
- package/engine/content-links.mjs +21 -26
- package/engine/content-lint.mjs +1 -5
- package/engine/content-secrets.mjs +3 -1
- package/engine/content-tables.mjs +47 -1257
- package/engine/culture-choice.mjs +32 -0
- package/engine/date-conversion.mjs +47 -0
- package/engine/declaration-check.mjs +87 -0
- package/engine/docs-checks.mjs +140 -0
- package/engine/documentation-links.mjs +31 -0
- package/engine/exported-scene.mjs +1 -1
- package/engine/extension-attributes.mjs +74 -0
- package/engine/field-reference.mjs +1 -1
- package/engine/field-spec.mjs +4 -0
- package/engine/foreign-catalog.mjs +5 -5
- package/engine/format-generated.mjs +15 -0
- package/engine/foundry-entries.mjs +3 -1
- package/engine/frontmatter-lint.mjs +5 -1
- package/engine/frontmatter.mjs +10 -0
- package/engine/generate.mjs +2 -2
- package/engine/helpers.mjs +71 -117
- package/engine/homepage.mjs +2 -3
- package/engine/index-records.mjs +1 -0
- package/engine/index.mjs +18 -3
- package/engine/infobox-registry.mjs +3 -1
- package/engine/infobox.mjs +38 -20
- package/engine/item-compiler.mjs +10 -8
- package/engine/item-docs.mjs +7 -6
- package/engine/itinerary-scenes.mjs +4 -2
- package/engine/journals.mjs +51 -10
- package/engine/macros.mjs +1 -1
- package/engine/map-build.mjs +1 -1
- package/engine/map-dot.mjs +1 -1
- package/engine/map-raster.mjs +4 -2
- package/engine/markdown-expressions.mjs +117 -0
- package/engine/metadata-index.mjs +7 -3
- package/engine/note-addresses.mjs +1 -0
- package/engine/note-dates.mjs +283 -61
- package/engine/note-package.mjs +5 -25
- package/engine/note-state.mjs +6 -2
- package/engine/note-vocabulary.mjs +74 -19
- package/engine/number-words.mjs +75 -0
- package/engine/office-holders.mjs +188 -0
- package/engine/pack-config.mjs +2 -15
- package/engine/pack-router.mjs +24 -5
- package/engine/packages.mjs +3 -2
- package/engine/pathnames.mjs +21 -24
- package/engine/pdf-build.mjs +217 -55
- package/engine/pdf-fonts.mjs +2 -2
- package/engine/pdf-images.mjs +82 -0
- package/engine/pdf-render.mjs +174 -22
- package/engine/populations.mjs +3 -2
- package/engine/project-init.mjs +499 -0
- package/engine/prose-config.mjs +1 -1
- package/engine/prose-lint.mjs +7 -6
- package/engine/readability-lint.mjs +86 -0
- package/engine/reckoning-markers.mjs +205 -93
- package/engine/scenes.mjs +5 -2
- package/engine/schema-check.mjs +3 -3
- package/engine/site-build.mjs +87 -37
- package/engine/site-config.mjs +8 -8
- package/engine/site-index.mjs +4 -0
- package/engine/site-maps.mjs +1 -1
- package/engine/site-preview.mjs +19 -8
- package/engine/social-ties.mjs +5 -1
- package/engine/sql-tables.mjs +92 -22
- package/engine/web-wikilinks.mjs +15 -31
- package/engine/wikilink-syntax.mjs +4 -1
- package/engine/wikilinks.mjs +12 -14
- package/hm3/item-fields.mjs +1 -1
- package/hm3/items.mjs +12 -45
- package/index.mjs +2 -14
- package/manifest.mjs +31 -4
- package/markdownlint-config.mjs +1 -1
- package/package.json +32 -11
- package/prettier-config.mjs +1 -1
- package/sohl/being-info.mjs +3 -145
- package/sohl/index.mjs +1 -1
- package/sohl/infobox.mjs +2 -1
- package/sohl/item-builders.mjs +3 -27
- package/sohl/item-fields.mjs +61 -22
- package/sohl/kb-passes.mjs +2 -2
- package/sohl/note-schemas.mjs +0 -8
- package/stage.mjs +19 -0
- package/types/config.d.mts +12 -3
- package/types/content-config.d.mts +15 -81
- package/types/engine/address-render.d.mts +13 -0
- package/types/engine/address.d.mts +2 -11
- package/types/engine/anchors.d.mts +3 -4
- package/types/engine/asset-bindings.d.mts +9 -2
- package/types/engine/base-compiler.d.mts +1 -2
- package/types/engine/being-measurements.d.mts +16 -0
- package/types/engine/calendar-choice.d.mts +14 -0
- package/types/engine/calendar-human.d.mts +4 -0
- package/types/engine/calendar-notes.d.mts +2 -0
- package/types/engine/calendars.d.mts +5 -3
- package/types/engine/code-fences.d.mts +6 -72
- package/types/engine/compile-corpus.d.mts +1 -3
- package/types/engine/content-admonitions.d.mts +24 -0
- package/types/engine/content-captions.d.mts +45 -0
- package/types/engine/content-footnotes.d.mts +10 -0
- package/types/engine/content-format-check.d.mts +17 -4
- package/types/engine/content-format.d.mts +24 -11
- package/types/engine/content-icons.d.mts +14 -23
- package/types/engine/content-images.d.mts +2 -2
- package/types/engine/content-index.d.mts +9 -2
- package/types/engine/content-links.d.mts +3 -5
- package/types/engine/content-tables.d.mts +11 -140
- package/types/engine/culture-choice.d.mts +14 -0
- package/types/engine/date-conversion.d.mts +4 -0
- package/types/engine/declaration-check.d.mts +24 -0
- package/types/engine/docs-checks.d.mts +28 -0
- package/types/engine/documentation-links.d.mts +7 -0
- package/types/engine/exported-scene.d.mts +6 -2
- package/types/engine/extension-attributes.d.mts +14 -0
- package/types/engine/field-spec.d.mts +9 -0
- package/types/engine/format-generated.d.mts +7 -0
- package/types/engine/frontmatter.d.mts +14 -4
- package/types/engine/generate.d.mts +1 -1
- package/types/engine/helpers.d.mts +37 -92
- package/types/engine/homepage.d.mts +2 -3
- package/types/engine/index.d.mts +5 -0
- package/types/engine/infobox-registry.d.mts +3 -1
- package/types/engine/infobox.d.mts +17 -6
- package/types/engine/item-compiler.d.mts +4 -4
- package/types/engine/item-docs.d.mts +4 -3
- package/types/engine/journals.d.mts +10 -7
- package/types/engine/map-graphviz.d.mts +1 -1
- package/types/engine/markdown-expressions.d.mts +17 -0
- package/types/engine/note-dates.d.mts +47 -1
- package/types/engine/note-package.d.mts +3 -19
- package/types/engine/note-state.d.mts +2 -1
- package/types/engine/note-vocabulary.d.mts +9 -0
- package/types/engine/number-words.d.mts +4 -0
- package/types/engine/office-holders.d.mts +14 -0
- package/types/engine/pack-config.d.mts +1 -1
- package/types/engine/packages.d.mts +3 -2
- package/types/engine/pdf-images.d.mts +38 -0
- package/types/engine/populations.d.mts +2 -2
- package/types/engine/project-init.d.mts +17 -0
- package/types/engine/prose-config.d.mts +1 -1
- package/types/engine/prose-lint.d.mts +3 -2
- package/types/engine/readability-lint.d.mts +22 -0
- package/types/engine/reckoning-markers.d.mts +4 -2
- package/types/engine/schema-check.d.mts +3 -3
- package/types/engine/site-build.d.mts +38 -28
- package/types/engine/site-config.d.mts +2 -2
- package/types/engine/site-index.d.mts +4 -0
- package/types/engine/site-preview.d.mts +1 -1
- package/types/engine/social-ties.d.mts +8 -2
- package/types/engine/sql-tables.d.mts +13 -2
- package/types/engine/web-wikilinks.d.mts +2 -1
- package/types/engine/wikilinks.d.mts +7 -1
- package/types/hm3/items.d.mts +5 -6
- package/types/labels.d.mts +40 -0
- package/types/manifest.d.mts +3 -3
- package/types/sohl/actors.d.mts +2 -2
- package/types/sohl/being-info.d.mts +3 -43
- package/types/sohl/index.d.mts +1 -1
- package/types/sohl/infobox.d.mts +5 -2
- package/types/sohl/item-fields.d.mts +4 -13
- package/types/stage.d.mts +9 -0
- package/CONTENT.md +0 -3239
package/docs/content-format.md
CHANGED
|
@@ -1,4255 +1,76 @@
|
|
|
1
|
-
## Content format: how a note becomes documents
|
|
2
|
-
|
|
3
|
-
### Documentation
|
|
4
|
-
|
|
5
|
-
Markdown generates a JournalEntry: content before the first H1 becomes its "Introduction" page, and each H1 begins another page. The document Address is `<package>-note-<note_type>-<shortcode>`.
|
|
6
|
-
|
|
7
|
-
An **infobox** is prepended to the entry's first page, before the prose. It is
|
|
8
|
-
not a page of its own: a page is what a Foundry UUID addresses, and a summary a
|
|
9
|
-
reader has to navigate to is a summary they do not see. What the box holds, and
|
|
10
|
-
where it sits in every other medium, is [the infobox](#the-infobox) below.
|
|
11
|
-
|
|
12
|
-
**There are two kinds of infobox.**
|
|
13
|
-
|
|
14
|
-
A **note infobox** summarises the subject itself, from `data:` — a scenario's
|
|
15
|
-
length, party size and required archetypes; a place's subtype and its parent; a
|
|
16
|
-
weapon's weight and value. None of that is SoHL or HM3 information, and it
|
|
17
|
-
deserves a summary panel all the same. Any type that declares `data:` fields has
|
|
18
|
-
one.
|
|
19
|
-
|
|
20
|
-
A **system infobox** summarises what one system makes of the note, drawn from
|
|
21
|
-
that system's block. There is one per system, and they are not alike: the two
|
|
22
|
-
systems describe a weapon with different values for impact, heft, reach and draw,
|
|
23
|
-
so neither box can stand for the other.
|
|
24
|
-
|
|
25
|
-
**Which boxes appear is decided by the note type; what they say is decided by the
|
|
26
|
-
note.** A type that both systems support always shows both system boxes, and a
|
|
27
|
-
box whose system this particular note produces no document for reads
|
|
28
|
-
**"Not available"**.
|
|
29
|
-
|
|
30
|
-
| note type | system boxes shown |
|
|
31
|
-
| --------------------------------------------------------------------- | -------------------------------------------------- |
|
|
32
|
-
| `weapon`, `skill`, `being`, `armorgear`, `containergear`, `miscgear`… | SoHL **and** HM3 — either may read _Not available_ |
|
|
33
|
-
| `affiliation`, `affliction`, `attribute`, `concoctiongear`, `mystery` | SoHL only |
|
|
34
|
-
| `armorlocation` | HM3 only |
|
|
35
|
-
|
|
36
|
-
This is stated rather than inferred from an empty block, because an absence is a
|
|
37
|
-
poor signal: noticing that something is missing requires already knowing it
|
|
38
|
-
should have been there, and a reader meeting one page has no way to know. A box
|
|
39
|
-
that says _Not available_ tells them outright.
|
|
40
|
-
|
|
41
|
-
It also separates two facts that an absence would conflate. `Spear (thrown)` is
|
|
42
|
-
HM3-only while spears plainly exist in SoHL — _that_ note has no SoHL form, and
|
|
43
|
-
its SoHL box says so. HM3, by contrast, has no affiliations, mysteries or
|
|
44
|
-
attributes **at all**; on those pages a box reading "Not available" would suggest
|
|
45
|
-
a gap in the note when the truth is about the system's scope, so no HM3 box is
|
|
46
|
-
drawn.
|
|
47
|
-
|
|
48
|
-
The set of boxes needs no new declaration: the note-type → document-subtype map
|
|
49
|
-
already records which systems each type reaches. Rendering one box per mapped
|
|
50
|
-
system makes the page checkable — _every page carries exactly the boxes its type
|
|
51
|
-
maps to_ is an assertion the build can make, so a missing infobox is a failure
|
|
52
|
-
rather than something nobody notices.
|
|
53
|
-
|
|
54
|
-
For web pages, the full Markdown body renders as HTML with its infoboxes.
|
|
55
|
-
|
|
56
|
-
### The infobox
|
|
57
|
-
|
|
58
|
-
**The infobox is generated content at a known position — prepended, before the
|
|
59
|
-
prose.** It is not a floating sidebar and it is not defined in a rendering
|
|
60
|
-
template. The toolchain settles what each box holds; each medium lays that out
|
|
61
|
-
its own way.
|
|
62
|
-
|
|
63
|
-
| medium | placement | collapsing |
|
|
64
|
-
| ------- | ------------------------------------------------------ | ------------------------- |
|
|
65
|
-
| book | flows in the column measure, breaking between sections | none, and none needed |
|
|
66
|
-
| website | side rail on wide screens, inline on narrow | `<details>`, default open |
|
|
67
|
-
| Foundry | inlined, the page being narrow | `<details>`, default open |
|
|
68
|
-
|
|
69
|
-
Two columns are print-only. A scrolling page has no fixed viewport, so columns
|
|
70
|
-
make a reader travel down and back up, and a narrow screen collapses to one
|
|
71
|
-
column regardless.
|
|
72
|
-
|
|
73
|
-
**Six rules hold in every medium.**
|
|
74
|
-
|
|
75
|
-
1. **It is content in document order**, prepended before the prose. An image
|
|
76
|
-
authored before it appears before it.
|
|
77
|
-
2. **It contains no image.** A picture is authored in the text with its own
|
|
78
|
-
directive, and its position governs.
|
|
79
|
-
3. **A section is the unit that flows.** Sections are whole and unbreakable and
|
|
80
|
-
the panel breaks _between_ them, which is what lets a long box cross a
|
|
81
|
-
column or page boundary without splitting a stat grid down the middle.
|
|
82
|
-
4. **An absent field is absent, not empty.** A row with no value is not
|
|
83
|
-
emitted, and neither is a section with no rows: a creature carrying no
|
|
84
|
-
equipment has no `EQUIPMENT` heading rather than an empty one. A placeholder
|
|
85
|
-
asserts a fact that is not there, and an empty heading asserts that something
|
|
86
|
-
was expected here and is missing. A corpus writes an absence three ways and
|
|
87
|
-
all three count: the key is omitted, it holds the field's own default, or it
|
|
88
|
-
holds a **sentinel** — `na`, `none`, `not applicable` — which is the same
|
|
89
|
-
absence written as a word. `Potency: Na` is not a fact about a potion.
|
|
90
|
-
5. **A system box is never an empty panel; it says which silence it is.** Rule
|
|
91
|
-
4 governs rows, and a box is not a row — a heading over nothing asserts that
|
|
92
|
-
something should have been there. See
|
|
93
|
-
[the three states of a system box](#the-three-states-of-a-system-box).
|
|
94
|
-
6. **Order is the toolchain's.** A medium renders boxes, sections and rows in
|
|
95
|
-
the order given.
|
|
96
|
-
|
|
97
|
-
**Embedded models link to documentation in an infobox.** The model Address
|
|
98
|
-
continues to identify the native Item used for compilation. Its displayed link
|
|
99
|
-
uses that same package, type and Shortcode under `note`, which names the
|
|
100
|
-
corresponding documentation journal. A model with no published documentation
|
|
101
|
-
keeps its known name as plain text; the infobox does not substitute an Actor or
|
|
102
|
-
Item link.
|
|
103
|
-
|
|
104
|
-
#### What the note box holds
|
|
105
|
-
|
|
106
|
-
**The note box's fields are the type's own `data:` vocabulary, in its declared
|
|
107
|
-
order** — the very table each `### type:` section below states. There is no
|
|
108
|
-
second list, so a key added to a type appears in the book, on the website and
|
|
109
|
-
in a compendium journal with no further edit. A `Name` row stands first on
|
|
110
|
-
every note, because every note has one.
|
|
111
|
-
|
|
112
|
-
A few keys carry no row, and the reason in each case is one of exactly two —
|
|
113
|
-
the key is **machinery**, steering a build or an interface rather than
|
|
114
|
-
describing the subject, or it is an **image**, which rule 2 keeps out of the
|
|
115
|
-
box:
|
|
116
|
-
|
|
117
|
-
| key | why it carries no row |
|
|
118
|
-
| ------------------------ | ------------------------------------------------ |
|
|
119
|
-
| `templatePriority` | template machinery, not a fact about the subject |
|
|
120
|
-
| `color` | sidebar machinery, not a fact about the subject |
|
|
121
|
-
| the art slots, `overlay` | an image, which rule 2 keeps out of the box |
|
|
122
|
-
|
|
123
|
-
A field whose value is a **mapping** carries no row either — a governance
|
|
124
|
-
ladder or a wall layout has no summary shape — and neither does one the note
|
|
125
|
-
left empty.
|
|
126
|
-
|
|
127
|
-
A being's `age`, `height`, `weight` and `appearance.*` compose into a single
|
|
128
|
-
**Appearance** clause rather than taking six rows of their own: _Age 34, 6′ 1″,
|
|
129
|
-
181 lbs, medium frame, brown eyes_.
|
|
130
|
-
|
|
131
|
-
#### What a system box holds
|
|
132
|
-
|
|
133
|
-
**A system box's fields are that system's own field declaration**, in its
|
|
134
|
-
order. There is no second list here either, so a field added to a type reaches
|
|
135
|
-
the box with no further edit.
|
|
136
|
-
|
|
137
|
-
Two things keep a fact off it:
|
|
138
|
-
|
|
139
|
-
- **A value the declaration would have supplied anyway.** A field answered by
|
|
140
|
-
its own default is a fact about the compiler, not about the note — and that is
|
|
141
|
-
true of the value, wherever it was written. A corpus writes its defaults out:
|
|
142
|
-
`improveFlag: false` and `combatCategory: none` are typed into hundreds of
|
|
143
|
-
notes that mean nothing by them.
|
|
144
|
-
- **A fact the note box already put on the page.** A gear item's weight is
|
|
145
|
-
system-agnostic and belongs to the note. What counts is what the note box
|
|
146
|
-
_shows_, not what its vocabulary declares — the same fact is written under
|
|
147
|
-
`data:` on one note and at its destination path on another, and a system box
|
|
148
|
-
that stood down on the declaration alone would leave it on no surface at all.
|
|
149
|
-
|
|
150
|
-
**A measured quantity carries its unit on its value** — `Price 160d`,
|
|
151
|
-
`Weight 1.1 lbs`. The unit belongs to the quantity rather than to the name of
|
|
152
|
-
the quantity, so it is not folded into the label: `Price (d)` beside `160` makes
|
|
153
|
-
a reader reassemble one fact from two cells, and reads worst in the book, whose
|
|
154
|
-
label column is a narrow small-caps rule. A medium cannot supply it either —
|
|
155
|
-
appending `d` to a price means knowing which row is the price, which is the one
|
|
156
|
-
thing a generic renderer must never know. A row carrying a unit is `text`,
|
|
157
|
-
because a number with a unit is text for presentation.
|
|
158
|
-
|
|
159
|
-
**What a field is called** is a **presentation overlay** on that same
|
|
160
|
-
declaration, per system. It carries two things a compiler's field list cannot,
|
|
161
|
-
because they are about a page rather than about a document: a reader's word
|
|
162
|
-
where the declaration's key is the compiler's — `assocSkillCode` is exactly
|
|
163
|
-
right in a DataModel and wrong in a panel somebody reads — and the handful of
|
|
164
|
-
keys that belong on no page at all, either because the box shows the same fact
|
|
165
|
-
whole somewhere else or because they steer a character sheet rather than
|
|
166
|
-
describe the subject. It is an overlay and not a list: a field it does not
|
|
167
|
-
mention still gets a row, under its own humanised name.
|
|
168
|
-
|
|
169
|
-
Four types are read differently, because their box is derived rather than read
|
|
170
|
-
field by field — a **being**, whose attributes, skills, mystical abilities and
|
|
171
|
-
carried gear are one flat `sohl.items` list; **armour**, whose protection is
|
|
172
|
-
shown for all four aspects with an unstated one rendered `0`; a **weapon**,
|
|
173
|
-
whose strike modes are shown one per line with an unstated value rendered `—`;
|
|
174
|
-
and a **projectile**, whose impact is three declared fields composing into the
|
|
175
|
-
one quantity a reader wants. Both of those placeholders are decided by the
|
|
176
|
-
toolchain and travel as data, so no renderer has to know which field it is
|
|
177
|
-
looking at.
|
|
178
|
-
|
|
179
|
-
A weapon's strike modes are authored either as a **list**, each mode carrying
|
|
180
|
-
its own `shortcode`, or as a **mapping** keyed by the mode's name. Both are
|
|
181
|
-
read, and both yield the same section.
|
|
182
|
-
|
|
183
|
-
#### The three states of a system box
|
|
184
|
-
|
|
185
|
-
A system box says something on every page that carries one. Which of the three
|
|
186
|
-
it says is decided here and travels as the box's `statement`, so a medium draws
|
|
187
|
-
one thing and decides none of it.
|
|
188
|
-
|
|
189
|
-
| state | when | what the box says |
|
|
190
|
-
| -------------- | ---------------------------------------------------------- | --------------------------------------- |
|
|
191
|
-
| content | the system holds something the note box has not shown | its sections, and no `statement` |
|
|
192
|
-
| not available | the system maps this note's type but produced no document | `statement: Not available` |
|
|
193
|
-
| nothing to add | the system produced a document holding nothing new to show | `statement: Nothing beyond the profile` |
|
|
194
|
-
|
|
195
|
-
The third state is not the second. _Not available_ is a statement about this
|
|
196
|
-
note — the system has no document for it, and a reader can act on that. A
|
|
197
|
-
system that _does_ compile the note and holds only facts the note box has
|
|
198
|
-
already given says so instead, because a reader told "not available" would go
|
|
199
|
-
looking for a document that exists, and an empty panel tells them nothing at
|
|
200
|
-
all.
|
|
201
|
-
|
|
202
|
-
A system that does not map the note's type gets **no box**. A box reading
|
|
203
|
-
_Not available_ on every affiliation page would suggest a gap in the note when
|
|
204
|
-
the truth is about the system's scope.
|
|
205
|
-
|
|
206
|
-
Whether a document was produced is the compile's own answer rather than a
|
|
207
|
-
reading of the frontmatter. Three things must hold: the note must carry that
|
|
208
|
-
system's block, its type must map to the system, and it must route to a pack of
|
|
209
|
-
the document class that map names. The block is the first of them because a
|
|
210
|
-
system block is what makes a game document — see
|
|
211
|
-
[A system block is what makes a game document](#a-system-block-is-what-makes-a-game-document).
|
|
212
|
-
A pack declaring **no** system is compiled by the fallback pass, which reads one
|
|
213
|
-
system's block and no other, so the question is the same one whether or not the
|
|
214
|
-
pack answered it.
|
|
215
|
-
|
|
216
|
-
#### Four section layouts
|
|
217
|
-
|
|
218
|
-
A section says how it is arranged, and a renderer switches on that and on a
|
|
219
|
-
value's `kind` — never on a field name, which is what keeps a field list from
|
|
220
|
-
leaking back into a template.
|
|
221
|
-
|
|
222
|
-
| `layout` value | shape | what carries it | where it is used |
|
|
223
|
-
| -------------- | ------------------------------- | --------------- | -------------------------- |
|
|
224
|
-
| `rows` | label/value pairs, one per line | `rows` | a profile |
|
|
225
|
-
| `grid` | short label/value cells | `cells` | attributes, armour aspects |
|
|
226
|
-
| `runin` | groups of comma-joined entries | `groups` | skills, equipment |
|
|
227
|
-
| `list` | one entry per line | `entries` | mystical abilities |
|
|
228
|
-
|
|
229
|
-
| `kind` value | the value is |
|
|
230
|
-
| ------------ | ------------------------------- |
|
|
231
|
-
| `text` | a string |
|
|
232
|
-
| `number` | a number |
|
|
233
|
-
| `link` | `{text, url?, uuid?, address?}` |
|
|
234
|
-
| `links` | a list of those |
|
|
235
|
-
| `list` | a list of strings |
|
|
236
|
-
|
|
237
|
-
A website renders a link by its `url`, a compendium journal by its `uuid`, and
|
|
238
|
-
the book by the `address`, which is the entry's own label in the volume. A
|
|
239
|
-
reference the index could not reach keeps its words rather than being dropped.
|
|
240
|
-
|
|
241
|
-
**Emitted shape**, as a page's front matter carries it:
|
|
242
|
-
|
|
243
|
-
```yaml
|
|
244
|
-
infoboxes:
|
|
245
|
-
- id: note
|
|
246
|
-
kind: note
|
|
247
|
-
title: Profile
|
|
248
|
-
sections:
|
|
249
|
-
- id: profile
|
|
250
|
-
layout: rows
|
|
251
|
-
rows:
|
|
252
|
-
- label: Name
|
|
253
|
-
kind: text
|
|
254
|
-
value: Brànwâal Dôrgaar
|
|
255
|
-
- label: Affiliations
|
|
256
|
-
kind: links
|
|
257
|
-
value:
|
|
258
|
-
- text: The Silent Talon Company
|
|
259
|
-
url: /thalorna/affiliation-slntlncmpny/
|
|
260
|
-
- id: sohl
|
|
261
|
-
kind: system
|
|
262
|
-
system: sohl
|
|
263
|
-
title: SoHL
|
|
264
|
-
available: true
|
|
265
|
-
sections:
|
|
266
|
-
- id: attributes
|
|
267
|
-
label: Attributes
|
|
268
|
-
layout: grid
|
|
269
|
-
cells:
|
|
270
|
-
- label: STR
|
|
271
|
-
value: 14
|
|
272
|
-
- id: skills
|
|
273
|
-
label: Skills
|
|
274
|
-
layout: runin
|
|
275
|
-
groups:
|
|
276
|
-
- label: Combat
|
|
277
|
-
entries:
|
|
278
|
-
- text: Melee 75
|
|
279
|
-
- id: hm3
|
|
280
|
-
kind: system
|
|
281
|
-
system: hm3
|
|
282
|
-
title: HM3
|
|
283
|
-
available: false
|
|
284
|
-
sections: []
|
|
285
|
-
statement: Not available
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
### Frontmatter has three regions
|
|
289
|
-
|
|
290
|
-
A HeroicLands note has opening YAML frontmatter with nonempty `shortcode` and
|
|
291
|
-
`type` values. Other Markdown files are ordinary Markdown. A note's frontmatter
|
|
292
|
-
has three regions:
|
|
293
|
-
|
|
294
|
-
| Region | Describes | Unknown keys |
|
|
295
|
-
| -------------------- | ------------------------------------------------------- | ------------ |
|
|
296
|
-
| **Top level** | The note's identity, name, description, and publication | An error |
|
|
297
|
-
| **`data:`** | The subject itself, shared by systems | An error |
|
|
298
|
-
| **`hm3:` / `sohl:`** | The subject as one system's documents | An error |
|
|
299
|
-
|
|
300
|
-
The complete top-level allowlist, in its required order, is `shortcode`, `name`,
|
|
301
|
-
`type`, `subType`, `description`, `tags`, `data`, `hm3`, `sohl`. Omit fields that
|
|
302
|
-
do not apply. The first fields of a typical note look like this:
|
|
303
|
-
|
|
304
|
-
```yaml
|
|
305
|
-
---
|
|
306
|
-
shortcode: example
|
|
307
|
-
name: { full: Example }
|
|
308
|
-
type: lore
|
|
309
|
-
subType: concept
|
|
310
|
-
description: A short page description.
|
|
311
|
-
tags: [draft]
|
|
312
|
-
data: {}
|
|
313
|
-
sohl: {}
|
|
314
|
-
---
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
Any other top-level key is an error. To keep an author-only note beside the
|
|
318
|
-
content, comment out its YAML lines with `#`. Comments remain valid YAML text
|
|
319
|
-
and do not become data in the JSONL index, generated pages, or Foundry documents.
|
|
320
|
-
There is no custom site metadata. `description` supplies the page's short
|
|
321
|
-
description; `tags` carries publication and classification tags.
|
|
322
|
-
|
|
323
|
-
Arrays and objects use one-line YAML flow form when the complete line is under
|
|
324
|
-
100 characters. At 100 characters or more, they use full block form. A multiline
|
|
325
|
-
flow collection is formatted into one of those forms; folded collections are
|
|
326
|
-
not used. `content-build format --write` applies this rule and the top-level
|
|
327
|
-
order without changing the Markdown body.
|
|
328
|
-
|
|
329
|
-
#### Dates and reckoning markers
|
|
330
|
-
|
|
331
|
-
A calendar names and orders the days within a year. An **era** states where its
|
|
332
|
-
year count begins. Several eras may use one calendar; each has its own origin.
|
|
333
|
-
An era may declare a short, uppercase `marker` for authored dates. The marker
|
|
334
|
-
selects that era and the calendar whose months interpret the date inside it:
|
|
335
|
-
|
|
336
|
-
```yaml
|
|
337
|
-
data:
|
|
338
|
-
born: VR(676/12/5)
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
`VR` is a marker declared on an era row of the Common Calendar. `VR(720/5/14)`
|
|
342
|
-
means its year 720, fifth month, fourteenth day. `VR(720/5)` states month
|
|
343
|
-
precision, and `VR(720)` states year precision. The parser retains that
|
|
344
|
-
precision; it does not invent a first day when the author only knows a year.
|
|
345
|
-
`VR(~-480)` keeps an approximate year. A date whose occurrence is known but
|
|
346
|
-
whose value is not recorded is `unknown`, with no marker.
|
|
347
|
-
|
|
348
|
-
The calendar-neutral spelling is `<year>.<day>[:HHMMSS]`: `720.136` names day
|
|
349
|
-
136 of canonical year 720, and `720.136:143005` adds 14:30:05. The dot means
|
|
350
|
-
**day of year**, not a month. Its day must fit the world's `data.year.days`,
|
|
351
|
-
and its time must fit a 24-hour day. A bare year is also accepted. Unmarked
|
|
352
|
-
slash dates are accepted as calendar-neutral numeric month/day values; their
|
|
353
|
-
month division has no named calendar, so use a marker when the month is part
|
|
354
|
-
of a people's reckoning.
|
|
355
|
-
|
|
356
|
-
An era's `start` locates its first year on the canonical timeline. The marker's
|
|
357
|
-
own start is written canonically, such as `start: 1`, so its definition has no
|
|
358
|
-
circular reference. Another era may place its start using a marker already
|
|
359
|
-
defined in the corpus. A year-precision start anchors the reckoning at the
|
|
360
|
-
first day of that canonical year for conversion; it does not date the event
|
|
361
|
-
that gave the era its name. A marker must be unique across the corpus and contain
|
|
362
|
-
uppercase letters or digits, beginning with a letter. An optional `end` bounds
|
|
363
|
-
dates in that era; a year-precision end includes the whole named year.
|
|
364
|
-
|
|
365
|
-
**`data:` is closed.** It holds the type-specific facts about the
|
|
366
|
-
subject — a weapon's weight, an affliction's transmission, a being's species —
|
|
367
|
-
and every note type declares which keys it may carry. A misspelled key is a
|
|
368
|
-
finding in either region.
|
|
369
|
-
|
|
370
|
-
Every note type may put a pinned document id in `data.id`, a shared compendium
|
|
371
|
-
route in `data.pack`, a shared folder Address in `data.packFolder`, and source
|
|
372
|
-
details in `data.harnworld`. A system block's `pack` or `packFolder` overrides
|
|
373
|
-
the shared value for that system. A `being` may also describe its social profile
|
|
374
|
-
in `data.social`.
|
|
375
|
-
|
|
376
|
-
**Tags are open, except the ones that classify.** A tag naming a theme or a region — `underworld`,
|
|
377
|
-
`byzaria`, `riverlands` — is the author's own and this build has no opinion about it.
|
|
378
|
-
A tag that classifies the subject is different, because something queries it. A
|
|
379
|
-
settlement tagged `village` appears in the list of villages and an untagged one
|
|
380
|
-
does not, so `vilage` does not merely look wrong: it removes the note from an
|
|
381
|
-
index, silently, and the index still renders. That is the same failure a
|
|
382
|
-
misspelled `data:` key is, and it gets the same answer — the vocabulary
|
|
383
|
-
is declared, so a near miss is a finding that names what you probably meant.
|
|
384
|
-
|
|
385
|
-
| group | applies to | tags |
|
|
386
|
-
| ------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
387
|
-
| **place kind** | `place` / `settlement` | `city`, `city-state`, `town`, `village`, `settlement`, `port`, `fortress`, `citadel`, `castle`, `stronghold`, `garrison`, `camp`, `oasis`, `waypoint`, `post`, `precinct`, `district`, `necropolis`, `hall`, `capital` |
|
|
388
|
-
| **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` |
|
|
389
|
-
| **place scale** | `place` / `region` | `continent` |
|
|
390
|
-
| **being station** | `being` | `tradesfolk`, `common-folk`, `soldiery`, `administration`, `clergy`, `mages`, `underworld`, `dependents`, `guilded`, `unguilded` |
|
|
391
|
-
| **being kind** | `being` | `character`, `creature` |
|
|
392
|
-
| **state** | any | `draft` |
|
|
393
|
-
|
|
394
|
-
**`draft` is the one tag either build reads.** It does two things and nothing
|
|
395
|
-
else: a link _into_ a draft note renders marked, and the note itself states that
|
|
396
|
-
it is unfinished wherever a reader meets it.
|
|
397
|
-
|
|
398
|
-
A link into it renders as
|
|
399
|
-
`<span class="sohl-draft-link" title="Draft — not yet written">…</span>` in a
|
|
400
|
-
compiled journal and on the website alike, with the appearance supplied by the
|
|
401
|
-
consuming system's stylesheet or the site theme.
|
|
402
|
-
|
|
403
|
-
The note's own statement is the **draft notice**, and it reads the same on every
|
|
404
|
-
surface:
|
|
405
|
-
|
|
406
|
-
> **Draft.** This entry is unfinished. What it states may change, and nothing in
|
|
407
|
-
> it is settled.
|
|
408
|
-
|
|
409
|
-
It leads the entry — above the infobox and above the prose — because a reader
|
|
410
|
-
deciding whether to rely on an entry has to be told before they read it. Where
|
|
411
|
-
it lands:
|
|
412
|
-
|
|
413
|
-
| Surface | What carries it |
|
|
414
|
-
| ------------ | -------------------------------------------------------------------- |
|
|
415
|
-
| JournalEntry | a `blockquote.sohl-draft-notice` at the head of the first page |
|
|
416
|
-
| Actor | the dossier the sheet draws — SoHL's `dossier`, HM3's `biography` |
|
|
417
|
-
| The book | a drawn mark and a left rule between the entry's plate and its panel |
|
|
418
|
-
| The website | the theme's own notice, above the infobox rail and the prose |
|
|
419
|
-
|
|
420
|
-
**An Actor carries it once.** A being's prose reaches two fields, and a document
|
|
421
|
-
saying the same thing twice teaches a reader to skip it — a printed sheet draws
|
|
422
|
-
both in turn — so the dossier carries the notice and the appearance is
|
|
423
|
-
untouched. The two systems pair by the authored section each field reads rather
|
|
424
|
-
than by its name, so `dossier` and `biography` carry it while `appearance` and
|
|
425
|
-
`description` do not.
|
|
426
|
-
|
|
427
|
-
**The mark is a notice, not a hazard** — `fa-circle-exclamation`, which Foundry
|
|
428
|
-
bundles, so a journal page carries it with no module stylesheet loaded. The book
|
|
429
|
-
draws its own, because a consumer declaring no icon family is the ordinary case
|
|
430
|
-
and a glyph a face lacks is silent in Typst. Stripped of mark and rule the
|
|
431
|
-
sentence still states the whole thing, which is the floor every surface is built
|
|
432
|
-
to.
|
|
433
|
-
|
|
434
|
-
**Nothing else changes**: the note compiles, validates, publishes and resolves
|
|
435
|
-
exactly as any other, and it stays in the packs, in the link manifest and on the
|
|
436
|
-
site. That is what separates the tag from the retired `draft:` field, whose whole
|
|
437
|
-
effect was to move a note from published to unresolvable without saying so — and
|
|
438
|
-
which is refused, by name, if you write it.
|
|
439
|
-
|
|
440
|
-
**The group's scope is what makes the check work.** A group names the types it
|
|
441
|
-
applies to, and a place's kinds are only ever checked on a place. Without that
|
|
442
|
-
the rule is wrong on every note it touches: `azravan` on a faith, `barter` on an
|
|
443
|
-
economy note and `secret` on three lore notes each sit a typo's distance from
|
|
444
|
-
`caravan`, `border` and `sacred`, and none of them is a mistake. Scoped, the same
|
|
445
|
-
corpus reports nothing — while a settlement tagged `vilage` is still caught, even
|
|
446
|
-
beside a correct `town`.
|
|
447
|
-
|
|
448
|
-
**Kind and character are separate because one slot could not hold both.** A
|
|
449
|
-
fishing village is a `village` that is `fishing`, and the field this list
|
|
450
|
-
replaced had to spell that `Fishing Village` as a value of its own — which is why
|
|
451
|
-
a query for villages found two of the eleven that existed.
|
|
452
|
-
|
|
453
|
-
**A station is not a rank.** Which kind of body a person belongs to — the
|
|
454
|
-
clergy, the soldiery, the tradesfolk — is a different axis from where they stand
|
|
455
|
-
inside one, which is what `data.lore` carries by naming the rank. A tag holds the
|
|
456
|
-
first because a person may be several at once and because nothing ranks
|
|
457
|
-
`clergy` against `mages`.
|
|
458
|
-
|
|
459
|
-
**A continent is a region carrying a tag, not a subtype**, because structurally it
|
|
460
|
-
is a region: the same fields, the same parent chain, everything but scale.
|
|
461
|
-
|
|
462
|
-
#### A being's kind is the one tag group that is a slot
|
|
463
|
-
|
|
464
|
-
A person and a beast carry very different amounts of data, and a being note says
|
|
465
|
-
which it is with a tag rather than leaving it to be guessed from how much the
|
|
466
|
-
note holds.
|
|
467
|
-
|
|
468
|
-
| `beingKind` value | the subject is |
|
|
469
|
-
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
470
|
-
| `character` | a person — someone with a name, a station, a history, and the attributes, skills and carried gear that go with living a life |
|
|
471
|
-
| `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 |
|
|
472
|
-
|
|
473
|
-
**The slot is filled once, or not at all.** The two are alternative answers to
|
|
474
|
-
one question, so a note carrying both has answered it twice and every reader of
|
|
475
|
-
the tag gets to pick; that is refused as an error naming the note. A being
|
|
476
|
-
carrying **neither** is not a finding. The kind is authored deliberately, and
|
|
477
|
-
nothing can tell a being nobody has classified from one whose author means to
|
|
478
|
-
leave it unclassified — a tree part-way through tagging is a tree with untagged
|
|
479
|
-
beings in it, and failing its build would be a rule about the schedule rather
|
|
480
|
-
than about the content.
|
|
481
|
-
|
|
482
|
-
**Nothing else fills the slot, and nothing else is refused for failing to.** A
|
|
483
|
-
being tagged `undead` or `beast-of-burden` is describing the subject in the
|
|
484
|
-
author's own words, in a region that is open, and this build has no standing to
|
|
485
|
-
take that back. What is refused is a near miss of one of the two — `charcter`
|
|
486
|
-
drops the note out of every query for characters while the list still renders —
|
|
487
|
-
and the two of them together. Those are the only two refusals a closed tag
|
|
488
|
-
vocabulary can make here, and both are errors, so a build carrying one fails.
|
|
489
|
-
|
|
490
|
-
**The same two words name two other things, and neither is this.** `hm3.type`
|
|
491
|
-
takes `character` or `creature` as the HM3 _document_ a being compiles to, in a
|
|
492
|
-
system block rather than in `tags:`. Both are also **retired note types**, so a
|
|
493
|
-
note writing `type: character` is refused by name and told to write `being`.
|
|
494
|
-
Three namespaces, one pair of words; the tag is the system-agnostic one, and it
|
|
495
|
-
is the only one that describes the subject rather than a document.
|
|
496
|
-
|
|
497
|
-
**Neither the book nor the website branches on the kind.** One flow serves both
|
|
498
|
-
— the authored image, then the infobox, then the prose — and a creature's box is
|
|
499
|
-
simply shorter, because it has less in it. A section with nothing to put in it is
|
|
500
|
-
not emitted at all, so a creature with no equipment has no `EQUIPMENT` heading
|
|
501
|
-
rather than an empty one, exactly as rule 4 of [the infobox](#the-infobox) drops
|
|
502
|
-
a row with no value. The tag classifies the subject, which is what makes a list
|
|
503
|
-
of the creatures in a setting a thing a query can ask for; it is not an
|
|
504
|
-
instruction to a renderer.
|
|
505
|
-
|
|
506
|
-
The mapping tables below describe the **document** destinations. A key that
|
|
507
|
-
appears in no table still reaches the web page; it simply reaches no Foundry
|
|
508
|
-
document.
|
|
509
|
-
|
|
510
|
-
**The shared source is a default; the system block overrides it.** Where a field
|
|
511
|
-
can be reached two ways — `data.weight` through the mapping table, or
|
|
512
|
-
`sohl.system.weightBase` written directly — the value in the system block wins.
|
|
513
|
-
`data:` says what is true of the thing in general; a system says what is true of
|
|
514
|
-
it _there_, and a system that disagrees is not in error. A weapon weighs what
|
|
515
|
-
`data.weight` says unless SoHL says otherwise, and then SoHL is right for SoHL.
|
|
516
|
-
|
|
517
|
-
This is the same rule as `hm3.type` overriding a derived document type, applied
|
|
518
|
-
to fields: derive from the shared source, and let the system state the exception.
|
|
519
|
-
|
|
520
|
-
**A shared source and an in-block key are separate declarations.**
|
|
521
|
-
`data.gender` is the shared source for HM3 gender; `hm3.gender` is its
|
|
522
|
-
in-block spelling, and `hm3.system.gender` is the native destination.
|
|
523
|
-
The native destination wins, followed by the in-block value and the shared
|
|
524
|
-
source. Field declarations name both source and destination explicitly.
|
|
525
|
-
|
|
526
|
-
**A field without a shared source reads its system declaration.** An
|
|
527
|
-
`affiliation` item's `system.title` is the style of address an office carries;
|
|
528
|
-
the note's heading reads `name.full`. The field reads
|
|
529
|
-
`<system>.system.<field>` and the in-block position, which describe the
|
|
530
|
-
document. Native species is independent of the shared lore Address in `data.species`.
|
|
531
|
-
`subType` has the same meaning at both levels.
|
|
532
|
-
|
|
533
|
-
**Some of a system's fields are runtime state, and a note may not write any of
|
|
534
|
-
them.** A data model declares everything a document stores, and part of that is
|
|
535
|
-
what _play_ writes: an affliction's `onsetDate` is the world time its onset
|
|
536
|
-
fired at. Writing `<system>.system.<field>` reaches such a field as directly as
|
|
537
|
-
any other — the block is a verbatim passthrough, and the field really is in the
|
|
538
|
-
schema — so a note left free to stamp one ships that world's play state to
|
|
539
|
-
every world that installs the compiled pack.
|
|
540
|
-
|
|
541
|
-
So a field the document writes for itself is **declared as such**, and the
|
|
542
|
-
declaration says both halves of the fact: authoring it is an error naming the
|
|
543
|
-
note, the key and what the field holds, and the compiled document leaves the key
|
|
544
|
-
out entirely so the data model's own initial value stands. This is a property of
|
|
545
|
-
the field rather than a list of names, so it covers every such field a system
|
|
546
|
-
adds later. The per-type tables below never list one — they are the vocabulary
|
|
547
|
-
an author writes — and each type that has any names them under its table.
|
|
548
|
-
|
|
549
|
-
**An `Address` becomes a `Shortcode` where the target field expects one.** SoHL
|
|
550
|
-
resolves a stored cross-reference among the items one actor holds, and packages do
|
|
551
|
-
not exist there, so the field holds a `Shortcode`. Four `affiliation` fields are
|
|
552
|
-
authored as Addresses and stored that way: `data.seat` becomes `system.seat`,
|
|
553
|
-
`data.parents` becomes `system.parents`, `data.domains` becomes `system.domain`,
|
|
554
|
-
and each authored key of `data.relations` becomes a key of `system.relations`. The
|
|
555
|
-
reduction happens at build time, and two things are errors naming the note: an
|
|
556
|
-
Address whose type the field does not accept, and two Addresses that reduce to one
|
|
557
|
-
shortcode, which the field cannot hold both of.
|
|
558
|
-
|
|
559
|
-
**A SoHL skill Address becomes a native Item UUID.** An affiliation's
|
|
560
|
-
`sohl.system.commonSkills` names skills in its own package or a declared
|
|
561
|
-
dependency. Its `system.commonSkills` contains the exact published Item UUIDs,
|
|
562
|
-
including each foreign module's own pack name. Native skill lookup uses those
|
|
563
|
-
UUIDs; the journal, website and book use documentation links for presentation.
|
|
564
|
-
|
|
565
|
-
**A `Code` suffix marks a different thing.** `system.assocSkillCode`,
|
|
566
|
-
`system.assocAffiliationCode` and `system.parentSkillCode` hold a `Shortcode` at
|
|
567
|
-
both ends — each is authored as one, under `<system>.system`, and emitted verbatim
|
|
568
|
-
— so no Address is reduced into them and none of them has a shared source. A value
|
|
569
|
-
carrying a separator is refused there by the shortcode charset rather than read as
|
|
570
|
-
an Address. `data.assocSkill`, `data.assocAffiliation` and `data.parentSkill` are
|
|
571
|
-
properties of the **page**: each is an Address naming the note an infobox row
|
|
572
|
-
links to, and none reaches a compiled document.
|
|
573
|
-
|
|
574
|
-
### The note vocabulary, and how it maps
|
|
575
|
-
|
|
576
|
-
A note has its own `type` and `subType`. **This is a third vocabulary** — not
|
|
577
|
-
SoHL's and not HM3's. It says what the note _is_ and the scope it covers,
|
|
578
|
-
irrespective of whether it becomes an Actor, an Item, a JournalEntry or nothing
|
|
579
|
-
at all in any particular system.
|
|
580
|
-
|
|
581
|
-
Each system then declares a map from the note's `(type, subType)` onto its own
|
|
582
|
-
document type — and, for SoHL, its own `system.subType`. The map is **declared**,
|
|
583
|
-
never inferred from a coincidence of names: `skill`, `weapongear`, `armorgear`,
|
|
584
|
-
`containergear` and `miscgear` exist in both systems with _different_ data
|
|
585
|
-
models, so name-matching there would not fail, it would succeed wrongly.
|
|
586
|
-
|
|
587
|
-
Because the map derives the document type, `hm3.type` and `sohl.system.subType`
|
|
588
|
-
are **overrides**, not required declarations. A note states them only when the
|
|
589
|
-
map cannot decide, or decides wrongly.
|
|
590
|
-
|
|
591
|
-
**Four rows cannot decide, and there `hm3.type` is required.** HM3 splits four
|
|
592
|
-
of the note vocabulary's types across several documents — `mysticalability` into
|
|
593
|
-
a `psionic`, a `spell` or an `invocation`; `trauma` into an `injury` or a
|
|
594
|
-
`trait`; `weapongear` into a `weapongear` or a `missilegear`; `being` into a
|
|
595
|
-
`character` or a `creature`. Nothing in the note's own vocabulary partitions
|
|
596
|
-
cleanly onto any of those splits, so the note says which, in its own block, and
|
|
597
|
-
a note that says nothing is **an error naming the note and listing the permitted
|
|
598
|
-
values**. It is never defaulted: a default would pick one and be right about
|
|
599
|
-
half the time.
|
|
600
|
-
|
|
601
|
-
A consequence worth knowing before you author a being's embedded items: a
|
|
602
|
-
`(type, shortcode)` reference has no block of its own to read a discriminator
|
|
603
|
-
from, so it **cannot address a one-to-many type**. `[[weapongear-spear]]` names
|
|
604
|
-
no single HM3 document, and the reference is refused rather than resolved to
|
|
605
|
-
whichever came first.
|
|
606
|
-
|
|
607
|
-
A note whose `(type, subType)` has no mapping for a system it carries a block
|
|
608
|
-
for is an **error naming the note**, never a silent skip and never a guess at the
|
|
609
|
-
first matching value. Where a mapping is missing for a whole class of note, that
|
|
610
|
-
is a gap in the vocabulary rather than something to write into every note: an
|
|
611
|
-
override that thousands of notes need is a missing subType value.
|
|
612
|
-
|
|
613
|
-
**A type's `subType` values are stated in one shape**, so that they can be read
|
|
614
|
-
and compared to the vocabulary that enforces them. Under the type's
|
|
615
|
-
heading, write `**subType**:` on a line of its own, then one bullet per value —
|
|
616
|
-
`- <value>` or `- <value>: <definition>` — and nothing else between the marker
|
|
617
|
-
and the list. A type that has no `subType`, or whose values are not enumerated
|
|
618
|
-
yet, writes no marker at all. Any other spelling is a build error naming the
|
|
619
|
-
line: five were in use, and a reader that accepted them all would keep accepting
|
|
620
|
-
the sixth by reading a section as declaring nothing, which is exactly the drift
|
|
621
|
-
the comparison exists to catch.
|
|
622
|
-
|
|
623
|
-
### Mappings every type shares
|
|
624
|
-
|
|
625
|
-
Eight rows were identical in all sixteen tables below, so they are stated once
|
|
626
|
-
here and omitted there. Each per-type table shows only what is particular to that
|
|
627
|
-
type.
|
|
628
|
-
|
|
629
|
-
| shared source | → sohl | → hm3 |
|
|
630
|
-
| ----------------------- | ------------------------- | ---------------------------- |
|
|
631
|
-
| `name.full` | `name` | `name` |
|
|
632
|
-
| `data.icon` | `img` | `img` |
|
|
633
|
-
| `data.id` | `_id` | `_id` |
|
|
634
|
-
| `data.packFolder` | `folder` | `folder` |
|
|
635
|
-
| `shortcode` | `system.shortcode` | NA |
|
|
636
|
-
| `data.templatePriority` | `system.templatePriority` | `flags.hm3.templatePriority` |
|
|
637
|
-
| `actionDefs` | `system.actionDefs` | NA |
|
|
638
|
-
| `notes` | `system.notes` | `system.notes` |
|
|
639
|
-
|
|
640
|
-
**A column reads NA wherever the type produces no document in that system.** An
|
|
641
|
-
`affiliation` has no HM3 form, so its whole HM3 column is NA; `armorlocation` has
|
|
642
|
-
no SoHL form, so its SoHL column is. Nothing else varies — which is why these
|
|
643
|
-
rows are worth stating once: repeated sixteen times they buried the differences
|
|
644
|
-
that matter.
|
|
645
|
-
|
|
646
|
-
**`data.icon` maps onto a document that has one, and not every type's does.**
|
|
647
|
-
The row is shared because the key is legal on every note whatever its type, not
|
|
648
|
-
because every document carries artwork: `doc`, `place`, `lore` and `scenario`
|
|
649
|
-
compile into a JournalEntry, which has no image of any kind, and a `folder`
|
|
650
|
-
compiles into a Foundry `Folder`, which has none either. A `homepage` compiles
|
|
651
|
-
into no compendium document at all. On any of those the mapping has no
|
|
652
|
-
destination, so the resolved value is simply dropped — the note validates, the
|
|
653
|
-
tree compiles, and the value goes nowhere.
|
|
654
|
-
|
|
655
|
-
**What a note writes is an address; what a document carries is a path.**
|
|
656
|
-
`data.icon` is an `Address` defaulting to type `icon`, so a being writes
|
|
657
|
-
`icon: anvil` and never a file name. The compiler resolves that address to the
|
|
658
|
-
file the owning package ships and writes the resulting path into `img`. The two
|
|
659
|
-
sides of this row are therefore in two different currencies, which is why the
|
|
660
|
-
row reads `data.icon → img` rather than naming one thing twice.
|
|
661
|
-
|
|
662
|
-
Since a dropped value looks exactly like a value never written, the frontmatter
|
|
663
|
-
lint **reports it**: an art key authored on a type whose passes emit none is a
|
|
664
|
-
warning naming the note and the key. A warning rather than an error, because the
|
|
665
|
-
note still compiles correctly and the key is not certainly unwanted — a note's
|
|
666
|
-
top level is the generated page's front matter as well, so a site template may
|
|
667
|
-
read there what no document carries. `null` is never reported: that is the
|
|
668
|
-
blessed way to say "this note names no art", and on a type with no art it is a
|
|
669
|
-
true and harmless thing to say.
|
|
670
|
-
|
|
671
|
-
Which types those are is **not a list**. It is asked of the passes: a type routes
|
|
672
|
-
to a document, a document to the pass that compiles it, and each pass declares
|
|
673
|
-
the art it writes (`emitsArt`). A second table of iconless types would be a table
|
|
674
|
-
free to drift from what is actually emitted, which is the defect rather than the
|
|
675
|
-
check.
|
|
676
|
-
|
|
677
|
-
#### The four art slots
|
|
678
|
-
|
|
679
|
-
A note declares the art its document needs, and every other image in it is
|
|
680
|
-
inline. All four slots are ordinary `Address` fields declaring a default type,
|
|
681
|
-
exactly as `seat` declares `place` — a bare shortcode takes its type from the
|
|
682
|
-
declaration, and qualification climbs the same short-form ladder every other
|
|
683
|
-
link uses.
|
|
684
|
-
|
|
685
|
-
| field | default type | accepts | resolves into | on |
|
|
686
|
-
| ----------- | ------------ | --------------- | ---------------------------- | ----------------------------------------- |
|
|
687
|
-
| `icon` | `icon` | `icon`, `image` | `document.img` | every Actor/Item type, and embedded items |
|
|
688
|
-
| `tokenIcon` | `icon` | `icon`, `image` | `prototypeToken.texture.src` | Actor types |
|
|
689
|
-
| `bgImage` | `image` | `icon`, `image` | `background.src` | map types |
|
|
690
|
-
| `banner` | `image` | `icon`, `image` | the site hero image | any note; not a Foundry field |
|
|
691
|
-
|
|
692
|
-
All four are authored under `data:`:
|
|
693
|
-
|
|
694
|
-
```yaml
|
|
695
|
-
data:
|
|
696
|
-
icon: anvil
|
|
697
|
-
banner: packagebuild-none-image-skillbnr
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
**Every slot accepts a second type beyond the one it defaults to**, and the two
|
|
701
|
-
defaults pair with the same accepted set: `icon` and `tokenIcon` default to
|
|
702
|
-
`icon`, `bgImage` and `banner` default to `image`, and all four accept `icon`
|
|
703
|
-
**and** `image` regardless of which one they default to. A bare shortcode still
|
|
704
|
-
takes the slot's default — `icon: anvil` above names an `icon`, not an `image`
|
|
705
|
-
— and it is a **qualified** value, one that states its own type, that the
|
|
706
|
-
accepted set governs. `sohl-kethira-basic` writes an `icon` slot this way
|
|
707
|
-
twenty times, once per faith tradition, because a deity's profile art is a full
|
|
708
|
-
illustration rather than a game icon:
|
|
709
|
-
|
|
710
|
-
```yaml
|
|
711
|
-
data:
|
|
712
|
-
icon: image-kpagrik
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
That value is accepted because `image` is in the slot's accepted set, not
|
|
716
|
-
because it matches the default — `icon` still defaults to `icon`. `audio` is
|
|
717
|
-
refused at all four slots: a sound is not art, and a value naming one — `icon:
|
|
718
|
-
audio-thunder`, say — is an error naming the set the slot accepts, never a
|
|
719
|
-
silent fall back to the note's default art.
|
|
720
|
-
|
|
721
|
-
**These properties are legal on _every_ note**, whatever its type. `data:` is a
|
|
722
|
-
closed container and the per-type vocabularies below are the only lists there
|
|
723
|
-
are, so a key legal everywhere is stated once here rather than repeated in
|
|
724
|
-
twenty-five tables where the one that was mistyped would be the one nobody
|
|
725
|
-
noticed:
|
|
726
|
-
|
|
727
|
-
| shared `data` property | Values | Description |
|
|
728
|
-
| ---------------------- | ---------------------- | -------------------------------------------------------------------- |
|
|
729
|
-
| `id` | `string` | A pinned document identity. |
|
|
730
|
-
| `pack` | `string` | The shared compendium route. |
|
|
731
|
-
| `packFolder` | `Address` or pack map | The shared compendium folder Address. |
|
|
732
|
-
| `harnworld` | `Map<string, unknown>` | HârnWorld source details. |
|
|
733
|
-
| `icon` | `Address` | The document's profile art — an `icon` address, resolved into `img`. |
|
|
734
|
-
| `banner` | `Address` | The page's hero image — an `image` address. |
|
|
735
|
-
|
|
736
|
-
`icon` is legal everywhere because most types compile into a document that
|
|
737
|
-
carries one; where a type's passes emit none, the lint says so and the value is
|
|
738
|
-
left alone, because a page template may still read it. `banner` reaches no
|
|
739
|
-
compiled document at all, and a page is what every note publishes.
|
|
740
|
-
|
|
741
|
-
The other two are narrower, and each type's own table names them: `tokenIcon`
|
|
742
|
-
on the Actor types, `bgImage` on a map.
|
|
743
|
-
|
|
744
|
-
**A portrait is not one of them.** A picture of the subject is a picture, so it
|
|
745
|
-
is authored in the prose that describes the subject, as an ordinary embedded
|
|
746
|
-
image — see [the lead image](#the-lead-image). A slot exists for art a
|
|
747
|
-
_document field_ needs; nothing else earns one.
|
|
748
|
-
|
|
749
|
-
**A reference carries no slashes and no extension.** That is what keeps it from
|
|
750
|
-
being read as a file name, and it is checked: a value shaped like a pathname is
|
|
751
|
-
an error naming the note and the key.
|
|
752
|
-
|
|
753
|
-
`tokenIcon` unset defaults to `icon`. A token has to read at grid scale and when
|
|
754
|
-
a map is zoomed out, so it is an `icon` rather than an `image`, and its fallback
|
|
755
|
-
has to be `icon`-typed too.
|
|
756
|
-
|
|
757
|
-
Actor types (`being`, `vehicle`) therefore add one row:
|
|
758
|
-
|
|
759
|
-
| shared source | → sohl | → hm3 |
|
|
760
|
-
| ---------------- | ---------------------------- | ---------------------------- |
|
|
761
|
-
| `data.tokenIcon` | `prototypeToken.texture.src` | `prototypeToken.texture.src` |
|
|
762
|
-
|
|
763
|
-
An actor's two pieces of art are two different questions. `data.icon` is the
|
|
764
|
-
profile art — what a directory listing shows beside the name and what the sheet
|
|
765
|
-
header carries. `data.tokenIcon` is what a token on the canvas wears. Both are
|
|
766
|
-
addresses and both resolve the same way; only the destination differs. An Item
|
|
767
|
-
has one piece of art, so this row applies to actor types alone.
|
|
768
|
-
|
|
769
|
-
**A `data:` source is still read at the top level, for now.** `data:` did
|
|
770
|
-
not invent the facts it holds — it gathered them out of the top level — so every
|
|
771
|
-
key it collected has a **pre-`data:` spelling** that is read after the declared
|
|
772
|
-
one and reported as retiring. This is the shared level's counterpart to the
|
|
773
|
-
in-block `<system>.<key>` retirement, and the two are separate: a note may have
|
|
774
|
-
moved one and not the other.
|
|
775
|
-
|
|
776
|
-
**No image key is one of those.** A top-level `img:` is not authored at all:
|
|
777
|
-
`data.icon` is a different key holding a different kind of value, and a note
|
|
778
|
-
writing `img:` is naming a file where an address belongs. `portrait:` is not authorable at any level; a portrait is an embed in the body.
|
|
779
|
-
|
|
780
|
-
**Two of the eight are Item-only in SoHL.** `actionDefs` and `notes` are declared
|
|
781
|
-
on every SoHL Item subtype and on no SoHL Actor, so on a `being` or a `vehicle`
|
|
782
|
-
the SoHL column of both reads NA.
|
|
783
|
-
|
|
784
|
-
`notes` is also the one row that is emitted rather than mapped: SoHL writes
|
|
785
|
-
`system.notes` empty on every Item, and no note-level key fills it yet. The row
|
|
786
|
-
states where such a key would land, which is what makes `armorlocation`'s
|
|
787
|
-
exception below sayable at all.
|
|
788
|
-
|
|
789
|
-
**A third SoHL Item mapping has no shared source, so it is not a row.** SoHL
|
|
790
|
-
writes `system.docHtml` on every Item from the note's own prose — the UUID of the
|
|
791
|
-
JournalEntry that prose compiled into, which is derived rather than authored.
|
|
792
|
-
HM3's data model has nowhere to put such a pointer, so the same prose reaches an
|
|
793
|
-
HM3 item only as its own journal.
|
|
794
|
-
|
|
795
|
-
**One exception.** HM3's `armorlocation` declares no `notes` — it is the one
|
|
796
|
-
subtype that extends the Foundry base directly with no templates — so `notes` is
|
|
797
|
-
NA on both sides for that type, and its table says so.
|
|
798
|
-
|
|
799
|
-
**And one divergence, tracked rather than specified away.** HM3 records a
|
|
800
|
-
template priority on an Actor and not on an Item: `hm3/actors.mjs` writes
|
|
801
|
-
`flags.hm3.templatePriority`, and HM3's Item pass writes no equivalent, so an HM3
|
|
802
|
-
item compiled from a template note loses the fact that it is one
|
|
803
|
-
The row states the mapping the format makes;
|
|
804
|
-
the gap is in the pass, not in the table.
|
|
805
|
-
|
|
806
|
-
#### A pathname names the package that owns the file
|
|
807
|
-
|
|
808
|
-
A note names a file once, and four surfaces have to serve it: a Foundry
|
|
809
|
-
install, this repository's own working tree, the website, and the book. Each
|
|
810
|
-
addresses the same file differently, so an authored pathname is a **statement
|
|
811
|
-
of ownership** and every surface derives its own address from it.
|
|
812
|
-
|
|
813
|
-
**The first segment names the package, when an `assets/` follows it.**
|
|
814
|
-
Everything after that `assets/` is the _suffix_ — the path inside the package's
|
|
815
|
-
shipped tree, and the one piece every derived form is built from. A pathname
|
|
816
|
-
that does not open `<package>/assets/` belongs to the package being compiled,
|
|
817
|
-
and the whole of it is the suffix.
|
|
818
|
-
|
|
819
|
-
```markdown
|
|
820
|
-
 <!-- this package's -->
|
|
821
|
-
 <!-- the sohl package's -->
|
|
822
|
-
```
|
|
823
|
-
|
|
824
|
-
The four forms, for a `thalorna` note (the `thalorna` package ships as the
|
|
825
|
-
Foundry module `sohl-thalorna`) writing `images/map.webp`:
|
|
826
|
-
|
|
827
|
-
| Surface | Address |
|
|
828
|
-
| ----------- | -------------------------------------------------------- |
|
|
829
|
-
| **Foundry** | `modules/sohl-thalorna/assets/images/map.webp` |
|
|
830
|
-
| **Local** | `assets/images/map.webp` |
|
|
831
|
-
| **Web** | `https://cdn.heroiclands.org/thalorna/images/map.webp` |
|
|
832
|
-
| **Book** | `assets/images/map.webp`, staged beside the Typst source |
|
|
833
|
-
|
|
834
|
-
And for the same note writing `sohl/assets/icons/noun/shield.svg`, a file the
|
|
835
|
-
system ships and this repository does not hold:
|
|
836
|
-
|
|
837
|
-
| Surface | Address |
|
|
838
|
-
| ----------- | --------------------------------------------------------- |
|
|
839
|
-
| **Foundry** | `systems/sohl/assets/icons/noun/shield.svg` |
|
|
840
|
-
| **Local** | `assets/icons/noun/shield.svg`, in the `sohl` repository |
|
|
841
|
-
| **Web** | `https://cdn.heroiclands.org/sohl/icons/noun/shield.svg` |
|
|
842
|
-
| **Book** | not carried — a build stages only what this package ships |
|
|
843
|
-
|
|
844
|
-
**The package's name is not its Foundry id.** `thalorna` is what the content is
|
|
845
|
-
called, what a note writes, and what the website serves it under.
|
|
846
|
-
`sohl-thalorna` is what Foundry installs the module as, and it appears in the
|
|
847
|
-
Foundry form alone. The two words are the same for `sohl` and for `hm3`, which
|
|
848
|
-
is exactly why they are kept apart here.
|
|
849
|
-
|
|
850
|
-
Which packages a build can resolve is derived from its configuration: its own
|
|
851
|
-
`contentPackage`, every game system it compiles content for (`systems:`,
|
|
852
|
-
`packs[].system`, `requiresSystem`, `relationships.systems`), and every package
|
|
853
|
-
it declares under `relationships`, whose `contentPackage` names the content
|
|
854
|
-
where that differs from the Foundry id. A pathname naming a package the build
|
|
855
|
-
does not know still resolves on the website and in the book — those need only
|
|
856
|
-
the name and the suffix — and has no Foundry address, which is refused rather
|
|
857
|
-
than guessed.
|
|
858
|
-
|
|
859
|
-
An art package that replaces another package's images can declare
|
|
860
|
-
`packageBuild.assets[].bindsTo` on the relevant asset copy. Each file's basename
|
|
861
|
-
then acts as a shortcode for the declared package and type. The related
|
|
862
|
-
`relationships.requires[].contentIndex` supplies the address index; with that
|
|
863
|
-
index enabled, an asset with no matching note is a located build error. See
|
|
864
|
-
[asset bindings](configuration.md#packagebuildstagedir-and-packagebuildassets)
|
|
865
|
-
for the configuration and cache requirements.
|
|
866
|
-
|
|
867
|
-
**The host is configuration.** `site.assets` in `package-build.config.yaml` is
|
|
868
|
-
the root the web form is joined onto, and a package-owned image on a page with
|
|
869
|
-
none set is an error naming that key.
|
|
870
|
-
|
|
871
|
-
**A pathname naming no package at all passes through on every surface** — an
|
|
872
|
-
absolute URL, a `data:` URI, a protocol-relative `//host/…`, or a `/`-rooted
|
|
873
|
-
path, which Foundry serves from its data root. That is how a note addresses
|
|
874
|
-
core Foundry art (`/icons/svg/mystery-man.svg`) or a package this build knows
|
|
875
|
-
nothing of (`/systems/dnd5e/icons/spell.webp`).
|
|
876
|
-
|
|
877
|
-
**A `systems/…` or `modules/…` pathname is refused**, with a finding naming the
|
|
878
|
-
replacement. It is a Foundry address written where an ownership statement
|
|
879
|
-
belongs: it resolves for Foundry and for nothing else, because neither the
|
|
880
|
-
website nor the book has any such directory. `systems/sohl/assets/ui/logo.webp`
|
|
881
|
-
is written `sohl/assets/ui/logo.webp`, and `systems/hm3/images/svg/sword.svg` —
|
|
882
|
-
where the package serves its pictures from its own root — is written
|
|
883
|
-
`hm3/assets/images/svg/sword.svg`, because `assets/` is where a package's files
|
|
884
|
-
sit in every derived form.
|
|
885
|
-
|
|
886
|
-
`worlds/` is not a Foundry root for this purpose. A package may not ship files
|
|
887
|
-
out of a world, so a note writing one has made a different mistake and gets the
|
|
888
|
-
ordinary "this package owns it" reading — a plainly broken path rather than a
|
|
889
|
-
plausible one that 404s with nothing reporting it.
|
|
890
|
-
|
|
891
|
-
**The rule is about ownership, not a list of directories.** A package owns its
|
|
892
|
-
whole `assets/` tree and the directory names inside it are its own business:
|
|
893
|
-
`sohl-kethira-basic` keeps art under `assets/artwork/`, and `artwork/deity.webp`
|
|
894
|
-
is that package's by the same rule that claims `icons/…` and `images/…`.
|
|
895
|
-
|
|
896
|
-
**Where a pathname is still authored, it follows this rule.** That is the
|
|
897
|
-
address of an image in a note's body, and a map note's `overlay`. The art
|
|
898
|
-
fields do not: `icon`, `tokenIcon`, `bgImage` and `banner` name
|
|
899
|
-
addresses, and the path comes from the record the resolved address points at —
|
|
900
|
-
which already carries the owning package, so there is nothing for a first
|
|
901
|
-
segment to state.
|
|
902
|
-
|
|
903
|
-
#### `banner:` reaches no compiled document
|
|
904
|
-
|
|
905
|
-
`data.banner` is the page's hero image, and it is the one art slot with no
|
|
906
|
-
Foundry destination: searching a built `packs-json` tree for it turns up
|
|
907
|
-
nothing. It reaches the generated page and the book's section plates, and
|
|
908
|
-
nothing else.
|
|
909
|
-
|
|
910
|
-
It is an `Address` all the same, defaulting to type `image`, so a section note
|
|
911
|
-
writes `banner: skillbnr` and a note borrowing another package's plate writes
|
|
912
|
-
`banner: packagebuild-none-image-skillbnr`. The resolved record carries the
|
|
913
|
-
owning package, and each surface joins the suffix onto its own root — the site
|
|
914
|
-
onto `site.assets`, the book onto the directory it stages banners into.
|
|
915
|
-
|
|
916
|
-
**`banner: null` is how a note says it wants no plate.** It is a true statement
|
|
917
|
-
on any type, and the frontmatter lint never reports it.
|
|
918
|
-
|
|
919
|
-
#### The pack a note compiles into
|
|
920
|
-
|
|
921
|
-
`data.pack` names which configured compendium receives the note's document.
|
|
922
|
-
|
|
923
|
-
```yaml
|
|
924
|
-
data:
|
|
925
|
-
pack: items-hm3
|
|
926
|
-
```
|
|
927
|
-
|
|
928
|
-
It is deliberately close to the retired `package:` and deliberately not the same
|
|
929
|
-
word: `package:` said which _distribution_ owned a note — now the repository's
|
|
930
|
-
`contentPackage`, and not authorable — while `pack:` says which
|
|
931
|
-
_compendium_ receives its document.
|
|
932
|
-
|
|
933
|
-
**It names the pack for the note's _own_ document.** A document derived from it
|
|
934
|
-
— an item's prose compiling into a `JournalEntry` of its own — is not what the
|
|
935
|
-
author was addressing, and is routed by the pass that produces it.
|
|
936
|
-
|
|
937
|
-
**`<system>.pack` names the pack for one system's document.** Routing is per
|
|
938
|
-
system: each block the note carries sends its own document where that block
|
|
939
|
-
says, and a note carrying two blocks may place them in two packs.
|
|
940
|
-
|
|
941
|
-
```yaml
|
|
942
|
-
data:
|
|
943
|
-
pack: items-sohl
|
|
944
|
-
hm3:
|
|
945
|
-
pack: items-hm3
|
|
946
|
-
```
|
|
947
|
-
|
|
948
|
-
**Unstated, a system's document goes to that system's default pack of its
|
|
949
|
-
class** — the pack of the class declaring that `system:`, or, where none does,
|
|
950
|
-
the pack of the class marked `default: true`. Where nothing answers, the build
|
|
951
|
-
refuses rather than guessing, and names the candidates.
|
|
952
|
-
|
|
953
|
-
**`pack` and `packFolder` are different questions.** `pack` names _which
|
|
954
|
-
compendium_ receives a document; `packFolder` names the folder note whose
|
|
955
|
-
address the document is filed under _within_ whichever compendium that is. They
|
|
956
|
-
compose, and both are read per system, so a note may place its two systems'
|
|
957
|
-
documents in different packs and different folders:
|
|
958
|
-
|
|
959
|
-
```yaml
|
|
960
|
-
sohl:
|
|
961
|
-
pack: characteristics
|
|
962
|
-
packFolder: none-folder-miscgear
|
|
963
|
-
```
|
|
964
|
-
|
|
965
|
-
**`pack: none` compiles the note into no document.** The note is walked,
|
|
966
|
-
published as a page, present in the content index with an address and no
|
|
967
|
-
Foundry UUID, and addressable by wikilink like any other; every pack compiler
|
|
968
|
-
passes over it without a finding, and the check that reports a note nothing
|
|
969
|
-
compiles says nothing about it. Write it on a page that belongs on the website
|
|
970
|
-
and in no compendium — a page of developer documentation, a page about the
|
|
971
|
-
package itself:
|
|
972
|
-
|
|
973
|
-
```yaml
|
|
974
|
-
shortcode: architecture
|
|
975
|
-
type: doc
|
|
976
|
-
subType: concept
|
|
977
|
-
data:
|
|
978
|
-
pack: none
|
|
979
|
-
```
|
|
980
|
-
|
|
981
|
-
A link to such a note resolves everywhere the note is addressable. On the web
|
|
982
|
-
it is an ordinary link to the page. In a compiled journal there is no document
|
|
983
|
-
to open, so the reader gets the link's text as prose and no `@UUID` — the
|
|
984
|
-
mirror of a link into a package that publishes documents and no pages.
|
|
985
|
-
|
|
986
|
-
It is accepted only on a type whose **sole document is the JournalEntry its
|
|
987
|
-
prose becomes** — `doc`, `place`, `lore` and `scenario`. On a type that
|
|
988
|
-
compiles an Item, an Actor, a Macro, a Scene or an Adventure it is refused by
|
|
989
|
-
name, because there it would drop the document the type exists to produce;
|
|
990
|
-
the message names that document. It is read per system like any other
|
|
991
|
-
`pack:`, so `<system>.pack: none` withholds one system's document while the
|
|
992
|
-
shared declaration names a pack for the rest, and a configured pack may not
|
|
993
|
-
be called `none`.
|
|
994
|
-
|
|
995
|
-
Four declarations are refused, each with the reason:
|
|
996
|
-
|
|
997
|
-
| written | why it is refused |
|
|
998
|
-
| ------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
999
|
-
| a **companion** pack | A companion is written by another pack's pass, so no note may be routed into one. |
|
|
1000
|
-
| a pack **nothing answers to** | The message lists the configured packs of that document type. |
|
|
1001
|
-
| a pack of **another document type** | A note's `pack:` names a pack of its own document type. |
|
|
1002
|
-
| **`none`** on a type with a second document | The Item, Actor, Macro, Scene or Adventure the type compiles into would be dropped, by name. |
|
|
1003
|
-
|
|
1004
|
-
#### Template priority: which template wins
|
|
1005
|
-
|
|
1006
|
-
A note can mark its document as a **starting template** the Create dialog offers
|
|
1007
|
-
to clone from, so a new being or item is born populated rather than blank. The
|
|
1008
|
-
value is a **priority**, and the priority is the whole mechanism — it decides
|
|
1009
|
-
which of several competing templates a player is actually offered.
|
|
1010
|
-
|
|
1011
|
-
The shared mapping table above names it `data.templatePriority`, targeting
|
|
1012
|
-
`system.templatePriority` in SoHL and `flags.hm3.templatePriority` in HM3.
|
|
1013
|
-
|
|
1014
|
-
> **`archetype` is the retiring spelling.** It is still read, in the `sohl:`
|
|
1015
|
-
> block and at the top level, so a tree sweeps on its own schedule
|
|
1016
|
-
> — but the frontmatter linter refuses it, and
|
|
1017
|
-
> what is compiled and emitted is `templatePriority` on all three sides.
|
|
1018
|
-
>
|
|
1019
|
-
> It is more than a rename, and the collision is **already live** rather than
|
|
1020
|
-
> pending: `archetypes` is specified above as the _sort_ a character is, and a
|
|
1021
|
-
> being's row declares it. So a number deciding which template wins and a list of
|
|
1022
|
-
> what sort of character this is would otherwise be distinguished **only by a
|
|
1023
|
-
> plural `s`**.
|
|
1024
|
-
>
|
|
1025
|
-
> A note carrying both spellings with **different** values is refused rather than
|
|
1026
|
-
> resolved quietly — `templatePriority: null` and `archetype: 0` say opposite
|
|
1027
|
-
> things, and picking either silently would decide it on the author's behalf.
|
|
1028
|
-
|
|
1029
|
-
```yaml
|
|
1030
|
-
data:
|
|
1031
|
-
templatePriority: 0 # a template, at the priority SoHL's own ship at
|
|
1032
|
-
```
|
|
1033
|
-
|
|
1034
|
-
```yaml
|
|
1035
|
-
data:
|
|
1036
|
-
templatePriority: null # not a template
|
|
1037
|
-
```
|
|
1038
|
-
|
|
1039
|
-
**Every note SoHL compiles into an Item or an Actor must state it.** Absent, the
|
|
1040
|
-
build refuses: "not a template" has to be _said_, not left out, or an omission
|
|
1041
|
-
and a decision look identical. The value is a number or `null`, and **`0` is a
|
|
1042
|
-
real priority** — the one SoHL's own templates ship at — not an absence.
|
|
1043
|
-
|
|
1044
|
-
**Where it lands differs by system, because HM3's data model has no field for
|
|
1045
|
-
it.** SoHL records it in `system`; HM3 keeps it under its own flag scope,
|
|
1046
|
-
`flags.hm3`, and a note that is not a template writes nothing there rather than a
|
|
1047
|
-
`null` nothing reads. Both HM3 Item and Actor passes write their own flag when
|
|
1048
|
-
the note declares a priority.
|
|
1049
|
-
|
|
1050
|
-
**How a winner is chosen.** Opening a Create dialog gathers every candidate
|
|
1051
|
-
across the world and every matching compendium, _including other modules'_. Those
|
|
1052
|
-
are filtered to the `(type, subType)` being created, deduped by **`shortcode`** —
|
|
1053
|
-
a template's stable identity, where the name is only presentation — and one
|
|
1054
|
-
winner is taken per shortcode:
|
|
1055
|
-
|
|
1056
|
-
1. the highest priority;
|
|
1057
|
-
2. then the nearest source — **world**, then **system**, then **module** — so a
|
|
1058
|
-
GM's own copy shadows a shipped one at equal priority;
|
|
1059
|
-
3. then a stable UUID, so the answer never depends on load order.
|
|
1060
|
-
|
|
1061
|
-
**The reserved ranges make a collision predictable.** Two packages can easily
|
|
1062
|
-
ship a template under one shortcode, and the number says which yields:
|
|
1063
|
-
|
|
1064
|
-
| priority | reserved for |
|
|
1065
|
-
| ---------- | -------------------------- |
|
|
1066
|
-
| `0`–`98` | SoHL and HM3 themselves |
|
|
1067
|
-
| `99`–`999` | other HeroicLands packages |
|
|
1068
|
-
| `1000`+ | everyone else |
|
|
1069
|
-
|
|
1070
|
-
HeroicLands reserves everything below `1000`. Since the highest priority wins,
|
|
1071
|
-
**anyone else's template always beats content shipped from here** — which is the
|
|
1072
|
-
point: a module author can override a standard template without coordinating with
|
|
1073
|
-
anybody, and be certain it takes effect.
|
|
1074
|
-
|
|
1075
|
-
#### The document id
|
|
1076
|
-
|
|
1077
|
-
A note's Foundry `_id` is **derived from its canonical address**, and no note
|
|
1078
|
-
needs to write one:
|
|
1079
|
-
|
|
1080
|
-
```yaml
|
|
1081
|
-
type: miscgear
|
|
1082
|
-
shortcode: bowlcer
|
|
1083
|
-
# no `id:` — the document is filed under makeId("document", "sohl-none-miscgear-bowlcer")
|
|
1084
|
-
```
|
|
1085
|
-
|
|
1086
|
-
The derivation is exactly:
|
|
1087
|
-
|
|
1088
|
-
```
|
|
1089
|
-
_id = makeId("document", "<package>-<system>-<type>-<shortcode>")
|
|
1090
|
-
```
|
|
1091
|
-
|
|
1092
|
-
— the note's canonical address, hashed with SHA-1 and truncated to the 16 hex
|
|
1093
|
-
characters a Foundry id is. Nothing else feeds it.
|
|
1094
|
-
|
|
1095
|
-
**One type hashes under a different namespace, and a consumer must not assume
|
|
1096
|
-
otherwise.** A `Folder` is a document of its own class, and its id is
|
|
1097
|
-
`makeId("folder", <address>)` — a separate namespace so that a folder and an
|
|
1098
|
-
item sharing a shortcode cannot derive one id, which Foundry would not report
|
|
1099
|
-
because it keys folders and documents in separate collections. See
|
|
1100
|
-
[`type: folder`](#type-folder).
|
|
1101
|
-
|
|
1102
|
-
**So the id is transported, not recomputed.** The content index publishes an
|
|
1103
|
-
`id` on every entry it gives an identity to, beside the `uuid` that identity
|
|
1104
|
-
ends in, and both are computed by whatever owns that entry's derivation — the
|
|
1105
|
-
folder pass for a folder, the journals pass for a documentation journal, this
|
|
1106
|
-
rule for everything else. Re-deriving one from the `canonical` key alone reads
|
|
1107
|
-
correctly and is wrong for a folder, and wrong in a way nothing outside the
|
|
1108
|
-
build can detect: the result is a plausible 16-character id that resolves to
|
|
1109
|
-
nothing.
|
|
1110
|
-
|
|
1111
|
-
**The Address determines the default id.** `content-lint` refuses a duplicate
|
|
1112
|
-
Address across the packs of a document type, so the derived id shares that
|
|
1113
|
-
uniqueness scope. An author pins a document's identity with `data.id` when it
|
|
1114
|
-
must remain stable across an Address change.
|
|
1115
|
-
|
|
1116
|
-
**An authored `data.id` wins**, and that is how a document's identity is
|
|
1117
|
-
**pinned**:
|
|
1118
|
-
|
|
1119
|
-
```yaml
|
|
1120
|
-
shortcode: bowlcer
|
|
1121
|
-
type: miscgear
|
|
1122
|
-
data:
|
|
1123
|
-
id: plaiQQm2T5zVK5mO # pinned: this document keeps this id
|
|
1124
|
-
```
|
|
1125
|
-
|
|
1126
|
-
A blank `data.id:` is not a pin — it is a deleted value with the key left behind, and
|
|
1127
|
-
is treated as absent.
|
|
1128
|
-
|
|
1129
|
-
**A rename moves the id, and that is the trade.** The address carries the
|
|
1130
|
-
shortcode, so renaming a shortcode gives the document a new `_id`. Two things
|
|
1131
|
-
make that acceptable: a shortcode rename already breaks every wikilink to the
|
|
1132
|
-
note, so it is a breaking change either way; and a note that must keep its
|
|
1133
|
-
identity across one pins its `id`, which is what the pin is for. One diagnostic
|
|
1134
|
-
narrows — `content-build` tells a **rename** from a **withdrawal** by matching
|
|
1135
|
-
document ids across releases, and for an unpinned note both sides now move
|
|
1136
|
-
together. It never reports a _wrong_ successor, and it stays exact for a pinned
|
|
1137
|
-
note; for the rest, the diff reports the vacated address.
|
|
1138
|
-
|
|
1139
|
-
**A note with no address gets no id, and no document.** `type` and `shortcode`
|
|
1140
|
-
are what a note is addressed by, so a note missing either cannot be filed and
|
|
1141
|
-
the build refuses it by that name rather than by a missing `id:`.
|
|
1142
|
-
|
|
1143
|
-
#### Address changes
|
|
1144
|
-
|
|
1145
|
-
`(type, shortcode)` is a published interface. Other packages can link to it or
|
|
1146
|
-
use it as an Item model, so changing either part requires those references to
|
|
1147
|
-
change too. `content-build addresses diff` compares the package with a released
|
|
1148
|
-
baseline and reports addresses whose published documents are absent. A pinned
|
|
1149
|
-
`data.id` preserves document identity across a shortcode change; it does not
|
|
1150
|
-
keep the old address available for links.
|
|
1151
|
-
|
|
1152
|
-
#### The compendium folder
|
|
1153
|
-
|
|
1154
|
-
A note says which folder of its pack it lands in:
|
|
1155
|
-
|
|
1156
|
-
```yaml
|
|
1157
|
-
data:
|
|
1158
|
-
packFolder: poisonsandtoxins # a folder note's address
|
|
1159
|
-
```
|
|
1160
|
-
|
|
1161
|
-
**`packFolder` is a folder note's address** — an ordinary address, resolved the
|
|
1162
|
-
way every other reference is, and written in any form [the grammar
|
|
1163
|
-
admits](#shorter-forms). The field supplies the type, so a bare shortcode is a
|
|
1164
|
-
complete address here; `folder-poisonsandtoxins` and the fully qualified
|
|
1165
|
-
`sohl-none-folder-poisonsandtoxins` name the same folder. An address no folder
|
|
1166
|
-
note answers to is a build error naming the folders the package does declare.
|
|
1167
|
-
|
|
1168
|
-
Note this is the _pack_ folder, not the note's directory. The directory is
|
|
1169
|
-
`file.path` / `file.folder`, which a content table reads separately.
|
|
1170
|
-
|
|
1171
|
-
**Where a folder materialises is derived from what references it.** Every pack
|
|
1172
|
-
holding a document that names a folder gets that folder, and its ancestors with
|
|
1173
|
-
it — so a documentation journal is filed beside the item it describes without
|
|
1174
|
-
the journals pack having to declare anything. A folder nothing references
|
|
1175
|
-
materialises nowhere.
|
|
1176
|
-
|
|
1177
|
-
One folder note and one Address supply every pack that references it.
|
|
1178
|
-
`data.packFolder` names a folder by Address. A top-level `folder:` key is
|
|
1179
|
-
refused with a finding that names `data.packFolder` and the authored line.
|
|
1180
|
-
|
|
1181
|
-
#### The knowledgebase category
|
|
1182
|
-
|
|
1183
|
-
`kbcat` names the group a note is listed under on the knowledgebase and the
|
|
1184
|
-
website. It is written in the system block:
|
|
1185
|
-
|
|
1186
|
-
```yaml
|
|
1187
|
-
sohl:
|
|
1188
|
-
kbcat: poisontoxin
|
|
1189
|
-
```
|
|
1190
|
-
|
|
1191
|
-
**It compiles into no document.** No pack compiler reads it and no `system`
|
|
1192
|
-
field receives it. It reaches a published page because a note's frontmatter is
|
|
1193
|
-
copied onto that page, where a list layout groups by `sohl.kbcat` — so `kbcat`
|
|
1194
|
-
is the one key in this section that answers _where does this appear_ for the
|
|
1195
|
-
web rather than for Foundry. `pack` and `packFolder` place a document in a
|
|
1196
|
-
compendium; `kbcat` places a page in a list.
|
|
1197
|
-
|
|
1198
|
-
That is also why it is specified here rather than in a type's table. A type's
|
|
1199
|
-
fields say what the **builder** compiles, and `kbcat` is never compiled — but
|
|
1200
|
-
what a note **may write** is broader than what any one consumer reads, and a
|
|
1201
|
-
check that equated the two reported thousands of correctly authored properties
|
|
1202
|
-
as unknown.
|
|
1203
|
-
|
|
1204
|
-
**It is editorial, and deliberately independent of `subType`.** The two are not
|
|
1205
|
-
alternative spellings of one classification and neither is derived from the
|
|
1206
|
-
other. `kbcat` both _subdivides_ a subtype — `trauma`/`physcond` is listed as
|
|
1207
|
-
`physdisability`, `physfeature` or `physprivations` — and _renames_ one for
|
|
1208
|
-
display, as `trauma`/`fear` listed under `phobias`. Most notes that carry a
|
|
1209
|
-
`kbcat` declare no `subType` at all. So the two are stated separately where both
|
|
1210
|
-
apply, and a reviewer should not read a disagreement between them as an error.
|
|
1211
|
-
|
|
1212
|
-
**The value is free-form, and nothing validates it.** There is no configured
|
|
1213
|
-
list of categories. The frontmatter check knows `kbcat` is a key every type may
|
|
1214
|
-
write and says nothing whatever about its value. A layout supplies display
|
|
1215
|
-
titles and an explicit order for the values it knows about, and appends any
|
|
1216
|
-
other value as its own group, titled by humanizing it.
|
|
1217
|
-
|
|
1218
|
-
The consequence is worth stating plainly, because it is the failure mode this
|
|
1219
|
-
key has: **a misspelled category is not a build error and is not dropped — it
|
|
1220
|
-
silently becomes a group of one**, sorted in after the known ones.
|
|
1221
|
-
|
|
1222
|
-
**A note that writes none is dropped from the list entirely.** Grouping is by
|
|
1223
|
-
the key, so a page carrying no value falls in no group and is absent from the
|
|
1224
|
-
list page — not listed last, not listed under a fallback heading, absent, with
|
|
1225
|
-
nothing reported at either build. Every note of a listed type in SoHL's tree
|
|
1226
|
-
carries one today, and nothing in this package enforces that; the content index
|
|
1227
|
-
is where the question _which notes carry no `kbcat`?_ is answered.
|
|
1228
|
-
|
|
1229
|
-
**It is also what a content table sections on.** `sohl.kbcat AS _section` in a
|
|
1230
|
-
`sql` fence is the ordinary case of _Content tables_ below, and the same
|
|
1231
|
-
free-form value decides the headings there.
|
|
1232
|
-
|
|
1233
|
-
### Addresses
|
|
1234
|
-
|
|
1235
|
-
Three types name one thing from another in a content tree, and they are not
|
|
1236
|
-
lengths of one another:
|
|
1237
|
-
|
|
1238
|
-
| type | written | where it is met |
|
|
1239
|
-
| ----------- | -------------------------------------------- | ----------------------------------------------------------- |
|
|
1240
|
-
| `Address` | `<package>-<system>-<note_type>-<shortcode>` | a frontmatter field, a `model:`, an art slot, a link target |
|
|
1241
|
-
| `Shortcode` | `<shortcode>` | a note's own identity, and a key a runtime resolves by |
|
|
1242
|
-
| `Wikilink` | `[[<Address>[#<anchor>]\|<text>]]` | body prose |
|
|
1243
|
-
|
|
1244
|
-
An **`Address`** is the tuple below, and it is what every field typed `Address`
|
|
1245
|
-
in the tables that follow holds. It is equally what a `model:` names, what an art
|
|
1246
|
-
slot points at and what a wikilink resolves through — so an address is the thing
|
|
1247
|
-
being named, and the brackets around one in prose are a separate matter. Three
|
|
1248
|
-
rules govern it, and the first three subsections below state them in turn: the
|
|
1249
|
-
tuple is read by position, any suffix of it may be written, and each omitted
|
|
1250
|
-
segment is supplied by a default the position declares.
|
|
1251
|
-
|
|
1252
|
-
A **`Shortcode`** is one segment, and nothing expands it — see _A shortcode is
|
|
1253
|
-
one segment, and nothing expands it_ below.
|
|
1254
|
-
|
|
1255
|
-
A **`Wikilink`** is the prose form, which contains an Address — see _Wikilinks_
|
|
1256
|
-
below.
|
|
1257
|
-
|
|
1258
|
-
#### The canonical address
|
|
1259
|
-
|
|
1260
|
-
```text
|
|
1261
|
-
<package>-<system>-<note_type>-<shortcode>
|
|
1262
|
-
```
|
|
1263
|
-
|
|
1264
|
-
The final segment is the shortcode, preceded by the note type, the system and
|
|
1265
|
-
the package. Each segment has one position. The system says which kind of target
|
|
1266
|
-
is named:
|
|
1267
|
-
|
|
1268
|
-
| system | target |
|
|
1269
|
-
| ------------- | --------------------------------------------------------------------------- |
|
|
1270
|
-
| `note` | Readable note content: a web page, a book entry, or a Foundry JournalEntry. |
|
|
1271
|
-
| `none` | A systemless target such as an asset, Folder, Macro or Scene. |
|
|
1272
|
-
| `sohl`, `hm3` | A game system's Actor or Item document. |
|
|
1273
|
-
|
|
1274
|
-
A note can publish more than one Address. A `being` note, for example, publishes
|
|
1275
|
-
`thalorna-note-being-bctrncml` for its readable content and
|
|
1276
|
-
`thalorna-sohl-being-bctrncml` for its SoHL Actor. An item note follows the same
|
|
1277
|
-
pattern for its page and Item. The type segment remains the authored note type
|
|
1278
|
-
in each Address.
|
|
1279
|
-
|
|
1280
|
-
#### Shorter forms
|
|
1281
|
-
|
|
1282
|
-
Authors may omit segments from the left. The complete Address states all four:
|
|
1283
|
-
|
|
1284
|
-
| form | expands to |
|
|
1285
|
-
| ------------------------------- | -------------------------------------------------------- |
|
|
1286
|
-
| `package-system-type-shortcode` | itself |
|
|
1287
|
-
| `system-type-shortcode` | `<this package>-system-type-shortcode` |
|
|
1288
|
-
| `type-shortcode` | `<this package>-<this position's system>-type-shortcode` |
|
|
1289
|
-
| `shortcode` | as above, with the type from the field's declaration |
|
|
1290
|
-
|
|
1291
|
-
There is no `package-type-shortcode` form. A reference to another package states
|
|
1292
|
-
its system as well as its package.
|
|
1293
|
-
|
|
1294
|
-
#### Authored references and generated references
|
|
1295
|
-
|
|
1296
|
-
Authored notes may use any permitted suffix. Builds leave source bytes unchanged.
|
|
1297
|
-
Generated content indexes, SQL records, site frontmatter and generated
|
|
1298
|
-
Wikilinks contain complete Addresses. For example, `data.parents: [north]` in
|
|
1299
|
-
`thalorna` emits `data.parents: [thalorna-note-place-north]`.
|
|
1300
|
-
|
|
1301
|
-
SQL queries compare Address-valued fields to complete Addresses. A note's
|
|
1302
|
-
`shortcode` is a distinct identity component, so it is not equal to an Address
|
|
1303
|
-
stored in `data.parents`, `data.domains` or a governance rank's `lore`. Use the
|
|
1304
|
-
target's complete identity for that comparison. A stub has no published page
|
|
1305
|
-
address; its note identity still contains the package, system, type and shortcode.
|
|
1306
|
-
|
|
1307
|
-
The toolchain holds parsed Addresses as complete tuples internally. Address-keyed
|
|
1308
|
-
relation maps hold typed targets and standing values. Only generated output
|
|
1309
|
-
serializes these properties to Address strings. Runtime Shortcodes, Foundry
|
|
1310
|
-
UUIDs, file paths and URLs keep their own representations.
|
|
1311
|
-
|
|
1312
|
-
An Address-valued `data:` field must name a target in this package or a declared
|
|
1313
|
-
dependency index. `content-build lint` reports a missing target at the value or
|
|
1314
|
-
map key that names it. A stub counts as a target for data even though it
|
|
1315
|
-
publishes no page. Art slots use their own missing-art warning and document
|
|
1316
|
-
fallback.
|
|
1317
|
-
|
|
1318
|
-
#### An omitted segment defaults from where the link is written
|
|
1319
|
-
|
|
1320
|
-
An omitted segment is a default, not a wildcard. Every short form expands to one
|
|
1321
|
-
complete Address before lookup.
|
|
1322
|
-
|
|
1323
|
-
- **Package:** the current package. A short address never falls through to a
|
|
1324
|
-
dependency.
|
|
1325
|
-
- **System in `[[link|text]]`:** `note`, for every note type. Use an explicit
|
|
1326
|
-
game system to name its Actor or Item, or `none` to name a systemless target.
|
|
1327
|
-
- **System in `![[link|text]]`:** `none`. An embed names an image or icon asset;
|
|
1328
|
-
it also supplies `image` as the type of a bare shortcode.
|
|
1329
|
-
- **System in frontmatter:** a system block such as `sohl:` or `hm3:` supplies
|
|
1330
|
-
its game system. Other Address fields default to `note`, except fields
|
|
1331
|
-
declared for images, icons or folders, which default to `none`.
|
|
1332
|
-
|
|
1333
|
-
For example, `[[macro-autoattack|Automated Attack]]` names the readable macro
|
|
1334
|
-
note, while `[[none-macro-autoattack|Automated Attack]]` names the Foundry Macro.
|
|
1335
|
-
`![[icon-anvil|Anvil]]` names the systemless icon asset.
|
|
1336
|
-
|
|
1337
|
-
**A complete Address is lowercase throughout.** Every segment — package,
|
|
1338
|
-
system, type and shortcode alike — contains only lowercase ASCII letters and
|
|
1339
|
-
digits (`^[a-z0-9]+$`). Declared Address fields reject uppercase segments.
|
|
1340
|
-
A model reference accepts mixed-case Shortcodes and resolves their lowercase
|
|
1341
|
-
Address identity; this does not change a native embedded item's Shortcode.
|
|
1342
|
-
|
|
1343
|
-
**Parsing is positional counting from the right, and nothing else.** Every
|
|
1344
|
-
segment is lowercase alphanumeric — shortcodes, **types** and **subTypes** are
|
|
1345
|
-
all `^[a-z0-9]+$`, systems come from a closed registry, and `contentPackage` is
|
|
1346
|
-
`^[a-z0-9]+$` too — so the hyphen is purely a separator. There is no longest-match
|
|
1347
|
-
against a roster and no vocabulary check before splitting.
|
|
1348
|
-
|
|
1349
|
-
**`type` and `subType` are held to that charset, not merely expected to meet
|
|
1350
|
-
it**. A type is the first segment of every address, so a hyphen in one is
|
|
1351
|
-
read back as a segment boundary that was never meant as one. A `subType` reaches
|
|
1352
|
-
no address, sections being retired, but it is held to the same rule all the
|
|
1353
|
-
same: it is a vocabulary term the whole toolchain keys on, one closed set away
|
|
1354
|
-
from being an address again, and a charset that holds for two of the three
|
|
1355
|
-
segments and half of a fourth is a rule nobody can state. Both are checked
|
|
1356
|
-
against the same constant a shortcode is checked against, and a note carrying a
|
|
1357
|
-
hyphenated value is reported where it wrote it:
|
|
1358
|
-
|
|
1359
|
-
```text
|
|
1360
|
-
Trauma/Blood_Loss.md:3:1: error: `subType` "blood-loss" is not a well-formed subType — a subType is lowercase letters and digits only (^[a-z0-9]+$), the same charset a type, a shortcode and a contentPackage are held to. It is a vocabulary term the whole toolchain keys on, and one closed set away from being an address segment again, so a charset that held for every term but this one would be a rule nobody could state in a sentence
|
|
1361
|
-
```
|
|
1362
|
-
|
|
1363
|
-
The `doc` subType is **`userguide`**. A hyphenated `user-guide` is refused by
|
|
1364
|
-
the shared charset check. Shortcodes likewise contain only lowercase letters
|
|
1365
|
-
and digits, preserving one unambiguous Address grammar.
|
|
1366
|
-
|
|
1367
|
-
`type/shortcode` with a slash is accepted. A slash is _unconditionally_ an
|
|
1368
|
-
address separator — pipe or no pipe — so an unknown type before one is an error
|
|
1369
|
-
rather than something to guess at.
|
|
1370
|
-
|
|
1371
|
-
#### A shortcode is one segment, and nothing expands it
|
|
1372
|
-
|
|
1373
|
-
A **`Shortcode`** is a single segment of the same charset an address segment is
|
|
1374
|
-
held to, and it is a type of its own rather than the shortest `Address`. It
|
|
1375
|
-
names a thing's identity, or it is the key something resolves by where the other
|
|
1376
|
-
three segments have nothing to say:
|
|
1377
|
-
|
|
1378
|
-
| where a `Shortcode` is written | what it names |
|
|
1379
|
-
| -------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
1380
|
-
| a note's own `shortcode:` | the note's identity — the name every address of it is built from |
|
|
1381
|
-
| a custom embedded entry's `system.shortcode` | that entry's identity within the actor that holds it |
|
|
1382
|
-
| an emitted `system.relations` key | the other body this one has a standing towards |
|
|
1383
|
-
| a mystery's `skillAptitudes` keys | the skill an aptitude weighs, where the key is not a `subType:` selector |
|
|
1384
|
-
| a `Code`-suffixed system field | what the resolved address is stored as — see _The note vocabulary, and how it maps_ |
|
|
1385
|
-
|
|
1386
|
-
**The test is whether the position can take the full four-tuple.** An `Address`
|
|
1387
|
-
accepts it by definition, and the positions above cannot: a `system.relations`
|
|
1388
|
-
key is read at runtime as `relations[shortcode]`, among the items one actor
|
|
1389
|
-
carries, where packages do not exist. So nothing defaults a `Shortcode` out to
|
|
1390
|
-
four segments, and a value persisted there as an address names no standing at
|
|
1391
|
-
all.
|
|
1392
|
-
|
|
1393
|
-
**The two halves of `relations` are two types.** The key an author writes is an
|
|
1394
|
-
`Address` — a body in another package is named in full, and one in this package
|
|
1395
|
-
by its shortcode alone — and the key the compiled Item carries is the
|
|
1396
|
-
`Shortcode` that address names. The builder performs the reduction, exactly as
|
|
1397
|
-
it does for a `Code`-suffixed field, and two authored keys reducing to one
|
|
1398
|
-
emitted key is an error naming both addresses: a shortcode is unique within one
|
|
1399
|
-
package's address space, so two packages may each declare one, and the compiled
|
|
1400
|
-
field holds one of the two.
|
|
1401
|
-
|
|
1402
|
-
**A bare shortcode in an `Address` field is not a `Shortcode`.** It is an
|
|
1403
|
-
Address written at its shortest, with the package, the system and the type all
|
|
1404
|
-
supplied by the position — the same value as the full tuple, spelled shorter.
|
|
1405
|
-
Which of the two a field holds is the field's own declaration, stated in the
|
|
1406
|
-
`Values` column of the tables below and nowhere else.
|
|
1407
|
-
|
|
1408
|
-
#### Every address resolves, and every build says so the same way
|
|
1409
|
-
|
|
1410
|
-
An address that names no note **fails the build** — in the link checker,
|
|
1411
|
-
in the pack compilers and in the site build alike.
|
|
1412
|
-
|
|
1413
|
-
A `draft` note exists, resolves, compiles and publishes; a link to it renders
|
|
1414
|
-
visibly marked. An Address landing nowhere is a typo or an omission and earns
|
|
1415
|
-
an error.
|
|
1416
|
-
|
|
1417
|
-
There are six ways a link can fail, and each is one **error** with one message
|
|
1418
|
-
wherever it is met:
|
|
1419
|
-
|
|
1420
|
-
| finding | what it means | the fix |
|
|
1421
|
-
| ---------------- | --------------------------------------------- | ------------------------------------------------------ |
|
|
1422
|
-
| `unlabelled` | no `\|`, so the link addresses nothing | write `[[type-shortcode\|Text]]` |
|
|
1423
|
-
| `not-an-address` | labelled, but the target is not an address | write the address, not the name |
|
|
1424
|
-
| `not-lowercase` | any segment of the address is capitalised | lowercase it — every segment is lowercase |
|
|
1425
|
-
| `unknown-type` | qualified, but names no type this build knows | correct the type segment |
|
|
1426
|
-
| `unresolved` | parses as an address; nothing publishes it | fix the shortcode, or qualify to reach another package |
|
|
1427
|
-
| `ambiguous` | _unreachable; kept for the manifest_ | — |
|
|
1428
|
-
| `unknown-anchor` | the address resolves; the `#section` does not | correct the anchor |
|
|
1429
|
-
|
|
1430
|
-
`ambiguous` is a reserved reason in the result vocabulary. An omitted segment
|
|
1431
|
-
defaults rather than wildcarding, so each written target expands to one
|
|
1432
|
-
canonical Address and a lookup returns one entry or none.
|
|
1433
|
-
|
|
1434
|
-
The vocabulary and the messages live in one module (`engine/wikilink-syntax.mjs`)
|
|
1435
|
-
precisely because an author meets whichever build ran first. Three resolvers read
|
|
1436
|
-
one authored link; they must not describe the same mistake in three ways, and
|
|
1437
|
-
they must never disagree about whether it is a mistake at all.
|
|
1438
|
-
|
|
1439
|
-
#### In frontmatter, the address is written bare
|
|
1440
|
-
|
|
1441
|
-
An **`Address` field** takes the address with no brackets:
|
|
1442
|
-
|
|
1443
|
-
```yaml
|
|
1444
|
-
data:
|
|
1445
|
-
parents:
|
|
1446
|
-
- hexhodai
|
|
1447
|
-
seat: tashal
|
|
1448
|
-
```
|
|
1449
|
-
|
|
1450
|
-
not `[[hexhodai]]`. The field is declared as an `Address`, so the schema knows
|
|
1451
|
-
what the value is and reads it as one; brackets are a wikilink's punctuation,
|
|
1452
|
-
which the reader would have to strip before it could do anything.
|
|
1453
|
-
|
|
1454
|
-
A frontmatter value is parsed by the address grammar above, so a single-segment
|
|
1455
|
-
value such as `hexhodai` names the target by its shortcode alone, with every
|
|
1456
|
-
other segment supplied by the position. Frontmatter is structure rather than
|
|
1457
|
-
prose: a field value is a reference something else will compile against, and it
|
|
1458
|
-
should say exactly what it points at.
|
|
1459
|
-
|
|
1460
|
-
**A field can supply the type.** A field naming one kind of note declares its
|
|
1461
|
-
default — `seat` a `place`, `parents` an `affiliation`, `stations` a `lore` — so
|
|
1462
|
-
the type segment defaults from the declaration and a bare shortcode is the
|
|
1463
|
-
ordinary case, not an abbreviation of one. That is why the examples above read
|
|
1464
|
-
`tashal` rather than `place-tashal`: the shorter form carries the same
|
|
1465
|
-
information, because the field already said what kind of thing it points at.
|
|
1466
|
-
|
|
1467
|
-
The frontmatter lint checks scalar Addresses, entries in Address lists, and
|
|
1468
|
-
Address map keys at the entry's own location. Segments contain lowercase ASCII
|
|
1469
|
-
letters and digits; wikilink brackets, labels, anchors, and spaces do not belong
|
|
1470
|
-
in these fields. Optional null values and empty lists or maps express no
|
|
1471
|
-
reference. Grammar and accepted types are checked independently of whether a
|
|
1472
|
-
target exists.
|
|
1473
|
-
|
|
1474
|
-
An affiliation's `economy` accepts only `affiliation` and `lore` Addresses.
|
|
1475
|
-
Each entry states its type: `affiliation-merchants` or `lore-tanvrcrncy`.
|
|
1476
|
-
There is no default type; a bare shortcode is an error.
|
|
1477
|
-
Bundle `contents` likewise requires a type for each document.
|
|
1478
|
-
|
|
1479
|
-
**The declaration constrains the type; it does not shorten the address.** A field
|
|
1480
|
-
value may be written at any length, and every length is equally correct so long
|
|
1481
|
-
as the type it names is the one the field declares. All four of these are valid
|
|
1482
|
-
in a `seat` field:
|
|
1483
|
-
|
|
1484
|
-
```yaml
|
|
1485
|
-
seat: tashal
|
|
1486
|
-
seat: place-tashal
|
|
1487
|
-
seat: none-place-tashal
|
|
1488
|
-
seat: kethira-none-place-tashal
|
|
1489
|
-
```
|
|
1490
|
-
|
|
1491
|
-
Each is a **suffix** of the canonical address, which is why the third one names
|
|
1492
|
-
the system and the fourth cannot omit it — see [Shorter forms](#shorter-forms).
|
|
1493
|
-
|
|
1494
|
-
An `affiliation-` prefix there is an **error naming the field and both types** —
|
|
1495
|
-
never a silent widening of what the field accepts. Where a field permits more
|
|
1496
|
-
than one type with no default among them, a bare shortcode is refused
|
|
1497
|
-
outright — an error naming the field and the types it accepts, never a
|
|
1498
|
-
resolution across them.
|
|
1499
|
-
|
|
1500
|
-
**A position's default and its accepted set are two different declarations,
|
|
1501
|
-
and most positions are not ambiguous at all.** The paragraph above is about a
|
|
1502
|
-
position that permits more than one type with no default among them; `seat` is
|
|
1503
|
-
the plainer case, permitting exactly one. Between those sits a third shape,
|
|
1504
|
-
and it is the common one: a position that nominates one of its accepted types
|
|
1505
|
-
as the default while accepting others besides. There a bare shortcode is never
|
|
1506
|
-
a candidate to weigh — it takes the default outright, every time — and the
|
|
1507
|
-
accepted set is what a value that states its own type is checked against
|
|
1508
|
-
instead: naming any member of the set is as valid as naming the default
|
|
1509
|
-
itself, and naming a type outside it is an error against the set, never
|
|
1510
|
-
against the default alone. [The four art slots](#the-four-art-slots) are
|
|
1511
|
-
declared exactly this way — each one accepts a second type beyond the one a
|
|
1512
|
-
bare shortcode takes.
|
|
1513
|
-
|
|
1514
|
-
**Only the type segment defaults.** Package and system are not the field's to
|
|
1515
|
-
supply — it has no opinion about which package holds the target — so reaching
|
|
1516
|
-
another package's note means qualifying, and because parsing is positional
|
|
1517
|
-
counting from the right, qualifying at all means naming the type too.
|
|
1518
|
-
`kethira-tashal` is not "the `tashal` place in `kethira`"; it is two segments, so
|
|
1519
|
-
it reads as type `kethira`, shortcode `tashal`, and fails.
|
|
1520
|
-
|
|
1521
|
-
So the ladder has one rung where the field helps and three where it only checks:
|
|
1522
|
-
a bare shortcode takes its type from the declaration, and every longer form
|
|
1523
|
-
states the type itself and is verified against it.
|
|
1524
|
-
|
|
1525
|
-
This is enforced rather than merely preferred: the build walks every frontmatter
|
|
1526
|
-
value, reports each bracketed link it finds, and reports each value whose
|
|
1527
|
-
qualification contradicts its field. A successful run says so — _no wikilink in
|
|
1528
|
-
frontmatter_ is part of what `content-build links` reports when it passes.
|
|
1529
|
-
|
|
1530
|
-
Brackets belong in prose, where a link sits inside a sentence and needs marking
|
|
1531
|
-
off from the words around it. A frontmatter value has nothing to be marked off
|
|
1532
|
-
from.
|
|
1533
|
-
|
|
1534
|
-
### Wikilinks
|
|
1535
|
-
|
|
1536
|
-
A **`Wikilink`** is how body prose names something:
|
|
1537
|
-
|
|
1538
|
-
```text
|
|
1539
|
-
[[<Address>[#<anchor>]|<text>]]
|
|
1540
|
-
```
|
|
1541
|
-
|
|
1542
|
-
The brackets, the `#<anchor>` and the `|<text>` label are the Wikilink's own.
|
|
1543
|
-
What sits inside them is an `Address` like any other — read by the same grammar,
|
|
1544
|
-
written at any of its lengths, and taking each omitted segment from where the
|
|
1545
|
-
link is written, which for body prose is this package and system `note`. So
|
|
1546
|
-
nothing about resolution belongs to the bracketed form: a wikilink and a
|
|
1547
|
-
frontmatter field ask the same question of the same value, and the findings above
|
|
1548
|
-
are the answers either of them gets.
|
|
1549
|
-
|
|
1550
|
-
The anchor is the Wikilink's, not the Address's. It names a section of the target
|
|
1551
|
-
by the slug that section's heading declares — `[[place-tashal#market|the
|
|
1552
|
-
market]]` — and one naming no section there is the `unknown-anchor` finding.
|
|
1553
|
-
Written alone, `[[#slug|Text]]` addresses a section of the page the link sits on,
|
|
1554
|
-
which is the one form carrying no address at all.
|
|
1555
|
-
|
|
1556
|
-
#### Every wikilink carries a label
|
|
1557
|
-
|
|
1558
|
-
There is one namespace, and the pipe is required:
|
|
1559
|
-
|
|
1560
|
-
| written | resolved as | displays |
|
|
1561
|
-
| ------------------- | ----------- | -------------------------- |
|
|
1562
|
-
| `[[Address\|]]` | an address | the target note's own name |
|
|
1563
|
-
| `[[Address\|Text]]` | an address | `Text` |
|
|
1564
|
-
| `[[Name]]` | nothing | a finding |
|
|
1565
|
-
|
|
1566
|
-
**A link written without a label addresses nothing**, and the correction
|
|
1567
|
-
is always the same: write `[[type-shortcode|Text]]`.
|
|
1568
|
-
|
|
1569
|
-
The bare form does not resolve by display name or alias. `name.aliases` is
|
|
1570
|
-
indexed for search and completion, while an authored link names an Address.
|
|
1571
|
-
A top-level `aliases:` key is refused.
|
|
1572
|
-
|
|
1573
|
-
Requiring the label is also what makes positional parsing safe. Note names
|
|
1574
|
-
contain hyphens — `Grukar-ahk` is a name, not a `Grukar` of type `ahk` — so a
|
|
1575
|
-
target that does not parse as an address is reported as one that does not, rather
|
|
1576
|
-
than split at an arbitrary place or quietly looked up somewhere else.
|
|
1577
|
-
|
|
1578
|
-
The **empty** label is not a way of writing no label. It says _address this
|
|
1579
|
-
target, and show whatever it calls itself_ — so a note renamed later takes its new
|
|
1580
|
-
name at every citation with no link edited. `[[x|]]` is labelled; `[[x]]` is not.
|
|
1581
|
-
|
|
1582
|
-
It is the label that is required, not a target: the anchor-only form above
|
|
1583
|
-
carries its pipe like every other link.
|
|
1584
|
-
|
|
1585
|
-
### Assets are types
|
|
1586
|
-
|
|
1587
|
-
`icon`, `image` and `audio` are types in the type vocabulary, and an address
|
|
1588
|
-
reaches one exactly as it reaches a being or a skill. Their trees are
|
|
1589
|
-
the one thing about them that differs: they sit beside `content/` rather than
|
|
1590
|
-
inside it, because the thing a note is addressing is a file rather than a note.
|
|
1591
|
-
|
|
1592
|
-
**Three roots, one per type.** The directory is named for what it holds and the
|
|
1593
|
-
type is named for what an address reaches, so the two differ by a letter and the
|
|
1594
|
-
mapping is declared rather than derived from the name:
|
|
1595
|
-
|
|
1596
|
-
| root | type |
|
|
1597
|
-
| --------------- | ------- |
|
|
1598
|
-
| `assets/icons` | `icon` |
|
|
1599
|
-
| `assets/images` | `image` |
|
|
1600
|
-
| `assets/audio` | `audio` |
|
|
1601
|
-
|
|
1602
|
-
**A file's extension decides whether it is an asset at all**, and its root
|
|
1603
|
-
decides which type. Every image-extensioned file anywhere under `assets/icons`
|
|
1604
|
-
is an icon, and every audio-extensioned file under `assets/audio` is an audio
|
|
1605
|
-
asset, however deep it sits.
|
|
1606
|
-
|
|
1607
|
-
That filter is load-bearing rather than tidy-minded: `provenance.yaml` files
|
|
1608
|
-
live **inside** these roots, at any level, and a walk that took every file would
|
|
1609
|
-
read attribution records as assets. A directory under `assets/` that is not one
|
|
1610
|
-
of the four holds no addressable asset — `assets/ui` is a package's own
|
|
1611
|
-
furniture, and nothing addresses it.
|
|
1612
|
-
|
|
1613
|
-
**An asset type carries no system.** Its addresses keep the fourth segment, and
|
|
1614
|
-
it is always `none` — `sohl-none-icon-anvil`, never `sohl-icon-anvil` — so key
|
|
1615
|
-
parsing stays uniform across every type. The path shape is a property of the
|
|
1616
|
-
type rather than a test on a value.
|
|
1617
|
-
|
|
1618
|
-
#### The filename is the shortcode
|
|
1619
|
-
|
|
1620
|
-
```text
|
|
1621
|
-
assets/icons/game-icons/lorc/anvil.svg → <package>-none-icon-anvil
|
|
1622
|
-
assets/images/beings/creatures/drake.webp → <package>-none-image-drake
|
|
1623
|
-
```
|
|
1624
|
-
|
|
1625
|
-
**The extension is not part of the address**, and neither are the directories
|
|
1626
|
-
above the file. A root's shortcodes are one flat namespace however deeply the
|
|
1627
|
-
tree nests, so two files under one root sharing a basename are two claims on one
|
|
1628
|
-
address and a build error naming both. Across roots they are no such thing:
|
|
1629
|
-
`icon-anvil` and `image-anvil` are different addresses.
|
|
1630
|
-
|
|
1631
|
-
Leaving the extension out is what makes a format change free. Changing an icon
|
|
1632
|
-
from SVG to WebP is dropping a different file in place, and no note changes;
|
|
1633
|
-
`abysdrksvg` and `abysdrkwebp` would be two addresses for one drake.
|
|
1634
|
-
|
|
1635
|
-
**Beneath the root the structure is arbitrary, exactly as it is under
|
|
1636
|
-
`assets/content`.** A note's address does not depend on where its `.md` sits,
|
|
1637
|
-
and an asset's address does not depend on where its file sits within its root.
|
|
1638
|
-
Both trees are arranged for the people who maintain them —
|
|
1639
|
-
`icons/game-icons/<contributor>/`, `images/beings/creatures/` — and either can be
|
|
1640
|
-
rearranged wholesale without a single reference changing.
|
|
1641
|
-
|
|
1642
|
-
**That freedom is the point.** Whoever maintains a tree organises it however
|
|
1643
|
-
makes sense to them — by contributor, by subject, by the source a set came from,
|
|
1644
|
-
by whatever the next thousand files want — and the build has no opinion. A
|
|
1645
|
-
layout that carried meaning would spend that freedom to buy nothing: moving a
|
|
1646
|
-
file would become a breaking change, and the arrangement would answer to the
|
|
1647
|
-
walk rather than to the person reading it.
|
|
1648
|
-
|
|
1649
|
-
**The root itself is the exception, and only because an asset root is
|
|
1650
|
-
homogeneous.** `assets/content` is mixed: it holds beings, places, skills and
|
|
1651
|
-
the rest side by side, and each note declares its own `type:`, so no directory
|
|
1652
|
-
needs to. An asset root holds one type and nothing else — everything under
|
|
1653
|
-
`assets/icons` is an icon — which is what lets the root supply the type. It has
|
|
1654
|
-
to: a `.webp` carries no frontmatter and has nowhere to say what it is.
|
|
1655
|
-
|
|
1656
|
-
So the two trees follow one rule about layout and differ on exactly one point:
|
|
1657
|
-
a note's type comes from the note, an asset's type comes from its root. That is
|
|
1658
|
-
also why the three roots are a closed list rather than a convention — a fourth
|
|
1659
|
-
directory under `assets/` declares nothing, so nothing in it is addressable.
|
|
1660
|
-
`assets/ui` and `assets/fonts` fall out of that rule rather than needing an
|
|
1661
|
-
exemption.
|
|
1662
|
-
|
|
1663
|
-
**A shortcode is lowercase alphanumerics.** A filename that is not — a version
|
|
1664
|
-
string, a hyphen, a date stamp — cannot be addressed, and the build says so
|
|
1665
|
-
rather than inventing a shortcode for it.
|
|
1666
|
-
|
|
1667
|
-
#### The asset record
|
|
1668
|
-
|
|
1669
|
-
An asset reaches the content index as a record of its own — one line per
|
|
1670
|
-
addressable file, in the same JSON Lines index the notes publish into. It
|
|
1671
|
-
carries no frontmatter, no anchors and no `foundry` block: a file declares
|
|
1672
|
-
nothing about itself, compiles into no document, and publishes no page, so the
|
|
1673
|
-
`address` holds the canonical key and no page slug.
|
|
1674
|
-
|
|
1675
|
-
| `asset` field | Source | What it says |
|
|
1676
|
-
| ------------- | ---------- | ------------------------------------------------------------------ |
|
|
1677
|
-
| `path` | the walk | Where the file sits inside the emitting package's asset directory. |
|
|
1678
|
-
| `attribution` | provenance | The person holding the rights, to whom attribution is legally due. |
|
|
1679
|
-
| `source` | provenance | Where it came from — a URL, or a sentence. |
|
|
1680
|
-
| `ai` | provenance | Whether the file is machine-generated — `true` or `false`. |
|
|
1681
|
-
| `license` | provenance | The licence it is used under — an SPDX identifier, or terms. |
|
|
1682
|
-
| `notes` | provenance | Anything else a person reading the attribution needs. |
|
|
1683
|
-
|
|
1684
|
-
Every field is present on every record, blank where nothing states one. A fixed
|
|
1685
|
-
shape is what lets a consumer read `asset.license` without first asking whether
|
|
1686
|
-
the package happened to record one.
|
|
1687
|
-
|
|
1688
|
-
**`path` is what makes resolution one step.** The record is emitted by the
|
|
1689
|
-
package holding the bytes, so the path is that package's path and each consumer
|
|
1690
|
-
joins its own root onto it:
|
|
1691
|
-
|
|
1692
|
-
| Consumer | `thalorna-none-image-thorn` resolves to |
|
|
1693
|
-
| -------- | ------------------------------------------------------------------ |
|
|
1694
|
-
| Web | `<cdn base>/thalorna/images/beings/characters/thorn.webp` |
|
|
1695
|
-
| Book | `<asset base>/thalorna/images/beings/characters/thorn.webp` |
|
|
1696
|
-
| Foundry | `modules/sohl-thalorna/assets/images/beings/characters/thorn.webp` |
|
|
1697
|
-
|
|
1698
|
-
Foundry names a **Foundry package id** rather than a content package, because
|
|
1699
|
-
the two are distinct — they are equal in the system only by coincidence, and in
|
|
1700
|
-
`sohl-thalorna` they differ.
|
|
1701
|
-
|
|
1702
|
-
##### Where provenance comes from
|
|
1703
|
-
|
|
1704
|
-
A `provenance.yaml` states the provenance fields in the table above, and nothing
|
|
1705
|
-
else: an unknown key is a finding rather than a silent drop, because `licence`
|
|
1706
|
-
beside `license` is otherwise an attribution record that looks complete and
|
|
1707
|
-
carries nothing.
|
|
1708
|
-
|
|
1709
|
-
One address resolves its record in this order:
|
|
1710
|
-
|
|
1711
|
-
1. A sibling `<filename.ext>.yaml`, which **replaces** an inherited record
|
|
1712
|
-
wholesale rather than merging over it. A sidecar exists precisely because the
|
|
1713
|
-
inherited answer is wrong for this one file, so carrying half of it forward
|
|
1714
|
-
would make the record's meaning depend on a directory two levels up.
|
|
1715
|
-
2. Otherwise the nearest `provenance.yaml`, searching the file's own directory
|
|
1716
|
-
and then its ancestors, **stopping at the type root**. A record above the root
|
|
1717
|
-
would speak for trees it says nothing about.
|
|
1718
|
-
3. Otherwise nothing, and the provenance fields are blank. A package that
|
|
1719
|
-
records no attribution is a fact to state, not a walk to fail.
|
|
1720
|
-
|
|
1721
|
-
**The nearest record is the whole answer.** It is never merged with the one
|
|
1722
|
-
above it, so a record states every key it means to claim — which is what lets a
|
|
1723
|
-
reader open one file and know what the tree beneath it carries, instead of
|
|
1724
|
-
resolving a chain by hand.
|
|
1725
|
-
|
|
1726
|
-
That is also why `attribution` and `license` are **required in any record that
|
|
1727
|
-
exists**: a key left out is not inherited from above, it is simply absent, so a
|
|
1728
|
-
record stating neither leaves every file it covers with no rights holder and no
|
|
1729
|
-
terms. Omitting one is a finding. `source`, `ai` and `notes` stay optional,
|
|
1730
|
-
because a blank is a truthful answer for each of them.
|
|
1731
|
-
|
|
1732
|
-
#### A font is not an asset
|
|
1733
|
-
|
|
1734
|
-
`assets/fonts` is not a root, and a font has no address.
|
|
1735
|
-
|
|
1736
|
-
**An asset type exists so a note can name a file and a package can substitute
|
|
1737
|
-
it.** Nothing about a font answers to either half. No note names a typeface —
|
|
1738
|
-
the art slots are `icon`, `tokenIcon`, `bgImage` and `banner` — and substituting
|
|
1739
|
-
one package's font for another's is not something anyone wants.
|
|
1740
|
-
|
|
1741
|
-
**Neither consumer of a font can use an address.** A stylesheet names a file
|
|
1742
|
-
with `url()`, resolved relative to the stylesheet and never through the content
|
|
1743
|
-
index. The book names a _family_ — `#set text(font: "Libertinus Serif")` — and
|
|
1744
|
-
the typesetter matches that against a directory it is pointed at. One wants a
|
|
1745
|
-
relative path, the other a family name; an address serves neither.
|
|
1746
|
-
|
|
1747
|
-
**And the model fits badly.** An address holds one file whose format is free to
|
|
1748
|
-
change, which suits a picture. A font family is a matrix of family, weight and
|
|
1749
|
-
style, and a filename already says which cell a file is — a shortcode would have
|
|
1750
|
-
to re-encode that, worse.
|
|
1751
|
-
|
|
1752
|
-
So fonts stay ordinary files a package ships and points a tool at, and their
|
|
1753
|
-
licences are recorded where a person reads them rather than in an index.
|
|
1754
|
-
|
|
1755
|
-
#### `icon` and `image` are two types, not one
|
|
1756
|
-
|
|
1757
|
-
An icon has to stay coherent drawn into a 32×32 slot, which is why icons are
|
|
1758
|
-
SVG. An image is unbounded, expected to be large, and unreadable at that size.
|
|
1759
|
-
That is a fitness property of the asset itself, and one merged type would leave
|
|
1760
|
-
every consumer inferring the handling from the extension; two types let a guard
|
|
1761
|
-
assert it instead.
|
|
1762
|
-
|
|
1763
|
-
So the two have **separate shortcode namespaces**. `icon-anvil` and
|
|
1764
|
-
`image-anvil` are different addresses for different purposes, and a rewrite rule
|
|
1765
|
-
reaches one without touching the other.
|
|
1766
|
-
|
|
1767
|
-
**An asset address is its own name.** `image-thorn` is an image called `thorn`;
|
|
1768
|
-
any resemblance to `being-thorn` is coincidence, and nothing derives one from
|
|
1769
|
-
the other.
|
|
1770
|
-
|
|
1771
|
-
#### `packagebuild` is a reserved package name
|
|
1772
|
-
|
|
1773
|
-
package-build is an npm package rather than a system or a module, and it ships
|
|
1774
|
-
a set of images — section banners chiefly — that many packages draw on.
|
|
1775
|
-
Addressing them as `packagebuild-none-image-<shortcode>` lets a note reach one
|
|
1776
|
-
without declaring a dependency on some parent system or module it otherwise has
|
|
1777
|
-
no relationship with.
|
|
1778
|
-
|
|
1779
|
-
**Its files reach the website and the book and no Foundry document.** Foundry
|
|
1780
|
-
installs no package of that name and no relationship can be declared with one,
|
|
1781
|
-
so `banner:` — the one art slot with no Foundry destination — is where a
|
|
1782
|
-
`packagebuild` address belongs. A body image naming one, written either way, is
|
|
1783
|
-
refused with the line and column it sits on.
|
|
1784
|
-
|
|
1785
|
-
`packagebuild` is therefore **reserved** in the package registry: nothing may
|
|
1786
|
-
create a real package that collides with it, and its resolution is special-cased,
|
|
1787
|
-
because no installed directory sits behind the name.
|
|
1788
|
-
|
|
1789
|
-
#### An embedded image is a wikilink
|
|
1790
|
-
|
|
1791
|
-
```text
|
|
1792
|
-
![[address|label]]
|
|
1793
|
-
```
|
|
1794
|
-
|
|
1795
|
-
There is no image grammar. An embed is the wikilink above, with `!` meaning
|
|
1796
|
-
_render it here_ rather than _link to it_, and the syntax supplying the default
|
|
1797
|
-
type the way a field declaration does — `image`. The short-form ladder, the
|
|
1798
|
-
package and system defaults, the lowercase rule, the ambiguity reporting and the
|
|
1799
|
-
findings vocabulary all apply unchanged.
|
|
1800
|
-
|
|
1801
|
-
| written | means |
|
|
1802
|
-
| ----------------------------------- | ---------------------------------------- |
|
|
1803
|
-
| `![[anvil\|]]` | decorative — the common case |
|
|
1804
|
-
| `![[anvil\|An anvil]]` | alt text where it carries meaning |
|
|
1805
|
-
| `![[anvil]]` | unlabelled, and a finding like any other |
|
|
1806
|
-
| `![[sohl-none-image-anvil\|Anvil]]` | qualified, reaching another package |
|
|
1807
|
-
|
|
1808
|
-
The parser distinguishes a missing label from an empty one, so _deliberately
|
|
1809
|
-
decorative_ and _not written_ differ without an exemption.
|
|
1810
|
-
|
|
1811
|
-
**The label is the alt text, and it stays with the referrer.** One image serves
|
|
1812
|
-
many documents, and only the referrer knows what it means where it sits.
|
|
1813
|
-
|
|
1814
|
-
**An embed reaches asset types only.** The syntax invites the broader reading —
|
|
1815
|
-
transclusion of an arbitrary note — so the restriction is a guard rather than a
|
|
1816
|
-
convention.
|
|
1817
|
-
|
|
1818
|
-
**An embed takes the same directive an image does**, in the same place —
|
|
1819
|
-
immediately after the closing `]]`, with no space:
|
|
1820
|
-
|
|
1821
|
-
```markdown
|
|
1822
|
-
![[branwldrgr|Brànwâal Dôrgaar]]{float: top-left}
|
|
1823
|
-
![[branwldrgr|Brànwâal Dôrgaar]]{size: medium, float: top-left}
|
|
1824
|
-
![[thalornamap|Map of Thalorna]]{.full-width}
|
|
1825
|
-
![[thalornamap|Map of Thalorna]]{.full-width, float: top-left}
|
|
1826
|
-
![[thalornamap|Map of Thalorna]]{float: top-left, .full-width}
|
|
1827
|
-
```
|
|
1828
|
-
|
|
1829
|
-
**The same parser reads both**, so an embed takes a width class, a `size:`, a
|
|
1830
|
-
`float:`, or a combination in any order, comma-separated — everything
|
|
1831
|
-
[an image's directive](#width-is-a-class-and-the-ordinary-width-carries-no-marker)
|
|
1832
|
-
takes and nothing beyond it. The width, size, and position vocabularies are the ones
|
|
1833
|
-
above, and they are closed here for the same reason: a directive that is quietly
|
|
1834
|
-
ignored looks exactly like one that worked, and a directive holding a problem is
|
|
1835
|
-
not honoured in part either.
|
|
1836
|
-
|
|
1837
|
-
**A being's portrait is one of these**, opening its `{#appearance}` section by
|
|
1838
|
-
convention rather than declared in a field — see
|
|
1839
|
-
[the lead image](#the-lead-image).
|
|
1840
|
-
|
|
1841
|
-
### Three states of a note
|
|
1842
|
-
|
|
1843
|
-
A note has three states, and two of them are nothing but conventions the format
|
|
1844
|
-
already carried.
|
|
1845
|
-
|
|
1846
|
-
| State | Body | Marker | Page | Document | Link target |
|
|
1847
|
-
| --------- | ------- | --------------- | ------------ | --------------------------------- | ----------- |
|
|
1848
|
-
| **stub** | empty | none | no | yes, where one would not be empty | no |
|
|
1849
|
-
| **draft** | written | `tags: [draft]` | yes, noticed | yes, noticed | yes |
|
|
1850
|
-
| **full** | written | none | yes | yes | yes |
|
|
1851
|
-
|
|
1852
|
-
"Document" here is the document a note's frontmatter earns. A system's Actor or
|
|
1853
|
-
Item is earned by that system's block, in every state —
|
|
1854
|
-
[a system block is what makes a game document](#a-system-block-is-what-makes-a-game-document).
|
|
1855
|
-
|
|
1856
|
-
**Full is the absence of everything.** It is the default and carries no marker,
|
|
1857
|
-
which is right: the overwhelming majority of a finished corpus should say
|
|
1858
|
-
nothing about its own finishedness.
|
|
1859
|
-
|
|
1860
|
-
There is no new frontmatter key, no new filetype and no second parser. A stub is
|
|
1861
|
-
a `.md` file the walk already finds, and the moment somebody writes a body it
|
|
1862
|
-
becomes an ordinary note — nothing moves, nothing is deleted, and every inbound
|
|
1863
|
-
link starts resolving.
|
|
1864
|
-
|
|
1865
|
-
#### A stub is a note with an empty body
|
|
1866
|
-
|
|
1867
|
-
> **The body is empty when everything after the closing frontmatter fence is
|
|
1868
|
-
> whitespace.** Nothing else is empty.
|
|
1869
|
-
|
|
1870
|
-
The rule is that severe because the test has to be one you can apply by looking
|
|
1871
|
-
at the file. Every softer rule makes two files that look different behave the
|
|
1872
|
-
same.
|
|
1873
|
-
|
|
1874
|
-
| After the fence | Empty? | What happens |
|
|
1875
|
-
| ----------------------- | ------ | --------------------------------------------------------------- |
|
|
1876
|
-
| Nothing, or blank lines | yes | A stub |
|
|
1877
|
-
| An HTML comment | no | A note with a body that renders to nothing. The lint reports it |
|
|
1878
|
-
| A horizontal rule | no | The same |
|
|
1879
|
-
| A lone heading | no | The same |
|
|
1880
|
-
| `_To be written._` | no | The same |
|
|
1881
|
-
|
|
1882
|
-
An HTML comment is deliberately not empty. An invisible marker deciding whether
|
|
1883
|
-
a page exists is exactly the failure the retired `draft:` field was removed for.
|
|
1884
|
-
|
|
1885
|
-
#### What a stub emits
|
|
1886
|
-
|
|
1887
|
-
A stub's index record is its frontmatter plus the derived keys, **minus
|
|
1888
|
-
`address` and `anchors`**. The absent address is the whole signal, and it is the
|
|
1889
|
-
column a table renderer already reads.
|
|
1890
|
-
|
|
1891
|
-
It keeps everything else:
|
|
1892
|
-
|
|
1893
|
-
- **`file`**, which is what you edit and what every diagnostic names.
|
|
1894
|
-
- **`id`**, which is the identity every diagnostic and every table reads.
|
|
1895
|
-
- **`foundry`**, naming each document that was made.
|
|
1896
|
-
|
|
1897
|
-
#### A document that would be empty is not created
|
|
1898
|
-
|
|
1899
|
-
> **If the resulting document would be empty, it is not created** — and the
|
|
1900
|
-
> index then names no artefact: `uuid`, `path` and `address` are `null`, which
|
|
1901
|
-
> is how a record says one was not made.
|
|
1902
|
-
|
|
1903
|
-
The test is about the **output**, which is what lets one rule cover every case:
|
|
1904
|
-
|
|
1905
|
-
| The note | The document | Made? |
|
|
1906
|
-
| ---------------------------------- | -------------------------------------- | ----- |
|
|
1907
|
-
| `place`, `lore`, `doc`, `scenario` | its JournalEntry, which _is_ the prose | no |
|
|
1908
|
-
| any item type, `macro` | the Item or Macro, from `data:` | yes |
|
|
1909
|
-
| the same note's documentation | a JournalEntry of the same prose | no |
|
|
1910
|
-
| `map` | the Scene, from `data:` | yes |
|
|
1911
|
-
|
|
1912
|
-
An unwritten arcane talent still compiles into its Item, because an Item is its
|
|
1913
|
-
`data:` and that is not empty. What it has no documentation journal for is the
|
|
1914
|
-
same thing an unwritten `place` has no journal for: there is nothing to put in
|
|
1915
|
-
one.
|
|
1916
|
-
|
|
1917
|
-
**An infobox does not count as content.** The panel is a rendering of `data:`
|
|
1918
|
-
that the index already publishes, so an entry holding nothing else would tell a
|
|
1919
|
-
reader exactly what the record told them.
|
|
1920
|
-
|
|
1921
|
-
Nothing anywhere names a document that was not made: a compendium UUID for one
|
|
1922
|
-
would send every consumer that resolves it to a document the pack does not hold.
|
|
1923
|
-
|
|
1924
|
-
What is suppressed with the page is the web address and the link target: a
|
|
1925
|
-
wikilink into a stub is refused, and a table cell listing one prints its name as
|
|
1926
|
-
plain text rather than as a link.
|
|
1927
|
-
|
|
1928
|
-
#### Two types are structural and exempt
|
|
1929
|
-
|
|
1930
|
-
`folder` and `homepage` have no body by design — a folder note's page is a
|
|
1931
|
-
generated section index, and the homepage's is the site's front door. An empty
|
|
1932
|
-
body on either keeps its address and resolves as **full**, because such a note is
|
|
1933
|
-
complete.
|
|
1934
|
-
|
|
1935
|
-
Every type in the vocabulary declares whether an empty body suppresses its page,
|
|
1936
|
-
so adding a type means answering the question rather than inheriting a default.
|
|
1937
|
-
|
|
1938
|
-
#### A stub is still referenceable as data
|
|
1939
|
-
|
|
1940
|
-
`borders.to` and `routes.to` name a place by `Address`, and those are **index
|
|
1941
|
-
relations rather than page links**: a border with a stub on the far side is a
|
|
1942
|
-
valid statement about the world and resolves. So does `parents` for the
|
|
1943
|
-
containment tree and `domains` for tenure — a stub appears in its region's
|
|
1944
|
-
`contains` and on its holder's `holdings`, rendered as plain text. What a stub
|
|
1945
|
-
cannot be is the target of a wikilink, because a wikilink points at a page.
|
|
1946
|
-
|
|
1947
|
-
### What a note produces
|
|
1948
|
-
|
|
1949
|
-
Note types fall into two groups, and only the first has a mapping table.
|
|
1950
|
-
|
|
1951
|
-
**Types that _may_ produce a system document** — a SoHL or HM3 Actor or Item,
|
|
1952
|
-
one per system, described by the tables further down.
|
|
1953
|
-
|
|
1954
|
-
**Types producing only a core document** — `lore`, `place`, `scenario`, `doc`,
|
|
1955
|
-
`map`, `macro` and `homepage`. A core document is one Foundry defines rather than
|
|
1956
|
-
a system: a JournalEntry, a Scene, a Macro. These are system-agnostic, so such a
|
|
1957
|
-
note carries no `sohl:` or `hm3:` block, has no mapping table, and shows no
|
|
1958
|
-
**system** infoboxes.
|
|
1959
|
-
|
|
1960
|
-
It may still show a **note** infobox, and several should: a scenario's length,
|
|
1961
|
-
party size and required archetypes, or a place's subtype and parent, are exactly
|
|
1962
|
-
what a reader wants at a glance. Being outside both systems is not the same as
|
|
1963
|
-
having nothing worth summarising.
|
|
1964
|
-
|
|
1965
|
-
Every note in **both** groups still produces its JournalEntry and its web page.
|
|
1966
|
-
The difference is only whether a system Actor or Item is created as well.
|
|
1967
|
-
|
|
1968
|
-
#### A system block is what makes a game document
|
|
1969
|
-
|
|
1970
|
-
> **A note produces a system's Actor or Item only where it carries that
|
|
1971
|
-
> system's block.** A note carrying no block for any system produces no Actor
|
|
1972
|
-
> and no Item anywhere, whatever its packs declare.
|
|
1973
|
-
|
|
1974
|
-
The block is not a place to put optional detail — it is the statement that this
|
|
1975
|
-
person or thing is meant to be played. Its absence is a statement too, and it is
|
|
1976
|
-
made silently: the note is passed over by the Actor and Item passes exactly as
|
|
1977
|
-
any note they do not own, with no finding.
|
|
1978
|
-
|
|
1979
|
-
**Prose is untouched by this.** A body with content still compiles into one
|
|
1980
|
-
JournalEntry, one web page and one PDF page, however many blocks the note
|
|
1981
|
-
carries and whether or not it carries any; an empty body still compiles none of
|
|
1982
|
-
the three. So a note has three states, and each reaches the next by one edit in
|
|
1983
|
-
one file, with its address, its id, its index record and every link into it
|
|
1984
|
-
unchanged:
|
|
1985
|
-
|
|
1986
|
-
| The note carries | Produces |
|
|
1987
|
-
| -------------------------- | ---------------------------------------------- |
|
|
1988
|
-
| no block, empty body | an index record — a name that can be queried |
|
|
1989
|
-
| no block, a written body | a page, a PDF page and a JournalEntry |
|
|
1990
|
-
| a block, a written body | all of those, plus that system's Actor or Item |
|
|
1991
|
-
| two blocks, a written body | one document per system, and one of each other |
|
|
1992
|
-
|
|
1993
|
-
Write the person; add a system block when someone will need a token. A setting's
|
|
1994
|
-
cast is mostly people nobody rolls dice against, and a note about one of them
|
|
1995
|
-
carries no block and occupies no slot in a compendium a referee browses.
|
|
1996
|
-
|
|
1997
|
-
**A note carrying no document publishes no UUID for one.** Its address, its
|
|
1998
|
-
page and its documentation journal are published as they are for any other note,
|
|
1999
|
-
and the `foundry` entry that would name an Actor or an Item is absent — the same
|
|
2000
|
-
statement [`pack: none`](#the-pack-a-note-compiles-into) makes about a pack,
|
|
2001
|
-
made about a system.
|
|
2002
|
-
|
|
2003
|
-
**A block for a system this package ships no pack for is an error.** The
|
|
2004
|
-
authored data would be discarded either way, so the build says so, once per note
|
|
2005
|
-
per system, naming the system and the document class:
|
|
2006
|
-
|
|
2007
|
-
```
|
|
2008
|
-
assets/content/People/Aelric.md:7:1: error: carries a `hm3:` block, and no Actor pack in this package compiles hm3 documents, so the block is discarded — declare `system: hm3` on one of the `packs:`, or remove the block.
|
|
2009
|
-
```
|
|
2010
|
-
|
|
2011
|
-
A pack declaring no `system:` is compiled by the fallback pass, which reads one
|
|
2012
|
-
system's block — so a systemless Actor pack serves that system and not every
|
|
2013
|
-
system a note might write. A package shipping for a second system declares
|
|
2014
|
-
`system:` on a pack of each class it compiles.
|
|
2015
|
-
|
|
2016
|
-
That includes actors. A being producing its Actor and nothing else would be
|
|
2017
|
-
a system-bearing note with readable content, giving a prose link
|
|
2018
|
-
naming it nowhere to land. It carries a documentation journal like every other
|
|
2019
|
-
such note, addressed
|
|
2020
|
-
`<package>-note-being-<shortcode>` beside the Actor's
|
|
2021
|
-
`<package>-<system>-being-<shortcode>`.
|
|
2022
|
-
|
|
2023
|
-
**A being keeps its prose inline as well.** `system.appearance` and
|
|
2024
|
-
`system.dossier` are still the rendered text, where an item's description is an
|
|
2025
|
-
`@UUID` pointer into its journal. The difference is deliberate and is about
|
|
2026
|
-
size: one item is embedded across hundreds of beings, so baking its description
|
|
2027
|
-
into every copy bloats the compendium by the length of the prose times the
|
|
2028
|
-
number of carriers, and the pointer buys that back. An actor is singular, so the
|
|
2029
|
-
same indirection would cost a reader a click and save nothing.
|
|
2030
|
-
|
|
2031
|
-
### One note is at most one document per system
|
|
2032
|
-
|
|
2033
|
-
A note produces **at most one document in each system**. That constraint is worth
|
|
2034
|
-
stating because the obvious counter-example is real, and the way it is resolved
|
|
2035
|
-
shapes how weapons are authored.
|
|
2036
|
-
|
|
2037
|
-
SoHL gives a weapon **strike modes**: `system.strikeModes` on a single
|
|
2038
|
-
`weapongear`, each mode melee or missile. A shorkana has four — the edge, the
|
|
2039
|
-
blunt of the swung blade, the haft or pommel, and the throw. HM3 has no such
|
|
2040
|
-
field: it assumes one usage per item, so those four modes are three
|
|
2041
|
-
`weapongear` documents and one `missilegear`.
|
|
2042
|
-
|
|
2043
|
-
**The mechanical values cannot be shared, so the modes cannot be lifted into
|
|
2044
|
-
`data:`.** Impact, heft, reach and draw all differ between the two systems even
|
|
2045
|
-
where the concept matches. `data:` holds what the _thing_ is; how it performs is
|
|
2046
|
-
each system's own, and belongs in that system's block.
|
|
2047
|
-
|
|
2048
|
-
**So the second document becomes a second note.** A `Spear` note carries a
|
|
2049
|
-
`sohl:` block describing four strike modes and an `hm3:` block describing its
|
|
2050
|
-
usual melee profile; a separate `Spear (thrown)` note carries **only** an `hm3:`
|
|
2051
|
-
block, describing the missilegear. That yields two shortcodes, which is what HM3
|
|
2052
|
-
needs, and the companion never appears in SoHL, which is what SoHL needs.
|
|
2053
|
-
|
|
2054
|
-
The population is small. Of HM3's eleven `missilegear` items, seven are distinct
|
|
2055
|
-
objects with a SoHL counterpart — `Arrow (Longbow)`, `Bolt (Crossbow)`,
|
|
2056
|
-
`Dart (Blowgun)`, `Stone (Sling)` and so on, which are ordinary notes carrying
|
|
2057
|
-
both blocks. Only four are companions: `Javelin (thrown)`, `Shorkana (thrown)`,
|
|
2058
|
-
`Spear (thrown)`, `Taburi (thrown)`.
|
|
2059
|
-
|
|
2060
|
-
**Every note carrying an `hm3:` block states its type.** Nothing can derive
|
|
2061
|
-
whether `Spear (thrown)` is a `weapongear` or a `missilegear` — there is no other
|
|
2062
|
-
system to infer from and no subType to read — and the same is true of the note
|
|
2063
|
-
carrying both blocks, whose SoHL strike modes describe every usage at once. So
|
|
2064
|
-
`hm3.type` is _required_ on both, not only on the companion.
|
|
2065
|
-
|
|
2066
|
-
The following special markdown sequences are recognized:
|
|
2067
|
-
|
|
2068
|
-
```
|
|
2069
|
-
# Heading {#id .class1 .class2 attr="value"}
|
|
2070
|
-
```
|
|
2071
|
-
|
|
2072
|
-
Any header can include curly braces. Inside the curly braces:
|
|
2073
|
-
|
|
2074
|
-
- `#id` represents an id anchor named `id` (only one allowed)
|
|
2075
|
-
- `.class1` represents a CSS class named `class1` (any number of classes allowed)
|
|
2076
|
-
- `attr="value"` represents an HTML attribute named `attr` whose value is `value` (any number of attr/value pairs allowed)
|
|
2077
|
-
|
|
2078
|
-
#### Images
|
|
2079
|
-
|
|
2080
|
-
An image is authored in the body, in the place it belongs, and every surface
|
|
2081
|
-
renders it there — the book, the website and a Foundry Journal Page alike. One
|
|
2082
|
-
authored statement, three renderers, which is the single-sourcing every other
|
|
2083
|
-
part of this format follows.
|
|
2084
|
-
|
|
2085
|
-
```markdown
|
|
2086
|
-
![[branwldrgr|Brànwâal Dôrgaar]]{float: top-left}
|
|
2087
|
-
![[branwldrgr|Brànwâal Dôrgaar]]{size: medium, float: top-left}
|
|
2088
|
-
|
|
2089
|
-
{.full-width}
|
|
2090
|
-
```
|
|
2091
|
-
|
|
2092
|
-
**An image is a block.** It stands alone in its paragraph, with a blank line
|
|
2093
|
-
either side of it, and every surface renders it as a figure. An image sharing a
|
|
2094
|
-
paragraph with prose is refused, because a width and a position mean nothing
|
|
2095
|
-
applied to a word in the middle of a sentence.
|
|
2096
|
-
|
|
2097
|
-
**The alt text is the caption.** Print has no `alt` attribute and has to put
|
|
2098
|
-
those words somewhere a reader can see them, so every surface draws them under
|
|
2099
|
-
the picture; the two HTML surfaces carry them as `alt` as well. A title —
|
|
2100
|
-
`` — is refused rather than dropped in silence: there is one
|
|
2101
|
-
place for those words and this is it.
|
|
2102
|
-
|
|
2103
|
-
##### Width is a class, and the ordinary width carries no marker
|
|
2104
|
-
|
|
2105
|
-
| `class` value | book | website | Foundry journal |
|
|
2106
|
-
| ------------- | ----------------------------- | ------------------ | --------------- |
|
|
2107
|
-
| _(none)_ | natural width within a column | natural width | natural width |
|
|
2108
|
-
| `.full-width` | page measure | full content width | full page width |
|
|
2109
|
-
|
|
2110
|
-
The simple case needs no spelling, which is why the ordinary width has no name.
|
|
2111
|
-
|
|
2112
|
-
**No pixel values, ever.** A number means something in a browser and nothing
|
|
2113
|
-
coherent in print, and a directive carrying a class and a dimension at once
|
|
2114
|
-
gives one question two answers with no rule for which wins. `{width=800}` is
|
|
2115
|
-
refused.
|
|
2116
|
-
|
|
2117
|
-
##### Named size is `size:`
|
|
2118
|
-
|
|
2119
|
-
| `size` value | website and Foundry | book |
|
|
2120
|
-
| ------------ | ------------------- | ------------ |
|
|
2121
|
-
| `auto` | natural size | natural size |
|
|
2122
|
-
| `small` | 64 CSS pixels | 1.6 cm |
|
|
2123
|
-
| `medium` | 128 CSS pixels | 3.2 cm |
|
|
2124
|
-
| `large` | 256 CSS pixels | 5.6 cm |
|
|
2125
|
-
| `xlarge` | 512 CSS pixels | 8 cm |
|
|
2126
|
-
| `full-width` | content measure | page measure |
|
|
2127
|
-
|
|
2128
|
-
These are maximum display widths for Markdown images and embedded assets;
|
|
2129
|
-
height follows the file's aspect ratio. The available column, page, or browser
|
|
2130
|
-
measure caps every size, and print also caps image height within the page.
|
|
2131
|
-
Omitting `size:` means `auto`, which uses the file's natural dimensions within
|
|
2132
|
-
those bounds. `full-width` spans the book's page rather than one column. The
|
|
2133
|
-
`.full-width` class also grants page scope in print; when it accompanies a
|
|
2134
|
-
bounded `size:`, the image keeps that bounded width inside the page figure.
|
|
2135
|
-
A `size:` may sit before or after `float:` in the same directive, and neither
|
|
2136
|
-
changes the other's behavior.
|
|
2137
|
-
|
|
2138
|
-
##### Position is `float:`
|
|
2139
|
-
|
|
2140
|
-
| `float` value | book | website and Foundry journal |
|
|
2141
|
-
| -------------- | -------------------- | ------------------------------- |
|
|
2142
|
-
| `top-left` | top of the column | floated left, text wraps |
|
|
2143
|
-
| `bottom-left` | bottom of the column | floated left, text wraps |
|
|
2144
|
-
| `top-right` | top of the column | floated right, text wraps |
|
|
2145
|
-
| `bottom-right` | bottom of the column | floated right, text wraps |
|
|
2146
|
-
| `center` | top of the column | centred, with no text beside it |
|
|
2147
|
-
|
|
2148
|
-
**Print cannot wrap text around an arbitrary shape.** A Typst float occupies
|
|
2149
|
-
the column measure, so in the book only the vertical half of a position has an
|
|
2150
|
-
effect — top of the column or bottom of it — while the horizontal half does
|
|
2151
|
-
not. The website and a Foundry journal get true CSS wrap from the same
|
|
2152
|
-
directive. Expect the same statement, not the same page.
|
|
2153
|
-
|
|
2154
|
-
An image with no `float:` is an ordinary block in the flow, where it was
|
|
2155
|
-
written. A `.full-width` image is set at the **top of a new page**: the book is
|
|
2156
|
-
set in two columns, only a float spans them both, and a float is placed where
|
|
2157
|
-
the page has room rather than where it was written — so the page breaks before
|
|
2158
|
-
the picture, and the prose that follows it runs below it on the same page. The
|
|
2159
|
-
picture can therefore never print above the prose that introduces it or after
|
|
2160
|
-
the prose that follows it, at the cost of the page before it ending short. A
|
|
2161
|
-
picture taller than the page takes a page of its own.
|
|
2162
|
-
|
|
2163
|
-
A `.full-width` image that **also states a `float:`** is asking for a float and
|
|
2164
|
-
gets one, with no break before it. A float is placed where it fits, which may be
|
|
2165
|
-
the next page.
|
|
2166
|
-
|
|
2167
|
-
##### All three vocabularies are closed
|
|
2168
|
-
|
|
2169
|
-
`{.fullwidth}`, `{.full_width}`, `{width=800}` and `{float: middle}` are
|
|
2170
|
-
refused, located by file, line and column, and the build fails. An unrecognised
|
|
2171
|
-
value rendering as the ordinary width is the failure worth preventing, because
|
|
2172
|
-
it looks exactly like a directive that worked.
|
|
2173
|
-
|
|
2174
|
-
A directive holding anything this section does not name is not honoured at all,
|
|
2175
|
-
even in the part that parsed: a half-honoured directive is the same silent
|
|
2176
|
-
failure in a smaller costume. The image keeps its braces and renders them as
|
|
2177
|
-
literal text, so the mistake is visible on the page as well as in the log.
|
|
2178
|
-
|
|
2179
|
-
**Nothing else reaches emitted markup.** The width and position classes and the
|
|
2180
|
-
address are all that leave a note; there is no `style`, no `id` and no arbitrary
|
|
2181
|
-
attribute, because data is never compiled into markup here. An address carries
|
|
2182
|
-
a scheme of `http:` or `https:`, or none at all.
|
|
2183
|
-
|
|
2184
|
-
##### Where an address resolves
|
|
2185
|
-
|
|
2186
|
-
An image's address follows [the pathname rule](#a-pathname-names-the-package-that-owns-the-file)
|
|
2187
|
-
— its first segment says which package owns the file — and each surface
|
|
2188
|
-
resolves it to what that surface serves:
|
|
2189
|
-
|
|
2190
|
-
- **Foundry** is handed the path inside the install, so `images/map.webp`
|
|
2191
|
-
reaches a journal page as `modules/<package>/assets/images/map.webp`. An
|
|
2192
|
-
address with no such path — a package this build declares no relationship
|
|
2193
|
-
with, or `packagebuild`, which Foundry installs nothing of — is **refused,
|
|
2194
|
-
located by file, line and column**, and the build fails. Foundry is the one
|
|
2195
|
-
surface a correctly written pathname can be dead on while the other two
|
|
2196
|
-
resolve it, and a `src` that resolves against nothing looks exactly like an
|
|
2197
|
-
address that worked.
|
|
2198
|
-
- **The book** is handed a copy of the file, staged out of this package's own
|
|
2199
|
-
asset tree into the build directory before the compiler runs. An address
|
|
2200
|
-
naming a file this package does not ship — another package's, or a URL —
|
|
2201
|
-
cannot be staged, and the book prints the caption alone and says so.
|
|
2202
|
-
- **The website** passes the address through exactly as authored, because a
|
|
2203
|
-
site serves its imagery from its own asset host and this package is not told
|
|
2204
|
-
what that host is. An address that has to resolve there is written as a full
|
|
2205
|
-
URL.
|
|
2206
|
-
|
|
2207
|
-
#### Content tables
|
|
2208
|
-
|
|
2209
|
-
A fenced `dataview` block is replaced by the table its query selects:
|
|
2210
|
-
|
|
2211
|
-
````markdown
|
|
2212
|
-
```dataview
|
|
2213
|
-
TABLE WITHOUT ID name.full AS "Name", shortcode AS "Code"
|
|
2214
|
-
WHERE type = "armorgear"
|
|
2215
|
-
```
|
|
2216
|
-
````
|
|
2217
|
-
|
|
2218
|
-
**A query that selects nothing is a build error.** A zero-row table publishes as
|
|
2219
|
-
a bare header and a rule, and a stale query — a renamed type, a retired
|
|
2220
|
-
category, a typo'd path — is then indistinguishable from a category that is
|
|
2221
|
-
legitimately empty. Eight tables in one note published that way for months after
|
|
2222
|
-
a type rename, and no build said a word.
|
|
2223
|
-
|
|
2224
|
-
Where a table is _meant_ to be empty, say so on the fence:
|
|
2225
|
-
|
|
2226
|
-
````markdown
|
|
2227
|
-
```dataview allow-empty
|
|
2228
|
-
TABLE WITHOUT ID name.full AS "Name"
|
|
2229
|
-
WHERE type = "affliction" AND sohl.kbcat = "not-written-yet"
|
|
2230
|
-
```
|
|
2231
|
-
````
|
|
2232
|
-
|
|
2233
|
-
The opt-in is on the fence rather than in the query because it is a statement
|
|
2234
|
-
about this directive, not part of the query language. Either way the table is
|
|
2235
|
-
still rendered — the finding is the point, not withholding the output.
|
|
2236
|
-
|
|
2237
|
-
##### In SQL, over the content index
|
|
2238
|
-
|
|
2239
|
-
`dataview` is being replaced by **SQL**, queried over the content index, and both
|
|
2240
|
-
fences work while the corpus is converted. The query is real SQL, run by
|
|
2241
|
-
DuckDB — not a dialect maintained by this package.
|
|
2242
|
-
|
|
2243
|
-
````markdown
|
|
2244
|
-
```sql
|
|
2245
|
-
SELECT address.slug AS _ref,
|
|
2246
|
-
sohl.kbcat AS _section,
|
|
2247
|
-
name.full AS "Name",
|
|
2248
|
-
sohl.weight AS "Weight"
|
|
2249
|
-
FROM notes
|
|
2250
|
-
WHERE type = 'miscgear'
|
|
2251
|
-
ORDER BY sohl.kbcat, name.full
|
|
2252
|
-
```
|
|
2253
|
-
````
|
|
2254
|
-
|
|
2255
|
-
**`FROM notes`** is the content index: one row per note, plus one per
|
|
2256
|
-
documentation entry, so `type = 'miscgear'` selects the items and never their
|
|
2257
|
-
journals. A nested field is addressed exactly as a note authors it —
|
|
2258
|
-
`sohl.weight`, `name.full`, `file.path` — because the index is read as JSON and
|
|
2259
|
-
every nested object is inferred as a struct. A field a note type does not carry
|
|
2260
|
-
reads `NULL` rather than failing.
|
|
2261
|
-
|
|
2262
|
-
**`notes` leaves out stubs; `entries` does not.** Both carry a derived `state`
|
|
2263
|
-
column — `stub`, `draft` or `full` — and `notes` is exactly
|
|
2264
|
-
`SELECT * FROM entries WHERE state <> 'stub'`. So every table already written
|
|
2265
|
-
selects what it always selected, and gains `state` for free; a table that should
|
|
2266
|
-
list the unwritten beside the written changes one word:
|
|
2267
|
-
|
|
2268
|
-
````markdown
|
|
2269
|
-
```sql
|
|
2270
|
-
SELECT address.slug AS _ref,
|
|
2271
|
-
name.full AS "Name",
|
|
2272
|
-
state AS "State",
|
|
2273
|
-
m.name AS "Market",
|
|
2274
|
-
data.population AS "People"
|
|
2275
|
-
FROM entries
|
|
2276
|
-
LEFT JOIN market m ON m.value = data.market
|
|
2277
|
-
WHERE type = 'place' AND subType = 'settlement'
|
|
2278
|
-
AND list_contains(data.parents, 'place-aelwyth')
|
|
2279
|
-
ORDER BY data.population DESC NULLS LAST, name.full COLLATE NOCASE
|
|
2280
|
-
```
|
|
2281
|
-
````
|
|
2282
|
-
|
|
2283
|
-
A written settlement renders as a link and a stub renders as plain text, from
|
|
2284
|
-
one query with no branch in it: `_ref` is the stub's absent address, and the
|
|
2285
|
-
renderer already prints a bare cell where it is null.
|
|
2286
|
-
|
|
2287
|
-
A fence selecting `FROM notes` that **would** gain rows from `FROM entries` is
|
|
2288
|
-
warned about at the fence, with the count — so adopting stubs in a given table
|
|
2289
|
-
stays a decision somebody makes rather than one they forget.
|
|
2290
|
-
|
|
2291
|
-
The ladder is what makes "how finished is this?" a query:
|
|
2292
|
-
|
|
2293
|
-
```sql
|
|
2294
|
-
SELECT state, count(*) AS n FROM entries GROUP BY state ORDER BY state
|
|
2295
|
-
```
|
|
2296
|
-
|
|
2297
|
-
**`market`** is the six-step market scale as a relation — `value`, `name`,
|
|
2298
|
-
`trade` — so a table prints `village` beside the number a note wrote without a
|
|
2299
|
-
second copy of the scale living in authored content.
|
|
2300
|
-
|
|
2301
|
-
**The state is derived, never stored.** No index record carries it, no note can
|
|
2302
|
-
author it, and nothing in `data:` may claim it: the only authored signal is the
|
|
2303
|
-
`draft` tag, which marks the one thing reading the body cannot reveal.
|
|
2304
|
-
|
|
2305
|
-
**Two aliases are read by the renderer rather than printed**, because which
|
|
2306
|
-
column links and where a section breaks are decisions about output, not
|
|
2307
|
-
relational operations:
|
|
2308
|
-
|
|
2309
|
-
| Alias | What it does |
|
|
2310
|
-
| ---------- | ---------------------------------------------------------------------- |
|
|
2311
|
-
| `_ref` | Makes the row's **first** rendered column a wikilink to that address. |
|
|
2312
|
-
| `_section` | Emits a headed table per distinct value, in the order the rows arrive. |
|
|
2313
|
-
|
|
2314
|
-
`_section` is why one query replaces the forty near-identical blocks a grouped
|
|
2315
|
-
table would otherwise need: the authored `ORDER BY` decides the section order too.
|
|
2316
|
-
|
|
2317
|
-
**Beware `packFolder`.** It is a note's _pack_ folder, not its directory — the
|
|
2318
|
-
directory is `file.folder`. (The `folder` field it replaced is retired; a query
|
|
2319
|
-
naming it matches nothing.)
|
|
2320
|
-
|
|
2321
|
-
###### Reading another package's notes
|
|
2322
|
-
|
|
2323
|
-
Each package this one **depends on** is attached as a schema named after it, so
|
|
2324
|
-
a satellite can tabulate what it builds on:
|
|
2325
|
-
|
|
2326
|
-
````markdown
|
|
2327
|
-
```sql
|
|
2328
|
-
SELECT name.full AS "Name", sohl.skillBase AS "Base"
|
|
2329
|
-
FROM sohl.notes
|
|
2330
|
-
WHERE type = 'skill'
|
|
2331
|
-
ORDER BY name.full
|
|
2332
|
-
```
|
|
2333
|
-
````
|
|
2334
|
-
|
|
2335
|
-
This package's own notes stay at the unqualified `notes`, and a query may read
|
|
2336
|
-
both at once — joining your beings against the skills they cite is one `FROM`
|
|
2337
|
-
clause. It needs no fetch and no configuration: a dependency's published index
|
|
2338
|
-
is already cached when a compile starts, because resolving addresses across
|
|
2339
|
-
packages needs it.
|
|
2340
|
-
|
|
2341
|
-
Which dataset a query reads is `FROM`'s job rather than a fence property. A
|
|
2342
|
-
fence naming a file would write a build artifact's path into the corpus, so
|
|
2343
|
-
renaming the artifact would mean sweeping every note that cited it.
|
|
2344
|
-
|
|
2345
|
-
###### Header arguments
|
|
2346
|
-
|
|
2347
|
-
Statements _about the directive_ — as opposed to the query — are written after
|
|
2348
|
-
the language as **org-babel header arguments**:
|
|
2349
|
-
|
|
2350
|
-
````markdown
|
|
2351
|
-
```sql :section-level 3 :allow-empty
|
|
2352
|
-
SELECT name.full AS "Name", sohl.kbcat AS _section
|
|
2353
|
-
FROM notes WHERE type = 'affliction'
|
|
2354
|
-
```
|
|
2355
|
-
````
|
|
2356
|
-
|
|
2357
|
-
The language word stays first and stays plain, so GitHub, Prettier and every
|
|
2358
|
-
other markdown reader still highlight the block as SQL and simply ignore what
|
|
2359
|
-
follows.
|
|
2360
|
-
|
|
2361
|
-
| Argument | What it does |
|
|
2362
|
-
| ---------------------- | ---------------------------------------------------------------------------------------- |
|
|
2363
|
-
| `:allow-empty` | A table selecting nothing is intended, not a stale query. Without it, empty is an error. |
|
|
2364
|
-
| `:section-level <1-6>` | The heading level `_section` emits. Default `2`. |
|
|
2365
|
-
|
|
2366
|
-
The grammar is org's, so it extends without inventing a spelling per property:
|
|
2367
|
-
|
|
2368
|
-
- a key is `:name` **starting a word**, so a colon inside or ending one is text
|
|
2369
|
-
— `:caption Gear: the tables` is a single argument;
|
|
2370
|
-
- a value runs to the next key or the end of the line, spaces included;
|
|
2371
|
-
- a key with no value means `true`, which is what `:allow-empty` is;
|
|
2372
|
-
- a value may be `"quoted"` to hold a word that would otherwise read as a key;
|
|
2373
|
-
- a repeated key takes its last value.
|
|
2374
|
-
|
|
2375
|
-
`dataview` keeps its own bare `allow-empty`; it is the retiring language and its
|
|
2376
|
-
grammar is frozen.
|
|
2377
|
-
|
|
2378
|
-
```
|
|
2379
|
-
:::secret
|
|
2380
|
-
This is secret text
|
|
2381
|
-
:::
|
|
2382
|
-
```
|
|
2383
|
-
|
|
2384
|
-
Use a whole-line `:::secret` opener and a whole-line `:::` closer to mark a GM passage. Markdown inside the block, including links and emphasis, renders normally. Keep the fences outside code blocks and close each one before starting another. An unclosed or nested block is a build error at the source line.
|
|
2385
|
-
|
|
2386
|
-
In FoundryVTT, the passage is a secret section visible to the GM and owners of the journal:
|
|
2387
|
-
|
|
2388
|
-
```html
|
|
2389
|
-
<section class="secret" id="secret-example">
|
|
2390
|
-
<p>This is secret text</p>
|
|
2391
|
-
</section>
|
|
2392
|
-
```
|
|
2393
|
-
|
|
2394
|
-
On webpages, the passage appears as a collapsed spoiler that any reader can open:
|
|
2395
|
-
|
|
2396
|
-
```html
|
|
2397
|
-
<details>
|
|
2398
|
-
<summary>Spoiler</summary>
|
|
2399
|
-
<p>This is secret text</p>
|
|
2400
|
-
</details>
|
|
2401
|
-
```
|
|
2402
|
-
|
|
2403
|
-
In books, the passage is visible under a **GM note** label. A printed page has no reader permissions or interactive disclosure control.
|
|
2404
|
-
|
|
2405
|
-
#### Actors
|
|
2406
|
-
|
|
2407
|
-
All actor types, including being and vehicle, have their entire markdown section processed as normal.
|
|
2408
|
-
|
|
2409
|
-
The following H1 headers are treated specially:
|
|
2410
|
-
|
|
2411
|
-
- `# ... {#appearance}`: The contents of this header become the `doc.appearance` property in sohl and `doc.description` in hm3.
|
|
2412
|
-
- `# ... {#dossier}`: The contents of this header become the `doc.dossier` property in sohl and `doc.biography` in hm3.
|
|
2413
|
-
- `# ... {#spoilers}`: The contents of this header are not written to the actor at all.
|
|
2414
|
-
|
|
2415
|
-
For JournalEntries, the following rules apply:
|
|
2416
|
-
|
|
2417
|
-
- `# ... {#spoilers}`: The contents of this header go into a page which is viewable only by the GM (`CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE`).
|
|
2418
|
-
|
|
2419
|
-
**Archetypes** describe characters in broad terms and help match them to an adventure. The list is:
|
|
2420
|
-
|
|
2421
|
-
- warrior: Can hold a line and win a fight.
|
|
2422
|
-
- skirmisher: Fights light — ambush, missile, mobility.
|
|
2423
|
-
- infiltrator: Gets in unseen — locks, stealth, disguise.
|
|
2424
|
-
- mage: Commands arcane practice.
|
|
2425
|
-
- cleric: Commands religious practice and standing.
|
|
2426
|
-
- healer: Treats wounds and illness.
|
|
2427
|
-
- scholar: Reads, researches, and knows things.
|
|
2428
|
-
- courtier: Navigates rank, negotiation, and intrigue.
|
|
2429
|
-
- woodsman: Travels and survives wild country.
|
|
2430
|
-
- mariner: Handles boats and blue water.
|
|
2431
|
-
- artisan: Builds, repairs, and appraises craft work.
|
|
2432
|
-
- trader: Moves goods, values them, and knows markets.
|
|
2433
|
-
|
|
2434
|
-
Note that archetypes are descriptive, not proscriptive, and a character may be described by multiple archetypes at once.
|
|
2435
|
-
|
|
2436
|
-
#### Items
|
|
2437
|
-
|
|
2438
|
-
All item types, including affiliation, affliction, armor, armorlocation, attribute, concoction, containergear, miscgear, mystery, mysticalability, projectile, skill, trauma, weapon, and lore, have their entire markdown section processed as normal.
|
|
2439
|
-
|
|
2440
|
-
The following H1 headers are treated specially:
|
|
2441
|
-
|
|
2442
|
-
- `# ... {#appearance}`: The contents of this header become the `doc.description` property in hm3. **SoHL has no Item `appearance` field** — none of its thirteen Item subtypes declares one (only Actors do), so this section is not written to a SoHL item.
|
|
2443
|
-
|
|
2444
|
-
### type: being
|
|
2445
|
-
|
|
2446
|
-
Generates a living (or undead, or spirit) being.
|
|
2447
|
-
|
|
2448
|
-
| `data` property | Values | Description |
|
|
2449
|
-
| --------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
2450
|
-
| `tokenIcon` | `Address` | What a token on the canvas wears — an `icon` address; defaults to `icon` |
|
|
2451
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
2452
|
-
| `archetypes` | `Archetype[]` | What sort of character this is. **Always an array** — `[]` where none apply; `null` is an error. |
|
|
2453
|
-
| `occupation` | `string` | Name of the character's occupation |
|
|
2454
|
-
| `social` | `Map<string, unknown>` | The being's social profile |
|
|
2455
|
-
| `stations` | `Address[]` | Name of the stations the character belongs to |
|
|
2456
|
-
| `lore` | `Address[]` | Lore concerning this being — the people it is of, the standing it holds, the law it lives under |
|
|
2457
|
-
| `homes` | `Address[]` | Place the being calls home |
|
|
2458
|
-
| `affiliations` | `Address[]` | Affilliations (e.g., arcane/divine traditions, polities, etc) |
|
|
2459
|
-
| `socialTies` | `Map<Address, Tie>` | Defining support and opposition, directed from this being to others |
|
|
2460
|
-
| `gender` | `male \| female \| other` | Gender of the character |
|
|
2461
|
-
| `species` | `Address` | Being's species (lore) |
|
|
2462
|
-
| `born` | `date \| unknown` | When the being was born; absent, it was never born |
|
|
2463
|
-
| `died` | `date \| unknown` | When the being died; absent, it is alive |
|
|
2464
|
-
| `age` | `34 \| ~34` | Age in years, stated only to override what `born` says; `~` marks an estimate |
|
|
2465
|
-
| `ageYears` | `number` | Written by the compiler beside an `age` estimate — the `~` stripped; a note never authors this |
|
|
2466
|
-
| `height` | `number` | Height in meters |
|
|
2467
|
-
| `weight` | `number` | Weight in kilograms |
|
|
2468
|
-
| `frame` | `scant \| light \| medium \| large \| massive` | Relative frame size |
|
|
2469
|
-
| `appearance.eye_color` | `string` | Eye color |
|
|
2470
|
-
| `appearance.hair_color` | `string` | Hair color |
|
|
2471
|
-
| `appearance.skin_color` | `string` | Skin color |
|
|
2472
|
-
| `appearance.complexion` | `string` | Complexion |
|
|
2473
|
-
| `appearance.extra_features` | `string[]` | Extra features |
|
|
2474
|
-
|
|
2475
|
-
#### The people who matter to a being
|
|
2476
|
-
|
|
2477
|
-
`data.socialTies` records relationships that define the subject's dossier, not
|
|
2478
|
-
everyone the subject knows. An absent entry says nothing about that relationship.
|
|
2479
|
-
An acquaintance belongs here when, for example, a guard will recognize the
|
|
2480
|
-
subject at a gate. A father and an independent adult son can have no tie in
|
|
2481
|
-
either note; a daughter who relies on her father may be his `dependent`.
|
|
2482
|
-
|
|
2483
|
-
The keys are bare Addresses with an explicit `being` or `affiliation` type.
|
|
2484
|
-
Values are `patron`, `friend`, `dependent`, `acquaintance`, `rival`, or
|
|
2485
|
-
`nemesis`. A patron supports the subject from greater power or status; a
|
|
2486
|
-
friend supports them from goodwill; a dependent relies on their protection;
|
|
2487
|
-
an acquaintance knows them without a strong disposition; a rival opposes them
|
|
2488
|
-
without implacable hostility; a nemesis opposes them personally and
|
|
2489
|
-
implacably. The infobox groups targets by term and links to their notes.
|
|
2490
|
-
|
|
2491
|
-
```yaml
|
|
2492
|
-
data:
|
|
2493
|
-
socialTies:
|
|
2494
|
-
being-kaldor: patron
|
|
2495
|
-
being-alys: dependent
|
|
2496
|
-
affiliation-silverguild: nemesis
|
|
2497
|
-
kethira-note-being-kaldas: rival
|
|
2498
|
-
```
|
|
2499
|
-
|
|
2500
|
-
The full Address names a target in another package. Each target has one tie:
|
|
2501
|
-
writing its short and full Address in the same map still names one target and is
|
|
2502
|
-
an error.
|
|
2503
|
-
|
|
2504
|
-
Each tie is one-sided. The target need not name the subject back, even when
|
|
2505
|
-
`patron` and `dependent` would describe opposite ends of the same support.
|
|
2506
|
-
Kinship, household membership, organizational membership, rank, and office are
|
|
2507
|
-
distinct facts. Their relevant effects may appear as ties, while their details
|
|
2508
|
-
belong in prose, `data.affiliations`, or governance fields. A tie has no
|
|
2509
|
-
mechanical modifier.
|
|
2510
|
-
|
|
2511
|
-
#### When a being was born, when it died, and how old it is
|
|
2512
|
-
|
|
2513
|
-
**Absence asserts a fact rather than recording a gap**, and that is what these
|
|
2514
|
-
three fields are built around.
|
|
2515
|
-
|
|
2516
|
-
- **`born` absent** says the being was **never born** — a construct, a spirit,
|
|
2517
|
-
an elemental, a thing that was made. **`born: unknown`** says it was born and
|
|
2518
|
-
the date is unrecorded, which is the commonest shape in a tree.
|
|
2519
|
-
- **`died` absent** says the being is **alive**, which is the common case and so
|
|
2520
|
-
takes the cheapest form. **`died: unknown`** says it is dead and the date is
|
|
2521
|
-
unrecorded. Both spellings of `died` mean dead.
|
|
2522
|
-
- **`age` is optional and is an override.** Written, it wins over anything the
|
|
2523
|
-
dates say. Unwritten beside a dated `born`, it is the age that follows from
|
|
2524
|
-
the date and the package's declared present — see below. Unwritten beside
|
|
2525
|
-
`born: unknown` or an absent `born`, the age is unknown.
|
|
2526
|
-
|
|
2527
|
-
Most people in a setting do not know their own birth date, so an age beside
|
|
2528
|
-
`born: unknown` is an ordinary complete record and not a note working around the
|
|
2529
|
-
schema. Where the number is somebody's estimate rather than a fact, write it
|
|
2530
|
-
`~34` — the same `~` a date carries. The estimate string is kept exactly as
|
|
2531
|
-
written; the compiler adds `ageYears` beside it, the plain number with the `~`
|
|
2532
|
-
stripped, for anything that needs to sort or filter on the age rather than
|
|
2533
|
-
read it.
|
|
2534
|
-
|
|
2535
|
-
#### The age a dated `born` computes to
|
|
2536
|
-
|
|
2537
|
-
A package states the day its setting stops on `data.present`, on its own
|
|
2538
|
-
`world` or `celestial` `place` note — see _What a body states about itself_
|
|
2539
|
-
under `type: place`. Against that date, a being with a dated `born` and no
|
|
2540
|
-
authored `age` compiles the age it implies: the two dates compared, not the
|
|
2541
|
-
two years subtracted, so a birthday that falls later in the year than the
|
|
2542
|
-
present counts one less than the years alone would say. A package that
|
|
2543
|
-
declares no present computes no age and says nothing about it — the same
|
|
2544
|
-
silence a package with no calendar mechanism meets.
|
|
2545
|
-
|
|
2546
|
-
**An authored `age` beside a dated `born` is expected to agree with what the
|
|
2547
|
-
date computes.** Where it does not, `content-build lint` raises a warning
|
|
2548
|
-
naming both values, so a stale age left behind after a `born` was corrected
|
|
2549
|
-
does not go unnoticed. The warning is silent beside `born: unknown` or an
|
|
2550
|
-
absent `born` — there is nothing there to disagree with — and silent wherever
|
|
2551
|
-
the package declares no present, for the same reason no age is computed at
|
|
2552
|
-
all.
|
|
2553
|
-
|
|
2554
|
-
`birthday:` is the retired spelling of `born:`. Both are read and `born` wins,
|
|
2555
|
-
so a note compiles identically either way; the old spelling is reported until
|
|
2556
|
-
every tree has moved.
|
|
2557
|
-
|
|
2558
|
-
#### The lead image
|
|
2559
|
-
|
|
2560
|
-
**A being's portrait is the first thing inside its `{#appearance}` section,
|
|
2561
|
-
always.**
|
|
2562
|
-
|
|
2563
|
-
```markdown
|
|
2564
|
-
# Appearance {#appearance}
|
|
2565
|
-
|
|
2566
|
-
![[<address>|<the being's full name>]]{float: top-left}
|
|
2567
|
-
```
|
|
2568
|
-
|
|
2569
|
-
**Nothing about that embed is special.** It is an ordinary embedded image with
|
|
2570
|
-
an ordinary directive, and no pass treats it differently for sitting where it
|
|
2571
|
-
sits. What is strict is the convention: the portrait opens the appearance
|
|
2572
|
-
section, so a reader opening any being note finds the picture in the same place,
|
|
2573
|
-
and an author writing one never has to decide where it goes.
|
|
2574
|
-
|
|
2575
|
-
**The section is the part that is not a convention.** `{#appearance}` is what
|
|
2576
|
-
becomes an actor's `system.appearance`, so a picture written above that heading
|
|
2577
|
-
is outside the section and reaches no document. The heading carries the anchor
|
|
2578
|
-
at **H1** — a deeper heading is not matched, and its whole section is extracted
|
|
2579
|
-
as empty rather than reported.
|
|
2580
|
-
|
|
2581
|
-
The label is the being's full name. That is what a reader needs when the picture
|
|
2582
|
-
does not load, and what a screen reader announces in its place.
|
|
2583
|
-
|
|
2584
|
-
**There is no `portrait` field**, at any level. A portrait is a picture of the
|
|
2585
|
-
subject, and a picture of the subject belongs in the prose that describes it —
|
|
2586
|
-
where an author can see it, move it, caption it or remove it, as with any other
|
|
2587
|
-
image. A field puts it somewhere only a template can reach, and every template
|
|
2588
|
-
then has to agree about where that is.
|
|
2589
|
-
|
|
2590
|
-
Later images are ordinary images too — a second picture of the being, a coat of
|
|
2591
|
-
arms, a map of its holding — and they sit wherever the prose wants them,
|
|
2592
|
-
including in `{#dossier}`.
|
|
2593
|
-
|
|
2594
|
-
**A being with no portrait writes no lead image.** There is no empty form and no
|
|
2595
|
-
placeholder.
|
|
2596
|
-
|
|
2597
|
-
#### A being's embedded items
|
|
2598
|
-
|
|
2599
|
-
`<system>.items` is a list, and each entry compiles into one embedded Item. An
|
|
2600
|
-
entry takes one of two shapes.
|
|
2601
|
-
|
|
2602
|
-
**A copy of a catalogue item** names it with `model:` — an address, read by the
|
|
2603
|
-
same grammar every wikilink is. The entry's remaining keys are merged over the
|
|
2604
|
-
model, so it carries the model's values except where it says otherwise.
|
|
2605
|
-
|
|
2606
|
-
```yaml
|
|
2607
|
-
sohl:
|
|
2608
|
-
items:
|
|
2609
|
-
- { model: skill-wpnc, system: { masteryLevelBase: 52 } }
|
|
2610
|
-
- { model: sohl-sohl-weapongear-whmr }
|
|
2611
|
-
- { model: sohl-sohl-weapongear-dgr, system: { shortcode: dgr2, name: Offhand dagger } }
|
|
2612
|
-
```
|
|
2613
|
-
|
|
2614
|
-
| key | required | meaning |
|
|
2615
|
-
| -------- | -------- | ------------------------------------------------------------- |
|
|
2616
|
-
| `model` | yes | The address of the item this entry is a copy of |
|
|
2617
|
-
| `system` | no | Values that override the model's |
|
|
2618
|
-
| `name` | no | A name of this entry's own, where it differs from the model's |
|
|
2619
|
-
| `data` | no | Art this entry names — see _An entry's art_ below |
|
|
2620
|
-
|
|
2621
|
-
**`type:` is not written beside a `model`.** The address already names the type,
|
|
2622
|
-
so a second statement of it is a place to be wrong, and it is refused.
|
|
2623
|
-
|
|
2624
|
-
**The address is written at whatever length says what it means.** Within this
|
|
2625
|
-
package `type-shortcode` is enough; reaching another package means the full
|
|
2626
|
-
`package-system-type-shortcode`, since the forms are suffixes of the canonical
|
|
2627
|
-
address. The system segment defaults from the block the entry sits in — an entry
|
|
2628
|
-
under `sohl.items` defaults to `sohl` — which is why the short form names an
|
|
2629
|
-
**Item** here while the same string in body prose names a page.
|
|
2630
|
-
|
|
2631
|
-
**A custom item** — one that copies nothing — states `name`, `type` **and**
|
|
2632
|
-
`system.shortcode`, all three required, plus whatever else its data model needs.
|
|
2633
|
-
It is written in **block form**, never inline, so the two kinds of entry are
|
|
2634
|
-
distinguishable at a glance:
|
|
2635
|
-
|
|
2636
|
-
```yaml
|
|
2637
|
-
sohl:
|
|
2638
|
-
items:
|
|
2639
|
-
- name: Whetstone
|
|
2640
|
-
type: miscgear
|
|
2641
|
-
system:
|
|
2642
|
-
shortcode: whetstone
|
|
2643
|
-
weight: 1
|
|
2644
|
-
value: 5
|
|
2645
|
-
durability: 0
|
|
2646
|
-
```
|
|
2647
|
-
|
|
2648
|
-
**A top-level `shortcode:` is retired**. It selected a template, while the
|
|
2649
|
-
`system.shortcode` beside it was the compiled item's identity — one word for two
|
|
2650
|
-
things — and it could not say which package a template came from, so an address
|
|
2651
|
-
resolved into a dependency only because no local pack claimed it and would have
|
|
2652
|
-
retargeted silently the day one did.
|
|
2653
|
-
|
|
2654
|
-
#### An entry's art
|
|
2655
|
-
|
|
2656
|
-
An entry is an item in every respect but where it is written, so it carries art
|
|
2657
|
-
by the two rules [an item note](#the-four-art-slots) does. It names an `icon`
|
|
2658
|
-
address under its own `data:`, and that resolves into the embedded document's
|
|
2659
|
-
`img`:
|
|
2660
|
-
|
|
2661
|
-
```yaml
|
|
2662
|
-
sohl:
|
|
2663
|
-
items:
|
|
2664
|
-
- name: Quiver (leather)
|
|
2665
|
-
type: miscgear
|
|
2666
|
-
data:
|
|
2667
|
-
icon: quiver
|
|
2668
|
-
system:
|
|
2669
|
-
shortcode: quiver
|
|
2670
|
-
```
|
|
2671
|
-
|
|
2672
|
-
`icon` is the only slot an entry has. The other three belong to a note: two
|
|
2673
|
-
describe an Actor or a map, and `banner` is a page's hero image, which an
|
|
2674
|
-
embedded document has no page for.
|
|
2675
|
-
|
|
2676
|
-
**Naming none is the common case, and it takes a default.** An entry copying a
|
|
2677
|
-
`model:` carries the catalogue item's art, and an entry copying nothing takes
|
|
2678
|
-
the default its type pairs in `itemBuilders` — the same default an item note of
|
|
2679
|
-
that type compiles with. An entry naming an address of its own overrides either.
|
|
2680
|
-
Writing `icon: ""` ships the document blank on purpose, and no default replaces
|
|
2681
|
-
it.
|
|
2682
|
-
|
|
2683
|
-
**`data:` reaches no compiled document.** It is where a note names art, and a
|
|
2684
|
-
compiled Item has `img` instead; the container is read and goes no further.
|
|
2685
|
-
|
|
2686
|
-
#### Identifying a being's embedded items
|
|
2687
|
-
|
|
2688
|
-
Each entry in `sohl.items` compiles into one embedded Item, and its `_id` is
|
|
2689
|
-
derived from **what the entry is**, never from where it sits in the list:
|
|
2690
|
-
|
|
2691
|
-
```
|
|
2692
|
-
_id = makeId(<the actor's id>, "<subType>:<system.shortcode>")
|
|
2693
|
-
```
|
|
2694
|
-
|
|
2695
|
-
An entry's identity is its **own `system.shortcode`**. The `model:` is not it —
|
|
2696
|
-
a model names the item this entry is a _copy of_, and is never written to the
|
|
2697
|
-
document — so two entries may share one model and are then two embodiments, each
|
|
2698
|
-
stating its own identity:
|
|
2699
|
-
|
|
2700
|
-
```yaml
|
|
2701
|
-
sohl:
|
|
2702
|
-
items:
|
|
2703
|
-
- model: weapongear-dgr # the catalogue's dagger
|
|
2704
|
-
name: Dagger 1
|
|
2705
|
-
system:
|
|
2706
|
-
shortcode: dgr1 # this dagger's own identity
|
|
2707
|
-
- model: weapongear-dgr
|
|
2708
|
-
name: Dagger 2
|
|
2709
|
-
system:
|
|
2710
|
-
shortcode: dgr2
|
|
2711
|
-
```
|
|
2712
|
-
|
|
2713
|
-
**Two entries resolving to one identity are a build error naming both.** Without
|
|
2714
|
-
their own `system.shortcode`, both daggers above carry the model's `dgr`,
|
|
2715
|
-
which makes them the same entity to everything that resolves by `(type,
|
|
2716
|
-
shortcode)` — compendium/world reconciliation, template shadowing, cohort
|
|
2717
|
-
membership, effect and expression references. A `name` cannot stand in: it is
|
|
2718
|
-
presentation, free to be localized or to diverge.
|
|
2719
|
-
|
|
2720
|
-
**Reordering the list moves no id.** A key carrying the entry's position
|
|
2721
|
-
so inserting an item renumbered every id after it and a re-import created new
|
|
2722
|
-
documents beside the old ones — while nothing about those documents had changed,
|
|
2723
|
-
only their neighbours. The same is now true of a note's journal pages: an
|
|
2724
|
-
unanchored page is keyed on its heading, so inserting a heading leaves every
|
|
2725
|
-
other page's id where it was. Two sibling pages sharing a heading is likewise a
|
|
2726
|
-
build error, matching the `MD024` lint rule that already refuses it.
|
|
2727
|
-
|
|
2728
|
-
A `sohl` block produces a SoHL actor of type `being`. An `hm3` block produces an HM3 actor. 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_.
|
|
2729
|
-
|
|
2730
|
-
A SoHL "being" document is created, as will an "HM3" document.
|
|
2731
|
-
|
|
2732
|
-
| shared source | → sohl | → hm3 |
|
|
2733
|
-
| ----------------- | ------ | ------------------- |
|
|
2734
|
-
| `data.species` | NA | NA |
|
|
2735
|
-
| `data.gender` | NA | `system.gender` |
|
|
2736
|
-
| `data.occupation` | NA | `system.occupation` |
|
|
2737
|
-
|
|
2738
|
-
`data.species` is a lore Address describing the being's species. HM3's
|
|
2739
|
-
`hm3.system.species` is independent native text; it does not inherit from
|
|
2740
|
-
`data.species`. The in-block `hm3.species` spelling is also accepted, with
|
|
2741
|
-
`hm3.system.species` taking precedence.
|
|
2742
|
-
|
|
2743
|
-
### type: homepage
|
|
2744
|
-
|
|
2745
|
-
The package's front page, published at `https://www.heroiclands.org/<package>/`.
|
|
2746
|
-
Exactly one note in a content tree declares this type, and it compiles into
|
|
2747
|
-
no Foundry document: it is a page and nothing else.
|
|
2748
|
-
|
|
2749
|
-
It declares a `shortcode` — conventionally `root` — because that is what a
|
|
2750
|
-
link is written with: `[[homepage-root|Text]]` is an ordinary wikilink, and it
|
|
2751
|
-
resolves to `/<package>/`. The shortcode names the page in links; its address
|
|
2752
|
-
is the package root. The page heading reads `name.full` and defaults to
|
|
2753
|
-
`packageBuild.manifest.title`. `description` supplies the page's short
|
|
2754
|
-
description.
|
|
2755
|
-
|
|
2756
|
-
The page is its body, published verbatim: no wikilink is resolved on it and
|
|
2757
|
-
no table expanded, so its links are markdown links, package-relative
|
|
2758
|
-
(`kb/rules/`) or external. An index of what the package publishes is not
|
|
2759
|
-
written here and is not generated anywhere — it is a `doc` note carrying a
|
|
2760
|
-
content table, linked from this page like any other.
|
|
2761
|
-
|
|
2762
|
-
The homepage uses the same top-level allowlist as every addressed note. It has
|
|
2763
|
-
no Foundry document id; a pinned `data.id` has no role on this type.
|
|
2764
|
-
|
|
2765
|
-
### type: vehicle
|
|
2766
|
-
|
|
2767
|
-
Represents a conveyance able to hold goods and people moving from one place to another.
|
|
2768
|
-
|
|
2769
|
-
| `data` property | Values | Description |
|
|
2770
|
-
| ------------------ | --------- | ------------------------------------------------------------------------ |
|
|
2771
|
-
| `tokenIcon` | `Address` | What a token on the canvas wears — an `icon` address; defaults to `icon` |
|
|
2772
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
2773
|
-
|
|
2774
|
-
If `sohl` is present, this becomes a `vehicle` actor.
|
|
2775
|
-
|
|
2776
|
-
It maps nothing beyond the shared rows above, actor row included: a vehicle
|
|
2777
|
-
carries a template priority and no field of its own.
|
|
2778
|
-
|
|
2779
|
-
### type: affiliation
|
|
2780
|
-
|
|
2781
|
-
Represents a related group of beings.
|
|
2782
|
-
|
|
2783
|
-
Note that affiliations may have multiple parent affiliations.
|
|
2784
|
-
|
|
2785
|
-
**A pantheon is not a subType.** It is a `faithtradition` carrying subordinate
|
|
2786
|
-
faith traditions — the individual religions of that pantheon name it in
|
|
2787
|
-
`parents`. There is no `pantheon` value, because every other subType answers
|
|
2788
|
-
_what kind of body is this_ while a pantheon answers _where does it sit_, and an
|
|
2789
|
-
enum that mixes the two makes a note choose between a kind and a level. The
|
|
2790
|
-
hierarchy already carries the answer, so nothing is lost by deriving it: a faith
|
|
2791
|
-
tradition with faith traditions beneath it is a pantheon.
|
|
2792
|
-
|
|
2793
|
-
The same reasoning is why the three traditions are siblings. `faithtradition`,
|
|
2794
|
-
`arcanetradition` and `spirittradition` partition by _what a practice concerns_ —
|
|
2795
|
-
the divine, magic, the spirit world — and that partition is load-bearing rather
|
|
2796
|
-
than descriptive: a system filtering which mystical practices may associate with
|
|
2797
|
-
an affiliation can only be as precise as the distinction it filters on.
|
|
2798
|
-
|
|
2799
|
-
**subType**:
|
|
2800
|
-
|
|
2801
|
-
- guild: A sworn association of craftsmen holding monopoly over a trade within a locality.
|
|
2802
|
-
- order: A body of members bound by vows or a rule of life to a shared purpose.
|
|
2803
|
-
- polity: A sovereign body ordering the persons within a territory — states, city-states, tribal confederations. Its ranks apply to all who fall under its authority.
|
|
2804
|
-
- faithtradition: A tradition of belief and practice concerning the divine, whether organized or not.
|
|
2805
|
-
- arcanetradition: A tradition of belief and practice concerning magic and its practice, whether organized or not.
|
|
2806
|
-
- spirittradition: A tradition of belief and practice concerning spirits — ancestors, totems, and the numinous world — whether organized or not.
|
|
2807
|
-
- lineage: A body claiming common descent from a known ancestor, whose standing and obligations pass by birth — clans, houses, dynasties, septs.
|
|
2808
|
-
- venture: A band bound by contract or shared undertaking rather than by vow or public authority — free companies, ships' crews, trading expeditions, adventuring parties.
|
|
2809
|
-
- criminal: An association organized to profit from activity its host polity forbids, sustained by its own enforcement rather than by law.
|
|
2810
|
-
- governmental: An organ constituted by a polity to exercise some portion of its authority — ministries, chanceries, courts, exchequers. Its ranks apply only to those who serve in it.
|
|
2811
|
-
- fellowship: A voluntary association without vow, trade monopoly, or public authority — formed for mutual company, aid, or shared practice.
|
|
2812
|
-
|
|
2813
|
-
**GovernanceModel**
|
|
2814
|
-
|
|
2815
|
-
- Autocracy: a single person holds unchecked authority, however acquired
|
|
2816
|
-
- Monarchy: one ruler legitimated by descent, election, or sacred office
|
|
2817
|
-
- Oligarchy: a small closed group rules, whether by birth, wealth, or rank — membership is not
|
|
2818
|
-
conferred by election and its authority is not held for a term
|
|
2819
|
-
- Republic: sovereignty rests in the citizen body and is exercised through offices held for a fixed
|
|
2820
|
-
term by election, which a ruling class supplies the holders of in practice
|
|
2821
|
-
- Council: a deliberating body governs collectively with no single head
|
|
2822
|
-
- Democracy: the general membership decides, directly or through representatives, and any member may
|
|
2823
|
-
hold office
|
|
2824
|
-
- Theocracy: authority derives from divine mandate and rests with its clergy
|
|
2825
|
-
- Meritocracy: position is earned by demonstrated skill, achievement, or expertise
|
|
2826
|
-
- Stratocracy: the armed force is itself the government
|
|
2827
|
-
- Feudal: authority flows through nested personal oaths rather than a central office
|
|
2828
|
-
- Confederation: autonomous members retain sovereignty under a weak common center
|
|
2829
|
-
- Anarchic: no formal governing authority — custom or force fills the gap
|
|
2830
|
-
|
|
2831
|
-
**Republic, Oligarchy and Democracy are three answers to one question**, and the
|
|
2832
|
-
boundaries are testable rather than a matter of taste. Ask who fills the offices
|
|
2833
|
-
and on what terms. If a closed group holds authority outright, with no election
|
|
2834
|
-
and no term, it is an **Oligarchy**. If offices are elective and time-limited but
|
|
2835
|
-
a propertied or senatorial order supplies nearly everyone who holds them, it is a
|
|
2836
|
-
**Republic**. If any member may hold office and the general body decides, it is a
|
|
2837
|
-
**Democracy**.
|
|
2838
|
-
|
|
2839
|
-
The distinction is not academic: it is the difference between a ladder whose top
|
|
2840
|
-
rungs are a class one is born or bought into, and one whose top rungs are an
|
|
2841
|
-
office one is voted into and then vacates. A republic's ladder therefore carries
|
|
2842
|
-
**both** — the civic status (Citizen) and the standing in the governing body above
|
|
2843
|
-
it (Senator) — because a citizen is not a member of the Senate, and conflating the
|
|
2844
|
-
two is the commonest way to get a non-monarchy wrong.
|
|
2845
|
-
|
|
2846
|
-
**Rank**
|
|
2847
|
-
Definition of a level within the organization (e.g., Priest, Layperson, Member, Gang Leader, Master, etc.)
|
|
2848
|
-
|
|
2849
|
-
| Property | Values | Description |
|
|
2850
|
-
| ------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
2851
|
-
| `level` | number | The ranking within the affiliation, increasing values starting with 1, 0 indicates intentional exclusion (expulsion/excommunication) |
|
|
2852
|
-
| `title` | string | Title associated with the Rank |
|
|
2853
|
-
| `description` | string | Description of the Rank |
|
|
2854
|
-
| `lore` | `Address` | The standing this rank _is_ — a `lore` note of subType `law`, shared with every other body that confers the same thing |
|
|
2855
|
-
|
|
2856
|
-
**A rank's `lore` is shared; its `title` is not.** A Normen kingdom calls it
|
|
2857
|
-
`Thrall` and a Vylarian province calls it `Slave`, and they mean one standing:
|
|
2858
|
-
owned outright, with no standing at law except through an owner. The title is
|
|
2859
|
-
what this body calls it and the description is how this body puts it, but the
|
|
2860
|
-
obligations and rights belong to the standing itself, so they are written once
|
|
2861
|
-
and cited by every ladder that confers it.
|
|
2862
|
-
|
|
2863
|
-
That is also what makes a rank answerable across bodies. Without it, asking what
|
|
2864
|
-
a `Naukrátissa` may do means reading the Bethûan fleet's ladder; with it, the
|
|
2865
|
-
rank names the standing, and the standing says.
|
|
2866
|
-
|
|
2867
|
-
**Standing**: aligned, unaligned, rival, nemesis
|
|
2868
|
-
|
|
2869
|
-
| `data` property | Values | Description |
|
|
2870
|
-
| -------------------- | ------------------------ | ---------------------------------------------------------------------------------------------- |
|
|
2871
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
2872
|
-
| `demonym` | `string` | What a member of this affiliation is called (a Vylarian) |
|
|
2873
|
-
| `epithet` | `string` | The by-name it is known by — a god's, an order's, a company's |
|
|
2874
|
-
| `symbol` | `string` | Its emblem in words: a feather atop a golden scale, a chisel carving a star |
|
|
2875
|
-
| `governance.model` | `GovernanceModel` | Type of government structure, if applicable |
|
|
2876
|
-
| `governance.summary` | `string` | summary of the governance situation |
|
|
2877
|
-
| `governance.ranks` | `Rank[]` | The ranks available to members of the affiliation |
|
|
2878
|
-
| `governance.offices` | `Map<name, description>` | Official offices in the affiliation |
|
|
2879
|
-
| `seat` | `Address` | Where the affiliation's authority sits |
|
|
2880
|
-
| `domains` | `Address[]` | Places over which this affiliation holds sway |
|
|
2881
|
-
| `population` | `number` | Number of people in the affiliation (precision 2 significant digits). |
|
|
2882
|
-
| `economy` | `Address[]` | Economic life: `affiliation` or `lore` Addresses, with an explicit type and no default |
|
|
2883
|
-
| `lore` | `Address[]` | Lore concerning it — the peoples it draws on, the god a faith venerates, its law, its calendar |
|
|
2884
|
-
| `parents` | `Address[]` | Affiliations that this affiliation is subordinate to |
|
|
2885
|
-
| `relations` | `Map<Address, Standing>` | Standing with other affiliations, keyed by the other body's Address |
|
|
2886
|
-
|
|
2887
|
-
**A faith tradition is not its god.** An `affiliation` of subType
|
|
2888
|
-
`faithtradition` is a _religion_ — a practice, with an ordained priesthood, a
|
|
2889
|
-
calendar and a body of observance — and it can outlive belief in the god
|
|
2890
|
-
entirely, while one god may be venerated by several religions that agree on
|
|
2891
|
-
nothing else. So the god is `lore` of subType `deity`, cited from the faith
|
|
2892
|
-
through `lore`, and never a property of it.
|
|
2893
|
-
|
|
2894
|
-
`epithet` and `symbol` stay with the religion for the same reason, and they are
|
|
2895
|
-
not a faith's alone: they are what the members call the thing and what they
|
|
2896
|
-
carve, so a guild has them as much as a cult does. What a god _is_ — its nature,
|
|
2897
|
-
its domains, its aspects — belongs on the deity note, where every religion that
|
|
2898
|
-
venerates it can point at one account.
|
|
2899
|
-
|
|
2900
|
-
**Every one of those describes the organisation.** A membership — which office a
|
|
2901
|
-
being holds, what title it bears, where it stands on the ladder — is recorded on
|
|
2902
|
-
the affiliation _as embedded on that being_, and nothing about it belongs in the
|
|
2903
|
-
catalogue entry. That is the split `governance` draws: the organisation publishes
|
|
2904
|
-
the ranks and offices that exist, and a membership names which of them it holds.
|
|
2905
|
-
|
|
2906
|
-
**Rank is the organisation's, not the member's.** A bare `level: 4` on a
|
|
2907
|
-
membership says nothing on its own; it means _Knight_ only because the polity
|
|
2908
|
-
declared that rung. So the ladder is authored once, on the body that confers it,
|
|
2909
|
-
and a member's rank is an index into it. Level 0 is reserved for the excluded —
|
|
2910
|
-
outlawed, expelled, excommunicated — which is a standing the organisation still
|
|
2911
|
-
recognises, and so still has to define.
|
|
2912
|
-
|
|
2913
|
-
**Offices are named, not ranked.** A Chancellor and a Marshal are both great
|
|
2914
|
-
officers and neither is above the other, so an office is a key with a
|
|
2915
|
-
description rather than a rung — and a being may hold an office at any rank, or a
|
|
2916
|
-
rank with no office at all.
|
|
2917
|
-
|
|
2918
|
-
**`domains` is territorial, and only territorial.** Seventy-seven divine
|
|
2919
|
-
affiliations currently spell `domain:` the other way, holding a deity's sphere of
|
|
2920
|
-
influence as prose — _Love, Beauty, and Prosperity_; _Fertility, Agriculture,
|
|
2921
|
-
Peace, and Healing_. That sense **folds into `description`**, which those notes
|
|
2922
|
-
leave empty in all but one case, and it does not become a field of its own.
|
|
2923
|
-
|
|
2924
|
-
A sphere is a characterisation, not a reference. Nothing compiles against it, no
|
|
2925
|
-
other note points at it, and rendering it as a list would imply a vocabulary that
|
|
2926
|
-
does not exist — whereas `description` is exactly the field for a one-line
|
|
2927
|
-
statement of what a thing is. The fuller treatment already has a home: `lore` of
|
|
2928
|
-
subType `deity` covers a god's nature, domains, epithets and aspects in prose.
|
|
2929
|
-
|
|
2930
|
-
That frees the name for the territorial sense, which earns it: every affiliation
|
|
2931
|
-
subType holds sway somewhere, while the divine sense applies to one.
|
|
2932
|
-
|
|
2933
|
-
**`domains` names what the affiliation holds directly**, and nothing beneath
|
|
2934
|
-
it. A polity whose `domains` names a region holds the region; the settlements
|
|
2935
|
-
within it are the region's, reached through its `contains`, and are not
|
|
2936
|
-
repeated here. A house holds its manors and names each; the crown holds the
|
|
2937
|
-
realm and names the realm. What lies below a holder is answered by the two
|
|
2938
|
-
hierarchies together — `place.parents` for where a place sits, and
|
|
2939
|
-
`affiliation.parents` for whom a holder answers to — so a subinfeudated manor
|
|
2940
|
-
sits in one region by the first and under a lord of another polity by the
|
|
2941
|
-
second, and both pages say so.
|
|
2942
|
-
|
|
2943
|
-
The website reads `domains` across the package and every fetched index and
|
|
2944
|
-
writes the affiliation's page a `holdings` list — every place its `domains`
|
|
2945
|
-
names, each entry `{ title, url, type, subType }`, sorted by `subType` then
|
|
2946
|
-
`title`, and absent when `domains` names nothing. Every place it names carries
|
|
2947
|
-
the affiliation back in `held_by`. Both are derived; a note that writes one has
|
|
2948
|
-
it replaced.
|
|
2949
|
-
|
|
2950
|
-
If `sohl` is present, this becomes an `affiliation` item.
|
|
2951
|
-
|
|
2952
|
-
| shared source | → sohl | → hm3 |
|
|
2953
|
-
| ---------------- | ------------------ | ----- |
|
|
2954
|
-
| `subType` | `system.subType` | NA |
|
|
2955
|
-
| `data.seat` | `system.seat` | NA |
|
|
2956
|
-
| `data.domains` | `system.domain` | NA |
|
|
2957
|
-
| `data.parents` | `system.parents` | NA |
|
|
2958
|
-
| `data.relations` | `system.relations` | NA |
|
|
2959
|
-
|
|
2960
|
-
An affiliation writes SoHL common skills under `sohl.system.commonSkills` as a
|
|
2961
|
-
list of skill Addresses. An abbreviated value names a skill in the authoring
|
|
2962
|
-
package; a skill in the SoHL system package uses its complete Address:
|
|
2963
|
-
|
|
2964
|
-
```yaml
|
|
2965
|
-
sohl:
|
|
2966
|
-
system:
|
|
2967
|
-
commonSkills:
|
|
2968
|
-
- herb
|
|
2969
|
-
- sohl-sohl-skill-law
|
|
2970
|
-
```
|
|
2971
|
-
|
|
2972
|
-
The compiled SoHL Item stores the exact published Item UUIDs in
|
|
2973
|
-
`system.commonSkills`. An empty or absent list emits `[]`. A foreign target must
|
|
2974
|
-
come from a declared `relationships.systems` or `relationships.requires` content
|
|
2975
|
-
index. Each UUID uses the module and pack named by that target's published index;
|
|
2976
|
-
a missing skill Item or Item UUID is an error at the authored Address. The
|
|
2977
|
-
content index retains complete Addresses. Journal, web and book infoboxes link
|
|
2978
|
-
to a skill's published documentation journal when one exists and otherwise show
|
|
2979
|
-
its name as plain text.
|
|
2980
|
-
|
|
2981
|
-
`governance` reaches no system field. Ranks and offices are the note's and the
|
|
2982
|
-
web page's — SoHL's affiliation item has nowhere to put them, and inventing a
|
|
2983
|
-
mapping for a field no schema declares is the drift these tables exist to catch.
|
|
2984
|
-
`system.society`, `system.office`, `system.title` and `system.level` are
|
|
2985
|
-
likewise absent here: they are filled on an embedded membership, never from a
|
|
2986
|
-
catalogue note's `data:`.
|
|
2987
|
-
|
|
2988
|
-
**`system.title` is the style of address the office carries**, Ajaw or Warden,
|
|
2989
|
-
which a being holds by virtue of its rank. The note's page heading reads
|
|
2990
|
-
`name.full`. Author the style of address on the membership — the `system.title`
|
|
2991
|
-
of the entry in a being's `sohl.items` — or, on a catalogue note that carries
|
|
2992
|
-
one, at `sohl.system.title`.
|
|
2993
|
-
|
|
2994
|
-
### type: affliction
|
|
2995
|
-
|
|
2996
|
-
Represents an affliction.
|
|
2997
|
-
|
|
2998
|
-
**subType**:
|
|
2999
|
-
|
|
3000
|
-
- disease: A biological affliction: an illness or parasite that infects the body or mind (e.g. typhoid, tuberculosis, river blindness)
|
|
3001
|
-
- poisontoxin: A chemical affliction: a toxic substance or venom that impairs or kills the host (e.g. hemotoxin, mandrake, wasp venom)
|
|
3002
|
-
- maladiction: A supernatural affliction: a curse, hex, or divine/spiritual blight that assails the body, mind, or aura by arcane, divine, or spirit means. The affliction is a metaphysical agent with a course and outcome
|
|
3003
|
-
|
|
3004
|
-
**TransmissionTypes**
|
|
3005
|
-
|
|
3006
|
-
- none: no transmission mode
|
|
3007
|
-
- airborne: Transmission through the air, such as via droplets or aerosols
|
|
3008
|
-
- contact: Transmission through direct physical (skin) contact
|
|
3009
|
-
- bodyfluid: Transmission through bodily fluids: blood, saliva, etc.
|
|
3010
|
-
- injested: Transmission through ingestion of contaminated substances
|
|
3011
|
-
- proximity: Transmission through close proximity to an infected individual, but separate from airborne or direct contact modes.
|
|
3012
|
-
- vector: Transmission through a vector, such as an insect or animal bite
|
|
3013
|
-
- perception: Transmission through sensory perception, such as sight or sound
|
|
3014
|
-
- arcane: Transmission through arcane means
|
|
3015
|
-
- divine: Transmission through divine means
|
|
3016
|
-
- spirit: Transmission through spirit means
|
|
3017
|
-
|
|
3018
|
-
| `data` property | Values | Description |
|
|
3019
|
-
| ----------------------------- | ------------------- | ----------------------------------------------------------------------------------------- |
|
|
3020
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3021
|
-
| `transmission` | `TransmissionTypes` | Method of transmission |
|
|
3022
|
-
| `outcome` | `death \| cured` | Result after affliction has run its course |
|
|
3023
|
-
| `healingRate` | `number` | Likelihood of positive outcome of healing test |
|
|
3024
|
-
| `contagionIndex` | `number` | how contagious the disease is |
|
|
3025
|
-
| `outcomeTraumas` | `SafeExpression` | Expression returning traumas that result from affliction recovery |
|
|
3026
|
-
| `onsetDurationFormula` | `RollFormula` | Formula to calculate duration until onset after contracting affliction |
|
|
3027
|
-
| `onsetDurationBase` | `number` | That duration in seconds, stated outright instead of rolled |
|
|
3028
|
-
| `healingCheckDurationFormula` | `RollFormula` | Formula to calculate duration until next healing check (measured from last healing check) |
|
|
3029
|
-
| `healingCheckDurationBase` | `number` | That duration in seconds, stated outright instead of rolled |
|
|
3030
|
-
| `resolutionDurationFormula` | `RollFormula` | Formula to calculate duration after onset to resolution |
|
|
3031
|
-
| `resolutionDurationBase` | `number` | That duration in seconds, stated outright instead of rolled |
|
|
3032
|
-
|
|
3033
|
-
If `sohl` is present, this becomes an `affliction` item.
|
|
3034
|
-
|
|
3035
|
-
| shared source | → sohl | → hm3 |
|
|
3036
|
-
| ---------------------------------- | ------------------------------------ | ----- |
|
|
3037
|
-
| `subType` | `system.subType` | NA |
|
|
3038
|
-
| `data.transmission` | `system.transmission` | NA |
|
|
3039
|
-
| `data.outcome` | `system.outcome` | NA |
|
|
3040
|
-
| `data.contagionIndex` | `system.contagionIndexBase` | NA |
|
|
3041
|
-
| `data.onsetDurationFormula` | `system.onsetDurationFormula` | NA |
|
|
3042
|
-
| `data.onsetDurationBase` | `system.onsetDurationBase` | NA |
|
|
3043
|
-
| `data.healingCheckDurationFormula` | `system.healingCheckDurationFormula` | NA |
|
|
3044
|
-
| `data.healingCheckDurationBase` | `system.healingCheckDurationBase` | NA |
|
|
3045
|
-
| `data.resolutionDurationFormula` | `system.resolutionDurationFormula` | NA |
|
|
3046
|
-
| `data.resolutionDurationBase` | `system.resolutionDurationBase` | NA |
|
|
3047
|
-
|
|
3048
|
-
**A timed phase is a triplet, and a note writes two thirds of it.** SoHL stores
|
|
3049
|
-
each phase as `{…DurationFormula, …DurationBase, …Date}`, and the three are
|
|
3050
|
-
authored differently:
|
|
3051
|
-
|
|
3052
|
-
| third | who writes it | what it says |
|
|
3053
|
-
| ------------------ | ------------- | -------------------------------------------------------- |
|
|
3054
|
-
| `…DurationFormula` | the note | what is **rolled** to get the interval |
|
|
3055
|
-
| `…DurationBase` | the note | the interval **outright**, in seconds, instead of a roll |
|
|
3056
|
-
| `…Date` | play | _when the phase actually fired_ |
|
|
3057
|
-
|
|
3058
|
-
Write the **formula** for a phase whose length varies — `"2d6*86400"` for an
|
|
3059
|
-
incubation of two-to-twelve days — and the **base** for one that does not. Both
|
|
3060
|
-
are intervals in **seconds**, and a bare number is a valid formula, so
|
|
3061
|
-
`onsetDurationFormula: 86400` and `onsetDurationBase: 86400` differ only in
|
|
3062
|
-
whether a roll is attempted.
|
|
3063
|
-
|
|
3064
|
-
**Omitting either omits the key**, rather than writing a default over it. That
|
|
3065
|
-
matters because the data model's own `null` is what several phases fall back on:
|
|
3066
|
-
a trauma that sets no `healingCheckDuration*` takes the world's configured
|
|
3067
|
-
healing-check interval, and one that sets no `bloodLossAdvanceDurationBase` does
|
|
3068
|
-
not bleed at all. A compile-time `null` is still a value, and would answer those
|
|
3069
|
-
questions for every note in every world.
|
|
3070
|
-
|
|
3071
|
-
**The `…Date` third is never authored.** It records when the phase fired, which
|
|
3072
|
-
only play can know, so `system.contractDate`, `system.onsetDate`,
|
|
3073
|
-
`system.treatmentDate` and `system.resolutionDate` are runtime state and a note
|
|
3074
|
-
that writes one fails the build. They are world times, and `0` is a valid one,
|
|
3075
|
-
so there is no blank a note could write either; leave them out and the data
|
|
3076
|
-
model's `null` stands.
|
|
3077
|
-
|
|
3078
|
-
`data.healingRate` and `data.outcomeTraumas` are page data. SoHL reads an
|
|
3079
|
-
affliction’s base healing rate from `sohl.healingRateBase`. Its item declaration
|
|
3080
|
-
does not read `data.outcomeTraumas`.
|
|
3081
|
-
|
|
3082
|
-
### type: armorgear
|
|
3083
|
-
|
|
3084
|
-
Note: `data.quantity` may not be specified. Quantity is always 1.
|
|
3085
|
-
|
|
3086
|
-
| `data` property | Values | Description |
|
|
3087
|
-
| ------------------ | -------- | ------------------------------------------ |
|
|
3088
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3089
|
-
| `weight` | `number` | Gear weight |
|
|
3090
|
-
| `value` | `number` | Gear value |
|
|
3091
|
-
| `quality` | `number` | Gear quality |
|
|
3092
|
-
| `durability` | `number` | Gear durability |
|
|
3093
|
-
|
|
3094
|
-
If a `sohl` property is present, a SoHL item of type "armorgear" is created.
|
|
3095
|
-
|
|
3096
|
-
The note type is `armorgear` in both systems.
|
|
3097
|
-
|
|
3098
|
-
| shared source | → sohl | → hm3 |
|
|
3099
|
-
| ----------------- | ----------------------- | --------------- |
|
|
3100
|
-
| `data.weight` | `system.weightBase` | `system.weight` |
|
|
3101
|
-
| `data.value` | `system.valueBase` | `system.value` |
|
|
3102
|
-
| `data.quality` | `system.qualityBase` | NA |
|
|
3103
|
-
| `data.durability` | `system.durabilityBase` | NA |
|
|
3104
|
-
|
|
3105
|
-
### type: armorlocation
|
|
3106
|
-
|
|
3107
|
-
| `data` property | Values | Description |
|
|
3108
|
-
| ------------------ | -------- | ------------------------------------------ |
|
|
3109
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3110
|
-
|
|
3111
|
-
if a `hm3` property is present, an HM3 item of type "armorlocation" is created.
|
|
3112
|
-
|
|
3113
|
-
| shared source | → sohl | → hm3 |
|
|
3114
|
-
| ------------- | ------ | ----- |
|
|
3115
|
-
| `notes` | NA | NA |
|
|
3116
|
-
|
|
3117
|
-
### type: attribute
|
|
3118
|
-
|
|
3119
|
-
| `data` property | Values | Description |
|
|
3120
|
-
| ------------------ | -------- | ------------------------------------------ |
|
|
3121
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3122
|
-
|
|
3123
|
-
if a `sohl` property is present, a SoHL item of type "attribute" is created.
|
|
3124
|
-
|
|
3125
|
-
| shared source | → sohl | → hm3 |
|
|
3126
|
-
| ------------- | ------ | ----- |
|
|
3127
|
-
|
|
3128
|
-
### type: concoctiongear
|
|
3129
|
-
|
|
3130
|
-
**subType**:
|
|
3131
|
-
|
|
3132
|
-
- mundane: ordinary and common in everyday use, generally simple in composition (often a single dried or otherwise prepared ingredient)
|
|
3133
|
-
- exotic: A complex and valuable concoction, often a mixture of different herbs and/or chemicals, with medicinal or other unique properties or effects, but not magical in nature.
|
|
3134
|
-
- elixir: An arcane alchemical concoction of great power.
|
|
3135
|
-
|
|
3136
|
-
| `data` property | Values | Description |
|
|
3137
|
-
| ------------------ | ------------------------------- | ------------------------------------------------- |
|
|
3138
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3139
|
-
| `weight` | `number` | Gear weight |
|
|
3140
|
-
| `value` | `number` | Gear value |
|
|
3141
|
-
| `quality` | `number` | Gear quality |
|
|
3142
|
-
| `durability` | `number` | Gear durability |
|
|
3143
|
-
| `quantity` | `number` | Gear quantity (default: 1) |
|
|
3144
|
-
| `potency` | `na \| mild \| strong \| great` | Concoction Potency (mundane/exotic concoctions) |
|
|
3145
|
-
| `strength` | `number` | Strength: higher the number, greater the strength |
|
|
3146
|
-
|
|
3147
|
-
if a `sohl` property is present, a SoHL item of type "concoctiongear" is created.
|
|
3148
|
-
|
|
3149
|
-
| shared source | → sohl | → hm3 |
|
|
3150
|
-
| ----------------- | ----------------------- | ----- |
|
|
3151
|
-
| `subType` | `system.subType` | NA |
|
|
3152
|
-
| `data.weight` | `system.weightBase` | NA |
|
|
3153
|
-
| `data.value` | `system.valueBase` | NA |
|
|
3154
|
-
| `data.quality` | `system.qualityBase` | NA |
|
|
3155
|
-
| `data.durability` | `system.durabilityBase` | NA |
|
|
3156
|
-
|
|
3157
|
-
`data.quantity` is page data for a concoction. SoHL sets `system.quantity` to
|
|
3158
|
-
`1` for each compendium item.
|
|
3159
|
-
|
|
3160
|
-
### type: containergear
|
|
3161
|
-
|
|
3162
|
-
Note: `data.quantity` may not be specified; quantity is always set to 1.
|
|
3163
|
-
|
|
3164
|
-
| `data` property | Values | Description |
|
|
3165
|
-
| ------------------ | -------- | ------------------------------------------ |
|
|
3166
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3167
|
-
| `weight` | `number` | Gear weight |
|
|
3168
|
-
| `value` | `number` | Gear value |
|
|
3169
|
-
| `quality` | `number` | Gear quality |
|
|
3170
|
-
| `durability` | `number` | Gear durability |
|
|
3171
|
-
| `capacity` | `number` | HM3 container capacity (in lbs) |
|
|
3172
|
-
|
|
3173
|
-
if a `sohl` property is present, a SoHL item of type "containergear" is created.
|
|
3174
|
-
|
|
3175
|
-
if a `hm3` property is present, an HM3 item of type "containergear" is created.
|
|
3176
|
-
|
|
3177
|
-
| shared source | → sohl | → hm3 |
|
|
3178
|
-
| ----------------- | ----------------------- | --------------------- |
|
|
3179
|
-
| `data.weight` | `system.weightBase` | `system.weight` |
|
|
3180
|
-
| `data.value` | `system.valueBase` | `system.value` |
|
|
3181
|
-
| `data.quality` | `system.qualityBase` | NA |
|
|
3182
|
-
| `data.durability` | `system.durabilityBase` | NA |
|
|
3183
|
-
| `data.capacity` | NA | `system.capacity.max` |
|
|
3184
|
-
|
|
3185
|
-
SoHL reads a container’s capacity in pounds from `sohl.maxCapacity`.
|
|
3186
|
-
|
|
3187
|
-
### type: miscgear
|
|
3188
|
-
|
|
3189
|
-
| `data` property | Values | Description |
|
|
3190
|
-
| ------------------ | -------- | ------------------------------------------ |
|
|
3191
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3192
|
-
| `weight` | `number` | Gear weight |
|
|
3193
|
-
| `value` | `number` | Gear value |
|
|
3194
|
-
| `quality` | `number` | Gear quality |
|
|
3195
|
-
| `durability` | `number` | Gear durability |
|
|
3196
|
-
| `quantity` | `number` | HM3 gear quantity (default: 1) |
|
|
3197
|
-
|
|
3198
|
-
if a `sohl` property is present, a SoHL item of type "miscgear" is created.
|
|
3199
|
-
|
|
3200
|
-
if a `hm3` property is present, an HM3 item of type "miscgear" is created.
|
|
3201
|
-
|
|
3202
|
-
| shared source | → sohl | → hm3 |
|
|
3203
|
-
| ----------------- | ----------------------- | ----------------- |
|
|
3204
|
-
| `data.weight` | `system.weightBase` | `system.weight` |
|
|
3205
|
-
| `data.value` | `system.valueBase` | `system.value` |
|
|
3206
|
-
| `data.quality` | `system.qualityBase` | NA |
|
|
3207
|
-
| `data.durability` | `system.durabilityBase` | NA |
|
|
3208
|
-
| `data.quantity` | NA | `system.quantity` |
|
|
3209
|
-
|
|
3210
|
-
SoHL sets `system.quantity` to `1` for each compendium item.
|
|
3211
|
-
|
|
3212
|
-
### type: mystery
|
|
3213
|
-
|
|
3214
|
-
**subType**:
|
|
3215
|
-
|
|
3216
|
-
- boon: A flat ±N modifier to an associated skill's mastery level, from any source.
|
|
3217
|
-
- boost: One or more temporary mastery boosts to an associated skill (Mastery Boost table).
|
|
3218
|
-
- fate: A mystery that quantifies the ability to alter destiny or fate.
|
|
3219
|
-
- grace: A mystery that quantifies ability to call effectually on divine favor.
|
|
3220
|
-
- birthsign: A mystery that describes the arcane sign under which the being was born.
|
|
3221
|
-
- other: A mystery that does not fit into the other predefined categories.
|
|
3222
|
-
- piety: A mystery that quantifies devotion to a religion.
|
|
3223
|
-
|
|
3224
|
-
**SkillAptitude**: either a single skill
|
|
3225
|
-
|
|
3226
|
-
| `data` property | Values | Description |
|
|
3227
|
-
| ------------------ | ---------------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
|
3228
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3229
|
-
| `assocSkill` | `Address` | Associated skill |
|
|
3230
|
-
| `assocAffiliation` | `Address` | Associated affiliation |
|
|
3231
|
-
| `skillAptitudes` | `Map<Shortcode, number>`, the key a skill's shortcode or `subType:<skill-subtype>` | Bonuses and penalties, per skill or per kind of skill |
|
|
3232
|
-
| `level` | `number` | Magnitude of the mystery |
|
|
3233
|
-
| `charges.value` | `number` | Current number of charges available, _null_ = charges not used |
|
|
3234
|
-
| `charges.max` | `number` | Maximum number of charges, _null_ = no maximum |
|
|
3235
|
-
|
|
3236
|
-
if a `sohl` property is present, a SoHL item of type "mystery" is created.
|
|
3237
|
-
|
|
3238
|
-
`system.assocSkillCode` and `system.assocAffiliationCode` are authored under
|
|
3239
|
-
`sohl.system` as shortcodes and have no shared source; `data.assocSkill` and
|
|
3240
|
-
`data.assocAffiliation` name the same two notes as Addresses, for the page.
|
|
3241
|
-
|
|
3242
|
-
| shared source | → sohl | → hm3 |
|
|
3243
|
-
| --------------------- | ----------------------- | ----- |
|
|
3244
|
-
| `subType` | `system.subType` | NA |
|
|
3245
|
-
| `data.skillAptitudes` | `system.skillAptitudes` | NA |
|
|
3246
|
-
| `data.charges.value` | `system.charges.value` | NA |
|
|
3247
|
-
| `data.charges.max` | `system.charges.max` | NA |
|
|
3248
|
-
|
|
3249
|
-
`data.level` is page data for a mystery. SoHL reads the item’s level from
|
|
3250
|
-
`sohl.levelBase`.
|
|
3251
|
-
|
|
3252
|
-
### type: mysticalability
|
|
3253
|
-
|
|
3254
|
-
**subType**:
|
|
3255
|
-
|
|
3256
|
-
- spiritrite: A prepared ceremony by which a practitioner petitions the spirit world.
|
|
3257
|
-
- spiritaction: A discrete supernatural act performed through an allied or bound spirit.
|
|
3258
|
-
- spiritpower: A standing power conferred on its bearer by a spirit.
|
|
3259
|
-
- ritualaction: A prescribed ritual act performed to earn the favour of a deity.
|
|
3260
|
-
- divineincantation: A spoken invocation channelling the power of a deity.
|
|
3261
|
-
- arcaneincantation: A formally learned spell, invoked by word and gesture.
|
|
3262
|
-
- arcanetalent: An innate arcane knack, possessed without formal training.
|
|
3263
|
-
- spirittalent: An innate affinity for the spirit world, possessed without training.
|
|
3264
|
-
- alchemy: The preparation of substances imbued with mystical potency.
|
|
3265
|
-
- divination: The practice of obtaining hidden knowledge or foreknowledge by mystical means.
|
|
3266
|
-
|
|
3267
|
-
| `data` property | Values | Description |
|
|
3268
|
-
| ------------------ | --------- | -------------------------------------------------------------- |
|
|
3269
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3270
|
-
| `assocSkill` | `Address` | Associated skill |
|
|
3271
|
-
| `assocAffiliation` | `Address` | Associated affiliation |
|
|
3272
|
-
| `masteryLevel` | `number` | Mastery Level |
|
|
3273
|
-
| `level` | `number` | Magnitude of the mystery |
|
|
3274
|
-
| `charges.value` | `number` | Current number of charges available, _null_ = charges not used |
|
|
3275
|
-
| `charges.max` | `number` | Maximum number of charges, _null_ = no maximum |
|
|
3276
|
-
|
|
3277
|
-
if a `sohl` property is present, a SoHL item of type "mysticalability" is created.
|
|
3278
|
-
|
|
3279
|
-
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.
|
|
3280
|
-
|
|
3281
|
-
`system.assocSkillCode` and `system.assocAffiliationCode` are authored under
|
|
3282
|
-
`sohl.system` as shortcodes and have no shared source; `data.assocSkill` and
|
|
3283
|
-
`data.assocAffiliation` name the same two notes as Addresses, for the page.
|
|
3284
|
-
|
|
3285
|
-
| shared source | → sohl | → hm3 |
|
|
3286
|
-
| -------------------- | ---------------------- | ------------- |
|
|
3287
|
-
| `subType` | `system.subType` | **see above** |
|
|
3288
|
-
| `data.charges.value` | `system.charges.value` | NA |
|
|
3289
|
-
| `data.charges.max` | `system.charges.max` | NA |
|
|
3290
|
-
|
|
3291
|
-
`data.masteryLevel` and `data.level` are page data for an ability. SoHL reads
|
|
3292
|
-
the item’s base mastery and level from `sohl.masteryLevelBase` and
|
|
3293
|
-
`sohl.levelBase`.
|
|
3294
|
-
|
|
3295
|
-
### type: projectilegear
|
|
3296
|
-
|
|
3297
|
-
**subType**:
|
|
3298
|
-
|
|
3299
|
-
- none
|
|
3300
|
-
- arrow
|
|
3301
|
-
- bolt
|
|
3302
|
-
- bullet
|
|
3303
|
-
- dart
|
|
3304
|
-
- other
|
|
3305
|
-
|
|
3306
|
-
| `data` property | Values | Description |
|
|
3307
|
-
| ------------------ | -------- | ------------------------------------------ |
|
|
3308
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3309
|
-
| `weight` | `number` | Gear weight |
|
|
3310
|
-
| `value` | `number` | Gear value |
|
|
3311
|
-
| `quality` | `number` | Gear quality |
|
|
3312
|
-
| `durability` | `number` | Gear durability |
|
|
3313
|
-
| `quantity` | `number` | HM3 gear quantity (default: 1) |
|
|
3314
|
-
|
|
3315
|
-
if a `sohl` property is present, a SoHL item of type "projectilegear" is created.
|
|
3316
|
-
|
|
3317
|
-
If an `hm3` property is present, an HM3 item of type "missilegear" is created.
|
|
3318
|
-
|
|
3319
|
-
Note that `weapon` can also produce an HM3 `missilegear`. Since `(type, shortcode)` is a
|
|
3320
|
-
flat namespace, a `projectilegear` and a `weapon` sharing a shortcode would collide on the
|
|
3321
|
-
HM3 side while remaining distinct on the SoHL side.
|
|
3322
|
-
|
|
3323
|
-
| shared source | → sohl | → hm3 |
|
|
3324
|
-
| ----------------- | ----------------------- | ----------------- |
|
|
3325
|
-
| `subType` | `system.subType` | NA |
|
|
3326
|
-
| `data.weight` | `system.weightBase` | `system.weight` |
|
|
3327
|
-
| `data.value` | `system.valueBase` | `system.value` |
|
|
3328
|
-
| `data.quality` | `system.qualityBase` | NA |
|
|
3329
|
-
| `data.durability` | `system.durabilityBase` | NA |
|
|
3330
|
-
| `data.quantity` | NA | `system.quantity` |
|
|
3331
|
-
|
|
3332
|
-
SoHL sets `system.quantity` to `1` for each compendium item.
|
|
3333
|
-
|
|
3334
|
-
### type: skill
|
|
3335
|
-
|
|
3336
|
-
**subType**:
|
|
3337
|
-
|
|
3338
|
-
- social
|
|
3339
|
-
- nature
|
|
3340
|
-
- craft
|
|
3341
|
-
- lore
|
|
3342
|
-
- language
|
|
3343
|
-
- script
|
|
3344
|
-
- mystical
|
|
3345
|
-
- physical
|
|
3346
|
-
- combat
|
|
3347
|
-
- combattechnique
|
|
3348
|
-
|
|
3349
|
-
| `data` property | Values | Description |
|
|
3350
|
-
| ------------------ | --------- | ------------------------------------------ |
|
|
3351
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3352
|
-
| `masteryLevel` | `number` | HM3 mastery level |
|
|
3353
|
-
| `parentSkill` | `Address` | Parent skill this skill specializes |
|
|
3354
|
-
|
|
3355
|
-
if a `sohl` property is present, a SoHL item of type "skill" is created.
|
|
3356
|
-
|
|
3357
|
-
If an `hm3` property is present, an HM3 item of type "skill" is created.
|
|
3358
|
-
|
|
3359
|
-
Note: `hm3.system.type` (skill types) use the values "Craft", "Physical", "Communication", "Combat", "Magic", and "Ritual". These do not cleanly map to the `subType` values. Because of this, the `hm3.system.type` value must be specified with the appropriate value when defining HM3 skills.
|
|
3360
|
-
|
|
3361
|
-
`system.parentSkillCode` is authored under `sohl.system` as the shortcode of the
|
|
3362
|
-
skill this one specializes, and has no shared source; `data.parentSkill` names that
|
|
3363
|
-
skill as an Address, for the page.
|
|
3364
|
-
|
|
3365
|
-
| shared source | → sohl | → hm3 |
|
|
3366
|
-
| ------------------- | ---------------- | --------------------- |
|
|
3367
|
-
| `subType` | `system.subType` | See notes above |
|
|
3368
|
-
| `data.masteryLevel` | NA | `system.masteryLevel` |
|
|
3369
|
-
|
|
3370
|
-
SoHL reads a skill’s base mastery from `sohl.masteryLevelBase`.
|
|
3371
|
-
|
|
3372
|
-
### type: trauma
|
|
3373
|
-
|
|
3374
|
-
**subType**:
|
|
3375
|
-
|
|
3376
|
-
- injury: Physical harm caused by an external force.
|
|
3377
|
-
- fear: Emotional response to a perceived threat or danger.
|
|
3378
|
-
- morale: Emotional state affecting group cohesion and individual morale.
|
|
3379
|
-
- pall: Influence of existential chaos, death, or life-draining forces.
|
|
3380
|
-
- psycond: Mental and emotional disorder.
|
|
3381
|
-
- physcond: A persistent physical condition of the body (descriptive; e.g. albinism, a limp, poor eyesight)
|
|
3382
|
-
- auralshock: Severe shock to the aura, resulting in temporary loss of aura-related abilities.
|
|
3383
|
-
- fatigue: Physical or mental exhaustion resulting from prolonged activity or stress.
|
|
3384
|
-
- infection: Swelling or inflammation exacerbating an existing condition or injury, often fatal.
|
|
3385
|
-
- shock: A prolonged physiological state of shock lasting hours or days, following severe trauma or blood loss — distinct from the transient combat-shock states.
|
|
3386
|
-
- coma: A prolonged state of unconsciousness.
|
|
3387
|
-
|
|
3388
|
-
| `data` property | Values | Description |
|
|
3389
|
-
| --------------------------------- | ------------- | ---------------------------------------------------------------------- |
|
|
3390
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3391
|
-
| `healingCheckDurationFormula` | `RollFormula` | Formula for the interval between healing checks |
|
|
3392
|
-
| `healingCheckDurationBase` | `number` | That interval in seconds, stated outright instead of rolled |
|
|
3393
|
-
| `bloodLossAdvanceDurationFormula` | `RollFormula` | Formula for the interval between blood-loss advances |
|
|
3394
|
-
| `bloodLossAdvanceDurationBase` | `number` | That interval in seconds. Setting it is what makes the wound bleed |
|
|
3395
|
-
| `courseDurationFormula` | `RollFormula` | Formula for the interval between course tests — shock, coma, infection |
|
|
3396
|
-
| `courseDurationBase` | `number` | That interval in seconds, stated outright instead of rolled |
|
|
3397
|
-
|
|
3398
|
-
if a `sohl` property is present, a SoHL item of type "trauma" is created.
|
|
3399
|
-
|
|
3400
|
-
If an `hm3` property is present, an HM3 item is created. `hm3.type` must be specified as either `injury` or `trait`.
|
|
3401
|
-
|
|
3402
|
-
| shared source | → sohl | → hm3 |
|
|
3403
|
-
| -------------------------------------- | ---------------------------------------- | ----- |
|
|
3404
|
-
| `subType` | `system.subType` | NA |
|
|
3405
|
-
| `data.healingCheckDurationFormula` | `system.healingCheckDurationFormula` | NA |
|
|
3406
|
-
| `data.healingCheckDurationBase` | `system.healingCheckDurationBase` | NA |
|
|
3407
|
-
| `data.bloodLossAdvanceDurationFormula` | `system.bloodLossAdvanceDurationFormula` | NA |
|
|
3408
|
-
| `data.bloodLossAdvanceDurationBase` | `system.bloodLossAdvanceDurationBase` | NA |
|
|
3409
|
-
| `data.courseDurationFormula` | `system.courseDurationFormula` | NA |
|
|
3410
|
-
| `data.courseDurationBase` | `system.courseDurationBase` | NA |
|
|
3411
|
-
|
|
3412
|
-
**A trauma's three timed phases follow the triplet rule** stated under
|
|
3413
|
-
`affliction`: write the formula or the base, both in seconds, and omitting each
|
|
3414
|
-
leaves the key out so the data model answers. Two of them fall back to a **world
|
|
3415
|
-
setting** when the trauma sets neither half — the healing check and the course —
|
|
3416
|
-
which is why a default written here would be wrong rather than merely
|
|
3417
|
-
redundant. `bloodLossAdvanceDurationBase` is the switch that makes a wound
|
|
3418
|
-
bleed: a trauma that sets it bleeds, one that leaves it unset does not.
|
|
3419
|
-
|
|
3420
|
-
**`system.contractDate` and `system.treatmentDate` are never authored.** They
|
|
3421
|
-
are the world times the injury was taken and last treated — runtime state, for
|
|
3422
|
-
the reason `affliction`'s four dates are — so a note that writes one fails the
|
|
3423
|
-
build.
|
|
3424
|
-
|
|
3425
|
-
### type: weapongear
|
|
3426
|
-
|
|
3427
|
-
Note: `data.quantity` may not be specified. Quantity is always 1.
|
|
3428
|
-
|
|
3429
|
-
| `data` property | Values | Description |
|
|
3430
|
-
| ------------------ | -------- | ------------------------------------------ |
|
|
3431
|
-
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3432
|
-
| `weight` | `number` | Gear weight |
|
|
3433
|
-
| `value` | `number` | Gear value |
|
|
3434
|
-
| `quality` | `number` | Gear quality |
|
|
3435
|
-
| `durability` | `number` | Gear durability |
|
|
3436
|
-
|
|
3437
|
-
if a `sohl` property is present, a SoHL item of type "weapongear" is created,
|
|
3438
|
-
carrying every strike mode the weapon has — melee and missile alike — on
|
|
3439
|
-
`system.strikeModes`.
|
|
3440
|
-
|
|
3441
|
-
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_.
|
|
3442
|
-
|
|
3443
|
-
| shared source | → sohl | → hm3 |
|
|
3444
|
-
| ----------------- | ----------------------- | --------------- |
|
|
3445
|
-
| `data.weight` | `system.weightBase` | `system.weight` |
|
|
3446
|
-
| `data.value` | `system.valueBase` | `system.value` |
|
|
3447
|
-
| `data.quality` | `system.qualityBase` | NA |
|
|
3448
|
-
| `data.durability` | `system.durabilityBase` | NA |
|
|
3449
|
-
|
|
3450
|
-
### type: lore
|
|
3451
|
-
|
|
3452
|
-
In-world information about people, places, or concepts.
|
|
3453
|
-
|
|
3454
|
-
**subType**:
|
|
3455
|
-
|
|
3456
|
-
- cosmology: The structure of reality — planes, realms, creation, and the ordering of what exists.
|
|
3457
|
-
- deity: Individual gods and their attributed natures, domains, epithets, and aspects.
|
|
3458
|
-
- theology: How the divine is held to operate — worship, sacrifice, afterlife, sin and grace.
|
|
3459
|
-
- arcana: How magic is held to operate — mechanism, traditions, and philosophies of practice.
|
|
3460
|
-
- spirit: The non-divine numinous — spirits, celestials, fae, and their natures.
|
|
3461
|
-
- economy: How wealth moves — barter, coinage, trade networks, credit, and measure.
|
|
3462
|
-
- law: How obligation is ordered and enforced — citizenship, custom, courts, and tenure.
|
|
3463
|
-
- calendar: How time is reckoned and marked — dating, seasons, festivals, and astrology.
|
|
3464
|
-
- history: What has happened — eras, events, chronicles, and genealogies of rule.
|
|
3465
|
-
- material: Substances and their properties — minerals, reagents, herbs, and preparations.
|
|
3466
|
-
- folk: Related sapient beings of a single or tightly related species: kindreds, ancestries.
|
|
3467
|
-
- culture: A social grouping of individuals with common beliefs, mores, and values.
|
|
3468
|
-
- bestiary: A kind of creature that is not a people — beasts, monsters, and the made things
|
|
3469
|
-
that were never born. What `folk` covers for the sapient, this covers for everything else.
|
|
3470
|
-
- gathering: A scheduled public occasion people travel to — a tournament or martial games, a
|
|
3471
|
-
great market or fair, a religious festival, a ceremony or rite. What these share is assembly
|
|
3472
|
-
on a cycle: a place, a time, and something contested or observed. Held apart from `calendar`,
|
|
3473
|
-
which covers the _reckoning_ — a festival's date is calendar and the festival is not, and a
|
|
3474
|
-
tournament is not a matter of time-reckoning at all — and from `culture`, which is a grouping
|
|
3475
|
-
of people rather than an occasion they attend.
|
|
3476
|
-
|
|
3477
|
-
| `data` property | Values | Description |
|
|
3478
|
-
| --------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
3479
|
-
| `epoch` | `date` | Which in-world day the world's clock reads zero on |
|
|
3480
|
-
| `months` | `{ name, abbreviation?, days }[]` | The months this calendar keeps, in order — the list sums to the world's year |
|
|
3481
|
-
| `weekdays` | `{ name, abbreviation? }[]` | The days of the week it names, in order; a calendar with no week writes none |
|
|
3482
|
-
| `seasons` | `{ name, abbreviation?, monthStart?, monthEnd?, dayStart?, dayEnd? }[]` | The seasons it marks, bounded by month or by day of year |
|
|
3483
|
-
| `eras` | `{ shortcode, name, marker?, abbreviation?, proclaimedBy?, start, end? }[]` | The year-counts kept in it; a marker names one era and uses this calendar's months |
|
|
3484
|
-
| `dateFormats` | `Map<slot, format>` | Calendaria display formats for this calendar |
|
|
3485
|
-
| `event` | `Map<field, value>` | A dated occurrence, with its sources, locations, reach, and relationships |
|
|
3486
|
-
|
|
3487
|
-
`data.event` is available on every `lore` subType. It holds structured
|
|
3488
|
-
chronology metadata, including the event's kind, date, sources, and the places
|
|
3489
|
-
it affects. The shared format checks that it is a map; a content package may
|
|
3490
|
-
check the details and relationships within that map. It is metadata for
|
|
3491
|
-
chronology tools and does not appear as an infobox row.
|
|
3492
|
-
|
|
3493
|
-
#### What a calendar note declares
|
|
3494
|
-
|
|
3495
|
-
**The calendar keys belong to `subType: calendar` and to no other genre of
|
|
3496
|
-
`lore`.** A note carrying one under any other subType is refused, and a calendar
|
|
3497
|
-
note that states no `months` and no `epoch` is refused too: a calendar divides
|
|
3498
|
-
the year and says where the count begins, and one that does neither is prose
|
|
3499
|
-
about time rather than a calendar.
|
|
3500
|
-
|
|
3501
|
-
```yaml
|
|
3502
|
-
shortcode: commoncal
|
|
3503
|
-
name:
|
|
3504
|
-
full: The Common Calendar
|
|
3505
|
-
type: lore
|
|
3506
|
-
subType: calendar
|
|
3507
|
-
data:
|
|
3508
|
-
epoch: VR(720/1/1)
|
|
3509
|
-
months:
|
|
3510
|
-
- { name: Floralis, abbreviation: Flor, days: 30 }
|
|
3511
|
-
- { name: Lusenar, abbreviation: Luse, days: 31 }
|
|
3512
|
-
# … ten more, summing to the world's year
|
|
3513
|
-
seasons:
|
|
3514
|
-
- { name: Spring, monthStart: 1, monthEnd: 3 }
|
|
3515
|
-
eras:
|
|
3516
|
-
- shortcode: founding
|
|
3517
|
-
name: After the Founding
|
|
3518
|
-
marker: VR
|
|
3519
|
-
abbreviation: AF
|
|
3520
|
-
proclaimedBy: vylarinmpr
|
|
3521
|
-
start: 1
|
|
3522
|
-
dateFormats: { full: "D MMMM, YYYY", yearLabel: "[Year] YYYY" }
|
|
3523
|
-
```
|
|
3524
|
-
|
|
3525
|
-
`dateFormats` accepts `short`, `long`, `full`, `time`, `weekHeader`,
|
|
3526
|
-
`yearHeader`, `yearLabel`, and `crossCalendar`. Each value is a Calendaria
|
|
3527
|
-
format string. Its date tokens may appear bare; ordinary words belong in
|
|
3528
|
-
`[brackets]` or `{braces}` so they print literally. A bare letter run that is
|
|
3529
|
-
not a Calendaria token is an error. The `time12` slot is outside this
|
|
3530
|
-
vocabulary.
|
|
3531
|
-
|
|
3532
|
-
**Position in the list is position in the year.** Days that belong to no month
|
|
3533
|
-
are a short month like any other: a five-day festival is one entry, and where it
|
|
3534
|
-
sits in the list is where it falls in the year. There is no intercalary
|
|
3535
|
-
mechanism, because there is nothing for one to do.
|
|
3536
|
-
|
|
3537
|
-
**How long the year is, how the day divides and how the moon moves are facts
|
|
3538
|
-
about the world, not about a calendar**, so no calendar note states any of them
|
|
3539
|
-
— they are written once on the world's own `place` note and on its moon's, and
|
|
3540
|
-
every calendar is compiled against them. The one arithmetic check follows from
|
|
3541
|
-
that: `months` must sum to `year.days`, and a list that does not describes a
|
|
3542
|
-
different world rather than a different calendar.
|
|
3543
|
-
|
|
3544
|
-
**An era is a way of counting years within a calendar**, so its rows sit beside
|
|
3545
|
-
the months. Each states its own `shortcode`, unique within the note. A date
|
|
3546
|
-
names an era with its marker, such as `VR(720/5/14)`. A calendar may contain
|
|
3547
|
-
several era rows, each with its own marker and year origin. The marker is an
|
|
3548
|
-
authoring identifier; the `abbreviation` is its printed label.
|
|
3549
|
-
`proclaimedBy` is optional: a reckoning whose proclaiming body is unknown, or
|
|
3550
|
-
whose body has no note, says so by leaving it out.
|
|
3551
|
-
|
|
3552
|
-
**A calendar with no week writes no `weekdays`.** An empty list and an absent
|
|
3553
|
-
one say the same thing, and nothing downstream shows a weekday for a calendar
|
|
3554
|
-
that names none.
|
|
3555
|
-
|
|
3556
|
-
### type: map
|
|
3557
|
-
|
|
3558
|
-
A map note has its own prose and a Foundry Scene. A map can depict a place or an encounter. Several maps can depict the same place; the map names that place with `sohl.place`, while the place note does not list its maps.
|
|
3559
|
-
|
|
3560
|
-
**subType**:
|
|
3561
|
-
|
|
3562
|
-
- `battlemap`: A tactical Scene built in Foundry.
|
|
3563
|
-
- `localmap`: A settlement or local-area Scene built in Foundry.
|
|
3564
|
-
- `regionalmap`: A large-scale chart with an authored image and scale.
|
|
3565
|
-
- `totm`: A gridless image Scene for theatre-of-the-mind play.
|
|
3566
|
-
|
|
3567
|
-
| `data` property | Values | Description |
|
|
3568
|
-
| ----------------- | ------------ | ---------------------------------------------------------- |
|
|
3569
|
-
| `scene` | object | Exported Foundry Scene for battle and local maps |
|
|
3570
|
-
| `fixup` | object[] | Asset address replacements in an exported Scene |
|
|
3571
|
-
| `bgImage` | Address | Background image for regional and theatre-of-the-mind maps |
|
|
3572
|
-
| `scale` | object | Regional distance and unit per grid unit |
|
|
3573
|
-
| `dimensions` | `[int, int]` | Optional shared dimensions metadata |
|
|
3574
|
-
| `pxPerGrid` | number | Optional shared grid size metadata |
|
|
3575
|
-
| `navName` | string | Short Scene navigation name |
|
|
3576
|
-
| `levelName` | string | Embedded Level name |
|
|
3577
|
-
| `backgroundColor` | string | Colour outside the art |
|
|
3578
|
-
| `overlay` | string | Foreground art path |
|
|
3579
|
-
| `walls` | object | Regional wall geometry |
|
|
3580
|
-
| `doors` | object | Regional door geometry |
|
|
3581
|
-
| `lights` | object | Regional light geometry |
|
|
3582
|
-
| `tiles` | object | Regional tile geometry |
|
|
3583
|
-
| `sounds` | object | Regional sound geometry |
|
|
3584
|
-
| `regions` | object | Regional region geometry |
|
|
3585
|
-
| `notes` | object | Regional pin geometry |
|
|
3586
|
-
| `place` | Address | Depicted place, when one exists |
|
|
3587
|
-
|
|
3588
|
-
#### Battle and local maps
|
|
3589
|
-
|
|
3590
|
-
Author the Scene in Foundry, export it, convert the export to YAML, and put the resulting object at `data.scene`. Preserve its walls, lights, tiles, regions, levels, environment, flags, and canvas settings. The export can name art from another Foundry package; that path is used as authored in Foundry. The Scene needs positive whole-number `width` and `height`, at least one Level, a valid `initialLevel` when present, and a unique 16-character `_id` on each embedded document. The build derives the top-level Scene `_id` from the map note, adds compendium keys and build metadata, and otherwise preserves the exported fields.
|
|
3591
|
-
|
|
3592
|
-
A pin's `text` can contain `#anchor`, where the map note has a heading such as `# Big Bad Wolf {#myanchor1}`. With `text: '#myanchor1'`, the compiled pin opens the JournalEntry and page generated from this map note and displays **Big Bad Wolf**. Its position, icon, other settings, and embedded `_id` remain as exported. A missing anchor is a build error. Pins whose text does not use `#anchor` retain their exported text and references.
|
|
3593
|
-
|
|
3594
|
-
`data.fixup` replaces exported asset paths without editing the Scene object by
|
|
3595
|
-
hand. Each entry has `path`, `type: address`, and `value`, an asset Address in
|
|
3596
|
-
the local or a fetched content index. Paths start at `data.scene` and use a
|
|
3597
|
-
property path with array indices or stable embedded `_id` selectors. A path
|
|
3598
|
-
must name one existing string or null field, and the address must resolve; any
|
|
3599
|
-
mistake fails the build. An `_id` selector remains stable when Foundry reorders
|
|
3600
|
-
an array on export.
|
|
3601
|
-
|
|
3602
|
-
```yaml
|
|
3603
|
-
data:
|
|
3604
|
-
fixup:
|
|
3605
|
-
- { path: ".notes[NNNNNNNNNNNNNNNN].texture.src", type: address, value: sohl-none-icon-book }
|
|
3606
|
-
- { path: ".sounds[0].path", type: address, value: sohl-none-audio-forest }
|
|
3607
|
-
```
|
|
3608
|
-
|
|
3609
|
-
Fixups change the compiled copy only. The exported Scene in the note stays
|
|
3610
|
-
intact, and pin anchor binding runs after fixups. A package name such as
|
|
3611
|
-
`fvtt` is an ordinary address package name: it needs published asset records
|
|
3612
|
-
before its addresses can resolve.
|
|
3613
|
-
|
|
3614
|
-
```yaml
|
|
3615
|
-
shortcode: wolfden
|
|
3616
|
-
name: { full: The Wolf's Den }
|
|
3617
|
-
type: map
|
|
3618
|
-
subType: battlemap
|
|
3619
|
-
data:
|
|
3620
|
-
scene:
|
|
3621
|
-
name: Wolf Den Scene
|
|
3622
|
-
width: 1900
|
|
3623
|
-
height: 2600
|
|
3624
|
-
grid: { type: 1, size: 100, distance: 5, units: ft }
|
|
3625
|
-
initialLevel: defaultLevel0000
|
|
3626
|
-
levels:
|
|
3627
|
-
- { _id: defaultLevel0000, background: { src: modules/maps/den.webp } }
|
|
3628
|
-
notes:
|
|
3629
|
-
- { _id: NNNNNNNNNNNNNNNN, text: "#myanchor1", x: 950, y: 2350 }
|
|
3630
|
-
---
|
|
3631
|
-
# Big Bad Wolf {#myanchor1}
|
|
3632
|
-
```
|
|
3633
|
-
|
|
3634
|
-
When the Scene has pins, its Adventure bundle carries the Scene and the JournalEntry generated from its Markdown. Importing that bundle preserves the IDs the pins address.
|
|
3635
|
-
|
|
3636
|
-
#### Regional maps
|
|
3637
|
-
|
|
3638
|
-
A `regionalmap` uses `data.bgImage`, an image Address, and `sohl.dimensions: [width, height]` plus `sohl.pxPerGrid` to build a gridless Scene. `data.scale: {distance: positive number, unit: nonempty string}` sets the distance per grid unit. Its shared `data` fields can also name `overlay`, `backgroundColor`, `levelName`, `navName`, `walls`, `doors`, `lights`, `tiles`, `sounds`, `regions`, and `notes`; the latter geometry uses the map-note conventions described in the SoHL map authoring guide. `data.scene` is not used for a regional map.
|
|
3639
|
-
|
|
3640
|
-
An SVG `data.bgImage` remains vector art on the site and in the book. The Foundry build stages a PNG at the note's stated dimensions with the installed SVG renderer; the source SVG is unchanged. Its aspect ratio must match the stated dimensions. A selected regional SVG map takes a full page in the PDF.
|
|
3641
|
-
|
|
3642
|
-
#### Theatre-of-the-mind maps
|
|
3643
|
-
|
|
3644
|
-
A `totm` map names its background with `data.bgImage` and its canvas size with
|
|
3645
|
-
`sohl.dimensions: [width, height]`. It builds a gridless Scene with token vision
|
|
3646
|
-
off. It needs no `sohl.pxPerGrid` or `data.scale`. The Markdown body remains the
|
|
3647
|
-
map's prose.
|
|
3648
|
-
|
|
3649
|
-
### type: place
|
|
3650
|
-
|
|
3651
|
-
**subType**:
|
|
3652
|
-
|
|
3653
|
-
- world: A self-contained whole in which places exist — a planet, plane, or realm.
|
|
3654
|
-
- region: A bounded division of a world or larger region — continents, marches, uplands, provinces.
|
|
3655
|
-
- settlement: A place where folk dwell together — cities, towns, villages, holdings, camps.
|
|
3656
|
-
- site: A place significant by what was made or done there — ruins, monoliths, henges, works, battlefields.
|
|
3657
|
-
- structure: A single building or habitation — halls, keeps, temples, inns, towers.
|
|
3658
|
-
- feature: A place significant by its terrain — forests, rivers, falls, passes, fords.
|
|
3659
|
-
- celestial: A body observed from a world rather than located on one — a sun, a planet, a comet.
|
|
3660
|
-
|
|
3661
|
-
| `data` property | Values | Description |
|
|
3662
|
-
| --------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
3663
|
-
| `demonym` | `string` | What a person from this place is called — a Vylarian |
|
|
3664
|
-
| `purpose` | `placeCharacter` tag | The reason a settlement, site, or structure exists |
|
|
3665
|
-
| `lore` | `Address[]` | Lore concerning this place — its peoples, its law, its calendar, its history |
|
|
3666
|
-
| `parents` | `Address[]` | Enclosing places within which this place is located |
|
|
3667
|
-
| `population` | `number` | Approximate population (precision 2 significant digits) |
|
|
3668
|
-
| `market` | `number` | Market class, 1 to 6 — what trade a settlement supports |
|
|
3669
|
-
| `borders` | `{ to, bearing }[]` | Places sharing a frontier with this one, and where each lies from here |
|
|
3670
|
-
| `routes` | `{ to, bearing, mode, days, terrain?, leagues? }[]` | Journeys from this place's centre to another place |
|
|
3671
|
-
| `world.equatorialCircumferenceKm` | `number` | The distance round the body at its equator, in kilometres |
|
|
3672
|
-
| `world.surfaceGravityG` | `number` | Surface gravity, as a multiple of Earth's |
|
|
3673
|
-
| `world.axialTiltDegrees` | `number` | The tilt of the axis, in degrees |
|
|
3674
|
-
| `year.days` | `number` | How many days the body's year holds |
|
|
3675
|
-
| `year.hoursPerDay` | `number` | How many hours the day divides into |
|
|
3676
|
-
| `year.minutesPerHour` | `number` | How many minutes the hour divides into |
|
|
3677
|
-
| `year.secondsPerMinute` | `number` | How many seconds the minute divides into |
|
|
3678
|
-
| `present` | `date` | The day the setting stops and play begins |
|
|
3679
|
-
| `body.diameterKm` | `number` | The body's diameter, in kilometres |
|
|
3680
|
-
| `body.orbitalRadiusKm` | `number` | How far it orbits from the body it circles, in kilometres |
|
|
3681
|
-
| `body.orbit` | `string` | The orbit's shape — `circular` means the cycle never varies |
|
|
3682
|
-
| `body.inclined` | `boolean` | Whether the orbit is inclined to the plane the world orbits in |
|
|
3683
|
-
| `moon.cycle` | `number` | How many days it takes to return to the same phase |
|
|
3684
|
-
| `moon.newOn` | `date` | A day it was new, written in the reference calendar |
|
|
3685
|
-
| `moon.eclipses` | `string` | `never`, `rare`, `occasional` or `frequent` |
|
|
3686
|
-
|
|
3687
|
-
#### Why a place exists
|
|
3688
|
-
|
|
3689
|
-
`data.purpose` selects one of the place's own `placeCharacter` tags. It is
|
|
3690
|
-
optional on settlements, sites, and structures. A mining settlement on a coast
|
|
3691
|
-
can carry `tags: [mining, coastal]` and `data.purpose: mining`: the coast locates
|
|
3692
|
-
it; the mine explains its existence. The value must belong to the declared
|
|
3693
|
-
`placeCharacter` vocabulary and also appear among that note's tags. The tags
|
|
3694
|
-
include `ford`, `portage`, `pass`, and `well` for places founded around a crossing,
|
|
3695
|
-
a break in navigation, a mountain route, or scarce water. The body explains the
|
|
3696
|
-
specific history; the selected tag gives tables a consistent grouping value.
|
|
3697
|
-
|
|
3698
|
-
#### What a body states about itself
|
|
3699
|
-
|
|
3700
|
-
**The four groups above are the world's own facts, and they are legal on a
|
|
3701
|
-
`world` or a `celestial` note and nowhere else.** A region, a settlement, a
|
|
3702
|
-
site, a structure or a feature is somewhere _within_ a world and says nothing
|
|
3703
|
-
about the world's year.
|
|
3704
|
-
|
|
3705
|
-
```yaml
|
|
3706
|
-
# the world — type: place, subType: world
|
|
3707
|
-
data:
|
|
3708
|
-
world: { equatorialCircumferenceKm: 40000, surfaceGravityG: 1.0, axialTiltDegrees: 23.5 }
|
|
3709
|
-
year: { days: 365, hoursPerDay: 24, minutesPerHour: 60, secondsPerMinute: 60 }
|
|
3710
|
-
present: 720
|
|
3711
|
-
|
|
3712
|
-
# its moon — a note of its own, because a moon is a body and not a property
|
|
3713
|
-
data:
|
|
3714
|
-
body: { diameterKm: 3800, orbitalRadiusKm: 388600, orbit: circular, inclined: true }
|
|
3715
|
-
moon: { cycle: 30, newOn: 720/1/1, eclipses: rare }
|
|
3716
|
-
```
|
|
3717
|
-
|
|
3718
|
-
**`year.days` is where the year's length lives, and every calendar in the
|
|
3719
|
-
package is compiled against it.** A package whose notes state no `year` has no
|
|
3720
|
-
calendar mechanism at all: no sum is checked, and nothing is emitted. That is
|
|
3721
|
-
the right answer for a package building a setting whose world facts somebody
|
|
3722
|
-
else wrote down.
|
|
3723
|
-
|
|
3724
|
-
**One year and one moon to a package.** Two years is two settings and nothing
|
|
3725
|
-
can say which one a calendar divides; a `moon` with no `year` anywhere is
|
|
3726
|
-
refused, because a cycle counted in days has no year to be measured against.
|
|
3727
|
-
|
|
3728
|
-
**`present` is the day an age is computed against**, written in the grammar a
|
|
3729
|
-
note's dates are written in, so a setting whose present is best said in an era
|
|
3730
|
-
says it that way.
|
|
3731
|
-
|
|
3732
|
-
**`world.axialTiltDegrees`, the circumference, the gravity, the moon's diameter
|
|
3733
|
-
and its orbital radius are read by nobody.** They are there so a reader knows
|
|
3734
|
-
what kind of world this is. What the build consumes is the year, the day's
|
|
3735
|
-
divisions, the present, the moon's cycle and epoch, and whether its orbit is
|
|
3736
|
-
circular and inclined.
|
|
3737
|
-
|
|
3738
|
-
#### What a settlement's market supports
|
|
3739
|
-
|
|
3740
|
-
`population` states how many people a settlement holds; `market` states what a
|
|
3741
|
-
traveller can buy there. Those are different questions — two settlements of
|
|
3742
|
-
eight hundred are a different proposition when one holds a weekly market and
|
|
3743
|
-
the other a chartered fair with a moneylender — so a note states both, either,
|
|
3744
|
-
or neither.
|
|
3745
|
-
|
|
3746
|
-
The value is a **class**, not a measurement. The numbers order six steps and do
|
|
3747
|
-
nothing else: a `5` is not a market two fifths larger than a `3`, it is a city
|
|
3748
|
-
rather than a town. `content-build lint` refuses a value off the scale instead
|
|
3749
|
-
of rounding it onto one, so a reader can trust that a number means the same
|
|
3750
|
-
thing on every page.
|
|
3751
|
-
|
|
3752
|
-
| `market` value | the settlement is | what can be had there |
|
|
3753
|
-
| -------------- | ----------------- | ------------------------------------------------------------------------------------------------- |
|
|
3754
|
-
| `1` | a hamlet | no market — what neighbours trade among themselves |
|
|
3755
|
-
| `2` | a village | a weekly market: staples, a smith, what a household cannot make |
|
|
3756
|
-
| `3` | a town | a regular market, most common goods, several trades working full time |
|
|
3757
|
-
| `4` | a market town | a chartered fair, goods carried from a week away, a moneylender |
|
|
3758
|
-
| `5` | a city | anything ordinary, in quantity; foreign goods; a guild structure |
|
|
3759
|
-
| `6` | a great city | the rare and the imported as a matter of course; banking; the market from which other markets buy |
|
|
3760
|
-
|
|
3761
|
-
The key is meaningful on a settlement, and the lint checks no `subType`
|
|
3762
|
-
condition — as it checks none for `population`. Which places a fact says
|
|
3763
|
-
something about is the author's judgement, and the `data:` container is closed
|
|
3764
|
-
per type rather than per subType.
|
|
3765
|
-
|
|
3766
|
-
#### Where a place is, in relation to other places
|
|
3767
|
-
|
|
3768
|
-
`parents` states what a place is **within**. Two more properties state what
|
|
3769
|
-
it is **next to** and what it is **reachable from**, as data rather than as
|
|
3770
|
-
prose, so that two notes describing one frontier cannot quietly disagree about
|
|
3771
|
-
which side of it each is on:
|
|
3772
|
-
|
|
3773
|
-
```yaml
|
|
3774
|
-
data:
|
|
3775
|
-
borders: # places sharing a frontier
|
|
3776
|
-
- { to: dunharargn, bearing: NE } # both required
|
|
3777
|
-
- { to: bethuargn, bearing: W }
|
|
3778
|
-
routes: # journeys from this place's centre
|
|
3779
|
-
- { to: steinmark, bearing: SW, mode: land, days: 3, terrain: [road, mountains] }
|
|
3780
|
-
- { to: denizara, bearing: W, mode: ship, days: 5 }
|
|
3781
|
-
- { to: kethramir, bearing: E, mode: land, days: 30, terrain: [dunes], leagues: 90 }
|
|
3782
|
-
```
|
|
3783
|
-
|
|
3784
|
-
- `to` — required; an `Address` naming the other place. Its type segment defaults
|
|
3785
|
-
to `place` and `place` is the whole of its accepted set, so `vylar`,
|
|
3786
|
-
`place-vylar` and `thalorna-none-place-vylar` name one place and a `to` naming
|
|
3787
|
-
anything else is an error. **A bare shortcode names a place in this package**,
|
|
3788
|
-
which is what the fully qualified form is for: naming a place in a declared
|
|
3789
|
-
dependency's index, so two packages using one shortcode never answer for each
|
|
3790
|
-
other.
|
|
3791
|
-
- `bearing` — required; where the neighbour or the destination lies from here.
|
|
3792
|
-
- `mode` — required on a route; how the journey is travelled.
|
|
3793
|
-
- `days` — required on a route; a marker meaning "about this, under normal
|
|
3794
|
-
conditions", from the scale below. Any other value is an error.
|
|
3795
|
-
- `terrain` — optional on a route; the terrains crossed, in travel order, from
|
|
3796
|
-
the registry below. Each terrain names the modes that cross it, so open sea
|
|
3797
|
-
on a land route — or a road on a ship route — is an error.
|
|
3798
|
-
- `leagues` — optional on a route; a distance, only where the note states one.
|
|
3799
|
-
|
|
3800
|
-
**A border is a shared frontier between areas.** Regions commonly state one;
|
|
3801
|
-
a settlement does so only when it actually shares a frontier with another
|
|
3802
|
-
place. Most settlements use `parents` for where they sit and `routes` for
|
|
3803
|
-
journeys between them. A pair appears in `routes` once per `mode` — a port and its
|
|
3804
|
-
neighbour may be three days by land and one by ship — and in `borders` once.
|
|
3805
|
-
**A place never borders its own parent or child**: containment is `parents`,
|
|
3806
|
-
and restating it as adjacency makes the two contradict each other.
|
|
3807
|
-
|
|
3808
|
-
**Both ends state the relation, and `content-build lint` checks that they
|
|
3809
|
-
agree.** A border at `NE` from one side is a border at `SW` from the other; a
|
|
3810
|
-
route stated by land in three days from one side is stated by land in three
|
|
3811
|
-
days from the other, at the opposite bearing. A neighbour that states nothing
|
|
3812
|
-
back is a warning naming both notes — the pair is half-written, not wrong. A
|
|
3813
|
-
neighbour that states a different bearing, a different `days` or a different
|
|
3814
|
-
`mode` is an error, because one of the two notes is wrong and a reader cannot
|
|
3815
|
-
tell which. Every `to` must resolve to a place, every value must come from its
|
|
3816
|
-
closed set, and each finding is located at the entry that states it.
|
|
3817
|
-
|
|
3818
|
-
The terrain names form one shared registry in package-build. A route crossing
|
|
3819
|
-
a package boundary uses the same terrain and mode rules at both ends.
|
|
3820
|
-
|
|
3821
|
-
For every place that participates in a border or route, the map builder draws
|
|
3822
|
-
an itinerary from that place. A site page carries it as a linked SVG. A PDF
|
|
3823
|
-
book that includes the place gives its map a separate full page after the
|
|
3824
|
-
entry. A configured Foundry Scene pack carries a gridless map backed by a
|
|
3825
|
-
generated PNG, with Notes linked to importable place journals. Places with no
|
|
3826
|
-
relations create no itinerary output.
|
|
3827
|
-
|
|
3828
|
-
**A relation that crosses a package boundary is written in full at both ends.**
|
|
3829
|
-
An omitted `<package>` segment is the package being built, in whatever note the
|
|
3830
|
-
value is read from — a note of this package's own, or a record a consumer fetched
|
|
3831
|
-
out of a dependency's index — so a short `to` names a different place in a
|
|
3832
|
-
different build. Only the fully qualified form names one place everywhere, which
|
|
3833
|
-
is why the long form is not optional for a border or a route between packages,
|
|
3834
|
-
and why a short `to` in a fetched record names the package being built rather
|
|
3835
|
-
than the package that wrote it.
|
|
3836
|
-
|
|
3837
|
-
| `bearing` value | where the other place lies |
|
|
3838
|
-
| --------------- | -------------------------- |
|
|
3839
|
-
| `N` | north |
|
|
3840
|
-
| `NE` | north-east |
|
|
3841
|
-
| `E` | east |
|
|
3842
|
-
| `SE` | south-east |
|
|
3843
|
-
| `S` | south |
|
|
3844
|
-
| `SW` | south-west |
|
|
3845
|
-
| `W` | west |
|
|
3846
|
-
| `NW` | north-west |
|
|
3847
|
-
|
|
3848
|
-
| `mode` value | travelled |
|
|
3849
|
-
| ------------ | ---------------------------- |
|
|
3850
|
-
| `land` | on foot, mounted, or by cart |
|
|
3851
|
-
| `boat` | on a river or a lake |
|
|
3852
|
-
| `ship` | on open sea |
|
|
3853
|
-
|
|
3854
|
-
| `days` value | meaning |
|
|
3855
|
-
| ------------ | -------------------------------------------- |
|
|
3856
|
-
| `1` | a day |
|
|
3857
|
-
| `2` | two days |
|
|
3858
|
-
| `3` | three days |
|
|
3859
|
-
| `5` | about five days |
|
|
3860
|
-
| `10` | a ten-day week |
|
|
3861
|
-
| `20` | two ten-day weeks |
|
|
3862
|
-
| `30` | three ten-day weeks |
|
|
3863
|
-
| `45` | a month and a half |
|
|
3864
|
-
| `60` | two months |
|
|
3865
|
-
| `90` | a season |
|
|
3866
|
-
| `180` | many months |
|
|
3867
|
-
| `360` | who knows — a journey nobody reckons in days |
|
|
3868
|
-
|
|
3869
|
-
| `terrain` value | crossed by |
|
|
3870
|
-
| --------------- | ------------------ |
|
|
3871
|
-
| `road` | land |
|
|
3872
|
-
| `track` | land |
|
|
3873
|
-
| `plain` | land |
|
|
3874
|
-
| `steppe` | land |
|
|
3875
|
-
| `hills` | land |
|
|
3876
|
-
| `mountains` | land |
|
|
3877
|
-
| `forest` | land |
|
|
3878
|
-
| `jungle` | land |
|
|
3879
|
-
| `marsh` | land |
|
|
3880
|
-
| `dunes` | land |
|
|
3881
|
-
| `desert` | land |
|
|
3882
|
-
| `ice` | land |
|
|
3883
|
-
| `coast` | land, boat or ship |
|
|
3884
|
-
| `open-sea` | ship |
|
|
3885
|
-
| `river` | boat |
|
|
3886
|
-
| `lake` | boat |
|
|
3887
|
-
|
|
3888
|
-
#### What lies within a place, and who holds it
|
|
3889
|
-
|
|
3890
|
-
`parents` states what a place is within; an affiliation's `domains` states
|
|
3891
|
-
what it holds. The website reads both across the package and every fetched
|
|
3892
|
-
index and writes each place's page two lists, each entry
|
|
3893
|
-
`{ title, url, type, subType }` and each absent when empty:
|
|
3894
|
-
|
|
3895
|
-
- `contains` — every place whose `parents` names this one, sorted by
|
|
3896
|
-
`subType` then `title`. A region lists its settlements, sites, structures
|
|
3897
|
-
and features; a settlement lists the structures within it.
|
|
3898
|
-
- `held_by` — every affiliation whose `domains` names this one, sorted by
|
|
3899
|
-
`title`.
|
|
3900
|
-
|
|
3901
|
-
Both are derived; a note that writes one has it replaced. **`domains` is
|
|
3902
|
-
never expanded**: a region held by a polity lists the polity in `held_by`, and
|
|
3903
|
-
a settlement within that region lists only the house or order whose `domains`
|
|
3904
|
-
names it — its lord's lord is reached through `affiliation.parents`.
|
|
3905
|
-
|
|
3906
|
-
**A settlement, a site or a structure is held by someone.** One of those that
|
|
3907
|
-
no affiliation's `domains` names — in this package or in a fetched index — is
|
|
3908
|
-
a warning from `content-build lint`, `unheld land`, located at the note's
|
|
3909
|
-
`type:` line, so a gap in tenure shows while a note is written. A `world`, a
|
|
3910
|
-
`region` and a `feature` are exempt: a region is held through its polity's
|
|
3911
|
-
`domains`, and a river by nobody.
|
|
3912
|
-
|
|
3913
|
-
#### Population figures
|
|
3914
|
-
|
|
3915
|
-
`population` is an approximate count, written to two significant digits. A
|
|
3916
|
-
place states one, and so does an affiliation — how many people it counts,
|
|
3917
|
-
wherever they are. `content-build lint` reads those figures against each other
|
|
3918
|
-
and reports four warnings, each named by the phrase its message begins with.
|
|
3919
|
-
|
|
3920
|
-
- **`over-held land`** — the polities whose `domains` name a place count more
|
|
3921
|
-
people than the place states. A polity subordinate to another polity holding
|
|
3922
|
-
the same place is already inside that polity's figure and is not counted
|
|
3923
|
-
twice. A **settlement is exempt**: the polity of a city-state holds the city
|
|
3924
|
-
and counts the hinterland around it, so its figure is properly the larger.
|
|
3925
|
-
- **`over-full region`** — the regions whose `parents` name a place count more
|
|
3926
|
-
people than the place states. A region naming both its parent and its
|
|
3927
|
-
grandparent counts under the nearer of the two, so a place written into two
|
|
3928
|
-
levels of the hierarchy is not counted at both.
|
|
3929
|
-
- **`oversized settlement`** — a settlement states more people than a place
|
|
3930
|
-
containing it does, whether its own region or any place above that.
|
|
3931
|
-
- **`disputed figure`** — a `doc` note carries a figure beside a wikilink to
|
|
3932
|
-
the place or affiliation it belongs to, and the two disagree. The finding is
|
|
3933
|
-
located at the number the page wrote, and a figure written twice is located
|
|
3934
|
-
twice.
|
|
3935
|
-
|
|
3936
|
-
The first three are reported on the note's own `data.population`; the fourth
|
|
3937
|
-
on the citation.
|
|
3938
|
-
|
|
3939
|
-
**A figure is read as a population only where it carries `~`.** That marker is
|
|
3940
|
-
what separates a population from the other numbers a table puts beside a place
|
|
3941
|
-
— a distance, a stage count, a tally of days — so `| [[place-rgn\|X]] | ~2,000 |`
|
|
3942
|
-
is a claim about people and `| [[place-rgn\|X]] | 3 |` is not.
|
|
3943
|
-
|
|
3944
|
-
**Rounding is admitted.** Two significant digits carry a half-unit of error,
|
|
3945
|
-
widest at the bottom of a decade, so a place whose figure falls up to five per
|
|
3946
|
-
cent below what it contains is not a finding.
|
|
3947
|
-
|
|
3948
|
-
**An unstated figure is silent.** A place or affiliation with no `population`
|
|
3949
|
-
contributes nothing to a sum and is never the subject of a finding, so a
|
|
3950
|
-
half-written region is quiet rather than noisy. A fetched index carries
|
|
3951
|
-
`parents` and `domains` but no figure, so a dependency's places sit in the
|
|
3952
|
-
geography and its people are counted nowhere.
|
|
3953
|
-
|
|
3954
|
-
**There is deliberately no urban-share rule.** The share of a region's people
|
|
3955
|
-
living in its named settlements cannot be measured from the notes: the
|
|
3956
|
-
settlement layer names the notable places and is complete nowhere, so the ratio
|
|
3957
|
-
reports how much of a region has been written rather than how much of it is
|
|
3958
|
-
urban.
|
|
3959
|
-
|
|
3960
|
-
**A place declares what is true of its ground.** Its governing affiliations
|
|
3961
|
-
carry their own `domains`; maps name the place they depict through `data.place`.
|
|
3962
|
-
The place's `lore` links cover its peoples, calendar, law and history. Political
|
|
3963
|
-
languages belong to a polity's `sohl.system.commonSkills`, while the note's
|
|
3964
|
-
`description` provides its page summary.
|
|
3965
|
-
|
|
3966
|
-
### type: scenario
|
|
3967
|
-
|
|
3968
|
-
Content prepared to be played — a situation with its cast, places, and possible outcomes.
|
|
3969
|
-
|
|
3970
|
-
**subType**:
|
|
3971
|
-
|
|
3972
|
-
- campaign: A long arc toward a goal, spanning many adventures — carries standing cast, factions, and its own timeline.
|
|
3973
|
-
- adventure: A self-contained undertaking with a specific objective, playable in a few sessions.
|
|
3974
|
-
- encounter: A single scene or challenge, reusable within an adventure or on its own.
|
|
3975
|
-
|
|
3976
|
-
| `data` property | Values | Description |
|
|
3977
|
-
| ------------------ | -------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
3978
|
-
| `parents` | `Address[]` | List of parent scenarios of this scenario (campaigns, etc.) |
|
|
3979
|
-
| `locations` | `Address[]` | List of locations associated with this scenario |
|
|
3980
|
-
| `cast` | `Address[]` | individuals associated with this scenario |
|
|
3981
|
-
| `factions` | `Address[]` | Affiliations associated with this scenario |
|
|
3982
|
-
| `follows` | `Address[]` | Prerequisite scenarios that should be completed before beginning this scenario |
|
|
3983
|
-
| `status` | `draft \| playtested \| published` | Playability status of this scenario |
|
|
3984
|
-
| `party.size` | `solo \| small \| standard \| large \| host` | Suggested party size (solo=1, small=2-3, standard=4-6, large=6-7, host=7+) |
|
|
3985
|
-
| `party.archetypes` | `Archetype[]` | Archetypes of characters suitable for completion |
|
|
3986
|
-
|
|
3987
|
-
### type: doc
|
|
3988
|
-
|
|
3989
|
-
**subType**:
|
|
3990
|
-
|
|
3991
|
-
- rules: The rules of the game, independent of medium — valid at a table with paper and dice.
|
|
3992
|
-
- userguide: How to operate the Foundry implementation to play by the rules.
|
|
3993
|
-
- reference: Out-of-world lookup material about the setting or system — correspondences, conversions, glossaries.
|
|
3994
|
-
- howto: A task with an outcome, written as the steps that reach it.
|
|
3995
|
-
- concept: An explanation of how something works and why it is shaped that way, read to understand rather than to follow.
|
|
3996
|
-
- settingguide: An orientation to a setting or region, bringing its places, peoples, institutions, and daily life into one usable frame with links to detailed notes.
|
|
3997
|
-
|
|
3998
|
-
A `doc` declares no properties of its own.
|
|
3999
|
-
|
|
4000
|
-
Choose `settingguide` for an entry point that stands on its own while guiding
|
|
4001
|
-
players and GMs into a setting. A `concept` explains one subject, a `reference`
|
|
4002
|
-
supports lookup, and a `userguide` explains how to operate the Foundry
|
|
4003
|
-
implementation. The setting guide can use ordinary SQL content tables and
|
|
4004
|
-
`:::secret` passages; its genre does not change its Address, journal, or page
|
|
4005
|
-
rendering.
|
|
4006
|
-
|
|
4007
|
-
```markdown
|
|
4008
1
|
---
|
|
4009
|
-
shortcode:
|
|
4010
|
-
name: { full:
|
|
2
|
+
shortcode: contentformat
|
|
3
|
+
name: { full: "Authoring HeroicLands content" }
|
|
4011
4
|
type: doc
|
|
4012
|
-
subType:
|
|
4013
|
-
description: A starting point for adventures in the Reed Flats.
|
|
5
|
+
subType: userguide
|
|
4014
6
|
---
|
|
4015
7
|
|
|
4016
|
-
|
|
4017
|
-
Their routes, customs, and rival groups shape everyday journeys.
|
|
4018
|
-
|
|
4019
|
-
:::secret
|
|
4020
|
-
The old ferry route is known to the local guides.
|
|
4021
|
-
:::
|
|
4022
|
-
```
|
|
4023
|
-
|
|
4024
|
-
The field is a genre and only a genre: it says what kind of page this is, never
|
|
4025
|
-
who reads it. A page written for a developer is a `howto` or a `concept` like
|
|
4026
|
-
any other, and the audience is the section it sits in.
|
|
4027
|
-
|
|
4028
|
-
**A page that introduces a type is an ordinary note, named by convention.**
|
|
4029
|
-
Write `type: doc`, `subType: reference`, `shortcode: <type>` — so the
|
|
4030
|
-
affiliations introduction is `doc-affiliation`, addressed and linked like
|
|
4031
|
-
anything else, and typically carrying a generated table of what it introduces.
|
|
4032
|
-
It has no build path of its own, and it is the only kind of index there is: a
|
|
4033
|
-
site is its homepage and its pages, and nothing is generated between them.
|
|
4034
|
-
|
|
4035
|
-
There is no landing page and no section. A section appears in **no address**: a
|
|
4036
|
-
page publishes at `/<package>/<type>-<shortcode>/`, which names no directory. A
|
|
4037
|
-
section is what Hugo calls a content directory, and the note format does not
|
|
4038
|
-
carry one.
|
|
4039
|
-
|
|
4040
|
-
So a `doc`'s `subType` is a **genre** and nothing else, closed to the values
|
|
4041
|
-
above.
|
|
4042
|
-
|
|
4043
|
-
### type: macro
|
|
4044
|
-
|
|
4045
|
-
A script offered on the macro bar, plus the prose explaining what it does and
|
|
4046
|
-
when to reach for it. Produces a Foundry **Macro** and, from the same note, the
|
|
4047
|
-
JournalEntry every note produces — so a macro's documentation is a document a
|
|
4048
|
-
player can open, not a comment nobody reads.
|
|
4049
|
-
|
|
4050
|
-
**The script is a page of the note, addressed by an anchor.** The macro's
|
|
4051
|
-
`command` is the first **language-tagged** JavaScript fence on the page whose
|
|
4052
|
-
heading carries `{#script}`:
|
|
4053
|
-
|
|
4054
|
-
````markdown
|
|
4055
|
-
# Script {#script}
|
|
4056
|
-
|
|
4057
|
-
```js
|
|
4058
|
-
await CONFIG.SOHL.class.Utility.currentCombatantAttack();
|
|
4059
|
-
```
|
|
4060
|
-
````
|
|
4061
|
-
|
|
4062
|
-
Three rules follow, and each is deliberate. The anchor names the **page**, not
|
|
4063
|
-
the heading text, so the heading may be worded freely and
|
|
4064
|
-
`[[macro-autoattack#script]]` still opens exactly this page. The fence must
|
|
4065
|
-
be **tagged** — an untagged fence is a code sample whose language nobody stated,
|
|
4066
|
-
and treating it as executable would make an illustrative snippet the macro. And
|
|
4067
|
-
only the **first** tagged fence counts, so a note may document its macro
|
|
4068
|
-
with examples that are plainly not the macro. A note with no `{#script}` page,
|
|
4069
|
-
or no tagged fence on it, is a **build error**: a macro with no command is a
|
|
4070
|
-
macro-bar button that does nothing.
|
|
4071
|
-
|
|
4072
|
-
**The executable copy is read from the raw markdown.** The journal's copy of the
|
|
4073
|
-
same fence has been through table expansion and wikilink conversion first, so
|
|
4074
|
-
the two diverge on purpose — the journal renders prose _about_ the script, while
|
|
4075
|
-
the macro runs exactly what the author typed.
|
|
4076
|
-
|
|
4077
|
-
**This is not compiling data into code.** A Macro's `command` is authored source
|
|
4078
|
-
shipped as content and run by Foundry's own macro runner under the permission
|
|
4079
|
-
model that governs every macro in a world. Nothing evaluates, compiles, or
|
|
4080
|
-
revives anything; the compiler copies text from a fence into a JSON field.
|
|
4081
|
-
|
|
4082
|
-
**Both settings are `sohl` properties, not `data` ones.** The compiler reads
|
|
4083
|
-
them with the same accessor every `sohl` field uses — the `sohl:` block first,
|
|
4084
|
-
then the note's top level — so `data.macroType` is not read, and a macro is not
|
|
4085
|
-
a journal-only note the way `place`, `lore` and `scenario` are: it produces a
|
|
4086
|
-
Foundry **Macro**, and these two describe that document.
|
|
4087
|
-
|
|
4088
|
-
| `sohl` property | Values | Description |
|
|
4089
|
-
| --------------- | --------------------------- | -------------------------------------------------------------------- |
|
|
4090
|
-
| `macroType` | `script` | The Foundry macro type. Defaults to `script`, and `chat` is an error |
|
|
4091
|
-
| `macroScope` | `global \| actors \| actor` | How far the macro reaches. Defaults to `global` |
|
|
4092
|
-
|
|
4093
|
-
**`macroType: chat` is an error, not an unimplemented feature.** A chat macro's
|
|
4094
|
-
`command` is chat text rather than source, so none of the `{#script}` fence
|
|
4095
|
-
rules describe it, and compiling one through this path would ship a macro whose
|
|
4096
|
-
body was a code block posted verbatim into chat. Chat macros as content would
|
|
4097
|
-
need an authoring convention of their own.
|
|
8
|
+
# Authoring HeroicLands content
|
|
4098
9
|
|
|
4099
|
-
|
|
4100
|
-
authors none takes Foundry's own `icons/svg/dice-target.svg`.
|
|
10
|
+
A content package begins with Markdown notes under `assets/content/`. Each note has YAML frontmatter describing its identity and data, followed by prose. Folder names beneath `assets/content/` are for authors; `type`, `subType`, and Addresses determine what the build creates.
|
|
4101
11
|
|
|
4102
|
-
|
|
12
|
+
Start with [your first note](authoring/first-note.md). It explains the smallest valid note and shows the JournalEntry, web page, book entry, and content-index record made from it. Then use the chapters below in the order your note needs them.
|
|
4103
13
|
|
|
4104
|
-
|
|
14
|
+
| Need | Read |
|
|
15
|
+
| --------------------------------------------------------- | ----------------------------------------------------------- |
|
|
16
|
+
| Frontmatter, `data`, system blocks, and infoboxes | [Frontmatter](authoring/frontmatter.md) |
|
|
17
|
+
| Addresses, links, anchors, fences, and inline expressions | [Links and markup](authoring/links-and-markup.md) |
|
|
18
|
+
| Images, icons, audio, provenance, and sidecars | [Assets](authoring/assets.md) |
|
|
19
|
+
| Canonical dates, calendars, eras, and conversions | [Dates and calendars](authoring/dates-and-calendars.md) |
|
|
20
|
+
| All accepted note types, subtypes, and fields | [Note type reference](reference/note-types.md) |
|
|
21
|
+
| Exact format details and system mappings | [Detailed reference](reference/format-details.md) |
|
|
22
|
+
| Optional readability and wording suggestions | [Prose analysis](commands.md#package-build-prose-lint-path) |
|
|
4105
23
|
|
|
4106
|
-
|
|
4107
|
-
| --------------- | ----------- | ------------------------------------------------------ |
|
|
4108
|
-
| `contents` | `Address[]` | The documents the Adventure holds; `[]` when unstated. |
|
|
24
|
+
## The shared note
|
|
4109
25
|
|
|
4110
|
-
|
|
4111
|
-
---
|
|
4112
|
-
type: bundle
|
|
4113
|
-
shortcode: hegovynvale
|
|
4114
|
-
name:
|
|
4115
|
-
full: The Hegóvyn Vale
|
|
4116
|
-
data:
|
|
4117
|
-
contents:
|
|
4118
|
-
- map-hegovynvale
|
|
4119
|
-
- miscgear-bowlcer
|
|
4120
|
-
- being-aurochs
|
|
4121
|
-
---
|
|
4122
|
-
Prose describing what the bundle is for.
|
|
4123
|
-
```
|
|
4124
|
-
|
|
4125
|
-
An `Adventure` carries **copies** of what it holds, not references: importing one
|
|
4126
|
-
creates or updates each document in the world, after which they live
|
|
4127
|
-
independently. So a bundle is not a folder — a folder is a live grouping that
|
|
4128
|
-
persists in the pack.
|
|
26
|
+
A nonempty note body creates one system-agnostic Foundry JournalEntry. Content before the first H1 becomes an introduction page; every H1 starts another JournalEntryPage. A lower-level heading with an explicit `{#anchor}` starts a page too, so a link can address it. The same prose becomes a web page and can be selected for a book. The content index records the note and its frontmatter for queries and navigation.
|
|
4129
27
|
|
|
4130
|
-
|
|
4131
|
-
follows, so there is one answer and not two. A note that compiles into _two_
|
|
4132
|
-
documents — an item and the JournalEntry its prose became — puts the second in a
|
|
4133
|
-
bundle when the bundle names its `note` Address:
|
|
4134
|
-
`sohl-miscgear-bowlcer` is the Item, `miscgear-bowlcer` its description page.
|
|
28
|
+
A `:::caption {#anchor}` fence labels the next prose, code, table, or image block. Its anchor makes the block addressable; `[[#anchor|]]` displays the generated kind and number, such as **Figure 12**. Foundry gives the captioned block a JournalEntryPage. See [captions and numbered references](authoring/links-and-markup.md#captions-and-numbered-references) for the syntax.
|
|
4135
29
|
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
30
|
+
Body extensions use braces with space-separated `key=value` attributes:
|
|
31
|
+
`![[icon-harbor|Harbor]]{float=top-left size=medium}` and an SQL fence with
|
|
32
|
+
`{allow-empty=true}` are examples. Inline font glyphs use the package's icon
|
|
33
|
+
registry, as in `:icon warning:{size=lg}`; image icons use asset Addresses.
|
|
34
|
+
`:::info` and `:::warn` add labelled boxes within the prose. The
|
|
35
|
+
[links and markup guide](authoring/links-and-markup.md) gives the complete syntax.
|
|
36
|
+
Footnotes use `[^id]` references and `[^id]:` definitions, with numeric output
|
|
37
|
+
markers scoped to each note. Definition lists use a term followed by one or
|
|
38
|
+
more `: definition` lines. See [footnotes and definition lists](authoring/links-and-markup.md#footnotes-and-definition-lists)
|
|
39
|
+
for examples and placement in journals, web pages, and books.
|
|
4140
40
|
|
|
4141
|
-
|
|
4142
|
-
Foundry renders on the import card. A bundle is something you hand someone, so
|
|
4143
|
-
its prose belongs on the document itself — which is why, unlike an item, a
|
|
4144
|
-
bundle earns no separate documentation journal.
|
|
41
|
+
An inline `{{sql "SELECT COUNT(*) FROM notes"}}` expression inserts one scalar query result. Use `{{words (sql "SELECT COUNT(*) FROM notes")}}` for a count in running prose, or `{{digits (sql "SELECT COUNT(*) FROM entries")}}` for grouped numerals. The query must return exactly one row and one column with a nonempty value; a mismatch is a located build error. Prefix `\{{` to show the syntax literally. The [expression reference](reference/format-details.md#dates-and-calendars) describes the helpers and the available SQL relations.
|
|
4145
42
|
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
43
|
+
Add `gm` to `tags` for a GM-only note. Public site and book builds omit it;
|
|
44
|
+
Foundry compiles it only into private compendiums. An untagged note cannot link
|
|
45
|
+
to it. The [frontmatter guide](authoring/frontmatter.md#frontmatter-and-system-blocks)
|
|
46
|
+
explains routing for documents and their prose journals.
|
|
4150
47
|
|
|
4151
|
-
|
|
4152
|
-
compiles map notes: that pack is written by the scenes pass, and a companion is
|
|
4153
|
-
written by its parent pass rather than routed to. A repository that authors
|
|
4154
|
-
bundles declares an Adventure pack of its own, and one that declares none is
|
|
4155
|
-
told so by name.
|
|
48
|
+
A note can also produce a system document: an Actor, Item, Scene, or Macro according to its type and system blocks. The `sohl:` and `hm3:` blocks supply game-specific mechanics and overrides; they do not create separate prose. The JournalEntry is shared between systems.
|
|
4156
49
|
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
HM3 packs and the system-neutral ones, so a member that publishes no HM3
|
|
4162
|
-
document is **left out rather than failing** — and named, because an installer
|
|
4163
|
-
that quietly ships half its contents is worse than one that fails. A pack
|
|
4164
|
-
declaring no system scopes nothing away, and a member it cannot find is a dead
|
|
4165
|
-
address.
|
|
50
|
+
| Authored section | SoHL Actor | HM3 Actor |
|
|
51
|
+
| ---------------- | ------------------- | -------------------- |
|
|
52
|
+
| `{#appearance}` | `system.appearance` | `system.description` |
|
|
53
|
+
| `{#dossier}` | `system.dossier` | `system.biography` |
|
|
4166
54
|
|
|
4167
|
-
|
|
4168
|
-
hold — Item, Actor, JournalEntry, Macro and Scene. That ordering is derived from
|
|
4169
|
-
what the pass declares it reads, not from the order `packs:` happens to list, so
|
|
4170
|
-
an Adventure pack declared first still compiles last.
|
|
55
|
+
An Item's `{#description}` page is its readable description. The note Address `<package>-note-<type>-<shortcode>#description` resolves to a JournalEntryPage UUID. The build writes the same `@UUID[...]` pointer to SoHL Item `system.docHtml` and HM3 Item `system.description`. An Item note without that anchor uses its first page as the description. Other pages and anchors remain available for links.
|
|
4171
56
|
|
|
4172
|
-
|
|
57
|
+
## A complete minimal note
|
|
4173
58
|
|
|
4174
|
-
|
|
4175
|
-
this package compiled from bespoke configuration (`*-folders.yaml`, five files
|
|
4176
|
-
per tree) rather than from a note. Those files are retired; a pack that
|
|
4177
|
-
still names one is refused.
|
|
59
|
+
Place this at `assets/content/Places/Harbor.md`:
|
|
4178
60
|
|
|
4179
|
-
```
|
|
61
|
+
```markdown
|
|
4180
62
|
---
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
data:
|
|
4186
|
-
parent: possessionsmiscgear
|
|
4187
|
-
color: "#7a4b2a"
|
|
63
|
+
shortcode: harbor
|
|
64
|
+
name: { full: Harbor }
|
|
65
|
+
type: lore
|
|
66
|
+
subType: concept
|
|
4188
67
|
---
|
|
4189
|
-
```
|
|
4190
68
|
|
|
4191
|
-
|
|
4192
|
-
| --------------- | ----------------------------------------- | ------------------------------------------------------------------------------ |
|
|
4193
|
-
| `parent` | `Address`, or a map of them keyed by pack | The folder this one sits in — one address, or one per pack. Unset at the root. |
|
|
4194
|
-
| `color` | `"#RRGGBB"`, a string | The folder's colour. Unset for Foundry's default. |
|
|
69
|
+
# Harbor
|
|
4195
70
|
|
|
4196
|
-
A
|
|
4197
|
-
`Folder` is a core Foundry document like a `JournalEntry` or a `Scene`, not a
|
|
4198
|
-
system's. Its shortcode is [an address segment](#the-canonical-address) like
|
|
4199
|
-
every other, so it is strictly lowercase alphanumeric: `possessionscooking`,
|
|
4200
|
-
never `possessions-cooking`, which would read as two segments and resolve to
|
|
4201
|
-
nothing.
|
|
4202
|
-
|
|
4203
|
-
**`color` must be quoted**, and YAML gives no third option: `color: #7a4b2a`
|
|
4204
|
-
parses as `null` (a `#` after a space opens a comment) and `color: 000000` parses
|
|
4205
|
-
as the number `0`. Write `"#RRGGBB"` to retain the string.
|
|
4206
|
-
|
|
4207
|
-
`parent` is an address, so a dangling one is an ordinary dead-address finding
|
|
4208
|
-
rather than a special-cased `Unknown folder id`, and a cycle is refused. Both are
|
|
4209
|
-
reported when the tree is read, not when something happens to reference the
|
|
4210
|
-
folder that carries them.
|
|
4211
|
-
|
|
4212
|
-
**`parent` may be a map keyed by pack**, because a folder's _identity_ is one
|
|
4213
|
-
thing and its _hierarchy_ is another. The same folder is deliberately filed
|
|
4214
|
-
under different parents in different packs: an item compendium is browsed by
|
|
4215
|
-
kind, a journal compendium is read by subject.
|
|
4216
|
-
|
|
4217
|
-
```yaml
|
|
4218
|
-
data:
|
|
4219
|
-
parent:
|
|
4220
|
-
default: ~ # at the root of the items pack
|
|
4221
|
-
journals: descriptions # under Rules/Descriptions in the journals pack
|
|
71
|
+
A sheltered place where ships load and unload.
|
|
4222
72
|
```
|
|
4223
73
|
|
|
4224
|
-
|
|
4225
|
-
_at the root there_, which is a different statement from saying nothing. A plain
|
|
4226
|
-
scalar — the everyday spelling, and the right one wherever the hierarchies agree
|
|
4227
|
-
— is exactly `{ default: <value> }`. The folder keeps **one id** across every
|
|
4228
|
-
pack it materialises in, which is what files a documentation journal beside the
|
|
4229
|
-
item it describes; only its parent differs.
|
|
4230
|
-
|
|
4231
|
-
Every key but `default` names a **pack the package declares**, and one that names
|
|
4232
|
-
none is a finding of its own. It cannot be a harmless surplus: the compile asks
|
|
4233
|
-
the map for the pack it is writing and falls back to `default` when there is no
|
|
4234
|
-
such key, so a mistyped `journal:` files the folder wherever the default puts it
|
|
4235
|
-
— exactly the hierarchy the key was written to override, and silently.
|
|
4236
|
-
|
|
4237
|
-
**A folder note carries no prose.** It is structure, not content, so it produces
|
|
4238
|
-
no documentation journal and takes no part in `docEntryTypes`.
|
|
4239
|
-
|
|
4240
|
-
**It declares no pack.** Which packs a folder materialises in is derived from
|
|
4241
|
-
[what references it](#the-compendium-folder), and its ancestors materialise with
|
|
4242
|
-
it; a folder nothing references materialises nowhere.
|
|
4243
|
-
|
|
4244
|
-
**Its Foundry `_id` is derived from its address**, stable across runs, so a new
|
|
4245
|
-
folder needs no invented id. An authored `id` is kept where one is present —
|
|
4246
|
-
which is what lets a tree sweep its folder YAML into notes without a world that
|
|
4247
|
-
already holds those folders losing them. Two folders claiming one id is a build
|
|
4248
|
-
error.
|
|
74
|
+
The note's readable Address is `<package>-note-lore-harbor`. In prose, link to it with `[[lore-harbor|Harbor]]`. The package segment comes from `package-build.config.yaml`; a regular wikilink defaults to the readable `note` system. An image embed such as `![[icon-harbor|Harbor]]` defaults to `none` instead.
|
|
4249
75
|
|
|
4250
|
-
The
|
|
4251
|
-
the namespace is **`folder`**, not the `document` one [every other note hashes
|
|
4252
|
-
under](#the-document-id): a folder and an item sharing a shortcode would
|
|
4253
|
-
otherwise derive the same id, and Foundry keys the two in separate collections
|
|
4254
|
-
so neither would complain. The content index publishes this value, so a consumer
|
|
4255
|
-
reads a folder's id rather than recomputing one.
|
|
76
|
+
The [first-note guide](authoring/first-note.md) shows the build commands and how to add a system document. Use the [command reference](commands.md) for exact flags and the [diagnostics guide](diagnostics.md) for located errors.
|