@heroiclands/package-build 24.0.1 → 24.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/assets/preview/gitea.ini +1 -1
  3. package/assets/styles/content-base.css +33 -0
  4. package/bin/content-commands.mjs +9 -7
  5. package/docs/api.md +6 -5
  6. package/docs/authoring/links-and-markup.md +164 -16
  7. package/docs/commands.md +16 -14
  8. package/docs/configuration.md +16 -0
  9. package/docs/content-format.md +4 -2
  10. package/docs/guides/writing-in-invented-scripts.md +68 -6
  11. package/docs/reference/format-details.md +143 -49
  12. package/docs/reference/note-types.md +47 -28
  13. package/docs/source-preview.md +2 -2
  14. package/engine/content-blocks.mjs +6 -2
  15. package/engine/content-format.yaml +2 -0
  16. package/engine/content-index.mjs +5 -1
  17. package/engine/content-render.mjs +2 -1
  18. package/engine/content-scripts.mjs +268 -21
  19. package/engine/content-scroll.mjs +389 -0
  20. package/engine/font-outlines.mjs +394 -0
  21. package/engine/language-data.mjs +154 -63
  22. package/engine/language-rows.mjs +32 -6
  23. package/engine/language-terms.mjs +32 -8
  24. package/engine/map-lexicon.mjs +31 -6
  25. package/engine/note-vocabulary.mjs +58 -6
  26. package/engine/pdf-fonts.mjs +1 -1
  27. package/engine/pdf-render.mjs +52 -11
  28. package/engine/run-rows.mjs +45 -3
  29. package/engine/script-direction.mjs +67 -0
  30. package/engine/script-emphasis-terms.mjs +11 -3
  31. package/engine/script-font-delivery.mjs +42 -9
  32. package/engine/script-fonts.mjs +7 -0
  33. package/engine/verse-metre.mjs +1 -0
  34. package/engine/written-in.mjs +208 -0
  35. package/package.json +1 -1
  36. package/site-theme/static/css/style.css +7 -0
  37. package/types/engine/content-scripts.d.mts +31 -6
  38. package/types/engine/content-scroll.d.mts +179 -0
  39. package/types/engine/font-outlines.d.mts +67 -0
  40. package/types/engine/language-data.d.mts +13 -0
  41. package/types/engine/language-rows.d.mts +4 -3
  42. package/types/engine/language-terms.d.mts +7 -0
  43. package/types/engine/map-lexicon.d.mts +4 -3
  44. package/types/engine/pdf-fonts.d.mts +10 -0
  45. package/types/engine/run-rows.d.mts +28 -6
  46. package/types/engine/script-direction.d.mts +10 -0
  47. package/types/engine/script-emphasis-terms.d.mts +10 -3
  48. package/types/engine/script-font-delivery.d.mts +5 -3
  49. package/types/engine/script-fonts.d.mts +9 -0
  50. package/types/engine/written-in.d.mts +76 -0
@@ -142,6 +142,29 @@ address of the language's `skill` note of `subType: language`, whose
142
142
  Without `note`, `lang=` only tags: there is no dictionary and no lexicon check.
143
143
  `package-build lint` checks at the key that `note` names a language note.
144
144
 
145
+ **A language names its script.** Most tongues are written in one script, so the
146
+ language note says which, as one map and one face:
147
+
148
+ ```yaml
149
+ data:
150
+ writtenIn: { map: semaron, font: semaron-kuriale }
151
+ ```
152
+
153
+ A run marked with the language alone then takes both: `[Zaron lonak]{lang=vyl}`
154
+ is mapped through Semaron and checked against Vylari exactly as
155
+ `[Zaron lonak]{lang=vyl font=semaron-kuriale map=semaron}` is, and so is a div,
156
+ a verse fence or a `scroll` fence marked `lang=vyl`. A run that states one of
157
+ the two keys keeps it, and takes the other from the language only where the
158
+ two agree: `font=` alone takes the language's map where that map is drawn in
159
+ the face, and `map=` alone takes the language's face where the map is drawn in
160
+ it. So a tongue written in several scripts states its usual one, and a run in
161
+ another names that script's keys. A language whose note states no `writtenIn`
162
+ only tags, as above. The checks are at the key:
163
+
164
+ > `` `data.writtenIn.map` is `semoran`, which no mapping note's `data.map.name` declares; did you mean `semaron`? ``
165
+
166
+ > `` `data.writtenIn.font` is `teng-chou`, which the map `semaron` is not drawn in; it is drawn in `semaron-kuriale` ``
167
+
145
168
  **A run inherits from its passage and overrides it.** A span inside a marked
146
169
  div or fence takes the passage's keys, and its own keys win. A Khelathi phrase
147
170
  inside a Vylari poem is checked against Khelathi's lexicon and tagged Khelathi
