@heroiclands/package-build 23.13.0 → 24.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +2 -1
  3. package/assets/preview/gitea.ini +12 -7
  4. package/assets/styles/content-base.css +150 -1
  5. package/bin/content-commands.mjs +356 -210
  6. package/bin/package-build.mjs +3 -2
  7. package/ci/check-banners.mjs +7 -7
  8. package/config.mjs +21 -6
  9. package/content-config.mjs +199 -80
  10. package/docs/api.md +93 -72
  11. package/docs/authoring/links-and-markup.md +316 -78
  12. package/docs/commands.md +244 -78
  13. package/docs/configuration.md +210 -102
  14. package/docs/content-format.md +2 -2
  15. package/docs/diagnostics.md +2 -2
  16. package/docs/getting-started.md +1 -1
  17. package/docs/guides/icons.md +1 -1
  18. package/docs/guides/module-project.md +5 -8
  19. package/docs/guides/release-and-deploy.md +2 -2
  20. package/docs/guides/site.md +112 -23
  21. package/docs/guides/system-project.md +1 -1
  22. package/docs/guides/writing-in-invented-scripts.md +1247 -0
  23. package/docs/index.md +1 -0
  24. package/docs/project-setup.md +28 -30
  25. package/docs/reference/format-details.md +589 -308
  26. package/docs/reference/note-types.md +40 -10
  27. package/docs/reference/theme-pages.md +127 -108
  28. package/docs/source-preview.md +15 -1
  29. package/engine/asset-shipping.mjs +22 -10
  30. package/engine/base-compiler.mjs +62 -104
  31. package/engine/changelog-lint.mjs +1 -1
  32. package/engine/content-address.mjs +1 -1
  33. package/engine/content-alerts.mjs +8 -4
  34. package/engine/content-blocks.mjs +26 -11
  35. package/engine/content-embeds.mjs +1 -1
  36. package/engine/content-expand.mjs +376 -0
  37. package/engine/content-figures.mjs +13 -6
  38. package/engine/content-format.yaml +8 -0
  39. package/engine/content-glyphs.mjs +213 -0
  40. package/engine/content-images.mjs +6 -7
  41. package/engine/content-links.mjs +3 -3
  42. package/engine/content-lint.mjs +1 -1
  43. package/engine/content-render.mjs +453 -0
  44. package/engine/content-scripts.mjs +326 -27
  45. package/engine/content-spans.mjs +4 -1
  46. package/engine/content-styles.mjs +91 -0
  47. package/engine/content-symbols.mjs +186 -0
  48. package/engine/content-verse.mjs +6 -18
  49. package/engine/core-report.mjs +169 -0
  50. package/engine/core-vocabulary.mjs +208 -0
  51. package/engine/dictionary-html.mjs +119 -0
  52. package/engine/field-reference.mjs +1 -1
  53. package/engine/frontmatter-lint.mjs +1 -1
  54. package/engine/heading-attributes.mjs +89 -0
  55. package/engine/helpers.mjs +61 -101
  56. package/engine/homepage.mjs +17 -29
  57. package/engine/html-minify.mjs +70 -0
  58. package/engine/index.mjs +8 -5
  59. package/engine/language-rows.mjs +8 -2
  60. package/engine/language-terms.mjs +115 -14
  61. package/engine/language-words.mjs +12 -0
  62. package/engine/logographic.mjs +255 -0
  63. package/engine/map-lexicon.mjs +221 -25
  64. package/engine/map-places.mjs +5 -4
  65. package/engine/markdown-expressions.mjs +0 -2
  66. package/engine/markdownlint-run.mjs +9 -3
  67. package/engine/markup-validation.mjs +21 -0
  68. package/engine/metre-declaration.mjs +134 -0
  69. package/engine/note-matter.mjs +2 -2
  70. package/engine/note-vocabulary.mjs +187 -3
  71. package/engine/pdf-build.mjs +68 -133
  72. package/engine/pdf-render.mjs +102 -44
  73. package/engine/post-fields.mjs +157 -0
  74. package/engine/preview-index.mjs +130 -0
  75. package/engine/preview-source.mjs +450 -0
  76. package/engine/project-init.mjs +5 -12
  77. package/engine/prose-config.mjs +3 -3
  78. package/engine/run-words.mjs +3 -2
  79. package/engine/script-emphasis-terms.mjs +38 -0
  80. package/engine/script-emphasis.mjs +295 -0
  81. package/engine/script-font-delivery.mjs +4 -4
  82. package/engine/site-build.mjs +259 -314
  83. package/engine/site-config.mjs +26 -238
  84. package/engine/site-files.mjs +175 -0
  85. package/engine/site-index.mjs +9 -4
  86. package/engine/site-preview.mjs +50 -523
  87. package/engine/site-render.mjs +593 -0
  88. package/engine/site-root.mjs +22 -13
  89. package/engine/site-search.mjs +161 -0
  90. package/engine/site-serve.mjs +273 -0
  91. package/engine/site-theme-banner.mjs +154 -0
  92. package/engine/site-theme-urls.mjs +125 -0
  93. package/engine/site-theme.mjs +926 -0
  94. package/engine/text-maps.mjs +57 -10
  95. package/engine/typographer.mjs +248 -0
  96. package/engine/verse-metre.mjs +357 -0
  97. package/engine/wikilink-syntax.mjs +29 -47
  98. package/engine/wikilinks.mjs +758 -279
  99. package/engine/word-forms.mjs +39 -24
  100. package/engine/word-rows.mjs +12 -2
  101. package/engine/word-terms.mjs +29 -4
  102. package/manifest.mjs +3 -4
  103. package/package.json +10 -7
  104. package/{hugo-theme → site-theme}/data/banners.yaml +6 -10
  105. package/site-theme/layouts/base.hbs +31 -0
  106. package/site-theme/layouts/blog-list.hbs +30 -0
  107. package/site-theme/layouts/blog-post.hbs +40 -0
  108. package/site-theme/layouts/home.hbs +35 -0
  109. package/site-theme/layouts/list.hbs +20 -0
  110. package/site-theme/layouts/not-found.hbs +31 -0
  111. package/site-theme/layouts/page.hbs +7 -0
  112. package/site-theme/partials/copy-buttons.hbs +37 -0
  113. package/site-theme/partials/draft-notice.hbs +5 -0
  114. package/site-theme/partials/exturl.hbs +2 -0
  115. package/{hugo-theme/layouts/partials/footer.html → site-theme/partials/footer.hbs} +10 -7
  116. package/site-theme/partials/header.hbs +44 -0
  117. package/site-theme/partials/hero-banner.hbs +11 -0
  118. package/site-theme/partials/infobox-entry.hbs +2 -0
  119. package/site-theme/partials/infobox-section.hbs +31 -0
  120. package/site-theme/partials/infobox-value.hbs +2 -0
  121. package/site-theme/partials/infobox.hbs +18 -0
  122. package/site-theme/partials/list-members.hbs +9 -0
  123. package/site-theme/partials/nav-links.hbs +5 -0
  124. package/site-theme/partials/page-body.hbs +48 -0
  125. package/site-theme/partials/page-list.hbs +14 -0
  126. package/site-theme/partials/related.hbs +24 -0
  127. package/site-theme/partials/search-meta.hbs +6 -0
  128. package/site-theme/partials/search-names.hbs +2 -0
  129. package/site-theme/partials/search.hbs +52 -0
  130. package/site-theme/partials/tags.hbs +4 -0
  131. package/site-theme/partials/toc.hbs +7 -0
  132. package/{hugo-theme → site-theme}/static/css/style.css +14 -0
  133. package/stage.mjs +1 -2
  134. package/types/config.d.mts +8 -4
  135. package/types/content-config.d.mts +15 -15
  136. package/types/engine/asset-shipping.d.mts +5 -2
  137. package/types/engine/base-compiler.d.mts +20 -2
  138. package/types/engine/content-address.d.mts +1 -1
  139. package/types/engine/content-alerts.d.mts +8 -2
  140. package/types/engine/content-blocks.d.mts +11 -4
  141. package/types/engine/content-expand.d.mts +288 -0
  142. package/types/engine/content-figures.d.mts +11 -3
  143. package/types/engine/content-glyphs.d.mts +85 -0
  144. package/types/engine/content-images.d.mts +6 -7
  145. package/types/engine/content-links.d.mts +1 -1
  146. package/types/engine/content-render.d.mts +199 -0
  147. package/types/engine/content-scripts.d.mts +62 -0
  148. package/types/engine/content-spans.d.mts +1 -1
  149. package/types/engine/content-styles.d.mts +33 -0
  150. package/types/engine/content-symbols.d.mts +35 -0
  151. package/types/engine/core-report.d.mts +45 -0
  152. package/types/engine/core-vocabulary.d.mts +53 -0
  153. package/types/engine/dictionary-html.d.mts +8 -0
  154. package/types/engine/field-reference.d.mts +1 -1
  155. package/types/engine/heading-attributes.d.mts +20 -0
  156. package/types/engine/helpers.d.mts +3 -10
  157. package/types/engine/homepage.d.mts +11 -24
  158. package/types/engine/html-minify.d.mts +9 -0
  159. package/types/engine/index.d.mts +2 -1
  160. package/types/engine/language-terms.d.mts +24 -2
  161. package/types/engine/logographic.d.mts +45 -0
  162. package/types/engine/map-lexicon.d.mts +48 -8
  163. package/types/engine/map-places.d.mts +8 -0
  164. package/types/engine/markdown-expressions.d.mts +0 -2
  165. package/types/engine/metre-declaration.d.mts +39 -0
  166. package/types/engine/pdf-render.d.mts +6 -2
  167. package/types/engine/post-fields.d.mts +44 -0
  168. package/types/engine/preview-index.d.mts +72 -0
  169. package/types/engine/preview-source.d.mts +51 -0
  170. package/types/engine/script-emphasis-terms.d.mts +21 -0
  171. package/types/engine/script-emphasis.d.mts +84 -0
  172. package/types/engine/script-font-delivery.d.mts +4 -4
  173. package/types/engine/site-build.d.mts +71 -113
  174. package/types/engine/site-config.d.mts +14 -134
  175. package/types/engine/site-files.d.mts +73 -0
  176. package/types/engine/site-index.d.mts +11 -3
  177. package/types/engine/site-preview.d.mts +15 -19
  178. package/types/engine/site-render.d.mts +171 -0
  179. package/types/engine/site-root.d.mts +12 -4
  180. package/types/engine/site-search.d.mts +54 -0
  181. package/types/engine/site-serve.d.mts +44 -0
  182. package/types/engine/site-theme-banner.d.mts +51 -0
  183. package/types/engine/site-theme-urls.d.mts +60 -0
  184. package/types/engine/site-theme.d.mts +127 -0
  185. package/types/engine/text-maps.d.mts +17 -3
  186. package/types/engine/typographer.d.mts +11 -0
  187. package/types/engine/verse-metre.d.mts +131 -0
  188. package/types/engine/wikilink-syntax.d.mts +30 -48
  189. package/types/engine/wikilinks.d.mts +330 -14
  190. package/types/engine/word-forms.d.mts +3 -2
  191. package/types/manifest.d.mts +3 -4
  192. package/types/stage.d.mts +1 -2
  193. package/types/vendor/markdownlint.d.mts +8 -0
  194. package/vendor/markdownlint.mjs +20388 -0
  195. package/engine/web-wikilinks.mjs +0 -594
  196. package/hugo-theme/layouts/404.html +0 -85
  197. package/hugo-theme/layouts/_default/baseof.html +0 -67
  198. package/hugo-theme/layouts/_default/list.html +0 -139
  199. package/hugo-theme/layouts/_default/single.html +0 -4
  200. package/hugo-theme/layouts/blog/list.html +0 -54
  201. package/hugo-theme/layouts/blog-post/single.html +0 -36
  202. package/hugo-theme/layouts/index.html +0 -75
  203. package/hugo-theme/layouts/partials/banner-url.html +0 -141
  204. package/hugo-theme/layouts/partials/cdn-url.html +0 -36
  205. package/hugo-theme/layouts/partials/dictionary-lists.html +0 -58
  206. package/hugo-theme/layouts/partials/draft-notice.html +0 -41
  207. package/hugo-theme/layouts/partials/draft-state.html +0 -26
  208. package/hugo-theme/layouts/partials/header.html +0 -35
  209. package/hugo-theme/layouts/partials/hero-banner.html +0 -85
  210. package/hugo-theme/layouts/partials/infobox/entry.html +0 -18
  211. package/hugo-theme/layouts/partials/infobox/items.html +0 -26
  212. package/hugo-theme/layouts/partials/infobox/section.html +0 -56
  213. package/hugo-theme/layouts/partials/infobox/value.html +0 -43
  214. package/hugo-theme/layouts/partials/infobox.html +0 -57
  215. package/hugo-theme/layouts/partials/page-body.html +0 -152
  216. package/hugo-theme/layouts/partials/related.html +0 -138
  217. package/hugo-theme/layouts/partials/search.html +0 -84
  218. package/hugo-theme/layouts/partials/site-url.html +0 -32
  219. package/hugo-theme/layouts/partials/tags.html +0 -30
  220. package/hugo-theme/layouts/partials/toc.html +0 -28
  221. package/hugo-theme/layouts/partials/warn-missing-banner.html +0 -3
  222. package/hugo-theme/layouts/shortcodes/cartouche.html +0 -64
  223. package/hugo-theme/layouts/shortcodes/exturl.html +0 -12
  224. package/hugo-theme/layouts/shortcodes/glyph.html +0 -43
  225. package/hugo-theme/layouts/shortcodes/pagelist.html +0 -63
  226. package/hugo-theme/theme.toml +0 -11
  227. package/types/engine/web-wikilinks.d.mts +0 -107
  228. /package/{hugo-theme → site-theme}/data/banner-kinds.yaml +0 -0
  229. /package/{hugo-theme → site-theme}/static/css/sohl-icons.css +0 -0
  230. /package/{hugo-theme → site-theme}/static/fonts/game-icons-kb.woff2 +0 -0
  231. /package/{hugo-theme → site-theme}/static/img/banners/takheperu-hieroglyphs.svg +0 -0
  232. /package/{hugo-theme → site-theme}/static/img/cartouches/takheperu.svg +0 -0
  233. /package/{hugo-theme → site-theme}/static/js/inline-spoilers.js +0 -0
