@heroiclands/package-build 23.12.5 → 23.12.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/assets/styles/content-base.css +25 -0
  3. package/bin/content-commands.mjs +264 -3
  4. package/bin/package-build.mjs +25 -0
  5. package/content-config.mjs +98 -0
  6. package/docs/api.md +18 -14
  7. package/docs/authoring/dates-and-calendars.md +29 -1
  8. package/docs/authoring/links-and-markup.md +442 -50
  9. package/docs/commands.md +127 -0
  10. package/docs/configuration.md +90 -3
  11. package/docs/content-format.md +3 -1
  12. package/docs/guides/book.md +11 -0
  13. package/docs/guides/module-project.md +17 -1
  14. package/docs/guides/site.md +19 -0
  15. package/docs/reference/format-details.md +434 -23
  16. package/docs/reference/note-types.md +45 -21
  17. package/engine/base-compiler.mjs +2 -0
  18. package/engine/bundles.mjs +7 -2
  19. package/engine/compile-corpus.mjs +3 -1
  20. package/engine/content-charset.mjs +21 -2
  21. package/engine/content-format.yaml +1 -2
  22. package/engine/content-index.mjs +33 -2
  23. package/engine/content-links.mjs +3 -0
  24. package/engine/content-poetry.mjs +22 -4
  25. package/engine/content-scripts.mjs +1219 -0
  26. package/engine/content-session.mjs +2 -0
  27. package/engine/data-keys.mjs +5 -1
  28. package/engine/dictionary-layout.mjs +18 -12
  29. package/engine/dictionary-rows.mjs +11 -2
  30. package/engine/event-rows.mjs +63 -18
  31. package/engine/event-views.mjs +28 -15
  32. package/engine/helpers.mjs +8 -2
  33. package/engine/index-records.mjs +2 -0
  34. package/engine/infobox.mjs +4 -8
  35. package/engine/journals.mjs +14 -3
  36. package/engine/language-data.mjs +137 -1
  37. package/engine/language-tag.mjs +103 -0
  38. package/engine/language-terms.mjs +16 -4
  39. package/engine/map-lexicon.mjs +762 -0
  40. package/engine/markdown-expressions.mjs +72 -17
  41. package/engine/markup-validation.mjs +17 -3
  42. package/engine/names.mjs +1 -1
  43. package/engine/note-calendars.mjs +314 -0
  44. package/engine/note-vocabulary.mjs +124 -16
  45. package/engine/pdf-build.mjs +28 -10
  46. package/engine/pdf-render.mjs +116 -11
  47. package/engine/reckoning-markers.mjs +63 -3
  48. package/engine/run-rows.mjs +119 -0
  49. package/engine/run-words.mjs +266 -0
  50. package/engine/scenes.mjs +1 -0
  51. package/engine/script-font-delivery.mjs +321 -0
  52. package/engine/script-fonts.mjs +172 -0
  53. package/engine/site-build.mjs +10 -3
  54. package/engine/site-config.mjs +5 -0
  55. package/engine/site-root.mjs +19 -5
  56. package/engine/sql-tables.mjs +120 -4
  57. package/engine/text-maps.mjs +607 -0
  58. package/engine/verb-pattern.mjs +99 -0
  59. package/engine/word-pattern.mjs +27 -0
  60. package/engine/word-rows.mjs +8 -2
  61. package/engine/word-terms.mjs +44 -0
  62. package/hugo-theme/layouts/_default/baseof.html +3 -0
  63. package/hugo-theme/static/css/style.css +10 -0
  64. package/manifest.mjs +9 -1
  65. package/package.json +1 -1
  66. package/types/content-config.d.mts +38 -0
  67. package/types/engine/compile-corpus.d.mts +2 -1
  68. package/types/engine/content-index.d.mts +17 -3
  69. package/types/engine/content-poetry.d.mts +7 -0
  70. package/types/engine/content-scripts.d.mts +226 -0
  71. package/types/engine/data-keys.d.mts +2 -2
  72. package/types/engine/dictionary-rows.d.mts +15 -0
  73. package/types/engine/event-rows.d.mts +16 -1
  74. package/types/engine/event-views.d.mts +2 -1
  75. package/types/engine/helpers.d.mts +5 -1
  76. package/types/engine/journals.d.mts +7 -2
  77. package/types/engine/language-data.d.mts +2 -0
  78. package/types/engine/language-tag.d.mts +8 -0
  79. package/types/engine/language-terms.d.mts +2 -1
  80. package/types/engine/map-lexicon.d.mts +274 -0
  81. package/types/engine/markup-validation.d.mts +7 -2
  82. package/types/engine/note-calendars.d.mts +60 -0
  83. package/types/engine/pdf-build.d.mts +26 -3
  84. package/types/engine/pdf-render.d.mts +17 -2
  85. package/types/engine/reckoning-markers.d.mts +32 -1
  86. package/types/engine/run-rows.d.mts +89 -0
  87. package/types/engine/run-words.d.mts +86 -0
  88. package/types/engine/script-font-delivery.d.mts +134 -0
  89. package/types/engine/script-fonts.d.mts +168 -0
  90. package/types/engine/site-build.d.mts +2 -1
  91. package/types/engine/site-root.d.mts +11 -3
  92. package/types/engine/text-maps.d.mts +175 -0
  93. package/types/engine/verb-pattern.d.mts +65 -0
  94. package/types/engine/word-pattern.d.mts +13 -0
  95. package/types/engine/word-terms.d.mts +6 -0
@@ -432,17 +432,55 @@ A named date uses `datefrom <calendar> <date>`, where the final text follows the
432
432
 
433
433
  `package-build datefrom <calendar> <date>` reads the default calendar pattern and prints a canonical day. `package-build dateto <calendar> <canonical-date>` prints the day in the covering era; `--format <name>` chooses another named output pattern. The [authoring guide](../authoring/dates-and-calendars.md) gives a complete example.
434
434
 