@@ -805,8 +828,8 @@ Lokamen lonak zaron.
805
828
 
806
829
  **The stress rule** is `data.sounds.stress`, `{ two, longer, mark }`. `two`
807
830
  (`first` or `last`) places the stress in a word of two syllables; `longer`
808
- (`antepenultimate`, `penultimate` or `final`) places it in a word of three or
809
- more, counted from its end; `mark` (`acute`, `grave` or `circumflex`) is a
831
+ (`first`, `antepenultimate`, `penultimate` or `final`) places it in a word of
832
+ three or more, on its first syllable or counted from its end; `mark` (`acute`, `grave` or `circumflex`) is a
810
833
  diacritic that moves the stress to the syllable it sits on. Syllables are vowel
811
834
  groups: a vowel is a letter whose base is `a`, `e`, `i`, `o` or `u`, and a pair
812
835
  that the language lists in `data.sounds.syllables.vowelPairs` is one syllable.
@@ -850,6 +873,43 @@ A repeated metre name is an error too:
850
873
 
851
874
  > `` `data.metres[1].name` is `trimeter`, which this note declares twice; a metre's name is unique ``
852
875
 
876
+ ## Scripts written in columns
877
+
878
+ A script written in vertical columns, read from the right, states it on its
879
+ script note:
880
+
881
+ ```yaml
882
+ data:
883
+ script:
884
+ fonts: [teng-chou]
885
+ emphasis: rubric
886
+ direction: vertical-rl
887
+ ```
888
+
889
+ A passage in that script goes in a `scroll` fence. Its lines are joined into
890
+ paragraphs, and each paragraph starts at the top of a fresh column. `rows=`
891
+ sets the characters in a column and `cols=` the columns in a panel:
892
+
893
+ ````markdown
894
+ ```scroll {rows=25 cols=12 lang=tsu map=tengchou}
895
+ chen teng chou hing
896
+ tan tsou dou rou
897
+
898
+ gen chei chou tsuen
899
+ ```
900
+ ````
901
+
902
+ Where the Tantsou note states `writtenIn: { map: tengchou, font: teng-chou }`,
903
+ ` ```scroll {rows=25 cols=12 lang=tsu} ` is enough.
904
+
905
+ The site, the preview and Foundry set it in columns, in a panel that scrolls
906
+ sideways from the right. The book draws it as panel images from the face's
907
+ outlines, each with its roman text as alt text. A span in the same script stays
908
+ horizontal, inline in its line. A `scroll` fence in a script that states no
909
+ direction is an error. See
910
+ [scrolls](../authoring/links-and-markup.md#scrolls) for every attribute, the
911
+ defaults on each target and the errors.
912
+
853
913
  ## The core vocabulary
854
914
 
855
915
  A setting can name the concepts every one of its languages has a word for, and
@@ -907,8 +967,9 @@ whose notes set no text in a face, ships none of it.
907
967
  `package-build transliterate <map> [--lang <language>] [--font <slug>] [--hex] [text..]`
908
968
  maps a passage exactly as the build maps a run marked `map=<map> lang=<language>
909
969
  font=<slug>`. The text is the words given; `-` or none reads standard input.
910
- `--font` is required when the mapping lists more than one face, and `--hex`
911
- prints `U+XXXX` code points separated by spaces.
970
+ `--font` defaults to the language's `writtenIn` face where the mapping is drawn
971
+ in it, and is otherwise required when the mapping lists more than one face.
972
+ `--hex` prints `U+XXXX` code points separated by spaces.
912
973
 
913
974
  ```console
914
975
  $ package-build transliterate semaron --lang vyl --hex zaron lonak
@@ -920,8 +981,9 @@ U+F0246 U+F0230 U+F023F U+F023D U+F023C
920
981
  The first run writes `zaron` by its exception, and the second, with no
921
982
  `--lang`, spells it by the rows.
922
983
 
923
- `package-build lexicon <language> --map <map> [--font <slug>] [--derived] [--json]`
924
- lists every word of the language with its native form, tab-separated: the form,
984
+ `package-build lexicon <language> [--map <map>] [--font <slug>] [--derived] [--json]`
985
+ lists every word of the language with its native form under the map, or under
986
+ the language's `writtenIn` script when `--map` is left out, tab-separated: the form,
925
987
  the native form, `exception` or `rows`, the class and the gloss. `--derived`
926
988
  adds the forms the language's rules derive, each with the word it comes from,
927
989
  `--json` prints an array instead, and under a logographic map the third column
@@ -1993,7 +1993,7 @@ errors.
1993
1993
 