@@ -0,0 +1,1247 @@
1
+ ---
2
+ shortcode: guidesinventedscripts
3
+ name: { full: "Write in invented languages and scripts" }
4
+ type: doc
5
+ subType: howto
6
+ ---
7
+
8
+ # Write in invented languages and scripts
9
+
10
+ A setting with invented languages needs three things from its notes: text that
11
+ reads as the language, text that is drawn in the language's own letters, and a
12
+ way to keep both honest. This guide explains how the toolchain does that, from
13
+ the model underneath to every finding it can report, for human authors and for
14
+ agents. The reference pages state each rule exactly: [links and
15
+ markup](../authoring/links-and-markup.md#marking-language-font-and-mapping),
16
+ [format details](../reference/format-details.md#text-marked-with-a-language-a-font-or-a-map),
17
+ [commands](../commands.md#package-build-transliterate-map-text),
18
+ [configuration](../configuration.md#languages) and
19
+ [diagnostics](../diagnostics.md). This guide ties them into one account and
20
+ works one example through from start to finish.
21
+
22
+ The examples write Vylari, a tongue spelled in roman letters and drawn in the
23
+ Semaron script. The lexicon and the notes in them are the guide's own sample.
24
+
25
+ ## The model
26
+
27
+ These ideas carry everything else.
28
+
29
+ 1. **A note records Unicode characters, and nothing else.** Which keyboard
30
+ typed them is the author's business. The markdown and the build neither
31
+ know nor care.
32
+ 2. **A font draws glyphs for code points.** A code point a font lacks draws a
33
+ box, so the build refuses it before a box reaches a reader. A font states no
34
+ sounds and no spelling, just as a Unicode block chart states none.
35
+ 3. **Each script has code points of its own,** as Runic and Ogham have their
36
+ own Unicode blocks. Semaron's `a` is not U+0061. Every face of one script
37
+ draws the same code points, so changing the face changes only the look. The
38
+ left-to-right scripts sit in plane 15, the Supplementary Private Use Area-A
39
+ (Semaron from U+F0200). The right-to-left scripts sit in unassigned code
40
+ points of plane 1 whose default direction is right to left, so their text
41
+ runs right to left in any editor, browser or typesetter with no control
42
+ characters.
43
+ 4. **Sounds and spelling belong to the language.** A script is not a language:
44
+ several languages are written in one script, as many Earth languages share
45
+ the Latin alphabet. A mapping therefore belongs to a script and serves every
46
+ language written in it. What differs between languages is their dictionaries.
47
+ 5. **Three optional keys mark text:** `lang=` says what language it is, `font=`
48
+ says how it is drawn, and `map=` names the mapping that rewrites it into the
49
+ script's code points. They are independent.
50
+ 6. **Without `map=`, the characters reach the page exactly as written.**
51
+ Nothing is checked against a language.
52
+ 7. **With `map=`, the build converts, and with `lang=` as well it checks.**
53
+ Each word is looked up in the language's dictionary first; a word the
54
+ dictionary does not write is spelled by the mapping's rows. A mapped run
55
+ with `lang=` must then consist of words of that language.
56
+
57
+ A script that writes meanings rather than sounds is **logographic**. It has no
58
+ sound rules, so every word is written from the dictionary and never guessed;
59
+ see [logographic scripts](#logographic-scripts).
60
+
61
+ One code path serves the build and its users. `package-build transliterate`
62
+ converts a passage exactly as the build does, and `package-build lexicon` lists
63
+ every word of a language with its native form; see
64
+ [the commands](#the-commands).
65
+
66
+ The pipeline for one marked run reads left to right:
67
+
68
+ | Stage | Reads | Reports |
69
+ | ------------- | ------------------------------------ | ------------------------------------------ |
70
+ | Keys | `lang=`, `font=`, `map=` | an unknown language, font or map |
71
+ | Dictionary | the language's `data.words[].native` | a native form the rows cannot capitalise |
72
+ | Rows | the mapping's `data.map.rows` | nothing: a character with no row passes on |
73
+ | Lexicon check | the language's words and register | a word the language does not hold |
74
+ | Face coverage | the face's `fonts.json` code points | a character the face does not draw |
75
+ | Element | the face, the tag and the direction | nothing |
76
+
77
+ ## The three keys
78
+
79
+ The keys go in the attribute block that a span, a fenced div or a `verse`
80
+ fence already takes:
81
+
82
+ | Written | Meaning |
83
+ | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
84
+ | `[Daimela, helthos.]{lang=vyl}` | Vylari, tagged `art-x-vyl`, in roman and in the page's font. Not checked. |
85
+ | `[…]{font=semaron-kuriale}` | The code points exactly as typed, drawn in Semaron Kuriale. Checked for coverage only. |
86
+ | `[Zaron lonak]{lang=vyl font=semaron-kuriale map=semaron}` | Vylari typed in roman, mapped through the dictionary and the rows into Semaron, and checked against Vylari. |
87
+ | `[…]{map=semaron}` with no `lang=` | The rows alone: no dictionary, no lexicon check. |
88
+ | `[ad astra]{lang=la font=serif}` | A real language's BCP 47 tag, unchecked, in the reader's serif. |
89
+ | `::: {lang=… font=… map=…}` … `:::` | The same keys for a passage of paragraphs. |
90
+ | a `verse` fence opened with `{lang=… font=… map=…}` | The same keys for a poem. |
91
+
92
+ **`lang=`** is read in this order. First a short name declared in the
93
+ configuration's `languages`; then a BCP 47 language tag for a real language
94
+ (`la`, `grc`, `en-GB`), checked for its form alone. A declared name wins over a
95
+ tag spelled the same way. A well-formed tag one edit from a declared name draws
96
+ a warning that names the declared one, since `vly` is a valid tag for another
97
+ language than `vyl`.
98
+
99
+ **`font=`** is a face of `@heroiclands/fonts`, named by its class without
100
+ `hl-` (`hl-semaron-kuriale` is `font=semaron-kuriale`), or one of the generic
101
+ families `mono`, `serif` and `sans`. A package that sets text in a face adds the
102
+ fonts to its own development dependencies:
103
+
104
+ ```bash
105
+ npm install --save-dev @heroiclands/fonts
106
+ ```
107
+
108
+ The installed package's `fonts.json` lists every face and every code point it
109
+ draws, which is the list to write a mapping from. This prints Semaron Kuriale's:
110
+
111
+ ```bash
112
+ jq -r '.families[] | select(.class == "hl-semaron-kuriale") | .codepoints[] | [.codepoint, .kind, .glyph, .name] | @tsv' \
113
+ node_modules/@heroiclands/fonts/fonts.json
114
+ ```
115
+
116
+ Each line is a code point, its kind (`letter`, `digraph`, `numeral`, `point`, `mark`
117
+ or `sign`), the glyph's name and the character's name; the glyph named `a` is the
118
+ letter that writes the sound `a`, and `a.long` the long vowel. Those names are
119
+ what a mapping's rows are written from.
120
+
121
+ On the site `serif` and `mono` are the site's own body and code faces and
122
+ `sans` is the reader's sans-serif; in Foundry they are the reader's generic
123
+ families; in a book they are the book's own serif, sans and mono. The generic
124
+ families are not checked for coverage.
125
+
126
+ **`map=`** is the `data.map.name` of a [mapping note](#mapping-notes). It
127
+ rewrites the run before anything else; a run's `font=` is then one of the faces
128
+ the mapping lists.
129
+
130
+ The `languages` block of `package-build.config.yaml` declares the short names:
131
+
132
+ ```yaml
133
+ languages:
134
+ vyl: { tag: art-x-vyl, note: skill-vylarilng }
135
+ khel: { tag: art-x-khel, note: skill-khelathlng }
136
+ ```
137
+
138
+ A name is lowercase letters, digits and hyphens, starting with a letter. `tag`
139
+ is a BCP 47 tag; a constructed language takes `art-x-<name>`. `note` is the
140
+ address of the language's `skill` note of `subType: language`, whose
141
+ `data.words` is the dictionary and whose `data.register` is the names register.
142
+ Without `note`, `lang=` only tags: there is no dictionary and no lexicon check.
143
+ `package-build lint` checks at the key that `note` names a language note.
144
+
145
+ **A run inherits from its passage and overrides it.** A span inside a marked
146
+ div or fence takes the passage's keys, and its own keys win. A Khelathi phrase
147
+ inside a Vylari poem is checked against Khelathi's lexicon and tagged Khelathi
148
+ but still drawn through Semaron's rows:
149
+
150
+ ```markdown
151
+ ::: {lang=vyl font=semaron-kuriale map=semaron}
152
+ Zaron lonak, [Gar-Anlagh]{lang=khel}, lokamen zaron.
153
+ :::
154
+ ```
155
+
156
+ To draw it in Qalezu instead, the span states its own face and mapping,
157
+ `[Gar-Anlagh]{lang=khel font=qalezu-hand map=qalezu}`. Qalezu runs right to left
158
+ inside the left-to-right line with no `dir=` written. To set a gloss in the
159
+ page's own font, `[an oath]{lang=en font=serif}` inside the passage drops both
160
+ the face and the map by naming new ones.
161
+
162
+ **What a run may hold.** A marked span or verse line is plain text: no
163
+ emphasis (other than the stars a face's run takes, see [emphasis in a
164
+ script](#emphasis-in-a-script)), link, image, footnote, wikilink, code or HTML.
165
+ A marked div holds paragraphs and line breaks only. The text is read as written,
166
+ so `[`, `]`, `"`, `<<`, `>>`, `!` and `@ # $ % ^ &` are characters; a bracket
167
+ pair is markup only when `(` makes a link, `{` a span or a second `[` a
168
+ wikilink, and an unbalanced bracket takes a backslash, `\[`. A code fence is
169
+ verbatim: `[…]{…}` inside it stays literal text. Text marked with a real
170
+ language's tag alone keeps its Markdown, so `[ad _astra_]{lang=la}` works.
171
+
172
+ The three keys are errors on a named block such as a secret block. Put a marked
173
+ div inside the block instead.
174
+
175
+ ## Without a map
176
+
177
+ A run that names `font=` and no `map=` is set exactly as typed:
178
+
179
+ ```markdown
180
+ The lintel is cut with [󰈹󰈰]{font=semaron-kuriale}.
181
+ ```
182
+
183
+ where the run holds Semaron's own code points. The only check is face coverage:
184
+ after the run is read, every character must be one the face draws (or white
185
+ space), and any other is an error at the character it came from. Each character
186
+ is reported once per run:
187
+
188
+ > `` `x` (U+0078) is not drawn by the font `semaron-kuriale` ``
189
+
190
+ Fix: type the face's code points, or add `map=`. The face's `fonts.json` states
191
+ what it draws.
192
+
193
+ There are two ways to type the code points. The fonts package ships keyboard
194
+ layouts and Emacs input methods for its scripts, under its `keyboards/`; and an
195
+ editor font that carries the scripts draws them where they are typed. In
196
+ either case the note holds the characters and needs only `font=`. Typing roman
197
+ and letting `map=` convert it keeps the note's source readable and diffable and
198
+ is the usual choice for a language with a dictionary.
199
+
200
+ A run with `lang=` and no `font=` is tagged and nothing more.
201
+
202
+ ## Mapping notes
203
+
204
+ A mapping note is a `doc` note whose `data.map` lists the rewrites. **There is
205
+ one mapping per script,** serving every language written in it. The note below
206
+ is complete; the rows are the Semaron letters this guide's sample uses.
207
+
208
+ **`assets/content/Lore/Semaron_Spelling.md`**
209
+
210
+ ```markdown
211
+ ---
212
+ shortcode: mapsemaron
213
+ name: { full: Semaron Spelling }
214
+ type: doc
215
+ subType: reference
216
+ data:
217
+ map:
218
+ name: semaron
219
+ vowels: "aeiou"
220
+ rows:
221
+ - ["a", "󰈰"] # U+F0230
222
+ - ["d", "󰈲"] # U+F0232
223
+ - ["e", "󰈳"] # U+F0233
224
+ - ["h", "󰈶"] # U+F0236
225
+ - ["i", "󰈷"] # U+F0237
226
+ - ["k", "󰈹"] # U+F0239
227
+ - ["l", "󰈺"] # U+F023A
228
+ - ["m", "󰈻"] # U+F023B
229
+ - ["n", "󰈼"] # U+F023C
230
+ - ["o", "󰈽"] # U+F023D
231
+ - ["r", "󰈿"] # U+F023F
232
+ - ["s", "󰉀"] # U+F0240
233
+ - ["t", "󰉁"] # U+F0241
234
+ - ["u", "󰉂"] # U+F0242
235
+ - ["z", "󰉆"] # U+F0246
236
+ - ["aa", "󰉈"] # U+F0248
237
+ - ["th", "󰉐"] # U+F0250
238
+ - ["Th", "󰉑"] # U+F0251
239
+ - ["L", "󰈊"] # U+F020A
240
+ - ["T", "󰈑"] # U+F0211
241
+ - ["Z", "󰈖"] # U+F0216
242
+ - { from: "y", to: "󰉅", before: vowel } # U+F0245
243
+ - ["y", "󰈷"] # U+F0237
244
+ - [".", "󰉨"] # U+F0268
245
+ - [",", "󰉨"] # U+F0268
246
+ fonts: [{ font: semaron-kuriale }]
247
+ ---
248
+
249
+ Semaron Spelling writes roman spelling in the Semaron script. A passage marked with
250
+ `map=semaron` is converted by the rows of this note before it is set, so the note's
251
+ source stays in roman letters and the page shows the script. Every language written
252
+ in Semaron shares these rows.
253
+ ```
254
+
255
+ The keys of `data.map`:
256
+
257
+ | Key | Meaning |
258
+ | ------------- | -------------------------------------------------------------------------------------------- |
259
+ | `name` | What `map=` names it by: lowercase letters, digits and `-`, unique in the package. Required. |
260
+ | `rows` | The rewrites, in authored order. Required. |
261
+ | `fonts` | The faces the mapping is drawn in, each by its slug, at least one. Required. |
262
+ | `vowels` | The characters that `before: vowel` reads as vowels. Any other letter is a consonant. |
263
+ | `logographic` | `true` for a script that writes meanings; see [logographic scripts](#logographic-scripts). |
264
+
265
+ **Rows.** A row is `[from, to]`, or `{ from, to, before, at }` with the two
266
+ conditions optional. `from` is one or more characters; `to` is any number of
267
+ them, none included. A mapping that drops a language's vowels writes `[a, ""]`.
268
+ `rows` is a list, never a keyed map, so `A` and `a` are two rows.
269
+
270
+ **How a mapping reads text.**
271
+
272
+ - **The longest match wins.** In the note above `th` is one letter, and `t` and
273
+ `h` are read only where `th` does not match. Among rows of one length, a row
274
+ whose condition holds comes before a row with none, and otherwise the earlier
275
+ row wins.
276
+ - **A row whose condition fails is skipped.** `before: vowel` holds when the next
277
+ character is one of `vowels`; `before: consonant` when it is any other
278
+ letter; neither holds at the end of the text. `at: start` holds at the first
279
+ character of the run or after white space or punctuation; `at: end` holds at
280
+ the last character or before white space or punctuation. In the note above,
281
+ `y` is the consonant letter before a vowel and the vowel `i` elsewhere.
282
+ - **Capitals and marks need rows of their own.** A row matches the character as
283
+ written, so `L` and `l` are two rows, and a script with no capitals gives both
284
+ the same code point. The full stop and the comma are characters too: a run
285
+ that holds one needs a row for it, or the face-coverage check reports it.
286
+ - **A character no row matches passes through unchanged,** and the face-coverage
287
+ check then reports it. A mapping for a face that draws nothing on roman code
288
+ points lists every letter the spelling uses.
289
+
290
+ **Fonts.** `fonts` lists each face the mapping may be used with, as a list of
291
+ `{ font, rows }`. A face may carry rows of its own, which a run in that face
292
+ tries first (longest match among them) before the shared rows. That is the way
293
+ to write a sound differently in a variant face. The generic families `mono`,
294
+ `serif` and `sans` are not listed. The lint checks that every listed face draws
295
+ every code point that its own rows, the shared rows it can reach and the
296
+ lexicons' native forms for it write.
297
+
298
+ **Code points above U+FFFF in YAML.** A double-quoted YAML string writes a
299
+ code point by its escape, and the left-to-right scripts sit in plane 15, so
300
+ they take the eight-digit `\U`: `"\U000F0230"` is U+F0230, and the four-digit
301
+ `\u` reaches only code points up to U+FFFF. Both forms mean the same thing.
302
+ `package-build format --write` (Prettier) rewrites such an escape into the
303
+ character itself, so a mapping note on disk usually holds the raw Private Use
304
+ characters, as the note above does. Those read only in one of the fonts
305
+ package's faces or its editor fonts, and elsewhere show as boxes. The
306
+ convention is a trailing comment, `# U+F0230`, on every row, which keeps the
307
+ note readable in any editor and survives formatting. A row that writes several
308
+ code points lists them all in its comment.
309
+
310
+ **Where the checks sit.** Every key is checked at its own line and column:
311
+
312
+ > `` `data.map.name` is `Vyl_Semaron`; a map's name is lowercase letters, digits and `-` ``
313
+
314
+ > `` `data.map.rows[4]` has `before: consonant`, and the map states no `vowels` to read it by ``
315
+
316
+ > `` `data.map.rows[1]` has an empty `from`; a row matches one or more characters ``
317
+
318
+ > `` `data.map.fonts` is empty; a map lists each face it is drawn in ``
319
+
320
+ > `` `data.map.rows[5]` writes U+F0239, which the font `semaron-kuriale`, listed in `data.map.fonts`, does not draw ``
321
+
322
+ A row with a fault is left out of the map and the rest apply.
323
+
324
+ ## How a mapping treats punctuation
325
+
326
+ The toolchain is used with one convention for marks: **mood, questions and
327
+ quotation are carried by words, not marks,** unless a language's note rules
328
+ otherwise. A mapping therefore lists the marks its script has and nothing more.
329
+ In the sample, a comma and a full stop are the script's one point:
330
+
331
+ ```yaml
332
+ - [".", "\U000F0268"] # U+F0268
333
+ - [",", "\U000F0268"] # U+F0268
334
+ ```
335
+
336
+ (Formatting rewrites the escapes, as above.) A character with no row passes
337
+ through unchanged, and the face does not draw it, so a question mark in a
338
+ mapped run is an error that names the mark and the mapping:
339
+
340
+ > `` `?` (U+003F) is not drawn by the font `semaron-kuriale`, and the map `semaron` has no row for it ``
341
+
342
+ The remedy is to write the question or the quotation in words, as the language
343
+ does, and leave the mark out of the run. A language whose note rules
344
+ otherwise gets rows for its marks in the mapping, and the check stops
345
+ reporting them. The same holds for the digits, the hyphen and the catch: a row
346
+ for each makes the mark part of the script; none leaves it out.
347
+
348
+ ## The dictionary first
349
+
350
+ The sound rules belong to the script and the exceptions belong to the language.
351
+ With `lang=` naming a language that has a `note`, the build reads the
352
+ language's `data.words` before the rows.
353
+
354
+ **How a run is split into words.** A word is a run of letters, combining marks
355
+ and digits, with an apostrophe (`'` or `’`), a hyphen or an en dash joining two
356
+ such runs into one word. `k'aralzon` is one word and `zaron-lonak` is one word
357
+ for lookup. Everything between words is spelled by the rows.
358
+
359
+ **How a word is written.** The word is looked up as written, and then in
360
+ lowercase. A word that states a `native` form for the run's mapping (for the
361
+ run's face where an entry names one, else the one for no face) is written by it
362
+ whole. Every other word, with every derived form of an exception word, is
363
+ spelled by the rows. There are no stem-level exceptions: the plural of an
364
+ exception word is another word, and the rows spell it.
365
+
366
+ **A native form states only an exception.** `native` is a list of
367
+ `{ map, font, text }` on a word:
368
+
369
+ ```yaml
370
+ - form: zaron
371
+ class: n
372
+ field: Law
373
+ gloss: an oath
374
+ native:
375
+ - { map: semaron, text: "\U000F0246\U000F0248\U000F023F\U000F023D\U000F023C" }
376
+ ```
377
+
378
+ `map` is the mapping's `name`, `font` optionally limits the form to one face, and
379
+ `text` is the word as the script writes it. A form that the rows already give is
380
+ redundant, and the lint says so:
381
+
382
+ > `` `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 ``
383
+
384
+ so a dictionary holds its exceptions and nothing else. The other checks of a
385
+ native form (an unknown map, a face the map is not for, a second form for one
386
+ map, a character the face does not draw) report at the entry:
387
+
388
+ > `` `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`? ``
389
+
390
+ > `` `data.words[0].native[2]` states a second native form for the map `semaron`; the first is `data.words[0].native[0]` ``
391
+
392
+ > `` `data.words[0].native[0]` holds U+F024F, which the font `semaron-kuriale`, listed in the map `semaron`, does not draw ``
393
+
394
+ **Capitals.** A capitalised word that is found only in lowercase takes its
395
+ capital from the rows: the rows spell the first sound as written and in
396
+ lowercase, and where the native form begins with the lowercase spelling, that
397
+ beginning is replaced by the capital. A native form that does not begin the way
398
+ the rows begin the word cannot be capitalised, and the capitalised word is an
399
+ error at its place in the run:
400
+
401
+ > `` `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 ``
402
+
403
+ The fix is the one the message names: a lexicon word `Orlan` with its own
404
+ native form, which the lookup as written finds first.
405
+
406
+ A character the face does not draw inside a native form reports at the word:
407
+
408
+ > `` `zaron` is written U+F024F by the lexicon of `vyl` for the map `semaron`, which the font `semaron-kuriale` does not draw ``
409
+
410
+ A run with `map=` and no `lang=`, a `lang=` that is a bare tag and a declared
411
+ language with no `note` are spelled by the rows alone.
412
+
413
+ ## The checks
414
+
415
+ A run with `lang=` and `map=`, whose language has a `note`, is checked word by
416
+ word. The comparison is case folded and NFC, with `’` read as `'`. A word passes
417
+ when it is any of these:
418
+
419
+ 1. **A lexicon word:** a `data.words[].form` of the language. A form listed in a
420
+ word's `variants` is a known word too. A multi-word or compound form
421
+ contributes each of its parts.
422
+ 2. **A word of a registered name:** a word of a `name` in the language note's own
423
+ `data.register`, under any `tongue` but `translation`, which stands for an
424
+ English rendering and never counts. A name of several words is checked word
425
+ by word.
426
+ 3. **A regular form of a lexicon word,** made by one rule the language states:
427
+ - a noun's plural, its own `plural` or the first `data.grammar.plural` row
428
+ that fits;
429
+ - a verb's form with one ending of `data.grammar.verbs.persons`, which takes
430
+ the place of a closing `data.grammar.verbs.replaces` where the pattern
431
+ states one;
432
+ - a suffix of `data.affixes` that states `replaces`, or its `front` form;
433
+ - an agreeing form or a participle; see
434
+ [agreement, participles and affixes](#agreement-participles-and-affixes).
435
+
436
+ A compound that is not a word whole is split at its hyphens and en dashes and
437
+ each part is checked, so `zaron-lonak` passes when `zaron` and `lonak` do. A
438
+ word with no letter, such as a numeral, is skipped. A prefix, and an affix that
439
+ states no `replaces`, make no form: `attaches` is prose.
440
+
441
+ Any other word is an error at its first character, reported once per run, and
442
+ it names the language as `lang=` writes it:
443
+
444
+ > `` `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 ``
445
+
446
+ **Fix: coin the word,** add it to the language note's `data.words` with its
447
+ class, field and gloss, after the invented-word rules of the setting (palette,
448
+ Earth collisions and readability) have approved it:
449
+
450
+ ```yaml
451
+ words:
452
+ - form: thiruthrimar
453
+ class: n
454
+ field: Law
455
+ gloss: a sworn witness
456
+ ```
457
+
458
+ **or register the name** with its tongue in the language's `data.register`:
459
+
460
+ ```yaml
461
+ register:
462
+ - { name: Revothar Baremzator, tongue: vylari }
463
+ ```
464
+
465
+ **Foreign names.** A foreign name used in a Vylari run is first listed in
466
+ Vylari's own register as `other:<tongue>` or `exonym:<tongue>`, and that row is
467
+ checked against the tongue's own register in turn: the language note that a
468
+ `data.tongues` entry names by its `language` must list the same name under its
469
+ own tongue. A misspelt foreign name is caught where it is listed:
470
+
471
+ > `` `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 ``
472
+
473
+ Bare `other`, `kept`, `ancient` and `translation` rows are not checked.
474
+
475
+ **Nothing requires a form.** The check recognises forms and never demands one: a
476
+ noun stated with its own plural and the plural the rule gives both pass, and no
477
+ check asks that a word takes the right ending.
478
+
479
+ **Face coverage** follows the mapping. A character left over after the rows
480
+ reports once per run, at the start of the matched `from`:
481
+
482
+ > `` `x` (U+0078) is not drawn by the font `semaron-kuriale`, and the map `semaron` has no row for it ``
483
+
484
+ and a row that writes a code point the face lacks reports where it is applied:
485
+
486
+ > `` `ch` maps to U+F0299 in the map `semaron`, which the font `semaron-kuriale` does not draw ``
487
+
488
+ **Keys that name nothing.** A key that names nothing is the one finding, and
489
+ the run is not then checked against a face or a lexicon it cannot reach.
490
+
491
+ > `` `semaron-kurial` is not a font: `font=` names an installed face, or `mono`, `serif` or `sans`; did you mean `semaron-kuriale`? ``
492
+
493
+ > `` `semron` is not a map: `map=` names the `data.map.name` of a mapping note; did you mean `semaron`? ``
494
+
495
+ > `` `q` is not a declared language (`languages` in package-build.config.yaml) or a BCP 47 tag ``
496
+
497
+ > `` `thuravark-carved` is not a font of the map `semaron`; it is for `semaron-kuriale` ``
498
+
499
+ > `` `semaron-kuriale` is not a font: `font=` names `mono`, `serif` or `sans`, or a face of @heroiclands/fonts, which this package has not installed ``
500
+
501
+ The last one means `@heroiclands/fonts` is not installed. A near miss of a
502
+ declared language is a warning that fails nothing:
503
+
504
+ > `` `vly` is read as a BCP 47 tag; did you mean the declared language `vyl`? ``
505
+
506
+ ## Agreement, participles and affixes
507
+
508
+ Three optional pieces of a language note let the word check accept more forms.
509
+ None is required, and none makes any check demand a form.
510
+
511
+ **Agreement** states which classes agree with a noun and the singular endings
512
+ they agree by, one per gender. A word of one of `classes` that closes on one of
513
+ `endings` (the longest that leaves a stem) also has that ending swapped for each
514
+ other one, and each of those forms takes the `plural` rule:
515
+
516
+ ```yaml
517
+ grammar:
518
+ plural:
519
+ - { ending: os, becomes: oi }
520
+ - { ending: on, becomes: oi }
521
+ - { ending: a, becomes: ai }
522
+ agreement:
523
+ classes: [adj, pron]
524
+ endings: [os, a, on]
525
+ ```
526
+
527
+ **Participles** attach to the verb root, which is the citation form less its
528
+ infinitive ending. A verb closing on one of `root` drops it, and each suffix of
529
+ `data.affixes` that `affixes` names attaches to what is left, as its own
530
+ `replaces` says or whole where it states none. A participle that closes on an
531
+ agreement ending agrees:
532
+
533
+ ```yaml
534
+ affixes:
535
+ - { form: "-andar", gloss: "doing it: the present participle", replaces: vowel }
536
+ - { form: "-thos", gloss: "done, suffered: the past participle" }
537
+ grammar:
538
+ participles:
539
+ root: [athen, ethen]
540
+ affixes: ["-andar", "-thos"]
541
+ ```
542
+
543
+ **`attachesTo`** limits a suffix to words of the classes it lists, as in
544
+ `attachesTo: [n, adj]`; a suffix that states none attaches to a word of every
545
+ class. `replaces: vowel` takes the place of a closing vowel, letters such as
546
+ `replaces: a` take the place of those letters, and a stem that closes otherwise
547
+ takes the suffix whole.
548
+
549
+ With the note above, a run accepts:
550
+
551
+ | Lexicon word | Rule | Forms accepted |
552
+ | ----------------- | ----------- | ------------------------------------------------- |
553
+ | _alathos_, pron. | agreement | _alatha_, _alathon_, _alathoi_, _alathai_ |
554
+ | _lokathen_, v. | participles | _lokandar_, _lokthos_ |
555
+ | _lokathen_, v. | both | _loktha_, _lokthon_, _lokthoi_, _lokthai_ |
556
+ | _marendos_, n. m. | neither | _marendoi_ by the plural rule alone; no _marenda_ |
557
+
558
+ A near miss such as _alathe_ is still an unknown word. To see every form the
559
+ rules give, each beside the word it comes from, run
560
+ `package-build lexicon vyl --map semaron --derived`.
561
+
562
+ The keys are checked where they sit:
563
+
564
+ > `` `data.grammar.agreement.classes[0]` is `adjective`, which is not a word class; did you mean `adj`? ``
565
+
566
+ > `` `data.grammar.agreement.endings` must state at least two endings for a word to agree among ``
567
+
568
+ > `` `data.grammar.participles.affixes[2]` is `-zz`, which is not a suffix of `data.affixes` ``
569
+
570
+ > `` `data.grammar.participles.affixes[3]` is `an-`, a prefix; a participle is a suffix on the root ``
571
+
572
+ > `` `data.affixes[1].attachesTo[0]` is `verb`, which is not a word class; did you mean `v`? ``
573
+
574
+ ## Logographic scripts
575
+
576
+ **A script that writes meanings is written from the dictionary, never by
577
+ sound.** Its mapping note states `logographic: true`, and every word of a run
578
+ under it comes from the language's lexicon. The rows write only the text
579
+ between words: spaces, points and anything else with no letter. A word never
580
+ falls through to the rows, so a word the lexicon gives no writing is an error
581
+ rather than a guess.
582
+
583
+ A word is written by the first of these that gives a writing, matched case
584
+ folded because the script has no capitals:
585
+
586
+ 1. **Its own `native` entry** for the map (the run's face where an entry names
587
+ one, else the one for no face), or the entry of a set compound written its
588
+ own way.
589
+ 2. **Its parts, joined.** The roots of the entry's `built` list in order; each
590
+ piece is a lexicon word or an affix of `data.affixes` with a `native` entry of
591
+ its own, resolved the same way down. A noun's plural and a verb's present
592
+ forms are the stem's writing followed by the ending's, which
593
+ `data.grammar.native` states once per ending: `plural`, or one of the persons
594
+ `I`, `you`, `heShe`, `we`, `youAll` and `they`.
595
+ 3. **Its hyphenated parts,** each by these same steps, so `Gar-halzunet` is
596
+ `Gar-`'s writing followed by `halzunet`'s.
597
+ 4. **Otherwise an error** at the word.
598
+
599
+ Forms outside the parts rule (a suffix that replaces a vowel, an agreeing form,
600
+ a participle) have no writing of their own. They are coined as lexicon words
601
+ with a writing, or written in signs.
602
+
603
+ ```yaml
604
+ affixes:
605
+ - form: "-unet"
606
+ gloss: "a thing made of"
607
+ native:
608
+ - { map: zethu, text: "𞯇" }
609
+ grammar:
610
+ plural:
611
+ - { ending: "", becomes: "im" }
612
+ native:
613
+ - { ending: plural, map: zethu, text: "𞯡" }
614
+ words:
615
+ - form: halzi
616
+ class: n
617
+ field: The body
618
+ gloss: heart
619
+ native:
620
+ - { map: zethu, text: "𞪻" }
621
+ - form: halzunet
622
+ class: n
623
+ field: The body
624
+ gloss: the account
625
+ built: [halzi, -unet]
626
+ ```
627
+
628
+ `[halzi halzunet halziim]{lang=khel font=khelathi-zethu-glyphs map=zethu}` is
629
+ written U+1EABB, then U+1EABB U+1EBC7, then U+1EABB U+1EBE1, with the spaces the
630
+ rows write between them. An affix's `native` and `data.grammar.native` are read
631
+ only under a logographic map; naming any other map is an error at the entry.
632
+
633
+ **Ambiguity.** One roman form that stands for entries written differently is an
634
+ error naming every entry's gloss, and so is one form with a writing for one
635
+ sense and none for another. Nothing is guessed. List the words with
636
+ `package-build lexicon khel --map zethu`: the third column says where each
637
+ writing comes from, `own` for a stored writing, `parts` for one the pieces give,
638
+ `none` for a word with no writing, and `ambiguous` for a form a run cannot
639
+ write. An ambiguous word's second column is still that entry's own writing, so
640
+ pick the entry by its gloss and write those signs directly in the run. Signs
641
+ typed in a run hold no letter, so they are no word and pass through as typed.
642
+
643
+ **Foreign names.** A registered name is written like any other word, so a name
644
+ with no writing is an error. A foreign name is written deliberately: its signs
645
+ typed directly, or through a mapping whose rows read a sign-by-sign notation
646
+ (sign ids and joiners, as in Manuel de Codage input), which an author writes the
647
+ way any other mapping is written, and used on a run with `map=` and no `lang=`.
648
+
649
+ **An unknown word gets two errors:** the lexicon check reports it is no word of
650
+ the language, and the writing check reports it has no writing.
651
+
652
+ > `` `zamlu` has no `zethu` writing in the `khel` lexicon, and its parts do not give one; state one, or write the word directly in signs ``
653
+
654
+ > `` `nelgir` stands for 2 words of the `khel` lexicon that the map `zethu` writes differently, “a god” and “god”; write the word directly in signs, choosing by its sense in `package-build lexicon khel --map zethu` ``
655
+
656
+ > `` `halzi` has no `zethu` writing: the map `zethu` is logographic, and the run names no `lang=` whose lexicon writes it; mark the run with its language, or write the word directly in signs ``
657
+
658
+ > `` `data.grammar.native[3]` writes the ending `they`, and `data.grammar.verbs` states no `persons` ``
659
+
660
+ ## Emphasis in a script
661
+
662
+ Inside a run set in a face, `*…*` and `**…**` are written as in English. No
663
+ script marks a stressed word by a bold or a slant, so the build sets emphasis by
664
+ the scribal convention the text's language or script states:
665
+
666
+ | Convention | Site and Foundry | Book |
667
+ | ---------- | -------------------------------------------------- | --------------------- |
668
+ | `rubric` | `<em class="emph-rubric">`, in red | in the book's red ink |
669
+ | `overline` | `<em class="emph-overline">`, with a line above it | under an overline |
670
+ | none | `<em>` with no class, in the face's Regular | in the face's Regular |
671
+
672
+ `**…**` gives a `<strong>` in the same way. The element stays an `<em>` or a
673
+ `<strong>`, so a screen reader and a copied passage keep the emphasis. The face
674
+ keeps its Regular: no slant and no added weight. `_…_` is emphasis equal to
675
+ `*…*`, and `__…__` equal to `**…**`; a strikethrough is an error in a face.
676
+
677
+ **The convention is the language's, else the script's.** A language note may
678
+ state `data.emphasis` as `rubric`, `overline` or `words`. Otherwise the script
679
+ note that lists the run's face states it in `data.script`; otherwise there is
680
+ none. A script note is a `skill` note of `subType: script`:
681
+
682
+ ```yaml
683
+ data:
684
+ script:
685
+ fonts: [semaron-kuriale]
686
+ emphasis: rubric
687
+ strongEmphasis: overline
688
+ ```
689
+
690
+ `fonts` lists the faces of the script (a face belongs to one script),
691
+ `emphasis` sets both `*…*` and `**…**`, and `strongEmphasis` optionally sets
692
+ `**…**` alone. A tongue that states nothing takes its script's convention.
693
+
694
+ **A `words` language makes emphasis an error.** `data.emphasis: words` says the
695
+ language marks stress with a word, so stars in its runs are refused:
696
+
697
+ > `` `ban` marks emphasis with a word, not a mark; write the word ``
698
+
699
+ Underscores and strikethrough are errors in a face. A stressed word is best
700
+ enclosed whole: a star inside a word splits it, so a row or lexicon word that
701
+ crosses the star is not matched.
702
+
703
+ **The `--rubric` token.** The theme's `--rubric` is a warm red for its dark
704
+ ground, and the book prints its own. A rubric carries emphasis by colour alone,
705
+ so a contrast guard holds it to 4.5:1 against every ground the text sits on and
706
+ 1.75:1 against the body text, so a reader who cannot see the hue still sees a
707
+ lighter or darker word. A site that overrides `--rubric` keeps both ratios.
708
+
709
+ The keys are checked where they sit:
710
+
711
+ > `` `data.script.emphasis` should be `rubric` or `overline`, but reads "rubrik". Did you mean "rubric"? ``
712
+
713
+ > `` `data.script.fonts` is empty; a script lists each face it is written in ``
714
+
715
+ > `` `data.emphasis` states how a language marks emphasis, and this note's `subType` is not `language` ``
716
+
717
+ ## Verse
718
+
719
+ ### A poem in a verse fence
720
+
721
+ A poem is written in a `verse` fence, whose lines and stanzas the build
722
+ preserves. The keys of the previous sections go on the fence. Prettier leaves
723
+ the contents of a fence byte for byte, which is why a poem is a fence and not
724
+ paragraphs: indentation, line breaks and spans survive formatting untouched.
725
+
726
+ ````markdown
727
+ ```verse {lang=vyl font=semaron-kuriale map=semaron}
728
+ Lokazor lonak,
729
+ lokamen zaron.
730
+ ```
731
+ ````
732
+
733
+ ### Verse in columns
734
+
735
+ A poem can be set beside its translation, or in both script and roman, line
736
+ for line. `col1=` names what the left column shows and `col2=` what the right
737
+ shows, and each column stacks one or more **entries** joined by `:`. An entry
738
+ is `language[/mapping[/font]]`. A poem has at most two columns, and `numstep=N`
739
+ numbers the lines (`numstep=1` every line, `numstep=5` every fifth, `0` none).
740
+ `form=` records the poem's kind, such as `form=epic`; the build infers nothing
741
+ from it.
742
+
743
+ The body is **one fence, with interleaved `lang` spans.** Each verse line is a
744
+ group of lines separated from the next by a blank line; every line of a group is
745
+ a span giving the verse line in one language, `[text]{lang=…}`, or plain text in
746
+ the fence's own `lang=`. A line of only `***` breaks a stanza. The Vylari is
747
+ typed once, in roman, and each column picks what it needs: the first entry of
748
+ `col1=` maps it into Semaron, and the second entry of `col2=` sets the same line
749
+ in roman.
750
+
751
+ ````markdown
752
+ ```verse {numstep=1 lang=vyl col1=vyl/semaron/semaron-kuriale col2=en:vyl}
753
+ [Lokazor lonak zaron.]{lang=vyl}
754
+ [He walks the oath road.]{lang=en}
755
+
756
+ [Lokamen lonak zaron.]{lang=vyl}
757
+ [I walk the oath road.]{lang=en}
758
+
759
+ ***
760
+
761
+ alathos lokazor.
762
+ [That one walks.]{lang=en}
763
+ ```
764
+ ````
765
+
766
+ In every column the first entry is set in the normal tone and each later one in
767
+ grey. Each target sets a verse line as one row that holds its columns level,
768
+ and a narrow screen stacks them. A part in a right-to-left face stays in its
769
+ column and runs right to left within it.
770
+
771
+ The rules of a verse in columns: `font=` and `map=` belong to the entries, not
772
+ to the poem; every verse line gives one span for each language its columns
773
+ name and none for a language they do not; a line is not indented and its span
774
+ takes only `lang=`. Each finding is an error at the text that is wrong:
775
+
776
+ ```text
777
+ assets/content/Lore/Walking.md:14:1: error: this verse line has no `en` line, which `col2=` sets
778
+ assets/content/Lore/Walking.md:18:1: error: this verse line gives `en` twice; give each language once
779
+ assets/content/Lore/Walking.md:22:1: error: no column sets `khel`; a verse line gives the languages `col1=` and `col2=` name
780
+ ```
781
+
782
+ ### Metre
783
+
784
+ `scan=` turns on a beat check. It names a metre declared once in the package,
785
+ as an entry of a `doc` note's `data.metres`:
786
+
787
+ ```yaml
788
+ data:
789
+ metres:
790
+ - { name: trimeter, beats: 3, kind: accentual }
791
+ ```
792
+
793
+ `name` is lowercase letters, digits and `-`, unique in the package; `beats` is
794
+ the beats of one line, a whole number above zero; `kind` is `accentual`, the
795
+ only kind: a beat falls on a stressed syllable and the length of a syllable is
796
+ not counted. A fence that states `scan=` with the declared name and a `lang=`
797
+ whose language note states `data.sounds.stress` is counted line by line:
798
+
799
+ ````markdown
800
+ ```verse {lang=vyl scan=trimeter}
801
+ Lokazor lonak zaron.
802
+ Lokamen lonak zaron.
803
+ ```
804
+ ````
805
+
806
+ **The stress rule** is `data.sounds.stress`, `{ two, longer, mark }`. `two`
807
+ (`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
810
+ diacritic that moves the stress to the syllable it sits on. Syllables are vowel
811
+ groups: a vowel is a letter whose base is `a`, `e`, `i`, `o` or `u`, and a pair
812
+ that the language lists in `data.sounds.syllables.vowelPairs` is one syllable.
813
+
814
+ **The beat convention, in plain words:**
815
+
816
+ - Every word of two or more syllables has one beat, on its stressed syllable.
817
+ - A one-syllable noun, verb, adjective, numeral or name has a beat. A
818
+ one-syllable particle, preposition, conjunction, adverb or pronoun has none.
819
+ The lexicon entry's class decides, and a form that the language's rules derive
820
+ takes its word's class. A word whose entries differ in class is a beat that
821
+ may be left out.
822
+ - A one-syllable word the lexicon does not hold is a content word and has a
823
+ beat; the word check reports the word separately.
824
+ - A word of four or more syllables may take a second, lighter beat on its first
825
+ syllable where the line needs it, unless its stress is already there.
826
+ - The caesura is not written.
827
+
828
+ A line scans when its count can be the metre's, choosing second beats as
829
+ needed. A line that cannot is a warning at that line, giving the count without
830
+ the second beats and, in brackets, with all of them:
831
+
832
+ > ``this line has 4 beats (5 with secondary beats); the metre `hexameter` has 6``
833
+
834
+ **The check is author-side.** Readers see nothing of it: the rendered fence
835
+ carries `data-metre` with the metre's name and nothing a reader sees. A fence
836
+ without `scan=` is not counted. `scan=` is refused where it cannot mean
837
+ anything, each an error:
838
+
839
+ > `` `hexamter` is not a declared metre: `scan=` names the `name` of a `data.metres` entry of a `doc` note; did you mean `hexameter`? ``
840
+
841
+ > `` `scan=` counts beats by a language's stress rule: the fence needs a `lang=` ``
842
+
843
+ > ``the language `vyl` states no `data.sounds.stress`, which `scan=` counts beats by``
844
+
845
+ > ``the language `vyl` is not a configured language with a `note`, so it has no stress rule for `scan=` to count beats by``
846
+
847
+ > `` `scan=` counts the beats of one language's lines; a verse in columns sets a line in each of two ``
848
+
849
+ A repeated metre name is an error too:
850
+
851
+ > `` `data.metres[1].name` is `trimeter`, which this note declares twice; a metre's name is unique ``
852
+
853
+ ## The core vocabulary
854
+
855
+ A setting can name the concepts every one of its languages has a word for, and
856
+ measure each language against them from data. Three keys carry it:
857
+
858
+ | Key | On | Meaning |
859
+ | ---------------------- | --------------- | ------------------------------------------------------------------------------------ |
860
+ | `data.concepts` | a `doc` note | The concept list: `{ id, gloss, group }` entries, in the order the report lists them |
861
+ | `data.core` | a language note | `true` where the language must cover the list |
862
+ | `data.words[].concept` | a word | The id of the concept the word serves, or a list of ids for a word serving several |
863
+
864
+ **The list lives in the same package as the words that use it.** An `id` is
865
+ lowercase letters, digits and `-`, unique in the package; `gloss` and `group`
866
+ are not blank. A word's `concept` that names no entry of a list is an error at
867
+ the id, with the nearest id offered:
868
+
869
+ > `` `smel-noun` is not a core concept; did you mean `smell-noun`? ``
870
+
871
+ A daughter language reads the words of its `data.family` chain as its own, so a
872
+ parent's tagged word covers the daughter's concept. `data.core` is not
873
+ inherited: each language that must cover the list states it.
874
+
875
+ `package-build core [--language <name>] [--json]` reports, for each language
876
+ that states `core: true`, the concepts it has no word for, grouped and counted;
877
+ `--language` reports one language by name, shortcode or reference whether or
878
+ not it states `core`, and `--json` serves an agent working through it. **A
879
+ missing concept is never a lint failure:** the report is the work list, and the
880
+ exit status stays 0.
881
+
882
+ ## Rendering and delivery
883
+
884
+ One pre-pass rewrites marked text at the head of every target: the site, the
885
+ Foundry compile, the book and the preview. It wraps the converted run in the
886
+ face's class (or a generic-family class), gives it the `lang` from the language's
887
+ tag, and gives a block in a right-to-left face `dir="rtl"`. It never writes
888
+ `<bdo>`, `<bdi>` or a Unicode direction control, because Foundry's editor strips
889
+ the first two and a right-to-left face carries its direction in its code points.
890
+ A run converted for a right-to-left face is hidden from the site's search index.
891
+
892
+ | Target | What a marked run becomes |
893
+ | ------- | ------------------------------------------------------------------------------------------ |
894
+ | Site | `<span class="script hl-semaron-kuriale" lang="art-x-vyl" data-pagefind-ignore="true">` |
895
+ | Foundry | the same classes and `lang`, with the font stylesheet in the module's `styles` |
896
+ | Book | the text set in the face; a real language's tag sets hyphenation, an invented tag does not |
897
+
898
+ Readers install nothing. When a note uses a face, the fonts are served from the
899
+ package's own output: the site copies `fonts.css`, the WOFF2 files and the
900
+ licence to `fonts/heroiclands/<version>/` beside the pages and links them from
901
+ every page; Foundry stages the same files; the book adds the TrueType files to
902
+ the typesetter's font search path. A package that has not installed the fonts, or
903
+ whose notes set no text in a face, ships none of it.
904
+
905
+ ## The commands
906
+
907
+ `package-build transliterate <map> [--lang <language>] [--font <slug>] [--hex] [text..]`
908
+ maps a passage exactly as the build maps a run marked `map=<map> lang=<language>
909
+ 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.
912
+
913
+ ```console
914
+ $ package-build transliterate semaron --lang vyl --hex zaron lonak
915
+ U+F0246 U+F0248 U+F023F U+F023D U+F023C U+0020 U+F023A U+F023D U+F023C U+F0230 U+F0239
916
+ $ package-build transliterate semaron --hex zaron
917
+ U+F0246 U+F0230 U+F023F U+F023D U+F023C
918
+ ```
919
+
920
+ The first run writes `zaron` by its exception, and the second, with no
921
+ `--lang`, spells it by the rows.
922
+
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,
925
+ the native form, `exception` or `rows`, the class and the gloss. `--derived`
926
+ adds the forms the language's rules derive, each with the word it comes from,
927
+ `--json` prints an array instead, and under a logographic map the third column
928
+ is `own`, `parts`, `none` or `ambiguous`.
929
+
930
+ ```console
931
+ $ package-build lexicon vyl --map semaron | cut -f1,3-
932
+ zaron exception n an oath
933
+ lonak rows n a road
934
+ lokathen rows v to walk
935
+ alathos rows pron he, that one
936
+ thiruthrimar rows n a sworn witness
937
+ ```
938
+
939
+ `package-build core [--language <name>] [--json]` reports the core vocabulary
940
+ (see [the core vocabulary](#the-core-vocabulary)).
941
+
942
+ ## A worked example
943
+
944
+ This builds a small package from nothing: a language, a script, a couplet and a
945
+ poem. It is the guide's own test: every file below is written into a project and
946
+ linted, and the findings printed are the ones the lint reports. The mapping note
947
+ is the one from [mapping notes](#mapping-notes).
948
+
949
+ Install the fonts, then write the configuration. A package that declares
950
+ dependencies in its configuration fetches their indexes with
951
+ `package-build deps fetch` before it lints; the sample declares none.
952
+
953
+ Install the fonts, then write the configuration:
954
+
955
+ ```bash
956
+ npm install --save-dev @heroiclands/fonts
957
+ ```
958
+
959
+ **`package-build.config.yaml`**
960
+
961
+ ```yaml
962
+ contentPackage: demo
963
+ packageKind: modules
964
+ compatibility:
965
+ minimum: "14.359"
966
+ stats:
967
+ lastModifiedBy: demobuilder0000
968
+ packs:
969
+ - { name: journals, label: Journals, type: JournalEntry, default: true }
970
+ languages:
971
+ vyl: { tag: art-x-vyl, note: skill-vylarilng }
972
+ ```
973
+
974
+ The concepts and the metre the sample uses live on a `doc` note:
975
+
976
+ **`assets/content/Docs/Core_Concepts.md`**
977
+
978
+ ```markdown
979
+ ---
980
+ shortcode: coreconcepts
981
+ name: { full: Core Concepts }
982
+ type: doc
983
+ subType: reference
984
+ data:
985
+ concepts:
986
+ - { id: walk-verb, gloss: to walk, group: motion }
987
+ - { id: road-noun, gloss: a road, group: places }
988
+ - { id: oath-noun, gloss: an oath, group: law }
989
+ - { id: see-verb, gloss: to see, group: senses }
990
+ metres:
991
+ - { name: trimeter, beats: 3, kind: accentual }
992
+ ---
993
+
994
+ The concepts every language of the sample setting has a word for, and the metres
995
+ its poems keep. This note is the guide's own sample and carries no canon beyond
996
+ what the guide's examples need to run.
997
+ ```
998
+
999
+ The language note holds the dictionary, the grammar, the stress rule and the
1000
+ core flag:
1001
+
1002
+ **`assets/content/Skills/Vylari.md`**
1003
+
1004
+ ```markdown
1005
+ ---
1006
+ shortcode: vylarilng
1007
+ name: { full: Vylari Language }
1008
+ type: skill
1009
+ subType: language
1010
+ data:
1011
+ core: true
1012
+ fields:
1013
+ - { name: Law, holds: "Oaths, seals and roads." }
1014
+ affixes:
1015
+ - {
1016
+ form: "-andar",
1017
+ gloss: "doing it: the present participle",
1018
+ replaces: vowel,
1019
+ attachesTo: [v],
1020
+ }
1021
+ - { form: "-thos", gloss: "done, suffered: the past participle" }
1022
+ grammar:
1023
+ plural:
1024
+ - { ending: os, becomes: oi }
1025
+ - { ending: on, becomes: oi }
1026
+ - { ending: a, becomes: ai }
1027
+ - { ending: "", becomes: oi }
1028
+ verbs:
1029
+ replaces: then
1030
+ persons: { I: men, you: zen, heShe: zor, we: mar, youAll: zar, they: zoi }
1031
+ agreement:
1032
+ classes: [adj, pron]
1033
+ endings: [os, a, on]
1034
+ participles:
1035
+ root: [athen, ethen]
1036
+ affixes: ["-andar", "-thos"]
1037
+ sounds:
1038
+ stress: { two: first, longer: penultimate }
1039
+ syllables:
1040
+ vowelPairs: [ai, au]
1041
+ words:
1042
+ - form: zaron
1043
+ class: n
1044
+ field: Law
1045
+ gloss: an oath
1046
+ concept: oath-noun
1047
+ native:
1048
+ - { map: semaron, text: "󰉆󰉈󰈿󰈽󰈼" }
1049
+ - { form: lonak, class: n, field: Law, gloss: a road, concept: road-noun }
1050
+ - { form: lokathen, class: v, field: Law, gloss: to walk, concept: walk-verb }
1051
+ - { form: alathos, class: pron, field: Law, gloss: "he, that one" }
1052
+ ---
1053
+
1054
+ The imperial tongue of the sample setting, written in the Semaron script and
1055
+ read aloud at its court. The lexicon here is a small sample made for the guide.
1056
+ ```
1057
+
1058
+ A note with a run. The couplet uses a word the lexicon does not hold:
1059
+
1060
+ **`assets/content/Lore/Oath_Road.md`**
1061
+
1062
+ ```markdown
1063
+ ---
1064
+ shortcode: oathroad
1065
+ name: { full: The Oath Road }
1066
+ type: lore
1067
+ ---
1068
+
1069
+ The lintel over the door of the oath road is cut with a couplet, and the
1070
+ stonecutter set the couplet in the script of the court, so that every traveller
1071
+ could read it as the court reads it.
1072
+
1073
+ [Lokazor lonak thiruthrimar, lokamen zaron.]{lang=vyl font=semaron-kuriale map=semaron}
1074
+ ```
1075
+
1076
+ Linting reports the unknown word, at its first character:
1077
+
1078
+ ```text
1079
+ assets/content/Lore/Oath_Road.md:11:16: error: `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
1080
+ ```
1081
+
1082
+ The word is a coinage. It goes into the dictionary before it is used, so the
1083
+ language note gains an entry:
1084
+
1085
+ **`assets/content/Skills/Vylari.md`**
1086
+
1087
+ ```markdown
1088
+ ---
1089
+ shortcode: vylarilng
1090
+ name: { full: Vylari Language }
1091
+ type: skill
1092
+ subType: language
1093
+ data:
1094
+ core: true
1095
+ fields:
1096
+ - { name: Law, holds: "Oaths, seals and roads." }
1097
+ affixes:
1098
+ - {
1099
+ form: "-andar",
1100
+ gloss: "doing it: the present participle",
1101
+ replaces: vowel,
1102
+ attachesTo: [v],
1103
+ }
1104
+ - { form: "-thos", gloss: "done, suffered: the past participle" }
1105
+ grammar:
1106
+ plural:
1107
+ - { ending: os, becomes: oi }
1108
+ - { ending: on, becomes: oi }
1109
+ - { ending: a, becomes: ai }
1110
+ - { ending: "", becomes: oi }
1111
+ verbs:
1112
+ replaces: then
1113
+ persons: { I: men, you: zen, heShe: zor, we: mar, youAll: zar, they: zoi }
1114
+ agreement:
1115
+ classes: [adj, pron]
1116
+ endings: [os, a, on]
1117
+ participles:
1118
+ root: [athen, ethen]
1119
+ affixes: ["-andar", "-thos"]
1120
+ sounds:
1121
+ stress: { two: first, longer: penultimate }
1122
+ syllables:
1123
+ vowelPairs: [ai, au]
1124
+ words:
1125
+ - form: zaron
1126
+ class: n
1127
+ field: Law
1128
+ gloss: an oath
1129
+ concept: oath-noun
1130
+ native:
1131
+ - { map: semaron, text: "󰉆󰉈󰈿󰈽󰈼" }
1132
+ - { form: lonak, class: n, field: Law, gloss: a road, concept: road-noun }
1133
+ - { form: lokathen, class: v, field: Law, gloss: to walk, concept: walk-verb }
1134
+ - { form: alathos, class: pron, field: Law, gloss: "he, that one" }
1135
+ - { form: thiruthrimar, class: n, field: Law, gloss: a sworn witness }
1136
+ ---
1137
+
1138
+ The imperial tongue of the sample setting, written in the Semaron script and
1139
+ read aloud at its court. The lexicon here is a small sample made for the guide.
1140
+ ```
1141
+
1142
+ The lint is now clean, and the conversion can be read. `package-build transliterate`
1143
+ prints the code points the build writes for the couplet:
1144
+
1145
+ ```console
1146
+ $ package-build transliterate semaron --lang vyl --hex "Lokazor lonak, lokamen zaron."
1147
+ U+F020A U+F023D U+F0239 U+F0230 U+F0246 U+F023D U+F023F U+0020 U+F023A U+F023D U+F023C U+F0230 U+F0239 U+F0268 U+0020 U+F023A U+F023D U+F0239 U+F0230 U+F023B U+F0233 U+F023C U+0020 U+F0246 U+F0248 U+F023F U+F023D U+F023C U+F0268
1148
+ ```
1149
+
1150
+ The site sets the run as
1151
+
1152
+ ```html
1153
+ <span class="script hl-semaron-kuriale" lang="art-x-vyl" data-pagefind-ignore="true"
1154
+ >󰈊󰈽󰈹󰈰󰉆󰈽󰈿 󰈺󰈽󰈼󰈰󰈹󰉨 󰈺󰈽󰈹󰈰󰈻󰈳󰈼 󰉆󰉈󰈿󰈽󰈼󰉨</span
1155
+ >
1156
+ ```
1157
+
1158
+ where the characters are the code points above: `Lokazor` begins with the
1159
+ capital `L` of the rows, `zaron` is the exception's long vowel, and the comma
1160
+ and full stop are the script's one point.
1161
+
1162
+ A poem in columns and a poem with its metre checked sit on one note. Both parse
1163
+ with no finding: the first sets Vylari in Semaron beside the English over the
1164
+ Vylari in roman, and the second's lines each carry three beats.
1165
+
1166
+ **`assets/content/Lore/Walking.md`**
1167
+
1168
+ ````markdown
1169
+ ---
1170
+ shortcode: walking
1171
+ name: { full: The Walking }
1172
+ type: lore
1173
+ ---
1174
+
1175
+ The walking of the oath road is sung in two settings. The first sets the poem
1176
+ beside its translation, and the second keeps the metre that the court keeps, so
1177
+ that a line with the wrong count is reported to the author who wrote it.
1178
+
1179
+ ```verse {numstep=1 lang=vyl col1=vyl/semaron/semaron-kuriale col2=en:vyl}
1180
+ [Lokazor lonak zaron.]{lang=vyl}
1181
+ [He walks the oath road.]{lang=en}
1182
+
1183
+ [Lokamen lonak zaron.]{lang=vyl}
1184
+ [I walk the oath road.]{lang=en}
1185
+
1186
+ ***
1187
+
1188
+ alathos lokazor.
1189
+ [That one walks.]{lang=en}
1190
+ ```
1191
+
1192
+ ```verse {lang=vyl scan=trimeter}
1193
+ Lokazor lonak zaron.
1194
+ Lokamen lonak zaron.
1195
+ ```
1196
+ ````
1197
+
1198
+ A script note gives Semaron its emphasis convention, and a run with double stars then
1199
+ sets them in red:
1200
+
1201
+ **`assets/content/Skills/Scripts/Semaron.md`**
1202
+
1203
+ ```markdown
1204
+ ---
1205
+ shortcode: semaronscrpt
1206
+ name: { full: Semaron Script }
1207
+ type: skill
1208
+ subType: script
1209
+ data:
1210
+ script:
1211
+ fonts: [semaron-kuriale]
1212
+ emphasis: rubric
1213
+ ---
1214
+
1215
+ The Semaron script, as the sample setting writes it. It marks a stressed word in
1216
+ red ink, the rubric of its scribes, and the guide's emphasis example depends on it.
1217
+ ```
1218
+
1219
+ **`assets/content/Lore/Marked_Door.md`**
1220
+
1221
+ ```markdown
1222
+ ---
1223
+ shortcode: markeddoor
1224
+ name: { full: The Marked Door }
1225
+ type: lore
1226
+ ---
1227
+
1228
+ The door is marked with the same words as the lintel, and the scribe stressed the
1229
+ second word so that no one would read it as the first, which is how the script
1230
+ marks a stressed word.
1231
+
1232
+ [Lokazor **lonak**, zaron.]{lang=vyl font=semaron-kuriale map=semaron}
1233
+ ```
1234
+
1235
+ Finally, the core report lists what the language still lacks:
1236
+
1237
+ ```console
1238
+ $ package-build core --language vylarilng
1239
+ Vylari Language (skill-vylarilng): 1 of 4 core concepts have no word
1240
+ senses: 1 of 1
1241
+ see-verb to see
1242
+ ```
1243
+
1244
+ Everything in this package comes from the guide. A new script follows the same
1245
+ steps: a mapping note for its face, `languages` entries for the languages
1246
+ written in it, a language note with the dictionary, and runs marked with the
1247
+ three keys.