435
+ ##### A note's calendar
436
+
437
+ A note's dates print in one calendar, the first of:
438
+
439
+ 1. its own `data.calendar`, which every note type accepts, and which always
440
+ wins;
441
+ 2. its culture's calendar: the culture note its `data.culture` names, resolved
442
+ by these same rules — in effect that culture's own `data.calendar`;
443
+ 3. for a `place`, its parents' calendar: each note its `data.parents` names,
444
+ resolved by these same rules;
445
+ 4. the package's [`defaultCalendar`](../configuration.md#defaultcalendar);
446
+ 5. otherwise none, and a date prints as it was written.
447
+
448
+ Steps 2 and 3 take only a calendar that some note states. A culture or a
449
+ parent that itself falls through to `defaultCalendar` contributes nothing, so a
450
+ place whose parents all fall through takes the default. Parents that resolve to
451
+ **two or more distinct calendars** are a build error at the place's
452
+ `data.parents`, naming the place and each calendar with the parent it came
453
+ through:
454
+
455
+ ```text
456
+ Places/Edge.md:6:3: error: place edge: data.parents resolve to different calendars (thalorna-note-lore-commoncal via north, thalorna-note-lore-vrcal via south); state data.calendar
457
+ ```
458
+
459
+ Stating `data.calendar` on the place settles it. A cycle through
460
+ `data.parents` or `data.culture` contributes nothing, and the chain goes on to
461
+ `defaultCalendar`. A culture or parent in another package contributes the
462
+ `data.calendar` its published entry states, and nothing else.
463
+
464
+ The calendar decides what a reader sees wherever the note prints a date: the
465
+ infobox's `born` and `died`, `dateformat` with no calendar named, the `events`
466
+ relation's `whenText`, and every date in the [event views](#event-views) the
467
+ note's page shows.
468
+
435
469
  Markdown prose accepts inline `{{...}}` expressions. A property path reads
436
470
  the note's frontmatter, and `dateformat` converts a date to the reckoning used by a