1994
1994
  - **`lang=`** is a name declared in [`languages`](../configuration.md#languages),
1995
1995
  and otherwise a BCP 47 tag checked for its form alone. It gives the language
1996
- tag and changes nothing else.
1996
+ tag, and the language's script where its note names one.
1997
1997
  - **`font=`** is a face of the installed `@heroiclands/fonts`, named by its
1998
1998
  `fonts.json` class without `hl-`, or `mono`, `serif` or `sans`.
1999
1999
  - **`map=`** is the `data.map.name` of a [mapping note](#mapping-notes). The
@@ -2003,6 +2003,14 @@ errors.
2003
2003
  - **`lang=` with `map=`** reads the language's lexicon first, when
2004
2004
  [`languages.<name>.note`](../configuration.md#languages) names its language
2005
2005
  note: see [a map reads the dictionary first](#a-map-reads-the-dictionary-first).
2006
+ - **`lang=` without `map=` or `font=`** takes the map and the face its
2007
+ language note names in `data.writtenIn`, where the note states one. A run
2008
+ that states `font=` alone takes the language's map only where the map is
2009
+ drawn in that face, and one that states `map=` alone takes the language's
2010
+ face only where that map is drawn in it. A run stating both takes nothing.
2011
+ The default is read after a div or fence has given the run its keys, and is
2012
+ its own language's. See
2013
+ [a language's script](../authoring/links-and-markup.md#a-languages-script).
2006
2014
 
2007
2015
  **A face draws the code points its `fonts.json` entry lists in `codepoints`,
2008
2016
  and whitespace.** After the map, any other character in a run set in a face is
@@ -2022,9 +2030,10 @@ with a real language's tag alone keeps its Markdown. See
2022
2030
  [marking language, font and mapping](../authoring/links-and-markup.md#marking-language-font-and-mapping).
2023
2031
 
2024
2032
  Each own-note metadata record carries `runs` in source order, an empty array
2025
- when the body marks nothing. Every entry has exactly `kind` (`span`, `block` or
2033
+ when the body marks nothing. Every entry has exactly `kind` (`span`, `block`, `scroll` or
2026
2034
  `verse`), `lang`, `tag`, `font`, `map`, `line` and `column`: `lang`, `font` and
2027
- `map` as written, its own or inherited, `tag` the BCP 47 tag `lang` resolves
2035
+ `map` as written, its own or inherited, else its language's `data.writtenIn`
2036
+ (a scroll fence that names no face takes its map's), `tag` the BCP 47 tag `lang` resolves
2028
2037
  to, each `null` when absent, and the position of the span's `[`, the div's
2029
2038
  `:::` or the fence's opening. Every value is a string or a number and none is a
2030
2039
  key, so tags that differ only in case are separate values. This is derived
@@ -2364,8 +2373,9 @@ the words' native forms; `package-build lexicon` resolves them.
2364
2373
 
2365
2374
  #### Script notes and emphasis
2366
2375
 
2367
- A `skill` note of `subType: script` states the faces its script is written in
2368
- and how the script marks a stressed word, in `data.script`:
2376
+ A `skill` note of `subType: script` states the faces its script is written in,
2377
+ how the script marks a stressed word, and the direction it is written in, in
2378
+ `data.script`:
2369
2379
 
2370
2380
  ```yaml
2371
2381
  data:
@@ -2381,6 +2391,25 @@ data:
2381
2391
  (or `_…_` and `__…__`) inside a run in one of the faces.
2382
2392
  - **`strongEmphasis`**, optional, is `rubric` or `overline`, and sets `**…**`
2383
2393
  alone.
2394
+ - **`direction`**, optional, is `vertical-rl` for a script written in columns,
2395
+ top to bottom, with the columns read from the right. A
2396
+ [`scroll` fence](../authoring/links-and-markup.md#scrolls) sets text in
2397
+ such a script, and is an error in any other. A script that states no
2398
+ direction is written in lines.
2399
+
2400
+ A `skill` note of `subType: language` may state `data.writtenIn`, the script
2401
+ the language is written in: `map`, the `data.map.name` of a mapping note, and
2402
+ `font`, one of the faces that map lists, both required.
2403
+
2404
+ ```yaml
2405
+ data:
2406
+ writtenIn: { map: semaron, font: semaron-kuriale }
2407
+ ```
2408
+
2409
+ A run marked with the language alone is mapped by `map` and set in `font`, as
2410
+ [text marked with a language, a font or a map](#text-marked-with-a-language-a-font-or-a-map)
2411
+ describes. A face a language's `writtenIn` names counts as used, so the
2412
+ package's fonts are delivered for it.
2384
2413
 
2385
2414
  A `skill` note of `subType: language` may state `data.emphasis`: `words`,
2386
2415
  `rubric` or `overline`. It overrides the script's convention in every run of the
@@ -2410,10 +2439,18 @@ Each of these is an error at the key or the entry at fault:
2410
2439
 
2411
2440
  > `` `data.script.emphasis` should be `rubric` or `overline`, but reads "rubrik". Did you mean "rubric"? ``
2412
2441
 
2442
+ > `` `data.script.direction` should be `vertical-rl`, but reads "vertical" ``
2443
+
2413
2444
  > `` `data.script` states a script's faces and emphasis, and this note's `subType` is not `script` ``
2414
2445
 
2415
2446
  > `` `data.emphasis` states how a language marks emphasis, and this note's `subType` is not `language` ``
2416
2447
 
2448
+ > `` `data.writtenIn` names the script a language is written in, and this note's `subType` is not `language` ``
2449
+
2450
+ > `` `data.writtenIn.map` is `semoran`, which no mapping note's `data.map.name` declares; did you mean `semaron`? ``
2451
+
2452
+ > `` `data.writtenIn.font` is `teng-chou`, which the map `semaron` is not drawn in; it is drawn in `semaron-kuriale` ``
2453
+
2417
2454
  > `` `data.script.fonts` is empty; a script lists each face it is written in ``
2418
2455
 
2419
2456
  > `` `data.script.fonts[0]` is `serif`, a generic family; a script lists the faces it is written in ``
@@ -3546,15 +3583,15 @@ for the keys.
3546
3583
  | `tongues` | `data.tongues` | `tag`, `meaning`, and `tongue_language`, the tongue's `language` |
3547
3584
  | `retired` | `data.retired` | `form`, `instead` (always a list, `NULL` where nothing is written instead), `why` |
3548
3585
  | `scope` | `data.scope` | No `position`: `kind`, the key the value is under (`folders`, `cultures`, `lore`, `parents`, `addresses`, `paths`, `types` or `skip`), and `value`, an Address in its canonical form or a path or a type |
3549
- | `letters` | `data.sounds.letters` | `letters` (a list), `kind`, `like`, `place`, `tongue` (a list, `NULL` for every tongue) |
3586
+ | `letters` | `data.sounds.letters` | `letters` (a list), `kind`, `like`, `place`, `ipa`, `tongue` (a list, `NULL` for every tongue); a [spelling guide's](#a-spelling-guides-sounds) rows too, keyed by the guide |
3550
3587
  | `edges` | `data.sounds.edges` | `edge`, `sounds` (a list: `[any]` for every sound, empty for none) |
3551
3588
  | `clusters` | `data.sounds.clusters` | No `position`: `max`, one row per language stating it |
3552
3589
  | `stress` | `data.sounds.stress` | No `position`: `two`, `longer` and `mark` (`NULL` where unstated), one row per language stating it |
3553
- | `finals` | `data.sounds.finals` | `final`, `like` |
3590
+ | `finals` | `data.sounds.finals` | `final`, `like`, `ipa` |
3554
3591
  | `syllables` | `data.sounds.syllables` | No `position`, one row per language stating it: `onsets`, `codas`, `finals`, `doubled`, `vowelPairs`, `neverInitial` and `signature` (lists, `any` a list of one), `initialVowel` and `finalVowel` (`true` or `false`), and the harmony as `harmony_back`, `harmony_front`, `harmony_neutral`, `harmony_neutralAs` and `harmony_partners` (a list of `{ vowel, partner }`) |
3555
3592
  | `joining` | `data.sounds.joining` | `first` and `second` (lists), `written`, `example` |
3556
3593
  | `joins` | `data.sounds.joins` | `meeting`, `rule`, `letters` (a list), `where` |
3557
- | `marks` | `data.sounds.marks` | `mark`, `letters` (a list), `kind`, `limit` |
3594
+ | `marks` | `data.sounds.marks` | `mark`, `letters` (a list), `kind`, `limit`; a spelling guide's rows too, keyed by the guide |
3558
3595
  | `skeleton` | `data.sounds.skeleton` | `second` (a list, `[any]` for every other), `between` |
3559
3596
  | `frames` | `data.sounds.frames` | `frame`, `shape`, `class` (a list), `makes`, `examples` (a list of `{ root, form }`) |
3560
3597
  | `read_past` | `data.sounds.readPast` | `written`, `readAs`, `what` |
@@ -3616,7 +3653,7 @@ including on an empty relation:
3616
3653
  | Column | SQL type | Meaning |
3617
3654
  | -------- | --------- | -------------------------------------------------------------------------------------------- |
3618
3655
  | `owner` | `VARCHAR` | Containing authored page's canonical NOTE Address, or `NULL` when the record has no Address. |
3619
- | `kind` | `VARCHAR` | `span`, `block` or `verse`. |
3656
+ | `kind` | `VARCHAR` | `span`, `block`, `verse` or `scroll`. |
3620
3657
  | `lang` | `VARCHAR` | The language as written, its own or inherited, or `NULL`. |
3621
3658
  | `tag` | `VARCHAR` | The BCP 47 tag `lang` resolves to, or `NULL` when it resolves to none. |
3622
3659
  | `font` | `VARCHAR` | The font as written, its own or inherited, or `NULL`. |
@@ -6181,12 +6218,13 @@ SoHL sets `system.quantity` to `1` for each compendium item.
6181
6218
  | `scope.types` | `string[]` | The note types covered; a note of another type is out of scope whatever else catches it |
6182
6219
  | `scope.skip` | `Address[]` | Notes out of scope whatever else catches them |
6183
6220
  | `sounds` | `{ letters?, edges?, clusters?, stress?, finals?, syllables?, joining?, joins?, marks?, skeleton?, frames?, readPast? }` | `language` only: the letters it writes and the rules its words keep |
6184
- | `sounds.letters` | `{ letters, kind, like?, place?, tongue? }[]` | The letters and the sounds they stand for |
6221
+ | `sounds.letters` | `{ letters, kind, like?, place?, ipa?, tongue? }[]` | The letters and the sounds they stand for |
6185
6222
  | `sounds.letters[].letters` | `string[]`, required | The letters or digraphs the row describes |
6186
6223
  | `sounds.letters[].kind` | `string`, required | What kind of sound they write, as `consonant` or `long vowel` |
6187
6224
  | `sounds.letters[].like` | `string` | What they sound like; absent where the row states only their kind and place |
6188
6225
  | `sounds.letters[].place` | `string` | Where in the mouth they are made, as `lips` or `tongue-tip` |
6189
- | `sounds.letters[].tongue` | `string` or `string[]` | The tongues that write them, as tags `tongues` declares; every one where absent |
6226
+ | `sounds.letters[].ipa` | `string` | The sounds they write, in the International Phonetic Alphabet, as `ʃ` |
6227
+ | `sounds.letters[].tongue` | `string` or `string[]` | The tongues that write them, each a tag `tongues` declares or a language note's Address; every one where absent |
6190
6228
  | `sounds.edges` | `{ edge, sounds }[]` | The sounds that may stand at each edge of a word, one entry per edge |
6191
6229
  | `sounds.edges[].edge` | `openConsonant`, `openCluster`, `closeConsonant`, `closeCluster`, `innerCluster`, `vowelPair`, `glide` or `syllabic`, required | Which edge the sounds stand at |
6192
6230
  | `sounds.edges[].sounds` | `string[]` or `any`, required | The sounds; `any` for every one, an empty list for none |
@@ -6198,11 +6236,12 @@ SoHL sets `system.quantity` to `1` for each compendium item.
6198
6236
  | `sounds.clusters.groups[].use` | `common` or `sparing`, required | How often the group's clusters stand inside a word |
6199
6237
  | `sounds.stress` | `{ two, longer, mark? }` | Where a word's stress falls, as a rule a program reads |
6200
6238
  | `sounds.stress.two` | `first` or `last`, required | The stressed syllable of a word of two syllables |
6201
- | `sounds.stress.longer` | `antepenultimate`, `penultimate` or `final`, required | The stressed syllable of a word of three or more, counted from its end |
6239
+ | `sounds.stress.longer` | `first`, `antepenultimate`, `penultimate` or `final`, required | The stressed syllable of a word of three or more: the first, or counted from its end |
6202
6240
  | `sounds.stress.mark` | `acute`, `grave` or `circumflex` | The diacritic that marks a stress the rule would not choose; it moves the stress to the syllable it sits on |
6203
- | `sounds.finals` | `{ final, like }[]` | The syllable finals, for a language whose syllables are parsed by them |
6241
+ | `sounds.finals` | `{ final, like, ipa? }[]` | The syllable finals, for a language whose syllables are parsed by them |
6204
6242
  | `sounds.finals[].final` | `string`, required | The final, as `ang` |
6205
6243
  | `sounds.finals[].like` | `string`, required | What it sounds like |
6244
+ | `sounds.finals[].ipa` | `string` | Its sound, in the International Phonetic Alphabet |
6206
6245
  | `sounds.syllables` | map of syllable rules | What a syllable and a word may open and close on |
6207
6246
  | `sounds.syllables.onsets` | `single` | What a syllable opens on: a single consonant |
6208
6247
  | `sounds.syllables.codas` | `string[]` or `any` | The consonants that may close a syllable |
@@ -6332,10 +6371,14 @@ SoHL sets `system.quantity` to `1` for each compendium item.
6332
6371
  | `affixes[].native[].font` | `string` | The one face the writing is for, one the map lists; unstated, every face it lists |
6333
6372
  | `affixes[].native[].text` | `string`, required | The affix as the script writes it |
6334
6373
  | `emphasis` | `words`, `rubric` or `overline` | `language` only: how the language marks a stressed word, over its script's; `words` makes `*…*` and `**…**` in its runs an error — see [script notes and emphasis](#script-notes-and-emphasis) |
6335
- | `script` | `{ fonts, emphasis, strongEmphasis? }` | `script` only: the faces the script is written in and how it marks emphasis — see [script notes and emphasis](#script-notes-and-emphasis) |
6374
+ | `writtenIn` | `{ map, font }` | `language` only: the script the language is written in, which a run marked with the language alone takes — see [script notes and emphasis](#script-notes-and-emphasis) |
6375
+ | `writtenIn.map` | `string`, required | The `data.map.name` of a mapping note |
6376
+ | `writtenIn.font` | `string`, required | A face the map lists |
6377
+ | `script` | `{ fonts, emphasis, strongEmphasis?, direction? }` | `script` only: the faces the script is written in, how it marks emphasis and its direction — see [script notes and emphasis](#script-notes-and-emphasis) |
6336
6378
  | `script.fonts` | `string[]`, required | Installed faces by the slug `font=` names them by; a face belongs to one script |
6337
6379
  | `script.emphasis` | `rubric` or `overline`, required | Sets `*…*` and `**…**` in a run in one of its faces |
6338
6380
  | `script.strongEmphasis` | `rubric` or `overline` | Sets `**…**` instead of `emphasis` |
6381
+ | `script.direction` | `vertical-rl` | Written in columns, top to bottom, read from the right, which a [`scroll` fence](../authoring/links-and-markup.md#scrolls) sets |
6339
6382
 
6340
6383
  #### A language's words
6341
6384
 
@@ -6815,8 +6858,11 @@ unless `types` is stated and leaves its type out, or `skip` names it.
6815
6858
 
6816
6859
  **`sounds` states the letters and the rules a word keeps.** `letters` gives
6817
6860
  each letter or group of letters, the kind of sound it writes, what it sounds
6818
- like, where in the mouth it is made and, in a language whose tongues write
6819
- differently, the tongues that write it. A row that states only a kind and a
6861
+ like, where in the mouth it is made, its sound in the International Phonetic
6862
+ Alphabet as `ipa` and, in a language whose tongues write differently, the
6863
+ tongues that write it. Each `tongue` is a tag `tongues` declares, or else the
6864
+ Address of a language note, so a row can name another language's note
6865
+ directly. A row that states only a kind and a
6820
6866
  place — one cell of a grid of consonants by manner and place — states no
6821
6867
  `like`. `edges` gives the sounds that may stand at each edge of a word, one
6822
6868
  entry per edge, the edge from a closed list:
@@ -6846,14 +6892,15 @@ closed list where its values are words:
6846
6892
 
6847
6893
  - `stress`: the rule a program reads, `{ two, longer, mark? }`. `two` is the
6848
6894
  stressed syllable of a word of two syllables, `first` or `last`; `longer` is
6849
- the stressed syllable of a word of three or more, counted from its end,
6850
- `antepenultimate`, `penultimate` or `final`; `mark` is the diacritic, `acute`,
6895
+ the stressed syllable of a word of three or more, `first`, or counted from its
6896
+ end, `antepenultimate`, `penultimate` or `final`; `mark` is the diacritic, `acute`,
6851
6897
  `grave` or `circumflex`, that marks a stress the rule would not choose and
6852
6898
  moves it to the syllable it sits on. Syllables are vowel groups, a pair listed
6853
6899
  in `syllables.vowelPairs` counting as one. A verse fence's `scan=` counts
6854
6900
  beats by this rule; see [metres](#metres).
6855
- - `finals`: the syllable finals, each with what it sounds like, for a language
6856
- whose syllables are parsed by them.
6901
+ - `finals`: the syllable finals, each with what it sounds like and, as `ipa`,
6902
+ its sound in the International Phonetic Alphabet, for a language whose
6903
+ syllables are parsed by them.
6857
6904
  - `syllables`: what a syllable opens on (`single`, a single consonant), the
6858
6905
  `codas` that close a syllable and the `finals` that close a word (a list, or
6859
6906
  `any`), the `doubled` consonants and `vowelPairs` a word may hold (none where
@@ -7040,6 +7087,40 @@ FROM edges WHERE language = 'thalorna-note-skill-dunharlng' ORDER BY position
7040
7087
  `cognates` read the same way; [content tables](#content-tables) gives every
7041
7088
  relation's columns.
7042
7089
 
7090
+ #### A spelling guide's sounds
7091
+
7092
+ One spelling guide can state the letters every tongue of a setting is written
7093
+ by. A `doc` note of any subType may state `data.sounds` with `letters` and
7094
+ `marks` alone, each row with a language note's keys, `ipa` included:
7095
+
7096
+ ```yaml
7097
+ type: doc
7098
+ subType: reference
7099
+ data:
7100
+ sounds:
7101
+ letters:
7102
+ - { letters: [sh], kind: consonant, like: ship, ipa: ʃ }
7103
+ - {
7104
+ letters: [ui],
7105
+ kind: vowel pair,
7106
+ like: gooey,
7107
+ ipa: ui,
7108
+ tongue: [skill-tantsoulng, skill-jurthatlng],
7109
+ }
7110
+ marks:
7111
+ - { mark: diaeresis, letters: [ë], kind: length, limit: any }
7112
+ ```
7113
+
7114
+ A guide declares no tags, so a letter row's `tongue` names language notes by
7115
+ their Addresses, and each must resolve to a `skill` of `subType: language`. Any
7116
+ other key of `sounds` is an error at the key, since the rules of a word belong
7117
+ to its language. The rows are rows of the `letters` and `marks` relations, with
7118
+ the guide's Address as `language`. Each of these is an error at the value:
7119
+
7120
+ > `` `data.sounds.letters[1].tongue` names `vylari`, which is no language note's Address; a spelling guide names each tongue by its language note ``
7121
+
7122
+ > `` `data.sounds.letters[1].tongue[1]` names thalorna-note-skill-riding, which is not a skill whose subType is language ``
7123
+
7043
7124
  if a `sohl` property is present, a SoHL item of type "skill" is created.
7044
7125
 
7045
7126
  If an `hm3` property is present, an HM3 item of type "skill" is created.
@@ -8190,35 +8271,48 @@ Content prepared to be played — a situation with its cast, places, and possibl
8190
8271
  - 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.
8191
8272
  - post: A blog post, which states the day it was published in `data.date` and may name its writer in `data.author`. A site whose `site.feed` names a section lists its posts there, newest first, and feeds them as Atom.
8192
8273
 
8193
- | `data` property | Values | Description |
8194
- | --------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
8195
- | `culture` | `Address` | `settingguide` only, and required there: the culture the guide introduces, naming a lore note with `subType: culture` |
8196
- | `concepts` | list of `{ id, gloss, group }` | A core vocabulary's concept list, any subType. See [the core vocabulary](#the-core-vocabulary) |
8197
- | `date` | `YYYY-MM-DD` | `post` only, and required there: the day the post was published |
8198
- | `author` | string | `post` only: who wrote the post; a post naming none carries the site's byline, the first entry of `authors` |
8199
- | `concepts[].id` | string | Required: lowercase letters, digits and `-`, unique in the package |
8200
- | `concepts[].gloss` | string | Required: what the concept means, in brief |
8201
- | `concepts[].group` | string | Required: the group the report lists the concept under |
8202
- | `map` | `{ name, vowels?, fonts, rows }` | A mapping, which text marked `map=` with its `name` is rewritten by; any subType. See [mapping notes](#mapping-notes) |
8203
- | `map.name` | string | Required: what `map=` names it by, lowercase letters, digits and `-`, unique in the index |
8204
- | `map.vowels` | string | The characters `before: vowel` reads as vowels |
8205
- | `map.rows` | list of `[from, to]` or `{ from, to, before?, at? }` | Required: the rewrites, in authored order |
8206
- | `map.rows[].from` | string of one or more characters | Required in a map row: the characters it matches |
8207
- | `map.rows[].to` | string, which may be empty | Required in a map row: what it writes in their place |
8208
- | `map.rows[].before` | `vowel \| consonant` | What must follow the match |
8209
- | `map.rows[].at` | `start \| end` | Where the match must sit |
8210
- | `map.fonts` | list of `{ font, rows? }` | Required, at least one: the faces the map is drawn in |
8211
- | `map.fonts[].font` | an installed face's slug | Required: the face, as `font=` names it; not `mono`, `serif` or `sans` |
8212
- | `map.fonts[].rows` | list of `[from, to]` or `{ from, to, before?, at? }` | Rows for this face alone, tried before the shared `rows` |
8213
- | `map.fonts[].rows[].from` | string of one or more characters | Required in a map row: the characters it matches |
8214
- | `map.fonts[].rows[].to` | string, which may be empty | Required in a map row: what it writes in their place |
8215
- | `map.fonts[].rows[].before` | `vowel \| consonant` | What must follow the match |
8216
- | `map.fonts[].rows[].at` | `start \| end` | Where the match must sit |
8217
- | `metres` | `{ name, beats, kind }[]` | The metres a verse fence's `scan=` names; any subType. See [metres](#metres) |
8218
- | `metres[].name` | string | Required: what `scan=` names it by, lowercase letters, digits and `-`, unique in the package |
8219
- | `metres[].beats` | `integer`, required | The beats of one line, above zero |
8220
- | `metres[].kind` | `accentual` | Required: how the beats are counted |
8221
- | `map.logographic` | `true` | The script writes meanings: a run's words are written by the lexicon alone, and the rows write only the text between them |
8274
+ | `data` property | Values | Description |
8275
+ | --------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
8276
+ | `culture` | `Address` | `settingguide` only, and required there: the culture the guide introduces, naming a lore note with `subType: culture` |
8277
+ | `concepts` | list of `{ id, gloss, group }` | A core vocabulary's concept list, any subType. See [the core vocabulary](#the-core-vocabulary) |
8278
+ | `date` | `YYYY-MM-DD` | `post` only, and required there: the day the post was published |
8279
+ | `author` | string | `post` only: who wrote the post; a post naming none carries the site's byline, the first entry of `authors` |
8280
+ | `concepts[].id` | string | Required: lowercase letters, digits and `-`, unique in the package |
8281
+ | `concepts[].gloss` | string | Required: what the concept means, in brief |
8282
+ | `concepts[].group` | string | Required: the group the report lists the concept under |
8283
+ | `map` | `{ name, vowels?, fonts, rows }` | A mapping, which text marked `map=` with its `name` is rewritten by; any subType. See [mapping notes](#mapping-notes) |
8284
+ | `map.name` | string | Required: what `map=` names it by, lowercase letters, digits and `-`, unique in the index |
8285
+ | `map.vowels` | string | The characters `before: vowel` reads as vowels |
8286
+ | `map.rows` | list of `[from, to]` or `{ from, to, before?, at? }` | Required: the rewrites, in authored order |
8287
+ | `map.rows[].from` | string of one or more characters | Required in a map row: the characters it matches |
8288
+ | `map.rows[].to` | string, which may be empty | Required in a map row: what it writes in their place |
8289
+ | `map.rows[].before` | `vowel \| consonant` | What must follow the match |
8290
+ | `map.rows[].at` | `start \| end` | Where the match must sit |
8291
+ | `map.fonts` | list of `{ font, rows? }` | Required, at least one: the faces the map is drawn in |
8292
+ | `map.fonts[].font` | an installed face's slug | Required: the face, as `font=` names it; not `mono`, `serif` or `sans` |
8293
+ | `map.fonts[].rows` | list of `[from, to]` or `{ from, to, before?, at? }` | Rows for this face alone, tried before the shared `rows` |
8294
+ | `map.fonts[].rows[].from` | string of one or more characters | Required in a map row: the characters it matches |
8295
+ | `map.fonts[].rows[].to` | string, which may be empty | Required in a map row: what it writes in their place |
8296
+ | `map.fonts[].rows[].before` | `vowel \| consonant` | What must follow the match |
8297
+ | `map.fonts[].rows[].at` | `start \| end` | Where the match must sit |
8298
+ | `metres` | `{ name, beats, kind }[]` | The metres a verse fence's `scan=` names; any subType. See [metres](#metres) |
8299
+ | `metres[].name` | string | Required: what `scan=` names it by, lowercase letters, digits and `-`, unique in the package |
8300
+ | `metres[].beats` | `integer`, required | The beats of one line, above zero |
8301
+ | `metres[].kind` | `accentual` | Required: how the beats are counted |
8302
+ | `map.logographic` | `true` | The script writes meanings: a run's words are written by the lexicon alone, and the rows write only the text between them |
8303
+ | `sounds` | `{ letters?, marks? }` | A spelling guide's letters and marks, for every tongue of the setting; any subType. See [a spelling guide's sounds](#a-spelling-guides-sounds) |
8304
+ | `sounds.letters` | `{ letters, kind, like?, place?, ipa?, tongue? }[]` | The letters, each row's keys as a language note's `sounds.letters` |
8305
+ | `sounds.letters[].letters` | `string[]`, required | The letters or digraphs the row describes |
8306
+ | `sounds.letters[].kind` | `string`, required | What kind of sound they write, as `consonant` or `vowel pair` |
8307
+ | `sounds.letters[].like` | `string` | What they sound like |
8308
+ | `sounds.letters[].place` | `string` | Where in the mouth they are made |
8309
+ | `sounds.letters[].ipa` | `string` | The sounds they write, in the International Phonetic Alphabet |
8310
+ | `sounds.letters[].tongue` | an Address, or a list of them | The language notes that write the letters; every tongue where absent |
8311
+ | `sounds.marks` | `{ mark, letters, kind, limit }[]` | The marks written on the letters, each row's keys as a language note's `sounds.marks` |
8312
+ | `sounds.marks[].mark` | `string`, required | The mark's name, as `acute` |
8313
+ | `sounds.marks[].letters` | `string[]`, required | The letters that carry it |
8314
+ | `sounds.marks[].kind` | `stress` or `length`, required | What it says of its letter |
8315
+ | `sounds.marks[].limit` | `one` or `any`, required | How many a word may carry |
8222
8316
 
8223
8317
  A setting guide introduces one culture's view of the setting, so it names that
8224
8318
  culture, and a `doc` of any other `subType` has no culture of its own to name.