437
- specified calendar. The first argument names a calendar note by shortcode or
438
- Address, or one of its eras by `<calendar>.<era>`. Use a full Address when a
439
- short name is ambiguous. The second argument is a canonical date or a
440
- frontmatter property holding a date. An optional third argument names an output
441
- format from the calendar note. An explicit era that does not cover the date is an error.
471
+ calendar. The first argument names a calendar note by shortcode or Address, or
472
+ one of its eras by `<calendar>.<era>`; left out, the calendar is
473
+ [the note's own](#a-notes-calendar), and a note with none prints the date as
474
+ written. Use a full Address when a short name is ambiguous. The date is a
475
+ canonical date or a frontmatter property holding a date. An optional last
476
+ argument names an output format from the calendar note. With two arguments,
477
+ the first is the calendar unless it is a date that names no calendar, so
478
+ `{{dateformat data.born "long"}}` prints the note's date in its own calendar's
479
+ `long` format. An explicit era that does not cover the date is an error.
442
480
 
443
481
  ```markdown
444
482
  {{name.full}} was born on {{dateformat "vrcal" data.born}}.
445
- {{dateformat data.calendar data.born}}
483
+ {{dateformat data.born}}
446
484
  The event fell on {{dateformat "vrcal" "300.25"}}.
447
485
  {{and (gt 3 5) (lt 4 2)}}
448
486
  {{sql "SELECT COUNT(*) FROM notes WHERE type = 'being'"}}
@@ -870,6 +908,7 @@ noticed:
870
908
  | `seeAlso` | list of `{ address, gloss? }` | Deliberate cross-references in reading order, shown on the site, in journals and in books. |
871
909
  | `seeAlso[].address` | a note `Address` | Required; the whole note to read next. |
872
910
  | `seeAlso[].gloss` | `string` | An optional short phrase saying why to follow the reference. |
911
+ | `calendar` | `Address` | The calendar note this note's dates print in — see [a note's calendar](#a-notes-calendar). |
873
912
  | `id` | `string` | A pinned document identity. |
874
913
  | `pack` | `string` | The shared compendium route. |
875
914
  | `packFolder` | `Address` or pack map | The shared compendium folder Address. |
@@ -1937,6 +1976,249 @@ subtypes conflict. One package advisory contains both groups and is located
1937
1976
  at the earliest reported use. Advisory findings preserve a successful lint
1938
1977
  exit status. Supplied-value errors prevent publication and compilation.
1939
1978
 
1979
+ #### Text marked with a language, a font or a map
1980
+
1981
+ `lang=`, `font=` and `map=` on a span, a generic div or a poetry fence are read
1982
+ by the build and never written through as ordinary attributes. Each is
1983
+ optional, and a run inside a div or a fence inherits the keys the div or fence
1984
+ states, its own keys overriding them. On a named block the three keys are
1985
+ errors.
1986
+
1987
+ - **`lang=`** is a name declared in [`languages`](../configuration.md#languages),
1988
+ and otherwise a BCP 47 tag checked for its form alone. It gives the language
1989
+ tag and changes nothing else.
1990
+ - **`font=`** is a face of the installed `@heroiclands/fonts`, named by its
1991
+ `fonts.json` class without `hl-`, or `mono`, `serif` or `sans`.
1992
+ - **`map=`** is the `data.map.name` of a [mapping note](#mapping-notes). The
1993
+ text is rewritten by the map before it is set; without `map=` it is set as
1994
+ typed. With `font=`, the face is one the map lists in `fonts`, and its own
1995
+ rows come first.
1996
+ - **`lang=` with `map=`** reads the language's lexicon first, when
1997
+ [`languages.<name>.note`](../configuration.md#languages) names its language
1998
+ note: see [a map reads the dictionary first](#a-map-reads-the-dictionary-first).
1999
+
2000
+ **A face draws the code points its `fonts.json` entry lists in `codepoints`,
2001
+ and whitespace.** After the map, any other character in a run set in a face is
2002
+ an error, located at the source character it came from; through a map, at the
2003
+ start of the matched `from`. Each character is reported once per run. The
2004
+ generic families are not checked. `input`, `layout` and `encoding` in
2005
+ `fonts.json` are not read.
2006
+
2007
+ Text marked with `font=` or `map=`, or with a declared language, is plain: a
2008
+ span or a verse holds no emphasis, link, image, footnote, wikilink, code or
2009
+ HTML, and no span but a marked one inside a div or a fence; a div holds
2010
+ paragraphs and line breaks only. The text is read as written, so `[`, `]`, `"`,
2011
+ `<<`, `>>`, `!` and `@#$%^&` are characters, and a bracket pair is markup only
2012
+ when `(`, `{` or a second `[` makes it a link, a span or a wikilink. Text marked
2013
+ with a real language's tag alone keeps its Markdown. See
2014
+ [marking language, font and mapping](../authoring/links-and-markup.md#marking-language-font-and-mapping).
2015
+
2016
+ Each own-note metadata record carries `runs` in source order, an empty array
2017
+ when the body marks nothing. Every entry has exactly `kind` (`span`, `block` or
2018
+ `poetry`), `lang`, `tag`, `font`, `map`, `line` and `column`: `lang`, `font` and
2019
+ `map` as written, its own or inherited, `tag` the BCP 47 tag `lang` resolves
2020
+ to, each `null` when absent, and the position of the span's `[`, the div's
2021
+ `:::` or the fence's opening. Every value is a string or a number and none is a
2022
+ key, so tags that differ only in case are separate values. This is derived
2023
+ metadata, not frontmatter. The [`runs` relation](#content-tables) flattens
2024
+ these entries with their containing NOTE Address.
2025
+
2026
+ #### Mapping notes
2027
+
2028
+ A `doc` note's `data.map` is a mapping: rows that rewrite sequences of
2029
+ characters into characters, which a run marked `map=` with its `name` is
2030
+ rewritten by before it is set. A mapping belongs to a script, and serves every
2031
+ language written in it.
2032
+
2033
+ ```yaml
2034
+ data:
2035
+ map:
2036
+ name: semaron
2037
+ vowels: "aeiouAEIOU"
2038
+ fonts:
2039
+ - font: semaron-kuriale
2040
+ - font: semaron-carved
2041
+ rows:
2042
+ - [th, "\U000F0251"]
2043
+ rows:
2044
+ - [a, "\U000F0230"]
2045
+ - [th, "\U000F0250"]
2046
+ - { from: y, to: "\U000F0246", before: consonant }
2047
+ ```
2048
+
2049
+ | Key | Shape | Meaning |
2050
+ | -------- | ---------------------------------------------------- | ------------------------------------------------------------------------------- |
2051
+ | `name` | string, required | What `map=` names it by: lowercase letters, digits and `-`, unique in the index |
2052
+ | `vowels` | string | The characters `before: vowel` reads as vowels |
2053
+ | `fonts` | list of `{ font, rows? }`, required, at least one | The faces the map is drawn in, by slug, each with any rows of its own |
2054
+ | `rows` | list of `[from, to]` or `{ from, to, before?, at? }` | The shared rewrites, in authored order |
2055
+
2056
+ **A face's own rows come first.** A run in a face tries that face's `rows`,
2057
+ the longest match among them, and only where none matches the shared `rows`; a
2058
+ face listed with no `rows` uses the shared rows alone, and a run with no
2059
+ `font=` uses them too. Rows take the same shapes and conditions in both places,
2060
+ and `vowels` serves them all. A run whose `font=` is not one of the map's
2061
+ `fonts` is an error at its `map=`:
2062
+
2063
+ > `` `thuravark-carved` is not a font of the map `semaron`; it is for `semaron-kuriale` and `semaron-carved` ``
2064
+
2065
+ A row's `from` is one or more characters and its `to` any number, none
2066
+ included, each a whole code point; a code point above U+FFFF is written in YAML
2067
+ with the eight-digit escape, as `"\U000F0200"`. The longest match wins; among rows of one length, a row with a
2068
+ condition that holds comes before a row with none, and otherwise the earlier
2069
+ row wins. A row whose condition fails is skipped, and a character no row
2070
+ matches passes through unchanged.
2071
+
2072
+ | Condition | Holds when |
2073
+ | ------------------- | ---------------------------------------------------------------------------- |
2074
+ | `before: vowel` | the next character is one of `vowels` |
2075
+ | `before: consonant` | the next character is any other letter |
2076
+ | `at: start` | the match is the run's first character, or follows whitespace or punctuation |
2077
+ | `at: end` | the match ends the run, or is followed by whitespace or punctuation |
2078
+
2079
+ Neither `before` condition holds at the end of the text. The note's body is
2080
+ for a reader: nothing reads it. Each row is a row of the
2081
+ [`maps` relation](#content-tables), never a struct key, so `A` and `a` are two
2082
+ rows; a face's own rows carry its slug in `font`.
2083
+
2084
+ The data-key check holds every key of `data.map` and of each row to the table
2085
+ under [`type: doc`](#type-doc), at its own line and column:
2086
+
2087
+ > `` `data.map.rows[2]` should be `[from, to]` or `{ from, to, before?, at? }`, but reads "a" ``
2088
+
2089
+ > `` `data.map.rows[3]` must state `to` — a string, which may be empty ``
2090
+
2091
+ > `` "when" is not a key of `data.map.rows[5]`; it takes only `from`, `to`, `before`, `at` ``
2092
+
2093
+ > `` `data.map.rows[6].before` should be `vowel` or `consonant`, but reads "liquid" ``
2094
+
2095
+ The map's own check reports the rest, each at its key:
2096
+
2097
+ > `` `data.map.name` is `Vyl_Semaron`; a map's name is lowercase letters, digits and `-` ``
2098
+
2099
+ > `` `data.map.name` is `vyl-semaron`, which Lore/Other.md also declares; a map's name is unique ``
2100
+
2101
+ > `` `data.map.rows[0]` is a list, and a row written as a list is two strings, `[from, to]` ``
2102
+
2103
+ > `` `data.map.rows[1]` has an empty `from`; a row matches one or more characters ``
2104
+
2105
+ > `` `data.map.rows[4]` has `before: consonant`, and the map states no `vowels` to read it by ``
2106
+
2107
+ > `` `data.map.fonts` is empty; a map lists each face it is drawn in ``
2108
+
2109
+ > `` `data.map.fonts[0].font` is `mono`, a generic family; a map lists the faces it is drawn in ``
2110
+
2111
+ The lint checks the listed faces against the installed `@heroiclands/fonts`,
2112
+ each at the entry or the row at fault. Every listed face draws every code point
2113
+ its own rows write, every code point the shared rows it can reach write (a
2114
+ shared row is out of a face's reach where a plain row of the face's own matches
2115
+ the start of its `from`), and every code point of the native forms that apply
2116
+ to it:
2117
+
2118
+ > `` `semaron-kurale` is not a font: `data.map.fonts[1].font` names an installed face; did you mean `semaron-kuriale`? ``
2119
+
2120
+ > `` `data.map.fonts[2]` lists the font `semaron-kuriale` a second time; the first is `data.map.fonts[0]` ``
2121
+
2122
+ > `` `data.map.fonts[1].rows[0]` writes U+F0251, which the font `semaron-carved` does not draw ``
2123
+
2124
+ > `` `data.map.rows[5]` writes U+F0239, which the font `semaron-kuriale`, listed in `data.map.fonts`, does not draw ``
2125
+
2126
+ A row with a fault is left out of the map; the rest apply.
2127
+
2128
+ #### A map reads the dictionary first
2129
+
2130
+ The sound rules belong to the script, and the exceptions to the language. A
2131
+ word of a [language's words](#a-languages-words) states a `native` form for a
2132
+ map only where the map's rows would spell it differently:
2133
+
2134
+ ```yaml
2135
+ words:
2136
+ - form: zaron
2137
+ class: n
2138
+ field: "Law, the court and the written act"
2139
+ gloss: an oath
2140
+ native:
2141
+ - { map: semaron, text: "\U000F024F\U000F0230\U000F023F\U000F023C" }
2142
+ - { map: semaron, font: semaron-carved, text: "\U000F024F\U000F0230\U000F023C" }
2143
+ ```
2144
+
2145
+ | Key | Shape | Meaning |
2146
+ | ------ | ---------------- | -------------------------------------------------------------------------------------------- |
2147
+ | `map` | string, required | The `data.map.name` the native form is written for |
2148
+ | `font` | string | The one face it is for; unstated, every face the map lists, and a face's own form wins there |
2149
+ | `text` | string, required | The word as the script writes it, in the map's characters |
2150
+
2151
+ A run marked `lang=` with a declared language whose
2152
+ [`note`](../configuration.md#languages) names its language note, and `map=`,
2153
+ is written word by word:
2154
+
2155
+ 1. **A word** is a maximal run of letters, combining marks and digits, an
2156
+ apostrophe (`'` or `’`), a hyphen or an en dash joining two such runs into
2157
+ one word. Everything between words is spelled by the rows.
2158
+ 2. **The word is looked up** as written, and then in lowercase. A word found is
2159
+ written by its native form for the run's map in the run's face, else by its
2160
+ native form for the map with no `font`, as one piece.
2161
+ 3. **A word found only in lowercase and written with a capital** takes its
2162
+ capital from the rows: the rows map the word's first sound as written and in
2163
+ lowercase, and where the native form begins with what the lowercase gives,
2164
+ that beginning is replaced by what the capital gives. A native form that
2165
+ does not begin as the rows begin the word cannot be capitalised by them, and
2166
+ the word is an error at its place in the run. The remedy is to state the
2167
+ capitalised word as a word of its own with its native form, which the
2168
+ lookup as written finds first.
2169
+ 4. **Every other word is spelled by the rows**, its derived forms included: a
2170
+ plural or a verb ending of an exception word is a different word, and the
2171
+ rows spell it.
2172
+
2173
+ A run with `map=` and no `lang=`, a `lang=` that is a bare BCP 47 tag, and a
2174
+ declared language with no `note` are spelled by the rows alone. A character the
2175
+ face does not draw inside a word written by its native form is an error at the
2176
+ word's start:
2177
+
2178
+ > `` `Orlan` is capitalised, and the native form the lexicon of `vyl` states for `orlan` under the map `semaron` does not begin as the rows spell its first letter, so the rows cannot capitalise it; state `Orlan` as a word of its own with its native form ``
2179
+
2180
+ > `` `zaron` is written U+F024F by the lexicon of `vyl` for the map `semaron`, which the font `semaron-kuriale` does not draw ``
2181
+
2182
+ **Every word of such a run is a word of the language**, case folded: a
2183
+ `data.words[].form`, a word of a name the language note's own `data.register`
2184
+ lists under a `tongue` other than `translation`, or a form one rule derives from a lexicon form —
2185
+ the `data.grammar.plural` plural of a noun or its own `plural`, a verb's form
2186
+ with one ending of `data.grammar.verbs.persons` (in place of a closing
2187
+ `data.grammar.verbs.replaces` where one is stated), or a suffix of `data.affixes`
2188
+ stating `replaces` (`vowel` takes the place of a closing `a`, `e`, `i`, `o` or
2189
+ `u`, accented or not; letters take the place of those letters; a stem closing
2190
+ otherwise takes the suffix whole). A compound not known whole is checked part by
2191
+ part at its hyphens and en dashes. Any other word is an error at its first
2192
+ character, once per run; see
2193
+ [a mapped run's words](../authoring/links-and-markup.md#a-mapped-runs-words):
2194
+
2195
+ > `` `thiruthrimar` is not a word of the `vyl` lexicon, a registered name, or a regular form of one; coin it in the lexicon (or register the name) first ``
2196
+
2197
+ Each native form is a row of the [`natives` relation](#content-tables), never a
2198
+ struct key, so forms that differ only in case are two rows. The data-key check
2199
+ holds each entry to its three keys; the lint checks the rest, each at the entry
2200
+ or the key at fault:
2201
+
2202
+ > `` `data.words[0].native[0].map` is `semron`, which is not a map: a native form names the `data.map.name` of a mapping note; did you mean `semaron`? ``
2203
+
2204
+ > `` `data.words[0].native[1].font` is `thuravark-carved`, which is not a font of the map `semaron`; it is for `semaron-kuriale` and `semaron-carved` ``
2205
+
2206
+ > `` `data.words[0].native[2]` states a second native form for the map `semaron`; the first is `data.words[0].native[0]` ``
2207
+
2208
+ > `` `data.words[0].native[0]` states what the rows already give: the map `semaron` spells `zaron` so in the font `semaron-kuriale`; a native form is stated only where the rows spell the word differently ``
2209
+
2210
+ > `` `data.words[0].native[0]` holds U+F024F, which the font `semaron-kuriale`, listed in the map `semaron`, does not draw ``
2211
+
2212
+ The "states what the rows already give" finding compares a native form stated
2213
+ for a face with that face's rows, and one stated for no face with every listed
2214
+ face's rows: it is redundant only where every face's rows give it. This is what
2215
+ keeps a lexicon to its exceptions.
2216
+
2217
+ `package-build transliterate` maps a passage, and `package-build lexicon` lists
2218
+ every word with its native form, through the same code as the build: see
2219
+ [`transliterate`](../commands.md#package-build-transliterate-map-text) and
2220
+ [`lexicon`](../commands.md#package-build-lexicon-language).
2221
+
1940
2222
  ### Assets are types
1941
2223
 
1942
2224
  `icon`, `image` and `audio` are types in the type vocabulary, and an address
@@ -2972,14 +3254,22 @@ ORDER BY whenSort
2972
3254
  ```
2973
3255
  ````
2974
3256
 
2975
- | Column | What it holds |
2976
- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2977
- | `note` | The canonical Address of the note the event is written on. |
2978
- | `address` | The event's own Address: `note#id` where the event has an `id`, the note's Address alone where it has none. |
2979
- | `id`, `kind`, `depth`, … | Every key of an event, each its own column under its own name — `id`, `kind`, `depth`, `when`, `until`, `recurs`, `summary`, `standing`, `names`, `where`, `who`, `follows`, `accounts`, `unresolved`, `sources`, `stated`. |
2980
- | `whenSort`, `untilSort` | The date's position on the canonical axis: the canonical year, with a day as its fraction of the year where the calendar states the year's length. `ORDER BY whenSort` is date order. |
2981
- | `whenText`, `untilText` | The date as a reader sees it, printed as `dateformat` prints it in the calendar the owning note names in `data.calendar`, else the one the world note names, else in the era the date itself names; `~` marks an approximate date. A date that does not resolve keeps the text its note wrote. |
2982
- | `whenYear`, `untilYear` | The canonical year alone, for a filter such as `whenYear BETWEEN 200 AND 299`. |
3257
+ | Column | What it holds |
3258
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
3259
+ | `note` | The canonical Address of the note the event is written on. |
3260
+ | `address` | The event's own Address: `note#id` where the event has an `id`, the note's Address alone where it has none. |
3261
+ | `id`, `kind`, `depth`, … | Every key of an event, each its own column under its own name — `id`, `kind`, `depth`, `when`, `until`, `recurs`, `summary`, `standing`, `names`, `where`, `who`, `follows`, `accounts`, `unresolved`, `sources`, `stated`. |
3262
+ | `whenSort`, `untilSort` | The date's position on the canonical axis: the canonical year, with a day as its fraction of the year where the calendar states the year's length. `ORDER BY whenSort` is date order. |
3263
+ | `whenText`, `untilText` | The date as a reader of the owning note sees it, printed as `dateformat` prints it in [the owning note's calendar](#a-notes-calendar), else in the era the date itself names; `~` marks an approximate date. A date that does not resolve keeps the text its note wrote. |
3264
+ | `whenIn`, `untilIn` | The date in every calendar: a struct keyed by each calendar note's canonical Address, holding the date as `dateformat` prints it there. `whenIn['thalorna-note-lore-commoncal']` reads it in that calendar; a date that calendar cannot print reads as in `whenText`. |
3265
+ | `whenYear`, `untilYear` | The canonical year alone, for a filter such as `whenYear BETWEEN 200 AND 299`. |
3266
+
3267
+ A fence chooses which calendar its dates print in. `whenText` prints each
3268
+ row in the calendar of the note that owns the event, so a table gathering the
3269
+ events of notes with different calendars reads in more than one count;
3270
+ `whenIn['<calendar Address>']` prints every row in one. The generated [event
3271
+ views](#event-views) do the second, in the calendar of the page showing the
3272
+ view.
2983
3273
 
2984
3274
  `when` and `until` read as the text the note wrote, `~` included. The sort
2985
3275
  keys and years are positions on the canonical axis, which counts a year `0`
@@ -3102,6 +3392,69 @@ metadata supplies `names` in that package's SQL schema. Public queries exclude
3102
3392
  GM-only owners; all/native contexts retain them. Only authored occurrences
3103
3393
  contribute rows, so generated queries and sections add no further names.
3104
3394
 
3395
+ **`runs`** is one row per span, generic div and poetry fence an authored note
3396
+ body marks with `lang=`, `font=` or `map=`. Each row has these columns,
3397
+ including on an empty relation:
3398
+
3399
+ | Column | SQL type | Meaning |
3400
+ | -------- | --------- | -------------------------------------------------------------------------------------------- |
3401
+ | `owner` | `VARCHAR` | Containing authored page's canonical NOTE Address, or `NULL` when the record has no Address. |
3402
+ | `kind` | `VARCHAR` | `span`, `block` or `poetry`. |
3403
+ | `lang` | `VARCHAR` | The language as written, its own or inherited, or `NULL`. |
3404
+ | `tag` | `VARCHAR` | The BCP 47 tag `lang` resolves to, or `NULL` when it resolves to none. |
3405
+ | `font` | `VARCHAR` | The font as written, its own or inherited, or `NULL`. |
3406
+ | `map` | `VARCHAR` | The map as written, its own or inherited, or `NULL`. |
3407
+ | `line` | `BIGINT` | Original 1-based source line of the span's `[`, the div's `:::` or the fence's opening. |
3408
+ | `column` | `BIGINT` | Original 1-based UTF-16 source column there. |
3409
+
3410
+ ```sql
3411
+ SELECT owner, kind, tag, line, "column"
3412
+ FROM runs
3413
+ WHERE font = 'semaron-kuriale'
3414
+ ORDER BY owner, line, "column"
3415
+ ```
3416
+
3417
+ **`maps`** is one row per well-formed row of a [mapping note](#mapping-notes)'s
3418
+ `data.map.rows` and of each face's own `rows`. Each row has these columns,
3419
+ including on an empty relation:
3420
+
3421
+ | Column | SQL type | Meaning |
3422
+ | -------- | --------- | ----------------------------------------------------------- |
3423
+ | `owner` | `VARCHAR` | The mapping note's canonical NOTE Address. |
3424
+ | `name` | `VARCHAR` | The map's `name`. |
3425
+ | `font` | `VARCHAR` | The face whose own row it is; `NULL` for a shared row. |
3426
+ | `row` | `BIGINT` | The row's 1-based place in its `rows`. |
3427
+ | `from` | `VARCHAR` | The characters it matches. |
3428
+ | `to` | `VARCHAR` | What it writes; empty for a row that drops what it matches. |
3429
+ | `before` | `VARCHAR` | `vowel`, `consonant` or `NULL`. |
3430
+ | `at` | `VARCHAR` | `start`, `end` or `NULL`. |
3431
+
3432
+ `from`, `to` and `at` are SQL keywords, so a query quotes them:
3433
+
3434
+ ```sql
3435
+ SELECT "row", "from", "to", before FROM maps WHERE name = 'vyl-semaron' ORDER BY "row"
3436
+ ```
3437
+
3438
+ **`natives`** is one row per entry of a word's `native` on every
3439
+ [language note](#a-map-reads-the-dictionary-first). Each row has these columns,
3440
+ including on an empty relation:
3441
+
3442
+ | Column | SQL type | Meaning |
3443
+ | ---------- | --------- | ---------------------------------------------------------------- |
3444
+ | `language` | `VARCHAR` | The language note's Address, as `words.language` holds it. |
3445
+ | `word` | `BIGINT` | The word's place in `data.words`, from `0`, as `words.position`. |
3446
+ | `form` | `VARCHAR` | The word's `form`. |
3447
+ | `native` | `BIGINT` | The entry's place in the word's `native`, from `0`. |
3448
+ | `map` | `VARCHAR` | The map the form is written for. |
3449
+ | `font` | `VARCHAR` | The one face it is for; `NULL` for every face the map lists. |
3450
+ | `text` | `VARCHAR` | The word as the script writes it; a value, so case is kept. |
3451
+
3452
+ The native form of a word with no entry is computed on demand, by the map's
3453
+ rows: `package-build lexicon` lists every word with it.
3454
+
3455
+ Declared dependency metadata supplies `runs`, `maps` and `natives` in that
3456
+ package's SQL schema, and public queries exclude GM-only owners, as for `names`.
3457
+
3105
3458
  **`language_lineage`** is one row per language note and each language it
3106
3459
  inherits words from through [`data.family`](#a-languages-words): `language`,
3107
3460
  `ancestor` and `depth`, with the language itself at depth `0`, its parent at
@@ -3777,7 +4130,7 @@ The build appends sections to the end of a note's body that list the events conc
3777
4130
  | Accounts | `accounts` | an `affiliation`, a `place`, a `lore` note of `subType: culture` | each `accounts[]` entry the note gives, with the event it concerns | When, Event, What they say, Agrees |
3778
4131
  | What followed | `followed` | a note holding events | events whose `follows[].event` names one of this note's events, with how | When, What followed, How |
3779
4132
 
3780
- - **Every view's first column is When**, the event's date written in the setting's calendar — the `events` relation's `whenText` column. An event whose date does not resolve shows the text its note wrote.
4133
+ - **Every view's first column is When**, every row's date written in [the calendar of the page showing the view](#a-notes-calendar), whichever note owns the event, so one table reads in one count. The fence reads `whenIn['<calendar Address>']` for that calendar; on a page with no calendar it reads `whenText`, each date in its owning note's calendar. An event whose date does not resolve shows the text its note wrote.
3781
4134
  - **A chronology's Felt here column appears only where some row is felt in the place.** A place whose rows are all events that happened there, or world events, has the When and What happened columns alone.
3782
4135
  - **A chronology is always set across the full page width in a PDF book**, whatever its columns.
3783
4136
  - **The views follow everything the author wrote**, in the order of the [generated sections](#generated-sections).
@@ -4080,7 +4433,6 @@ See [a being's subtype describes the subject](#a-beings-subtype-describes-the-su
4080
4433
  | `gender` | `male \| female \| nonbinary \| none \| other` | Gender of the being — see [a being's look](#a-beings-look) |
4081
4434
  | `species` | `Address` | Being's species (lore) |
4082
4435
  | `born` | `date \| unknown` | When the being was born; absent, it was never born |
4083
- | `calendar` | `Address` | Calendar note that prints the being's dates |
4084
4436
  | `died` | `date \| unknown` | When the being died; absent, it is alive |
4085
4437
  | `age` | `34 \| ~34` | Age in years, stated only to override what `born` says; `~` marks an estimate |
4086
4438
  | `ageYears` | `number` | Written by the compiler beside an `age` estimate — the `~` stripped; a note never authors this |
@@ -5528,15 +5880,16 @@ SoHL sets `system.quantity` to `1` for each compendium item.
5528
5880
  | `grammar.plural` | `{ ending, becomes }[]` | Literal suffix replacements in authored order; the first match wins |
5529
5881
  | `grammar.plural[].ending` | `string`, required, may be empty | Bare suffix to replace; empty matches every noun |
5530
5882
  | `grammar.plural[].becomes` | `string`, required, may be empty | Bare replacement; empty deletes the matching suffix |
5531
- | `grammar.verbs` | `{ invariant: true }` or `{ persons: { I, you, heShe, we, youAll, they } }` | One regular present pattern, printed once at the dictionary head |
5883
+ | `grammar.verbs` | `{ invariant: true }` or `{ persons: { I, you, heShe, we, youAll, they }, replaces? }` | One regular present pattern, printed once at the dictionary head |
5532
5884
  | `grammar.verbs.invariant` | `true` | Verbs do not change; excludes `persons` |
5533
- | `grammar.verbs.persons` | a map of all six ending strings | Each ending is added to the citation form; excludes `invariant` |
5885
+ | `grammar.verbs.persons` | a map of all six ending strings | Each ending is added to the citation form, or replaces its closing `replaces`; excludes `invariant` |
5534
5886
  | `grammar.verbs.persons.I` | `string`, required, may be empty | Ending for I |
5535
5887
  | `grammar.verbs.persons.you` | `string`, required, may be empty | Ending for you |
5536
5888
  | `grammar.verbs.persons.heShe` | `string`, required, may be empty | Ending for he/she |
5537
5889
  | `grammar.verbs.persons.we` | `string`, required, may be empty | Ending for we |
5538
5890
  | `grammar.verbs.persons.youAll` | `string`, required, may be empty | Ending for you all |
5539
5891
  | `grammar.verbs.persons.they` | `string`, required, may be empty | Ending for they |
5892
+ | `grammar.verbs.replaces` | `string`, nonempty | Closing letters each ending takes the place of; a citation form closing otherwise takes the ending whole. With `persons` only |
5540
5893
  | `classModifiers` | `string[]` | `language` only: the word class modifiers the language uses, each from the [master word classes](#word-classes); empty when unstated |
5541
5894
  | `fields` | `{ name, holds }[]` | `language` only: the fields its words are grouped under |
5542
5895
  | `fields[].name` | `string`, required | The field's name, which a word's `field` names |
@@ -5559,6 +5912,10 @@ SoHL sets `system.quantity` to `1` for each compendium item.
5559
5912
  | `words[].tones` | `string` | A tone pattern, as `LHL` |
5560
5913
  | `words[].root` | `string` | A consonantal skeleton, as `d-l-k` |
5561
5914
  | `words[].frame` | `string` | The frame a root is set in, named in `sounds.frames` of the language or an ancestor |
5915
+ | `words[].native` | `{ map, font?, text }[]` | The word's native form under a map, stated only where the rows spell it differently — see [a map reads the dictionary first](#a-map-reads-the-dictionary-first) |
5916
+ | `words[].native[].map` | `string` | Required: the `data.map.name` the form is written for |
5917
+ | `words[].native[].font` | `string` | The one face the form is for, one the map lists; unstated, every face it lists |
5918
+ | `words[].native[].text` | `string` | Required: the word as the script writes it |
5562
5919
  | `affixes` | `{ form?, zero?, kind?, given?, where?, gloss, attaches?, examples?, barredAfter?, tones? }[]` | `language` only: the affixes a word's `built` pieces may name; each states a `form` or `zero: true` |
5563
5920
  | `affixes[].form` | `string` | The affix as a `built` piece names it, as `-rith`; absent on a zero affix |
5564
5921
  | `affixes[].zero` | `boolean` | The affix is written as nothing, as a case with no ending; it states no `form`, prints as `∅`, and no `built` piece names it |
@@ -5818,7 +6175,27 @@ The raw `words` relation retains only the authored `plural`; the
5818
6175
  the `persons` map shown above. A supplied `persons` map has all six string
5819
6176
  endings, each added to the citation form; `""` leaves that form unchanged.
5820
6177
  The dictionary prints the pattern once in a small table before its letter
5821
- headings. A custom dictionary's default or `key=true` prints that table even
6178
+ headings, headed "Ending added to citation form".
6179
+
6180
+ Where the present endings take the place of the citation form's closing
6181
+ letters, the pattern states them as `replaces`:
6182
+
6183
+ ```yaml
6184
+ grammar:
6185
+ verbs:
6186
+ replaces: then
6187
+ persons: { I: men, you: zen, heShe: zor, we: mar, youAll: zar, they: zoi }
6188
+ ```
6189
+
6190
+ A form is then the citation form with its closing `then` swapped for the
6191
+ ending: `lokathen` gives `lokamen`, `lokazor` and the rest, and an empty
6192
+ ending drops the `then`. A citation form that does not close on `replaces`
6193
+ takes the ending whole, as a suffix does when its `replaces` does not match,
6194
+ so `varos` gives `varoszor`. It is not a finding, because no word is checked
6195
+ against a grammar rule. The
6196
+ dictionary's table is then headed "Ending in place of a closing -then", and
6197
+ an empty ending prints as "-then dropped". `replaces` is a literal, nonempty
6198
+ string, and only accompanies `persons`. A custom dictionary's default or `key=true` prints that table even
5822
6199
  when no abbreviation key is needed. `key=false` fragments omit both the key
5823
6200
  and table; `key=only` prints the abbreviation key alone. Separate results
5824
6201
  with their heads enabled each print their own tables.
@@ -6069,6 +6446,24 @@ affiliation in `scope` having it as a rank or office title, and every anchor of
6069
6446
  a note in `scope` having it as its name. A folder note carries no name, and a
6070
6447
  language that states no `scope` reads every note of the package.
6071
6448
 
6449
+ **A row tagged `other:<tongue>` or `exonym:<tongue>` names a name of that
6450
+ tongue's own register.** The tongue names the language note that a
6451
+ `data.tongues` entry tagged with it names by its `language`, in any language
6452
+ note of the package: `{ tag: vylari, language: skill-vylarilng }` makes
6453
+ `exonym:vylari` read the Vylari note. That note's `data.register` must hold a
6454
+ row of the same name, case folded and NFC, whose `tongue` is the tongue itself,
6455
+ so a misspelt foreign name is caught where it is listed. A tongue that no entry
6456
+ links to a language note, or that entries link to two, is a warning at the
6457
+ row's `tongue`; a missing name is a warning at its `name`:
6458
+
6459
+ > `` `Dunharra` is registered as `other:vylari`, but the register of Vylari Language (skill-vylarilng) lists no `vylari` name `Dunharra`; correct the spelling, or register the name there first ``
6460
+
6461
+ > `` the tongue `nordmal` in `exonym:nordmal` names no language note: no language note's `data.tongues` tags `nordmal` with a `language` ``
6462
+
6463
+ Bare `other`, which names no tongue, and every other tag are not checked. A
6464
+ language note of another package is not read, since its register does not
6465
+ reach the package's index.
6466
+
6072
6467
  **`retired` lists the forms the setting does not write**, each with what is
6073
6468
  written in its place, one form or several, and why. A form nothing replaces
6074
6469
  states no `instead`.
@@ -6244,6 +6639,8 @@ Each finding is located at the key at fault:
6244
6639
  | A `scope` Address that does not resolve, or a type that is no note type | error |
6245
6640
  | A tongue `language` or a cognate form's language that is no language note | error |
6246
6641
  | A register row with neither a `tongue` nor a `kind`, at the row | error |
6642
+ | A register row tagged `other:<tongue>` or `exonym:<tongue>` whose tongue no `data.tongues` entry links to one language note, at the tongue | warning |
6643
+ | A register row tagged `other:<tongue>` or `exonym:<tongue>` whose name the tongue's own register does not list under that tongue, at the name | warning |
6247
6644
  | A sound-change set's name stated twice, at the second | error |
6248
6645
  | A step label stated twice in one set, at the second | error |
6249
6646
  | A set's `from` or `to` that is no language note, at the value | error |
@@ -6982,7 +7379,6 @@ level with no background prints nothing.
6982
7379
  | `data` property | Values | Description |
6983
7380
  | --------------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
6984
7381
  | `events` | event entries — see [Events](#events) | What happened to or at this subject: dated, attributed events |
6985
- | `calendar` | `Address` | Calendar note that prints this place's dates |
6986
7382
  | `demonym` | `string` | What a person from this place is called — a Vylarian |
6987
7383
  | `purpose` | `placeCharacter` tag | The reason a settlement, site, or structure exists |
6988
7384
  | `lore` | `Address[]` | Lore concerning this place — its peoples, its law, its calendar, its history |
@@ -7440,9 +7836,24 @@ Content prepared to be played — a situation with its cast, places, and possibl
7440
7836
  - concept: An explanation of how something works and why it is shaped that way, read to understand rather than to follow.
7441
7837
  - settingguide: A culture's guide to the setting, which names its culture in `data.culture`. It orients a reader to a setting or region, bringing its places, peoples, institutions, and daily life into one usable frame with links to detailed notes.
7442
7838
 
7443
- | `data` property | Values | Description |
7444
- | --------------- | --------- | --------------------------------------------------------------------------------------------------------------------- |
7445
- | `culture` | `Address` | `settingguide` only, and required there: the culture the guide introduces, naming a lore note with `subType: culture` |
7839
+ | `data` property | Values | Description |
7840
+ | --------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
7841
+ | `culture` | `Address` | `settingguide` only, and required there: the culture the guide introduces, naming a lore note with `subType: culture` |
7842
+ | `map` | `{ name, vowels?, fonts, rows }` | A mapping, which text marked `map=` with its `name` is rewritten by; any subType. See [mapping notes](#mapping-notes) |
7843
+ | `map.name` | string | Required: what `map=` names it by, lowercase letters, digits and `-`, unique in the index |
7844
+ | `map.vowels` | string | The characters `before: vowel` reads as vowels |
7845
+ | `map.rows` | list of `[from, to]` or `{ from, to, before?, at? }` | Required: the rewrites, in authored order |
7846
+ | `map.rows[].from` | string of one or more characters | Required in a map row: the characters it matches |
7847
+ | `map.rows[].to` | string, which may be empty | Required in a map row: what it writes in their place |
7848
+ | `map.rows[].before` | `vowel \| consonant` | What must follow the match |
7849
+ | `map.rows[].at` | `start \| end` | Where the match must sit |
7850
+ | `map.fonts` | list of `{ font, rows? }` | Required, at least one: the faces the map is drawn in |
7851
+ | `map.fonts[].font` | an installed face's slug | Required: the face, as `font=` names it; not `mono`, `serif` or `sans` |
7852
+ | `map.fonts[].rows` | list of `[from, to]` or `{ from, to, before?, at? }` | Rows for this face alone, tried before the shared `rows` |
7853
+ | `map.fonts[].rows[].from` | string of one or more characters | Required in a map row: the characters it matches |
7854
+ | `map.fonts[].rows[].to` | string, which may be empty | Required in a map row: what it writes in their place |
7855
+ | `map.fonts[].rows[].before` | `vowel \| consonant` | What must follow the match |
7856
+ | `map.fonts[].rows[].at` | `start \| end` | Where the match must sit |
7446
7857
 
7447
7858
  A setting guide introduces one culture's view of the setting, so it names that
7448
7859
  culture, and a `doc` of any other `subType` has no culture of its own to name.