# Verifying the accuracy claim

This site tells readers its Grade 2 output is 99.98% identical to the reference
implementation, and lists the words it gets wrong. That claim is the reason to
trust anything else here, so it has to be checkable — by anyone, not just by
whoever wrote it.

The original check ran inside the forge build. That source no longer exists, and
the site is now maintained as its own built output, so the measurement lives here
instead, beside the thing it measures.

## Running it

```
powershell -ExecutionPolicy Bypass -File verify/get-liblouis.ps1
node verify/verify-ueb.js
```

The first command downloads the reference implementation once (~5 MB) and
verifies its SHA-256. The second runs the measurement. Exit code 0 means the
published claim holds; 1 means it does not.

Before a deploy, run all seven — each exits 1 on a failure:

```
node verify/verify-ueb.js
node verify/verify-rulebook.js
node verify/verify-defects.js
node verify/verify-dropped.js
node verify/verify-pages.js
node verify/verify-wider.js
node verify/verify-languages.js
```

## What it actually checks

1. **The reference is the right one.** liblouis **3.39.0** specifically — the
   version the published figures are measured against. UEB tables change
   between releases, so a different build would produce a different number and
   quietly invalidate the comparison. The harness refuses to run on any other
   version rather than reporting a figure that looks fine and isn't. Moving to a
   new release is a decision, made and dated on the page (17 September 2026:
   3.29.0 to 3.39.0).

2. **The match rate still meets what the site prints.** The floor is not
   hard-coded here; it is parsed out of
   `research/how-accurate-are-braille-translators/index.html` — the page that
   makes the claim. A number the engine cannot meet fails this check instead of
   sitting unchallenged on the site.

3. **The page's figures are the measured ones.** The count of identical words,
   the total and the number that differ are all read from the page and compared
   with the run, so a figure the harness cannot read fails instead of being
   skipped.

4. **The differing words are still the same words.** `expected-differences.tsv`
   holds the one, with the exact cells from both sides. If a word starts or
   stops differing, or its cells change, the run fails and names it. A drift
   that keeps the *count* at 1 while changing *which* word differs would
   otherwise pass unnoticed.

5. **The UEB Shortforms List.** The 5,000 commonest words hold few shortforms and
   fewer of the longer words that keep one, so the list is measured on its own:
   the 75 shortforms, 537 longer words — the 531 the earlier Appendix 1 listed
   and the six the 2024 edition adds (aftermentioned, aftertheater,
   beforementioned, mustardy, unrepaid, unsaid) — and each of those with s added:
   1,065 entries. Every one must also read back through the decoder as the same
   word. None still differ, and `expected-shortform-differences.tsv` is
   empty — a word appearing in it is a regression. The 2024 list also counts every word
   rule 10.9.3 admits; the engine applies that rule, but no word list can
   enumerate it, so it is checked by the rulebook's own examples instead.

## Files

| File | What it is |
|---|---|
| `verify-ueb.js` | The measurement. Loads `tools/engine.js` and compares it to liblouis. |
| `verify-defects.js` | Checks the ten liblouis defects still open on /research/reference-table-defects/ (nine of its eleven rows, and the shadda after a vowel mark, added 11 October 2026) from both ends, the table lines each cites, and that the two Portuguese rows liblouis 3.39.0 fixed are still fixed. |
| `verify-rulebook.js` | Checks the 1,898 worked examples in `ueb-rulebook-examples.tsv` (Rules of UEB, 2024) are still written cell for cell, and the 1,892 marked `reads_back` still decode to their print: standing alone, wordsigns beside punctuation, quotation marks, the apostrophe, the question mark, punctuation that could be read as a contraction, numeric mode and letters after numbers (Section 6), and capitals (Section 8) — the capitals word indicator, the terminator, capitals passages and contractions beside a capital sign; shortforms in longer words and the letters that would be misread as one (10.9.3, 10.9.8, 10.9.9); strong groupsigns standing alone (10.4); every initial-letter contraction the rulebook gives a rule of its own (10.7.1 to 10.7.9); and bridging — no groupsign reaching across the join between the parts of a compound word (10.11). |
| `ueb-rulebook-examples.tsv` | Those examples: section, print, the rulebook's braille, and whether the decoder reads it back. They are examples the engine writes; the measurement is the Word List below. |
| `ueb-word-list.tsv` | The rulebook's Word List (Appendix 2): all 1,043 example words of its contractions section, each with the rulebook's braille. `verify-rulebook.js` writes every one and fails unless the entries that differ are exactly those in the next file. |
| `expected-word-list-differences.tsv` | The 9 Word List entries the engine writes differently, with the rulebook's cells, the engine's, and the reason. |
| `verify-wider.js` | Compares the engine with liblouis over `wider-list.txt` and fails unless the words that differ are exactly those in `expected-wider-differences.tsv`. Also holds two counts the pages publish: liblouis writes 1,030 of the 1,043 Word List entries as the rulebook gives them, and 500 of the 504 words beginning be are written identically. |
| `wider-list.txt` | 46,717 words: every token of letters in the first 50,000 lines of hermitdave/FrequencyWords `content/2018/en/en_50k.txt` (OpenSubtitles 2018, CC BY-SA 4.0), in frequency order. |
| `expected-wider-differences.tsv` | The 35 of those words the engine and liblouis write differently, with both sides' cells. |
| `verify-languages.js` | Spanish, Portuguese, Arabic and Hindi. Re-measures each engine against its published word list in `/data/` (and the counts its research page prints), against the examples in its own standard (Grafia Braille 14, 16, 18, 37.1 and 38; Documento técnico B 2, 6 and 14.3), and against liblouis on about 120 to 160 patterns of numbers and punctuation in context; then checks that each engine reads back what it wrote. |
| `expected-pattern-differences.tsv` | Every one of those patterns where an engine and liblouis differ, or where an engine does not read its own braille back as the print, with the reason. A line that stops being true fails the check; so does a difference that is not here. |
| `verify-dropped.js` | Checks that the tools' "left out" notices never fire on supported text, that skipped cells are reported, that the blank cell U+2800 and line breaks read as themselves in all five decoders, that ¿ ¡ carry the cells in UEB Appendix 3, and that dot numbers typed in Arabic-Indic or Devanagari digits are read. |
| `verify-pages.js` | Checks that the pages say what the translator does: every braille example marked `data-print` on an English page is exactly what the engine writes, every word a contraction page lists as written without its sign is written without it, and each of those pages' general rule holds when probed. The other checks guard figures; this one guards prose. It was written on 27 September 2026, after all 33 contraction pages spent nine days describing position rules the engine had dropped — fourteen of them contradicting it — and it fails in 172 places when run against that older engine. |
| `get-liblouis.ps1` | Fetches liblouis 3.39.0, pinned and hash-checked. |
| `corpus.txt` | 5,000 English words in frequency order: the first 5,000 lines of `google-10000-english.txt` in first20hours/google-10000-english, which is cut from Peter Norvig's word counts of Google's Trillion Word Corpus (public web pages). Compared with that file on 10 October 2026: identical, sha256 of the lines joined by LF `07ffadeaf0de99199de61137589aab0b65977385c92cf9606165ecc258a49d93`, and `verify-pages.js` holds the hash. Committed on purpose. |
| `expected-differences.tsv` | The one known difference, with both sides' cells. |
| `ueb-shortforms-list.tsv` | The ICEB Shortforms List: each shortform and the longer words that keep it. |
| `expected-shortform-differences.tsv` | The Shortforms List words that still differ. Empty: a word appearing in it is a regression. |
| `liblouis/` | The downloaded reference. Not committed — re-fetch it. |

The corpus and the expected results are committed so the ground truth can be
inspected directly, rather than taken on trust from a script that claims to have
computed it. The binary is not, because it is 5 MB of build output that anyone
can re-fetch from the project that produced it.

## Single letters

The corpus holds 5,000 entries, 26 of them single letters, and all 5,000 are
compared. A standalone letter that is also a wordsign — b is "but" — is written
with the grade 1 indicator (`⠰⠃`), as the reference writes it; a, i and o are not
wordsigns and take none. Until 14 September 2026 the engine did not do this and
the harness left the 26 letters out.

## Result as of 19 September 2026

```
reference   : liblouis 3.39.0, unicode.dis,en-ueb-g2.ctb
corpus      : 5000 words (frequency order)
identical   : 4999
differing   : 1
match rate  : 99.98%
site claims : 4999 of 5000, 99.98%, 1 words differing

shortforms  : 75 (ICEB Appendix 1 lists 75)
list words  : 1065 (shortforms, listed longer words, and those with s)
identical   : 1065
read back   : 1065 of 1065
differing   : 0, matching expected-shortform-differences.tsv exactly

with s      : 300 of 300 identical, 300 read back
3 yrs 6 mths: ⠼⠉ ⠰⠽⠗⠎ ⠼⠋ ⠍⠹⠎ (rulebook 10.12.2: ⠼⠉ ⠰⠽⠗⠎ ⠼⠋ ⠍⠹⠎)
PASS
```

The 99.98% on the page is a floor: the run must meet it, not equal it.

On 1 September 2026 the same corpus gave 4,960 of 4,974 words (single letters
excluded) and 14 differences. The two that went, newsletter and newsletters, are
words on the Shortforms List. Before 14 September the engine's list had 73
shortforms, including o'clock, which UEB eliminated, and missing braille, oneself
and thyself; it did not use shortforms inside the longer words ICEB lists.

Later on 14 September cds joined the differences. The engine now keeps a
shortform's cells when s is added (Rules of UEB 10.9.5), so c-d-s also spells
coulds, and it marks the letters with a grade 1 indicator as rule 10.9.7
requires — the rulebook writes "3 yrs 6 mths" as ⠼⠉ ⠰⠽⠗⠎ ⠼⠋ ⠍⠹⠎. liblouis
3.29.0 wrote all 75 shortforms with s the same way but left that indicator
out, so there the reference was the one out of step.

On 17 September 2026 the reference moved to liblouis 3.39.0, the current
release, and six words left the differences because the engine now follows the
rule the reference already did: blvd and llc (a grade 1 indicator, rule
10.9.8), ed and er (strong groupsigns used wherever their letters occur,
10.4.1), experienced (the sign for ence in "enced", 10.10.6) and money (the
sign for one in a word ending "oney", 10.7.6). cds left because 3.39.0 now
writes the indicator. One word joined: ar, which the rulebook writes as its
groupsign ("-ar" is ⠤⠜) and 3.39.0 spells out.

On 18 September 2026 four more left — multimedia, reverse, severe and coupons —
when the engine took up the rulebook's own rules for the initial-letter
contractions (10.7.2 to 10.7.9) in place of the positions it had measured
against the reference. Three of those rules are spelling and are applied as
written: "one" not after an o, "ever" only when the letters are not preceded by
e or i, "under" not after a or o. The rest are pronunciation — "had" when the a
is short, "time" when the letters are said as the word — and there the rulebook
decides by example, so the words it writes without the contraction are held in
the engine with their inflections. The same change closed eight of the ten
Shortforms List differences (the hereinabove, hereinafter and hereinagain
families, and greatgrandmotherhood).

Later the same day the engine took up bridging itself (10.11): a groupsign may
not reach across the join between the parts of a compound word, nor across a
prefix where the letters are "ea". That closed reaction (re|action), whereas
(where|as) and both remaining Shortforms List words (parent|hood), which is why
the list now matches on all 1,065 entries.

Which words are compounds is a fact about English rather than about braille, and
no spelling rule settles it. A splitter reading "fat" and "her" out of father
bars a contraction the rulebook uses; one told that a prefix always makes a join
breaks mistake, renew and agreeable, which 10.11.5 contracts straight through.
Both were measured before being rejected: the splitter took the 12,900-word
benchmark from 16 differences to 44 and broke 26 rulebook examples. So the engine
holds the words instead — every word the rulebook names in 10.11 with the
position of its join, and eight found in the benchmark — and a compound it has
never seen is contracted as before.

One word remains, ar, and there the rulebook sides with the engine: it writes
"-ar" with the groupsign and 3.39.0 spells out a-r. The corpus no longer
separates the two, so the next measurement this needs is a larger and harder
one.

## 2 October 2026: the Word List and the wider list

The larger and harder measurement. `ueb-rulebook-examples.tsv` had been built by
keeping the rulebook examples the engine wrote correctly, so it could show a
regression and could not show a gap. Appendix 2 of the rulebook is the complete
list of the example words in its contractions section, 1,043 of them, and the
engine as deployed that morning wrote 31 of them wrongly. Twenty-two were gaps in
rules it claimed to follow, and are fixed. Nine remain, each with its reason in
`expected-word-list-differences.tsv`: two ligatures the engine does not write,
three examples printed in bold or italics, two abbreviations and one
abbreviated word whose reading depends on meaning, and Al, where the Word List
and rule 10.9.7 disagree. liblouis 3.39.0 writes 1,030 of the 1,043.

The same day the engine was compared with liblouis over 46,717 words from a
subtitle frequency list. They differed on 160. Most were compounds with a
groupsign across the join, which the engine now holds; evert was a plain error
(10.7.4). They now differ on 35, and `verify-wider.js` fails if that set moves.

```
word list   : 1034 of 1043 in Appendix 2 written as the rulebook gives them
differing   : 9, matching expected-word-list-differences.tsv exactly

word list   : the reference writes 1030 of 1043 in Appendix 2 as the rulebook gives them
be- words   : 500 of 504 identical
words       : 46717 (frequency order, OpenSubtitles 2018)
identical   : 46682
differing   : 35 (0.07%)
```

## 2 October 2026: the other four languages

Until this date only English was re-measured before a deploy. The Spanish and
Portuguese figures rested on `/data/es-reference.tsv` and `/data/pt-reference.tsv`,
which nothing re-ran; the Arabic and Hindi word lists were not in this repository
at all, though both research pages said they were. They were rebuilt from the
source the pages name (hermitdave/FrequencyWords, 2018: `ar_50k.txt`, first 5,000
words of Arabic letters and harakat; `hi_full.txt`, first 5,000 in the Devanagari
block) and every published figure came back: 4,999 of 5,000, 370 vowelled, 159
with lam-alif; 4,997 of 5,000, 1,257 with a halant, 313 with a nukta, 250 written
differently by the 2014 table. One figure did not: 205 Hindi character probes,
whose list is lost. `/data/hi-characters.tsv` replaces it with 122 that are published.

A word list has no digits and almost no punctuation, so none of this could show
the faults that mattered. Numbers and punctuation in context did: Portuguese
wrote 15h as 158 (no dot 5 before the letter, Grafia 16 c), Hindi wrote the dash
as a hyphen and could not read 1.5 back, Spanish dropped the raya, the Arabic
decoder returned the Urdu full stop. `verify-languages.js` holds all of it now.

```
es word list ......... 5032 lines, 5032 identical to liblouis, 5032 read back
pt word list ......... 5041 lines, 5041 identical to liblouis, 5038 read back
ar word list ......... 5000 lines, 4999 identical to liblouis, 5000 read back
hi word list ......... 5000 lines, 4997 identical to liblouis, 4996 read back
hi characters ........ 122 probes, 122 identical
pt standard examples . 40 of 40 written as the standard gives them
es standard examples . 6 of 6 written as the standard gives them
```

## The reference-table defects

```
node verify/verify-defects.js
```

/research/reference-table-defects/ names five rows in liblouis 3.39.0's Arabic
table, three in its Spanish table and one in its Portuguese table (new in 3.39.0)
that depart from their own rule or the published standard, and two rows
of its Portuguese table that did in 3.29.0 and are fixed in 3.39.0. For each one this checks both halves the page states — the
reference still gives the answer quoted, and this site's engine
(`tools/engine-ar.js`, `tools/engine-pt.js`) gives the other — plus that the
table lines cited really hold what the page says (for example, the 36 shadda
rules on lines 97–132 of `ar-ar-g1-core.uti`). If liblouis fixes one, the run
fails and the page is what must change — which is how the two Portuguese fixes
were caught. For those two it checks the fix still holds.

`verify/liblouis-3.29.0/`, when present, is the previous reference, kept only to
compare releases; nothing reads it by default.

## Not covered

Nemeth and music braille are **not** verifiable this way. liblouis 3.29.0 shipped
`nemethdefs.cti` — a definitions include — but no standalone Nemeth translation
table, so there is nothing to measure against. Do not publish Nemeth tables on
this site until there is a reference implementation to check them with. The
Spanish, Portuguese, Arabic and Hindi engines are measured by `verify-languages.js`.

## The book: which contractions do the work (4 October 2026)

`measure-usage.js` translates the whole of `data/alice-body.txt` in Grade 2 and asks the engine,
through `BR.explainText()`, which sign it used for every word where the word stands. A sign's saving
is the letters it stands for less the cells it takes. The script stops if the savings of all signs,
less the grade 1 indicators Grade 2 adds before letters standing alone, do not equal the difference
between the Grade 1 and Grade 2 transcriptions exactly: 34,447 - 18 = 34,429 = 120,501 - 86,072.

    node verify/measure-usage.js            # summary
    node verify/measure-usage.js --write    # rewrites data/alice-contractions.tsv

`verify-pages.js` (section 7) measures it again and fails if the data file, the research page
`/research/which-contractions-do-the-work/`, the "In a whole book" section of any contraction page,
or the cells, lines and pages printed on `/research/how-many-braille-pages/` and `/pages/` say
anything else.

Found while reconciling: `layoutBraille()` translated one word at a time, so it could not tell a
closing single quotation mark from an apostrophe. It now translates each paragraph whole, which
added one cell to each grade of the book and moved no line.

## Generators kept with the checks

Three sections of the site are written by scripts rather than by hand, and the scripts are kept here
so they can be run again when the engine changes (run each without `--write` first to see the result):

    node verify/build-usage-pages.js --write    # /research/which-contractions-do-the-work/ and "In a whole book" on 147 contraction pages
    node verify/build-digit-pages.js --write    # "What is particular to N" on the ten digit pages
    node verify/build-phrase-cells.js --write   # the cell-by-cell line under each phrase on /phrases/
    node verify/build-punctuation-grade2.js --write   # "In Grade 2, the same cell" on the eight punctuation mark pages (18 October 2026)

    node verify/build-grades-guide.js --write   # /learn/grade-1-vs-grade-2/
    node verify/build-learn-guides.js --write   # /learn/what-is-braille/, /learn/how-to-read-braille-by-touch/, /learn/how-braille-is-written/

The three guides built by `build-learn-guides.js` state nothing about people, teaching or history
that is not in a source named at the foot of the page; the sources and the date each was read are
listed at the top of the script. What they say about cells is computed and asserted there.

## Printable tables as PDF (10 October 2026)

    node verify/build-chart-pdfs.js    # prints /es/chart/ and /pt/chart/ to PDF with headless Chrome

The files are the pages themselves, printed with the site's print stylesheet, so their cells are the
cells the suites already check. `verify-pages.js` fails if a chart page changes after its PDF was
printed (`verify/chart-pdfs.json` records what each was printed from). Arabic and Hindi are not
offered yet: the page prints correctly, but the text layer of the file comes out with decomposed
letters, which a screen reader would misread.

## The book, letter by letter (10 October 2026)

`measure-letters.js` counts, for each of the 26 letters over `data/alice-body.txt`, how often it is
printed, how often Grade 2 still writes it as its own cell, which contractions take the rest, and
what its cell is doing each time it appears. It stops unless spelled + inside contractions equals
printed for every letter, and every appearance of a letter's cell has a job found for it.

    node verify/measure-letters.js --write        # data/alice-letters.tsv
    node verify/build-letter-usage.js --write     # /research/what-grade-2-does-to-each-letter/ and "In a whole book" on the 26 letter pages

`verify-pages.js` (section 8) counts it again and fails if the data file, the study or any letter
page says anything else.

## Letter-page word counts (10 October 2026)

Every Spanish, Portuguese, Arabic and Hindi letter page says in how many of the 5,000 commonest
words its letter appears. Nothing counted those again until 10 October 2026, when 32 of the 39
Portuguese figures turned out to be too high and three Hindi ones one out. `verify-languages.js`
(section 5) now counts all 154 from `data/<lang>-reference.tsv` and fails on any that differ.

## Places and counts from the 5,000-word list (10 October 2026)

The contraction pages say where a word stands in the 5,000-word list, how many of the 5,000 are
written with a sign, and how many carry an initial-letter contraction inside them. Nothing counted
these again until this date, and 53 of 97 did not come back: 31 places had been counted with the 26
single-letter entries left out (their 56th for 58th, character 1,560th for 1,586th) and were stated
as places "in English"; nine counts no longer matched the engine (ea 150 for 148, -tion 209 for 206,
be- 24 where 15 words take the groupsign); the page for "many" put it third in its group, and it is
fourth. A place is now the position in `corpus.txt`, as on the accuracy study, and the pages say
which list. `verify-pages.js` (section 9) counts every place, count and comparison again, holds the
hash of the list, and fails on a page that gives a place "in English".

The same section checks `explainWord()`, which the contraction finder prints: over the 5,000, the
wider list and every listed shortform word with and without an s (47,659 words), the segments must
spell the word and add up to the cells the translator writes. Until this date they did not for
listed words with something before the shortform (thereafter was explained as the shortform "after"
alone, 122 words like it; unfriendly had its letters out of place), for a listed word with s added
(newsletters had no explanation), or for a word respelled under 10.6.10 (bein). The cells were
right throughout: over 189,244 word forms and the whole book the translator's output is unchanged.

## The letter pages of four languages (11 October 2026)

`verify-languages.js` section 6. Each Spanish, Portuguese, Arabic and Hindi letter page says which
other codes put a letter on the same cell; the sentence had been written once and never checked. No
Arabic or Hindi page named Portuguese, the hamza forms and क्ष/ज्ञ were missed, and four Hindi pages
(आ, औ, छ, ख) said no other code had a letter on a cell that Portuguese or Arabic uses. The sentence
is now generated from the five engines (the letters of a code are the letters that have a page in
its alphabet) and the suite builds it again for all 159 pages.

The same section holds 47 sentences on the Hindi and Arabic pages about cells and about the word
lists, each rebuilt from the engines or from `data/hi-reference.tsv` and `data/ar-reference.tsv`:
the dots of the ten Hindi aspirate pairs and what each pair shares (eight pages had said "no dot"),
ड़ as its own cell ⠻ and ढ़ as ⠐⠻, the commonest conjuncts, how often न् and anusvara are written
before a dental, the Arabic letter ranks, the sixteen Arabic letters whose cell is a letter in
English, Spanish and Hindi too. It also fails on any Hindi or Arabic letter page that says why the
code is as it is ("यह संयोग नहीं है", "الهيكل اللاتيني"): no source for those was ever read. Run
against the pages as they stood on 10 October it fails in 213 places.

Section 5's pattern for the Hindi count read "शब्दों?" — optional anusvara, not optional plural — and
so could not match "१ शब्द"; it is "शब्द(?:ों)?" now.

`/learn/perkins-brailler/` was checked the same day against the two Perkins documents it names
(History of the Perkins Brailler, J. Seymour-Ford, April 2002; Howe Press & the Perkins Brailler,
McGinnity, Seymour-Ford and Andries, 2004) and Perkins' brailler store, and now links all three.

`verify-languages.js` section 7 rebuilds the computed sentences of the Spanish and Portuguese letter
pages (letters one dot away, the decade of the cell, the cells English braille spends on the letter):
155 sentences, all of which held when first counted.

## The two older studies (11 October 2026)

`/research/contraction-positions/` and `/research/shortform-endings/` report surveys taken against
liblouis 3.29.0 over a word list that was not kept, and say so; those counts cannot be taken again.
`verify-pages.js` section 10 holds what can be: each percentage is the ratio of the counts beside it,
the totals add up, the table of endings is the engine's own `shortformEndings`, 72 shortforms take an
ending and three (about, almost, him) take none, and the number of 10.7 examples is the number in
`ueb-rulebook-examples.tsv`. It found two things: the page said 225 examples where the file has 226,
and the endings table had no row for "braille".

`verify-pages.js` section 11 does the same for the 26 English letter pages: 146 computed sentences
(the dots of the letter, the letters one dot away, the family of three, the digit, "N cells instead
of M" beside each practice word). All held when first counted. Ten pages carried the sentence the
contractions hub lost on 10 October — that the alphabet "runs out", which is why and, for, of, the
and with have cells — and it is gone from them too; the section fails if it comes back.

## Every example that says what it is (11 October 2026)

`verify-languages.js` section 8, over all five languages:

- (a) every `.braille-line` and `.braille-text` whose label names its print ("“una” en braille: 3
  celdas") holds the cells the engine of that language writes, and the count in the label is the
  number of cells — 1,141 examples;
- (b) every `.braille-inline` whose label gives dots holds those dots — 177;
- (c) every table row holding braille in Spanish, Portuguese, Arabic and Hindi is borne out by the
  print beside it, the dot numbers beside it or the cell count, and any dot numbers in the row are
  the dots of its cells — 1,013 rows;
- (d) numbers: the Arabic decimal separator U+066B and thousands separator U+066C are written
  between digits as the decimal mark (2-5-6) and the comma (2) the unified Arabic reference gives
  (Mada, braille.mada.org.qa, read this day), and reported as left out anywhere else. Until now
  they were dropped and ١٫٥ read back as 15. liblouis' ar-ar-g1 does not define either character
  and writes its code point; both lines are in `expected-pattern-differences.tsv`;
- (e) the 63-cell tables on the five dots pages name, for every cell, exactly the letters the
  engines write with it — 756 columns (found: Hindi ड़ on dots 1-2-4-5-6 was missing);
- (f) the counts in the headings of the Hindi and Arabic printable tables are the rows below them
  (found: 19 punctuation marks announced, 21 listed).

`verify-pages.js` section 12 rebuilds what the eight punctuation-mark pages and their hub say:
which letter each mark is when moved down a row (six of the eight are one), and which marks stand
one dot apart.

Read against liblouis 3.39.0 the same day and found true: the Hindi halant page's account of the
four two-letter rules in `devanagari.cti` (lines 317–322) and of कर्म and भर्ती; the Arabic pages'
account of the tatweel (two cells of dots 2-5) and of the letter sign missing before fa.

## Signs, the home page and w (11 October 2026)

`verify-pages.js` section 14 holds the signs section to the 2010 ADA Standards as read on ada.gov
that day: the five ranges of Table 703.3.1 in inches and millimetres on two pages, the clause
numbers and figures of 703.2, 703.3, 703.4 and 216 on the pages that give them, and Article 56a and
54(a) on the packaging guide. Those figures are the standard's, not computed; the check exists so
that one changed by accident fails. Two things are computed: the six elevator messages of Table
407.4.7.1.3, which the translator must write in the cells the standard counts (stop 3, alarm 4,
open 3, close 5, main 3, phone 4), and the cells each legend on the capital-letters page costs in
capitals and as the standard allows. It also fails if a page gives a reason for a rule that the
standard does not give — the pages had a "Reason" column, and "which is why braille is set by
machine".

`verify-languages.js` section 9 rebuilds what the home page says about the three Latin-script
codes: that the capital sign, full stop, question mark and brackets differ from UEB's in both
Spanish and Portuguese; that the decimal comma does not (the page had said it did); which accented
letters the two share, which they write differently and which only Portuguese has (à was missing);
and that the studies it counts are the cards on `/research/` (it said four; there are eight).

The same section holds five pages and `llms.txt` on the letter w. They said the code had no w and
that it was added later, with no source. Edward T. Morman's introduction to the National Federation
of the Blind's edition of Louis Braille's 1829 book says w is the tenth sign of the fourth series
there, out of alphabetical order because in French it appears only in loan words. The pages now say
what is computed — w is j with dot 6 added; u, v, x, y and z are a to e with dots 3 and 6 added;
a to i with dot 6 added are ch, gh, sh, th, wh, ed, er, ou and ow in UEB — and name that source for
the history. The check fails if "added later" comes back or the source goes.

## The tool pages and the hubs (11 October 2026)

`verify-pages.js` sections 15 to 18 try, on the engine, what the tool pages say the engine does.

- 15, translator and decoder: ABCs takes the capitals terminator and McDONALD'S does not (the page had
  it as an example of the terminator; Rules of UEB 8.4.2 and 8.6.3); BSc, a passage of three words in
  capitals, two words that are not a passage; no contraction across a capital sign; a letter sign on
  a to j after a digit, a decimal point or a comma, and on no other letter; the `.brf` in ASCII, 40
  cells by 25 lines; dots 2-3-6 read as a quotation mark before a word and a question mark after one;
  what the Grade 1 setting does with contracted braille ("shop" comes back as "op" with the first
  cell named as left out — the page had said the cells are spelled out as letters); and that more
  than half of the 63 cells have more than one reading in the lookup's table.
- 16, name, bracelet and practice: the 24 names (letters, and cells one more); the seven accented
  names in three codes; José in Spanish and Portuguese, alike but for the é, and each decoder
  reading the other's as "Jos" with the cell named (the page had said each reads a different
  letter); the Spanish é as the UEB cell for "the"; the bead count of a six-letter word with and
  without a capital; and the drill's rules as its script has them — three wrong answers from the
  cells one dot away first, five boxes, local storage.
- 17, page estimator and finder: every figure on `/pages/` and its study that follows from the layout
  of the book — the worked table, 0.124 lines a word, 26.2 cells a line, 29% and 22%, the five
  ratios of braille pages to a print page, and the words three and four braille pages need (the
  table predated the present measurement: 4.6 for 4.7, 582 and 776 for 580 and 773). The constants
  in `tools/estimator.js` must be the book's as laid out today; its two cells-per-word figures were
  one cell behind and were rewritten. BANA Braille Formats §1.6.1 is held as read on
  brailleauthority.org. The finder's preference rules are tried: thence in three cells (10.10.2),
  ence before a, d and r (10.10.6), one-cell groupsigns before two-cell contractions (10.10.7).
- 18, alphabet hub: six of the first ten letters are punctuation marks when moved down a row, and
  the alphabet is ten, ten, five and w.

Not re-measured: the two other novels the estimator page mentions (2.0% and 6.4% more words on a
contracted page). Their texts are not in the repository.

## Spanish and Portuguese: hubs, numbers, punctuation and tools (11 October 2026)

`verify-languages.js` section 10 rebuilds from the two engines what those pages say beyond their
tables:

- the hubs' account of the alphabet — the second decade is the first with dot 3, u v x y z are the
  first five with dots 3 and 6, and which accented letters come out of those two rows (Spanish á é ú
  from h i j, and ñ ü w from g h j with dot 6; Portuguese ç é á ú, and â ê ô à ü õ w). Both hubs had
  "three groups of ten" above a group of five;
- the punctuation pages — no letter has an empty top row, most marks in the table have one, and an
  apostrophe between two letters is the full stop's cell and is read back as a full stop;
- the Spanish decoder — ¿Qué?, ¡Hola! and ¡¿qué?! come back as written (two pages said the opening
  mark could not be returned), and all double quotation marks come back as « »;
- the Portuguese decoder — the share of the reference list ending in o (1,166 of 5,041: the page said
  a guessed º would spoil one word in three), and 1º read back from the lowered digit;
- the tables of names on both name pages, letters and cells.

Held to a source, not computed: the Portuguese hub's sentence on the Grafia Braille para a Língua
Portuguesa (first version drawn up by Brazil and Portugal under a cooperation agreement of 2001 —
presentation of the 3rd Brazilian edition, MEC, 2018).

## Arabic and Hindi: numbers, punctuation and tools (11 October 2026)

`verify-languages.js` section 11 tries on the Arabic engine what `/ar/numbers/`, `/ar/punctuation/`,
`/ar/translator/` and `/ar/decoder/` say: the seven letters that stand on digit cells and take a
letter sign after a number; ١٢٣٤ and 1234 as the same five cells; lam and alif as one cell; the
shadda written before its letter and read back onto it; the 24 marks of the punctuation table; and
that the Arabic comma is dot 5 while the comma inside a number is dot 2 (the page had called them
one shape). Later the same day the Latin ; and ? were added as the Arabic marks' twins — the unified
Arabic reference prints the semicolon's row with ";" itself, and liblouis writes both so — and four
rows left `expected-pattern-differences.tsv`. The Latin comma outside a number is still not written
and is named as left out: the reference calls it the mathematical comma and gives it dot 2, which
after a letter is the cell of the fatha, and that is what liblouis writes for it.

Section 12 does the same for Hindi. It also holds a fix to the decoder made that day: dots 3-4-5-6
at the start of a word is the number sign only when a digit's cell follows, and ण otherwise. Until
then णमो came back as मो, the ण dropped without a word. A word beginning with ण and then a letter
on a digit's cell (णब) is still read as a number; the code has no letter sign to tell them apart,
and the pages say so. The 5,000-word list round-trips as before (four malformed lines fail before
and after).

Also in section 10: the share of each list's first 5,000 words that carry one of the code's own
letters (890 in Spanish, 791 in Portuguese), as the two research pages give it.

## Two more novels, and the ligatures (11 October 2026)

`data/pride-body.txt` and `data/frankenstein-body.txt` are the story text of Project Gutenberg
eBooks #1342 and #84, cut as `data/alice-body.txt` was: from the first heading to the last line of
the novel, nothing of Project Gutenberg's or the edition's around it, blank lines between
paragraphs kept single. In Pride and Prejudice (the George Allen edition of 1894) the 154
illustration blocks are taken out, brackets matched, and the first chapter's heading, which the
edition sets inside one of them, is written CHAPTER I. Frankenstein starts at Letter 1.

`verify-pages.js` section 17 lays both out as the estimator lays out Alice and holds the two pages
to the result: 199.5 and 205.4 words to a contracted page, 3.4% and 6.4% more than Alice; cells to
a word 3.26, 3.33 and 3.30; 33, 57 and 95 words to a paragraph. The pages had 2.0% for Pride and
Prejudice. That figure reproduces exactly if the illustration blocks are left in and laid out as
text, which is how it was made. It fails if either text holds an illustration block or the words
Gutenberg, or if the translator leaves any character of them out.

It did leave one out. `tools/engine.js` had no æ or œ: 21 in Frankenstein, one in Pride and
Prejudice, each named as left out. They are now written as the rulebook gives them (4.3.1 to
4.3.3): the two letters with the ligature indicator, dots 4-5 then 2-3-5, between them; no
contraction takes in a joined letter; a capital Æ under a single capital sign carries a second
sign before the indicator, and in a word in capitals it does not. `verify-ueb.js` compares 34
words with liblouis 3.39.0 (identical in Grade 2, and read back in both grades) and holds the
rulebook's three examples. Two rows left `expected-word-list-differences.tsv` (ædile, færie):

```
word list   : 1036 of 1043 in Appendix 2 written as the rulebook gives them
differing   : 7, matching expected-word-list-differences.tsv exactly
```

## Running text: three novels against the reference (11 October 2026)

`verify-wider.js` now also translates the three novels in `data/` whole, in Grade 2, with this
translator and with liblouis 3.39.0, and compares them word by word: 223,107 words with their
capitals, punctuation, quotation marks and numbers in context.

`lou_translate` breaks a long input line into several output lines, so a paragraph cannot be given to
it whole. (The first attempt did that, and nearly every paragraph of the two longer books came back
out of step; it measured nothing.) Each paragraph is cut at spaces into pieces of at most 300
characters, preferring the end of a clause, and the reference's pieces are joined again. This
translator is given the paragraph whole, because what closes a quotation may be many words on.

```
running text: 22 of 223107 words in three novels differ (0.010%), each in expected-context-differences.tsv
```

`expected-context-differences.tsv` has the 22: book, the words around, the word, both spellings and
the rule that decides it. Eleven are an apostrophe opening a word (’Tis, ’em), which the reference
writes as a closing single quotation mark (7.6.15); six are two chapter headings of Alice, where
the reference makes CHAPTER, the numeral and the A that begins the title one capitals passage
(8.5.4); two are the apostrophe in General----’s; two the long dash of Frankenstein's "——" (7.2.4);
one is the closing mark of ‘Times’, which the reference writes as an apostrophe.

The first run found one fault in this translator. Inside single quotation marks, the mark after a
plural possessive was taken for the closing mark: ‘to pay three months’ rent and to lose the produce
of your garden.’ It is now an apostrophe when the word ends in s, a word follows, and a mark that
follows punctuation closes the quotation before another opens. The rulebook says of this that it
"cannot be wholly automated with complete accuracy" (7.6.13); ‘the girls’ school’, with nothing
before the last mark to show that it closes, is still read the old way. `verify-pages.js` section 19
holds eight sentences of that kind and the figures the study and `/checks/` give.

`build-checks.js` publishes the new file: eighteen files in `/checks/verify/` (nineteen with the four languages' file, added later the same day).

## Running text in four more languages (11 October 2026)

`verify-languages.js` section 13 translates one whole book in each language with this site and with
liblouis 3.39.0 and compares them word by word. Both are given the same pieces, each paragraph cut at
spaces to at most 300 characters. Every word that differs is a row of
`expected-running-text-differences.tsv`: language, kind, the words around, the word, both spellings,
the reason. A piece the two do not split into the same number of words is a row of its own.

| | text in `data/` | words | differ |
|---|---|---|---|
| Spanish | `quijote-body.txt` — Don Quijote, Project Gutenberg #2000, from the first part to the end of the second | 379,584 | 353 |
| Portuguese | `casmurro-body.txt` — Dom Casmurro, Project Gutenberg #55752, in the spelling of 1899 | 65,119 | 132 |
| Arabic | `kalila-body.txt` — Kalila wa-Dimna, the 19 chapter pages of Arabic Wikisource | 45,749 | 58 |
| Hindi | `sevasadan-body.txt` — Sevasadan (Premchand, 1919), scan pages 6 to 299 of the Hindi Wikisource index, once each | 89,098 | 161 |

What it found:

- **Portuguese.** The translator left out a vowel with a grave accent that has no cell (flòr, què) and
  named it. The Grafia Braille para a Língua Portuguesa (3rd edition, 54.2) puts a diacritic before
  the letter — 2-6 grave, 4 circumflex, 4-5 diaeresis, 5 tilde. `engine-pt.js` now does, for è ì ò ù
  î û ä ë ï ö ñ, and reads them back; the 2-6 of the grave and of the question mark are told apart by
  what follows. The reference writes ò with the cell of ó. The other 99 differences are the
  underscores the edition marks italics with.
- **Arabic.** 57 of the 58 are one thing: a shadda that follows its letter's vowel mark. That is the
  order Unicode normalisation (NFC) produces — the shadda's combining class is 33, the fatha's 30. The
  reference table moves a shadda before its letter only when the two are adjacent
  (`ar-ar-g1-core.uti`, the `always بّ 6-12` rules), so here it leaves it after the vowel. The
  5,000-word list has no word typed in that order, which is why it never showed. It is on the
  defects page as a dated card after the five Arabic rows, and `verify-defects.js` holds it from both
  ends (ten defects now). No fix is offered: none has been tried on a copy of the table.
- **Spanish.** Nothing but the edition's two apostrophes for an inner quotation (346) and seven words
  with ï, à or ù.
- **Hindi.** Eleven kinds, most of them slips in the transcription that the two recover from
  differently: a comma with no space after it, a colon typed for the visarga, a mark typed twice.
  One is the reference's two-letter rule again (खम्भे, the fault of भर्ती).

The Hindi text needed care. The chapter pages of the Wikisource edition overlap one another from
chapter 21 on and repeat passages up to six times; the body is built from the scan pages themselves,
rendered once each in order (`ws_range.py`, `hi_body.py` in the working notes).

Regenerate the expected file with the generator kept beside these notes; it assigns each difference
its kind and stops at one it cannot place.

### The lost sections

`verify-pages.js` section 18 and `verify-languages.js` section 12 were missing from two releases this
day. The helper that inserted a section replaced everything from that section's start to the end of
the file's checks, so putting section 17 or 11 back removed 18 and 12. Nothing failed, because a
missing check cannot fail. They are restored; sections are now put in one at a time, and
`verify-pages.js` section 19 fails if the numbering of either suite has a gap. Section 16, which had
not been lost, caught two cells of the name page that the Portuguese change had made wrong, on the
first full run after it.

## The last pages read (11 October 2026)

`verify-languages.js` section 10 now also holds the two alphabet hubs: how many letters are one of
the ten base shapes with dots added (31 of 33 in Spanish, all but í and ó; 36 of 39 in Portuguese,
all but ã, í and ó), the dots of all thirteen Portuguese letters in the answer that lists them, the
15.8% the study gives, and that the pages say the 2018 Grafia's table has no ü (read on page 23 of
the 3rd Brazilian edition; 54.2 writes Führer with the diaeresis diacritic) while the engine, like
liblouis 3.39.0, gives it dots 1-2-5-6.

Section 14 recounts the tables of names and phrases on the Arabic and Hindi pages (35 rows: letters,
cells, and the cells of the Latin spelling in English braille), tries what the matra and nukta pages
say (कइ is क, dot 1, इ; both spellings of क़ are dot 5 and then क; liblouis 3.29.0 wrote ऩ, ऱ and
ऴ with the cells of the anusvara, ड़ and ल), counts the two spellings of a nukta letter in the
5,000-word list (85 and 228), and holds the Arabic study to the number of differences it lists,
which is six since the shadda after a vowel mark was added.

## Changing every number by one (11 October 2026)

Reading a page finds what is wrong on it. It does not find what no check would notice going wrong
later. So every number of two or more digits in the prose of the 233 English pages was changed by one,
a page at a time, and the six suites that read English pages were run. A change counted as noticed when
a failure named its page.

Of 2,252 runs of digits, 407 were not numbers a check could own (the 39 of an apostrophe's character
reference, "CC BY 4.0", "letter 3 of 26"). Of the 1,845 that were, the suites noticed 464.

About 830 of the rest stood in sections that a generator writes: the checks had compared the figures
they were written for and not the sentence around them. `verify-pages.js` section 20 now runs the six
generators with the writing replaced by a comparison, and fails if any of the 192 files is not what its
generator writes today. Where the generators are absent (they are not published) it says so.

The remaining 553 are not this site's to compute, or are computed by nothing kept: a clause number, a
figure read in a standard on a stated day, a count from the 5,000-word list given once in a sentence.
They are held instead. `build-number-ledger.js` writes `page-numbers.tsv`, every number on every page
in order, and section 21 reads each page again and fails on the first number that differs.

**What the ledger is not.** A number in it is not thereby true. It says the number is the one that was
read, and that it has not changed since without the ledger changing with it.

    node verify/build-number-ledger.js            # what differs
    node verify/build-number-ledger.js --write    # after a change that was meant; read the difference first

Run again with the ledger, every one of the 2,079 runs of digits that are numbers was noticed: 881 named
by a suite's report and 1,198 by the ledger alone. (The report names twelve pages at a time and the test
changes every page at once, so the second figure was measured on the ledger directly.)

### What the ledger did not hold

The ledger was then tried by itself on every run of digits on all 444 pages — 85,025 of them, in the
text, the title and descriptions, the structured data and the attributes a reader is given. It held all
37,104 in the text it was written to read and none of the rest:

| where | runs of digits | held by the first ledger |
|---|---|---|
| the label of a drawn cell or a run of braille (`aria-label`) | 37,205 | 0 |
| text of `<main>`, titles, descriptions, text of the structured data | 37,102 | all |
| navigation and footer | 1,880 | 2 |
| term code in the structured data | 1,627 | 0 |
| image text (`alt`) and its caption in the structured data | 2,590 | 0 |
| dot numbers set in `<code>`, `<pre>`, a `<select>` | 674 | 0 |
| the example in a decoder's empty box (`placeholder`) | 24 | 0 |
| not text: colours, viewport, addresses, dates, licence | 3,923 | 0 |

The ledger now takes all of it but addresses, dates, colours and the viewport, and `llms.txt` and the 23
files under `/api/` with the pages: 83,413 numbers when first written. Tried again the same way it held
81,873 of 85,029 runs of digits, and the 3,156 it left are those four kinds and nothing else.

Holding a number is the lesser half. Nothing had ever read the labels on five languages' pages against
the cells they label, or the term codes, the image texts and the data files against the engines. They
were read, and three sections added:

- `verify-pages.js` section 22: each drawn cell against its label, each labelled run of braille against
  its label, each term code and image text against the engine of its language, each decoder example
  against itself, and each engine's own drawing of all 63 cells.
- `verify-pages.js` section 23: `llms.txt` against the pages it speaks of.
- `verify-languages.js` section 15: the 23 data files, cell by cell and entry by entry, against the
  engines, and their figures against the measurements above.

What they found:

- **The four other engines drew their cells wrong.** `cellHtml` in `engine-es.js`, `engine-pt.js`,
  `engine-ar.js` and `engine-hi.js` put a cell's six dots in as 1 to 6. The cell is a grid of two
  columns filled row by row, so the pages write them 1, 4, 2, 5, 3, 6. Dot 2 was drawn where dot 4
  belongs, 3 where 2 does, 4 where 5 does and 5 where 3 does: b was drawn as c. An unraised dot had no
  outline, the class being `dot` where the stylesheet wants `dot off`. This was the default view of the
  translator and the row of cells on the name page in Spanish, Portuguese, Arabic and Hindi, eight
  pages, and had been so since before the first record kept of those files (1 September 2026). The braille characters, the dot numbers, the copy and the
  `.brf` were right throughout. No page held the fault — the cells are drawn when the tool runs — and
  every check until now read pages. Section 22 (f) asks each engine for all 63 cells.
- Two image texts, the Spanish and Portuguese letter a: "los punto 1", "os ponto 1".
- Four Hindi letters (ऋ, ॠ, ऌ, ॡ) with the term code `5---1-2-3` for `5 1-2-3`.
- A label on `/alphabet/` that said "dots 6" for each space between the words of THE BIG RED DOG.
  Section 1 had passed it, because it built the label it expected the same way.
- The card of the reference-table study. Its image reads 9, the rows still open. Its image text said 8,
  its caption 5, and a sentence added this day said it reads eleven.
- `/api/ar/full.json` listed five differences from the reference; the study has listed six since this
  day. `/api/` and two lines of `llms.txt` said five with it.
- `llms.txt` and seven Spanish letter pages gave 18%, and `llms.txt` 16%, for shares the studies give
  as 17.8% and 15.8%.

One claim in `llms.txt` is on no page: that EN 301 549 V3.2.1 mentions braille eight times and gives no
dimension. It was read in the standard (ETSI EN 301 549 V3.2.1, 2021-03): the word occurs eight times,
on pages 23, 31 (twice), 35 (twice), 44, 84 and 85, each time as a form of output. Section 23 holds its
three numbers by name.

Every cell in all 23 data files was right when first read: 1,114 cells beside their dots, 1,142 entries
tried on the engines.

### Not covered by this

- The images themselves. A card's figure is checked in its text, not in its pixels; the seven cards were
  read by eye on 11 October 2026.
- What a tool draws beyond a single cell was not covered when this was written. It was read in a
  browser later the same day: the next section.
- Single digits in `llms.txt` prose (dot numbers), which section 23 leaves to the checks that read cells.

## The tools in a browser (11 October 2026)

The fault in the four engines' drawing was on no page: a tool writes its cells when it runs. So each
of the 25 pages that load a script was opened in a browser, given input in its own language, and what
it drew was measured — where each raised dot really stands in its cell, what the label says, which way
a row runs — against what its engine writes for the same input.

First every page as it loads: 445 pages, 9,405 drawn cells. Each raised dot stood where its label says,
each unraised dot had its outline, and no run of cells went right to left.

Then the tools with something typed into them. What held: the five translators in all three views and
both English grades; the five decoders from braille characters; the cell finders' answers; the bracelet's
cells and bead counts; the contraction finder's counts; 96 rounds of the practice drill, where the cell
shown was always the cell named in the answer.

What did not:

- **Arabic braille ran backwards.** On `/ar/translator/` and `/ar/name/` the row of drawn cells took the
  page's direction: the first cell stood at the right. The translator's list of dot numbers did the
  same. Each cell pins its own direction; the row did not. `translator.js` and `name.js` now wrap what
  they draw in `.cell-run` with `dir="ltr"`, as the static pages do.
- **The cards under a name were not the name's cells.** A card was made for each character, by
  translating that character alone. So प्रिया showed प and then a halant, where the braille has the
  halant first; ज्ञानी showed ज, a halant and ञ for the one cell of ज्ञ; a nukta typed after its letter
  was dropped; the Arabic shadda came after its letter, where it is written before; JOHN showed a
  capital sign on each letter, where the row has two at the front; and the apostrophe of O'Neil showed
  as a quotation mark. `name.js` now cuts the name only where the cut changes nothing — where the cells
  of what stands before and after are together the cells of the whole — and takes each card's cells
  from the row. Tried on 21 names in five languages: the cards read in order are the row.
- **The cell finders in Arabic and Hindi** had their six dots in a row. The container's class was
  `dot-picker`; the stylesheet lays out `.dots-picker` as a cell.
- **The decoder and the slash.** A space between dot numbers separates cells, not words, and no page
  said how to type the space between words. A group with no digits is read as that space, and the
  translator's own list prints a slash for it — which the decoder then reported as left out. The five
  decoder pages now say to type a slash, and a slash alone is not reported. On its Grade 1 setting the
  decoder also told a reader who typed an x that the Grade 2 setting would read it.
- **The estimator.** The word-count box had `max="100"` and a value of 50,000, so a browser marked it
  invalid; the script's own limit is 100,000,000. It wrote "saves 1 pages". And its sentence for a count
  in print pages still ended "the trade's rule of thumb is three to four", which the page had dropped
  on 10 October for want of a source: the claims sweep read pages and not scripts. Every sentence in
  the tool scripts was then read; that was the only one of its kind.
- **Arabic counts.** Under a name and after a `.brf` download the count used one form for one and
  another for the rest, which is not how Arabic counts: ٨ خليّة for ٨ خلايا. A page can now name its
  forms as `Intl.PluralRules` names them, and the two Arabic pages give five.

`verify-pages.js` section 22 (g) to (i) holds each of these where it can be held without a browser: in
the script or in the page. That is weaker than measuring a drawing, and the section says so. The
measuring itself is not in the suites; it was done by hand-written scripts in a browser pane, and is
described here so that it can be done again:

1. Load each page in a frame 1,100 pixels wide. For each `.cell`, take each of its six children's centre
   relative to the cell: left or right half, and which third from the top. That is the dot it is.
2. A child with the class `on` is raised. The raised dots, as places, are the cell drawn.
3. Compare with the digits of the cell's `aria-label`, and for a tool with what `BR.textToBraille` gives
   for the input typed.
4. Two cells that are neighbours in the page and on one line: the second is to the right of the first.

## Pictures, the nineteen, the privacy page, a phone (12 October 2026)

What the two days before had left unchecked.

**The pictures.** `verify-pages.js` section 24 decodes each page image (a PNG, 1200 by 630) and finds
its circles as regions of one colour: a filled white disc is a raised dot, a grey ring an unraised one.
Their centres fall into three rows and pairs of columns, which gives the dots of each cell. All 350
images of a sign showed the cells of their page's term code, the twelve section images what the engine
writes for the print they stand for, and the four charts their cells in reading order. The words inside
an image are pixels of a dot-matrix face and are not read; a sample was read by eye.

**The nineteen.** The contractions chart is headed "The 19 one-cell groupsigns of Grade 2". The hub's
table caption, its answer to "how many contractions are there", its coverage list and the two data
files had it as "19 strong groupsigns". The Rules of Unified English Braille (2024) list five strong
contractions (10.3: and, for, of, the, with), twelve strong groupsigns (10.4: ch, gh, sh, th, wh, ed,
er, ou, ow, st, ing, ar) and ten lower groupsigns (10.6), of which in and en are two. Read on pages
146, 147 and 154 of the PDF. The page now says so and answers with the count, 182 in nine families,
which is what `measure-usage.js` has counted since 4 October. `/api/contractions.json` held 162 of the
182; it now holds all of them — `strongWordsigns`, `lowerWordsigns` and `lowerGroupsigns` are new, and
each of the nineteen carries its `family` — and `verify-languages.js` section 15 (f) fails unless each
family in the file is exactly the engine's. The chart image itself is unchanged: its heading is pixels,
and the page beside it says what the nineteen are.

**The privacy page.** Section 25 holds what `/privacy/` says to the scripts it speaks of: one snippet
on every page and none on the embedded translator; consent set before configuration, the advertising
signals denied everywhere and the analytics cookie denied in the EU's 27, IS, LI, NO, GB and CH; three
events from `site.js`, each with a tool's name and a braille code and nothing else; two things in local
storage; nothing loaded from another site but the analytics script. Measured in a browser on the live
site: typing into the translator sent one request, `tool_used` with `tool=translator` and
`braille_code=ueb`; scrolling to the foot of the page sent none; the embedded translator loaded nothing
from Google. Three sentences were wrong and are corrected: that typing into a tool makes no request
(it makes that one), that the theme is the only thing stored (the practice drill stores its progress),
and that a counted download is a cell diagram (a printable chart is counted too).

**A phone.** Every page was loaded in a frame 375 pixels wide and its width compared with what it needs:
446 pages, none wider than the screen. A box 600 pixels wide put into one of them was reported, so the
measure works. Two tools ran off the side when given one word longer than the screen — the decoder's
text and the finder's heading — and both now break a word anywhere.

**Copy.** With the clipboard replaced by a recorder, each of the 16 copy buttons copied what its engine
writes. Each then said "Copied" in English, on the button and through the live region, whatever the
page's language. The two messages now come from the button (`data-copied`, `data-copy-failed`), and
section 22 (k) fails if a button on a Spanish, Portuguese, Arabic or Hindi page has none. The site
script is named for its hash and kept for a year, so it was renamed and every page's reference with
it; section 22 (j) checks the name against the file. The old file was deleted in that same release, and
for about two minutes a page still held in a cache asked for a script that answered 404. It is back
beside the new one. A hashed file is not to be removed in the release that stops asking for it: pages
are cached for four hours.

## Structure, contrast and the PDFs (12 October 2026)

**Structure.** Each of 446 pages was loaded in a browser and asked what a screen reader asks: its
language and direction, one h1 and one main, a skip link, headings that do not jump a level, ids used
once, a text for every image, a name for every control, words in every button and link, a caption and
scopes in every table, and every `for`, `aria-labelledby`, `aria-describedby` and in-page link pointing
at something there. One page failed: nine header cells without a scope on `/signs/character-height/`.
`verify-pages.js` section 26 reads the same things from the markup on every run; six faults put in on
purpose were each reported.

**Contrast.** With transitions switched off — the first measurement was taken 80 milliseconds after
changing theme, while the colours were still moving, and reported faults that were not there — 14,174
pieces of text on 46 kinds of page were measured against their real background in both themes. None
was below 4.5 to 1 (lowest: 5.74 dark, 4.73 light). The outline of an unraised dot was: 1.41 and 1.31,
against the 3 to 1 a shape needs when it carries meaning, and here it does — one raised dot is dot 1 or
dot 4 only by where it stands among the outlines. `--dot-empty` is now `#66728a` (dark) and `#7c8494`
(light): 3.23 and 3.30 at the lowest of the four backgrounds a cell stands on, measured on 996 cells.
Section 27 computes every pair from the stylesheet's own colours. Not changed: the print palette
(`#999` on white, 2.85) and the rings in the images, which are pixels.

**The PDFs.** Section 28 reads a PDF's drawing instructions from the file's bytes, with no PDF library:
streams are inflated where compressed, paths are followed through the transforms in force, a small
round path of curves is a dot — filled if raised, stroked if not — and six on a grid are a cell. The
two English charts (written by hand, uncompressed, with their text in the clear) print a name over each
cell and its dots under it: all 48 cells in each are drawn as printed and as the engine writes them.
The Spanish and Portuguese charts are Chrome prints of their pages and hold exactly the cells those
pages hold: 81, 81 and 98. A dot changed from filled to stroked in one file was reported. Until this
day the English files were held to nothing and the other three only to a hash of their page.

Both hashed files changed on this day were kept beside their successors: `site.bea07c22.js` and
`theme.d43ad906.css` can go in a later release.

## The keyboard, and paper (12 October 2026)

**The keyboard.** Real key presses were sent to the pages and the trail of focus recorded. Every stop
has the stylesheet's outline, three pixels of the accent colour; the order is the page's order; the dot
finder's six dots take Space and Enter; the small-screen menu opens with Enter and closes with Escape,
which returns focus to its button. Two things were wrong.

- The practice drill redraws itself after every answer and every change of setting, and the redraw
  destroyed the button that had focus. Focus fell back to the top of the page, so each question began
  with a walk through the header; and the result was announced from a paragraph the redraw replaced
  850 milliseconds later. `practice.js` now puts focus back — on the setting chosen, or on the first
  answer of the new question, where focus was inside the drill — and speaks from a region made once
  beside the drill. It also redrew the score only with the next question, so a right answer stood for a
  moment over "Score 0 of 0".
- Below 56rem the menu collapses a grid row to nothing. That clips its links and leaves them in the tab
  order and in the accessibility tree: focus went into links nobody could see. The closed list is now
  `visibility: hidden`, delayed by the time the row takes to close.

Section 26 holds both in the script and the stylesheet. A caution about the measuring: the pane these
pages were driven in draws no frames while it is out of sight, so nothing timed advances — a smooth
scroll, a transition. A focus that "did not scroll into view" and a menu that "stayed visible after
Escape" were that, not faults. Animations were run to their end with `document.getAnimations()` before
anything that depends on them was read.

**Paper.** The outline of an unraised dot was 2.85 to 1 in the three Chrome prints (`#999` on white) and
1.83 in the two English charts, whose rings are stroked at 0.4 points in `#bebebe`. On a printed chart
that outline is the only thing that tells a from a comma. The print stylesheet now uses `#8a8a8a`, the
Spanish and Portuguese charts were printed again with `build-chart-pdfs.js`, and in the English files
the stroke colour was changed in place (the same number of bytes, so the file's offsets stand). All
five are 3.45 to 1; sections 27 and 28 hold the stylesheet and the files to 3.

## Markup, no scripts, and the printed page (12 October 2026)

**Markup.** Every page was parsed: tags that balance, no end tag on an element that has no end,
structured data that is valid JSON, one canonical link that is the page's own address, language versions
that name each other both ways (507 links), and no two pages with the same title or description. Found:
six `<br></br>` on `/ar/research/`, which a browser reads as two breaks each, and six `</input>` on
`/pages/`. `verify-pages.js` section 29 does the same on every run.

**Without scripts.** Each of the 25 pages that load a tool has a `<noscript>` in its own language. The
gap was the frame every page shares: below 56rem the menu opens only by script, and the theme switch
and the ten print buttons do nothing without one. The stylesheet now has `@media (scripting: none)`:
those buttons are not shown, and the small-screen menu simply is. It could not be tried with scripting
really off; the browser was asked whether it understands the query (it does, and reports scripting as
enabled) and the same rules were applied without the condition: the six links then stood on a screen
375 pixels wide. A browser too old for the query behaves as before.

**The printed page.** Not a real print: each page's print rules were applied as if they were screen
rules, in a frame as wide as the printable part of a sheet (718 pixels for A4, 739 for US Letter), and
every table, image and line of braille was measured against that width. Page breaks are not seen this
way. On 444 pages one thing was too wide: the table on `/dots/`, by 34 pixels. Print now trims the
padding of table cells, and all 444 fit both sheets. The Spanish and Portuguese charts were printed
again with the new stylesheet and are byte for byte the size they were.

## Pointers, the server, and what a program is handed (12 October 2026)

**Pointers.** Before every release two scripts kept outside the repository followed every link and
checked the sitemap, the FAQ data and the plain requirements of a page. A reader of `/checks/` could
not see them, and they would not have outlived the work they were written for. `verify-pages.js`
section 30 does what they did: 19,306 links and files named, each there, each in-page link with its id,
none through a redirect; the sitemap against the pages; 77 FAQ questions and answers against the text
of their pages; breadcrumbs; dates; image sizes. Nothing was wrong on the day they were moved.

**The server.** Asked directly: an address that is not a page answers 404 with the not-found page; a
missing trailing slash, `index.html`, `http:` and `www.` each redirect to the one address. The embedded
translator was framed from another origin and drew; an ordinary page framed the same way was refused,
as `_headers` intends. In the widget a browser blocks Cloudflare's analytics beacon — its policy admits
scripts from this site alone — where `/privacy/` had said both of Cloudflare's scripts run there.
Section 25 now holds that sentence to the widget's rule in `_headers`.

**What a program is handed.** `/.well-known/api-catalog`, the agent-skills index and `SKILL.md` had
last been read on 2 October, and nothing checked them. Section 31: the index's SHA-256 is the file's;
the skill file names exactly the fields `/api/full.json` returns; its code sample, run over all 587
entries of the five `full.json` files, gives each entry's character from its dots, except the 30 digits
of the three Latin-script files, where `char` carries the number sign and the file now says so; ten of
its rules for English are tried on the engine; every address it and the catalogue name is here; and
`tools/webmcp.js` is run against a stand-in for `navigator.modelContext`, where each of its three tools
answers what the engine answers. The three files are in the ledger of numbers as well.

## A first visit, in a browser that draws (12 October 2026)

The browser pane used for the rounds above does not draw frames while it is out of sight, and it
keeps what earlier visits left. So a Chrome was started with an empty profile and driven from Node,
with nothing but what Node has built in, and the live site was read the way a first visit reads it.
The six scripts are in `verify/maintain/`.

**One fact, one number.** `same_fact.py` takes every number in the text with the words after it and
lists each phrase that follows more than one number. Over all five languages no fact was found
carrying two numbers; what it lists are different facts in the same words, counts that belong to
their page, and the corrections log quoting what it replaced.

**What a page costs** (`page_weight.js`, empty cache, bytes over the wire). An English page is
about 82 KB of this site's own: the page 5 to 18, the stylesheet 17, the script 4, one font 50.
Nothing blocks drawing but the stylesheet. Three things are larger than that and are not faults of
any file here. Google's analytics script is 170 KB on every page, twice the page. A page in one
language that shows a single letter of another — the "same cell in the other codes" line on the
letter pages — fetches that script's whole font: 57 KB for Arabic, 123 KB for Devanagari, on
138 pages, for at most four characters; a cut-down font of the 43 Arabic and 56 Devanagari
characters such pages use would end it, and needs a font tool this machine does not have. And
Cloudflare's bot detection adds 20 KB.

**How a page arrives** (`page_vitals.js`, 375 pixels wide, slowed connection). On seventeen pages
of all five languages the largest element was painted between 0.8 and 1.2 seconds, and what moved
after first being drawn came to at most 0.005 — a line of Arabic or Devanagari settling by three
pixels when its font arrives. Nothing to change.

**What the browser reports** (`page_errors.js`). All 445 pages were opened under the headers they
are really served with. 443 reported nothing: no error, no warning, no refusal, no failed request.
The other two were `/embed/translator/`, where the refusal of Cloudflare's beacon is intended, and
`/embed/`, where it was not: the header rule for the widget was written `/embed/*`, and a splat
matches nothing as well as something, so it also covered the page that documents the widget. That
page's own analytics were refused by the widget's policy, so its visits were never counted, and
`frame_check.js` showed it drawn in a frame on another origin beside the widget, where `/` and
`/translator/` were refused. The rule is now `/embed/translator/*`.

**What a visit leaves** (`page_cookies.js`, each address in a context of its own). Every page,
the widget included, left two cookies set by Cloudflare's bot detection: `cf_clearance`, a year,
not readable by script, set by the answer to `/cdn-cgi/challenge-platform/…`; and `__cf_ob`, for
the session. Outside the consent regions Google Analytics adds `_ga` and `_ga_YW34MYQBZG`. Nothing
was in local storage. `/privacy/` described the bot-detection script and not its cookies, and
`/embed/` said the widget "sets no cookies of its own". Both now say what was measured and cite
Cloudflare's two pages: that the result of its JavaScript detections is stored in `cf_clearance`,
and that it holds the cookies it lists to be strictly necessary — a list that does not name
`__cf_ob`. Whether the detection should be on at all is a setting of the Cloudflare zone, not of
any file here.

Section 25 now fails if a rule in `_headers` other than `/embed/translator/*` carries
`frame-ancestors`, if any rule sits on `/embed/` or `/embed/*`, if `/embed/` and `/privacy/`
disagree with that rule about the beacon, or if either page stops naming the cookies. Ten faults
put in on purpose were each reported. What the edge sets is in no file, so the cookies themselves
are a measurement to repeat, not a check that runs.

Also read: the redirects (`http:`, `www.`, a missing slash, `index.html`: one hop each to the one
address), the cache and compression headers of each kind of file, the web manifest's icons, and
the 37 addresses the site links out to (33 answer; Britannica and EUR-Lex refuse a script).

## Backgrounds: contrast themes and the print dialog (12 October 2026)

`verify/maintain/shot.js` draws a page of the working folder in that same fresh Chrome and writes a
PNG to look at, or a PDF. It can ask for a width, the dark theme, the print rules, and forced
colours — what Windows' contrast themes ask of a browser. These were the first pictures of the
site looked at in this audit; everything before was read from the page's structure.

**A raised dot was a background.** Under forced colours a browser paints every background in the
reader's page colour. The letter a was drawn as five rings and a gap: its raised dot gone, its
five unraised dots outlined. Every cell on the site and in the embedded translator was the
complement of itself, for the readers most likely to have a contrast theme on. The print dialog
does the same thing by default: printed without "background graphics", the six filled discs on
the page for the letter a were filled white (`pdf_discs.py` counts a printed page's discs by fill
colour: white before, black with backgrounds on, black after). The PDF charts are printed with
backgrounds on and were right. Now `.cell .dot` carries `print-color-adjust: exact`, and a
forced-colours block takes the dots' colours back and draws a raised dot as a disc, and an
unraised one as a ring, of `CanvasText`. The same block draws the pressed dots of the picker, the
bars of the menu button, the theme button's half disc and an underline on the current page, each
of which was also a background and nothing else; `tools/dots.js` outlines the row a link lands on
as well as tinting it.

**Three more things in the pictures.** The widget's outline of an unraised dot was 1.49 to 1 on
white and 1.70 on its dark page — the stylesheet's had been corrected the day before and the
widget, which carries its own style, had not; it is 3.76 and 3.90. The page estimator's five
fields, styled in their own attributes, and the dot picker's six buttons were edged in `--border`,
1.35 to 1; they and the widget's text field now use the colour section 27 measures for the edge
of a control. A table's caption had no padding and stood against the corner of its box, on 321
pages. And at 375 pixels the translator ended a line after "qu" and began the next with "ick":
cells are boxes with nothing between them, and a line may end between any two. The translator, the
name tool and the widget now write each word's cells into one box.

Section 32 holds the rules for both sheets: the print adjustment, the forced-colours block, one
colour for disc and ring, a forced-colours rule for every rule that marks a state with a
background (five found), the menu bars, the row outline, the widget's contrast in both palettes,
and the edge colour of every field styled in its own attribute. Section 22 (g) holds the word
boxes. Twenty-three faults put in on purpose were each reported. The drawing itself is not something
a suite here can do; after a change to how cells are drawn, look at them with `shot.js --forced`.

**The bracelet's space.** Looking at the bracelet tool with two words typed showed seven cells for
"love you", the e and the y side by side. The engine writes the space between words as a space
character, which is no braille cell, and the tool skipped it — though its own code had a label
ready for a blank cell. A space is now drawn as a blank cell and counted (8 cells, 48 positions,
23 raised). Section 16 runs the tool's script against a stand-in for its page with one, two and
three words and holds what it draws to what the engine writes, cell for cell.

**A run of dot numbers cut in two.** On the chart laid out for paper the number sign's dots read
"3-4-5-" over "6". Chrome ends a line after a hyphen between two digits; Firefox does not.
`verify/maintain/measure.js` asks one question of every page in a browser that draws them, and at
375 pixels it found 546 runs split on 136 pages. A cell, a `<code>` or a `<strong>` holding only
dot numbers is now marked `class="dn"`, and a run inside a cell, a definition or a hint line that
also holds words is in a `<span class="dn">`; the stylesheet keeps `.dn` on one line. 1,355 runs
are marked on 158 pages, each checked to be rising digits from 1 to 6. Measured again: no marked
run on two lines, no page wider than the window, every chart still on one sheet; 121 runs inside
sentences still wrap with their sentence, by choice. The Spanish and Portuguese PDFs were printed
again because their pages' markup changed. The two English PDFs come from a generator that is not
in this repository and were not printed again; whether their number sign is split was not read.
Section 33 holds the marking on every page and in the two generators that write dot numbers.

## Questions asked of every page (13 October 2026)

`verify/maintain/measure.js` opens every page in a fresh Chrome and evaluates one expression in
it. The expressions are kept in `verify/maintain/questions/`, and each was run on all 444 pages:

| question | asked at | answer |
|---|---|---|
| `reflow.js` — does anything stick out sideways | 320, 375, 768; 717 under print rules | nothing |
| `scrollers.js` — can every scrolling box be scrolled to all it holds, either way | 375, 768 | every one |
| `print_cut.js` — on paper, does a box cut off what it holds | 717 under print rules | none |
| `targets.js` — a target under 24 pixels whose 24-pixel circle meets another's (WCAG 2.5.8) | 375 | none; small links are 30 pixels apart at the least |
| `focus.js` — every control focused as the keyboard does: an outline, whole, in view | 1100, 375 | all but one link in a sideways-scrolling table, half shown |
| `spacing.js` — anything cut off under the text spacing of WCAG 1.4.12 | 375, 1100 | one number field |
| `motion.js` — anything that moves under reduced motion | 375, 1100, six pages | nothing |
| `split_dots.js` — a run of dot numbers on two lines | 375; 717 under print rules | 121 in sentences, none marked |
| `nojs.js` — with scripts off (`--no-js`, added 18 October 2026): a control with no word that it needs a script, a menu that cannot be opened, a button that does nothing | 375, 1100 | 22 tool pages drew buttons that did nothing (copy, clear, download, the picker's six dots, the finder's examples); now none. The menu shows and the theme switch is gone at both widths; with the menu's rule taken out on purpose, every page reports it hidden |

**Two measurements that could not have seen a fault.** The overflow question first answered
"nothing" at 320 pixels and was then given a page with a 500-pixel box put into it: still nothing.
In phone emulation Chrome zooms out to fit a page that is too wide, and the window then measures as
wide as the page. `measure.js` now opens a plain window of the asked width, the question compares
the page with the document's own width, and the planted box is reported; the runs were made again.
The focus question first answered that no control on any page shows an outline: a focus given by
a script is not drawn as the keyboard's is. `--tab` presses the Tab key once first. Each question
was tried on a fault put in on purpose before its answer was kept.

**What was wrong.** On 57 pages 83 tables stood inside two `.table-scroll` boxes, one within the
other — the home page, `/contractions/`, `/dots/` and 36 Arabic letter pages among them. The inner
box's margin showed as an empty bordered band under the table, and the two boxes scrolled
separately. Each table now has one box. And the page estimator's word count takes up to
100,000,000 in a field 5.5rem wide, which shows five digits; under the WCAG text spacing even
its starting value was cut. It is 9.5rem, and the print-pages field 6.5rem; each field's largest
number was then typed into it and fits. Section 34 holds both.

**Left as it is.** One link in the table on `/dots/`, at 375 pixels, is half outside the scrolling
box when the keyboard reaches it and Chrome does not scroll it in; the table can be scrolled. And
the Arabic and Hindi pages write some numbers in Western digits and some in their own: on the
Arabic pages the count column of the word tables (148 cells on 42 pages), "letter 1 of 28" (28
pages) and the dot numbers under the cards of the alphabet (43, on two pages) are Western, where the
sentences use ١٢٣. Which is meant is the owner's to say.

## What a screen reader is handed (13 October 2026)

**The computed tree.** `verify/maintain/ax_tree.js` asks Chrome for the accessibility tree of each
page — roles and names as assistive technology gets them, after labels are resolved and hidden
things dropped — and lists a control, an image or a table with no name, landmarks of one kind not
told apart, a skipped heading level, and a name that is only braille characters. On 444 pages at
1100 and at 375 pixels: 20,840 links, 6,787 images, 625 tables and every button and field named;
one main landmark and one first-level heading a page; no level skipped. The embedded translator
has no main landmark and no heading, being a widget for someone else's page. Each of those
detectors was tried on a page made wrong on purpose and reported it.

**The language of parts.** A screen reader changes voice only where the markup says the language
changes (WCAG 3.1.2), and nothing had checked that. Unmarked: the names of the other languages at
the foot of every page (1,776 links, each carrying `hreflang`, which says where a link goes and
not what its words are); "Full site in English" on 48 Arabic pages; the English column of the
Spanish and Portuguese tables of 63 cells, and the English lists that four tools write; 28 runs of
Hindi and Arabic letters in the corrections log, and one Arabic letter on a Hindi page. All are
marked. Marking a language name also hands it to the rules that give Arabic and Hindi text their
fonts, which would fetch 180 KB on every page for two words, so a link that carries both
`hreflang` and `lang` keeps the font of its line; `page_weight.js` shows the home page still
fetching one font. `/about/` now fetches both, for the letters its log quotes.

**Dot 4.** Putting the English readings side by side — the English tool against the four others
and three tables, 441 comparisons — found one difference: the others gave dot 4 no reading in
English braille, and the English page gives "the opening cell of the two-cell signs @, $ and &".
The engine writes @ as dots 4, 1; $ as 4, 2-3-4; & as 4, 1-2-3-4-6. All five now agree.

Section 35 holds the marking of every Arabic and Devanagari run, the language names, the English
readings' marking and their equality with `tools/dots.js`, and the font rule. Eleven faults put in
on purpose were each reported.

**The two English PDFs.** `verify/maintain/pdf_text.py` reads a PDF's strings with where they are
placed. In `braille-chart.pdf` and `braille-chart-a4.pdf` the number sign's dots, "3-4-5-6", are
one string on one line, so the split seen on the page laid out for paper is not in them.

**Also seen.** In the tools, a line of cells could begin with the gap that follows a word; the gap
is now inside the word's box and every line begins at the same place (section 22 (g)).

## One fact on two pages, and the dates a page states (13 October 2026)

Dot 4 was found by putting two pages side by side, so more were put side by side. Each letter page
names the letter that the same cell writes in the other codes: 430 such letters, across the 185 letter pages, were
looked up in the other code's own data file, and each has the page's cell. The English table of 63
cells gives a column to each other code: all 155 letters in the four data files stand in the row
of their cell, and where the table says a code has "no reading of its own" that code's tool has
none but the digit every code shares. Nothing was wrong.

**Dates.** Twelve pages state dates in their structured data. Read against the sitemap, whose
dates come from the history of the files: five research articles gave a `dateModified` of 2 or
4 October where their words last changed on the 10th and 11th; four gave only a `datePublished`
in August and had changed in October; and `/checks/` gave the 11th, a date written into the
script that makes it, as two other generators had theirs. `lastmod.py --write` now writes a
page's `dateModified` when it writes its sitemap date, and the three generators read the date
from the sitemap. Section 36 holds a page's own dates to the sitemap and the dates the research
page gives for its articles to the articles' own. Six faults put in on purpose were each reported.

**schema.org's validator.** One page of each kind of structured data was given to
validator.schema.org in a browser. The nine it answered had no error and no warning, covering
WebSite, ImageObject, BreadcrumbList, DefinedTerm, DefinedTermSet, WebApplication, FAQPage,
Dataset, Article and AboutPage. Two cautions. Changing only the address after the `#` does not
run a new test: the first seven answers were the first page's, word for word, until each address
was loaded afresh. And after sixteen requests the validator, which Google hosts, showed its
"unusual traffic" page; nothing was done to get past it, so CollectionPage (the research page) and
the other language sections were not validated there.

**The link graph.** Every page in the sitemap is linked from the text of at least one other page,
and all are within four clicks of the home page (77 at one click, 357 at two, 8 at three, 1 at
four). No page in the sitemap is marked noindex and each is its own canonical. A handful are
linked from the text of only one page — `/numbers/1-to-100/`, `/bracelet/`, the dots pages of the
other four codes — which is a thing to improve, not a fault.

## A long text (13 October 2026)

Nothing had been timed. Each engine was given whole books. Writing braille keeps step with length:
the 700,000 characters of Pride and Prejudice in 0.7 seconds in Grade 1 and 1.9 in Grade 2, Don
Quixote's two million in 1.3. Reading braille back did not: 71 seconds for that novel in English,
40 for Dom Casmurro in Portuguese, with twice the text taking four times as long. A small
experiment found the cause, and it is not obvious: a loop that adds to a string and, each time
round, reads any part of it — its last character, a test against its end, a slice — makes the
engine copy the whole string each time, whichever way it is read; asking only its length does
not. The three decoders that asked "what does the output end with" at every cell now add to it
through one function that keeps its last 64 characters beside it, and read those. After: 5.5
seconds in English Grade 2, 1.7 in Grade 1, a quarter of a second in Portuguese, a tenth in
Spanish. Arabic and Hindi were in step already.

**That nothing else changed.** `verify/maintain/decoder_same.js` gives the same braille to an
engine and to another copy of it and compares the print: the books whole and line by line, every
word of the word lists in three capitalisations, 20,000 random strings of cells weighted towards
the signs a decoder treats specially, and constructions aimed at the new code's edges — runs of
opening brackets longer than its 64 characters followed by a cell whose reading turns on whether
a word starts there, and long runs of digits with a fraction after them. 344,592 pieces in
English, 42,400 in Portuguese, 42,049 in Spanish: every one the same as the engine it replaced.
The comparison was first tried on engines broken on purpose. Two of the first three breaks were
no breaks at all (a tail of no characters is, by the rules of `slice`, the whole string), and
two real ones went unseen until the constructions above were added; it now reports all four.

**The page.** In a browser, the translator drew Alice's 148,000 cells as dots in five seconds,
making 898,000 elements, and did it again at every keystroke; the Unicode and dot-number views
took under half a second. The drawing of more than 4,000 cells now waits a quarter of a second
for typing to pause. What Copy and Download use is set before any drawing, as it was. Typing
into 50,000 characters then held the page about 40 milliseconds a keystroke, where it had held
it a second, and after the pause all 41,570 cells were drawn.

Section 37 holds the three decoding loops to adding through `put()` and not reading the output,
times eight times the braille at no more than twenty-four times the time (the new code measures
6 to 11; the old, put back to try it, 49 and 154), and holds the translator's order: copy first,
drawing after, a waiting drawing dropped when the text changes.

## What nobody types on purpose (13 October 2026)

**Markup in a field.** The site's policy allows inline scripts, so a tool that wrote what was
typed into the page as markup would run it. `questions/injection.js` types seven such strings
into every field of every page, under every setting of its radio buttons, and into the address
after the `#` that the dots page reads, then looks for a script that ran or an element that came
from them. Nineteen pages have fields; none wrote any of it as markup. The question was first
tried on a page made to do exactly that, and reported it five ways.

**Spoiled storage.** `questions/stored.js` puts eleven spoiled values where the practice drill
keeps its progress and two where the theme is kept, opens the page afresh each time and answers
seven questions. With `5` or a quoted string stored — both are valid JSON — the first answer
threw ("cannot create property on number") and nothing was recorded again. The drill now takes
only a plain object from storage and only a whole number from 1 to 5 as a box. Section 16 runs
those two functions against six spoiled values; put back, the old ones fail it.

**Awkward text.** `verify/maintain/engine_fuzz.js` gives each engine 30,000 strings mixing its own
letters with lone combining marks, halves of surrogate pairs, joined emoji, direction marks,
control characters, odd spaces and digits of other scripts, one in five hundred of them 3,000
characters long. 180,000 strings in all were written, read back and accounted for: nothing threw,
nothing but braille cells and white space was written, and the slowest took 66 milliseconds.

## Older browsers (13 October 2026)

Everything above was measured in one current Chrome. The stylesheet and scripts were read for
what an older browser does not know. The scripts are written in the old syntax throughout, and
each newer function they call is tried or guarded (`closest`, `Intl.PluralRules`); only the file
written for AI agents uses newer syntax, and it is loaded by itself. In the stylesheet one
thing mattered. A browser throws away a whole declaration it cannot read, and the header that
stays at the top of the window had a single background: `color-mix(in srgb, var(--bg) 88%,
transparent)`, which Safari before 16.2, Chrome before 111 and Firefox before 113 do not know.
Taking that declaration out, as such a browser does, the header's background measured
`rgba(0, 0, 0, 0)` over the scrolled page. It now has `background: var(--bg)` before it, and
`-webkit-backdrop-filter` beside `backdrop-filter`. Section 34 fails if a colour function of
that kind is the only value a rule gives a property. The other newer things degrade without
harm: `:has()` only tints the chosen radio button's label and trims the printed chart;
`:focus-visible` missing leaves the browser's own focus ring, which nothing removes; the
`scripting` media query missing leaves a no-script reader with the buttons as they were. No
older browser was run; this is from reading the rules, and from taking the declaration out.

## How much of each page is its own (13 October 2026)

`verify/maintain/unique_text.py` cuts the text of every page into sentences and asks of each
whether it stands word for word on another page of the same language, or differs from one only in
its numbers, braille cells, single letters and one quoted word — a template with its blanks filled
in. What is left is the page's own. Read sentence by sentence on the Spanish page for b, the
English page for q and the contraction page for mother, the sorting holds: "Letter 2 of 33", "In
the 5,000 commonest Spanish words b appears in 513", "B differs by one dot from A, F, H and L" are
true and particular to the page, and are the same sentence on 33 pages with other numbers in it.

The 344 pages made one to a letter, a number or a contraction — 78% of the pages, 59% of the
words — are 34% shared, 42% filled in and 24% their own; 193 of them have less than a quarter of
their own, 57 less than a tenth. The other 98 pages are 92% their own. By family, the middle
page's own share: English numbers 8%, English letters 14%, Portuguese letters 16%, Spanish letters
19%, contractions 24%, Arabic letters 35%, Hindi letters 38%. An earlier count (4 October, a
sentence repeated on three or more siblings) put the letter pages at 61-78% unique; this one is
stricter, because it also counts a sentence whose only differences are the page's own numbers.

This is a measurement of repetition and nothing else. It cannot tell a true sentence from a false
one or a useful sentence from filler, and a filled-in sentence can hold a fact found nowhere
else. What it shows is that most of these pages are still, in their wording, one page written
many times — the shape the 22 August demotion was diagnosed as. What to do about it — fold them as
the dot and phrase pages were folded, write each anew, or leave them — is the owner's to decide.

## Sentences with a shelf life (17 October 2026)

The English pages were searched for wording whose truth depends on when it is read: latest,
current, currently, as of, recently, so far, still, today, will be, next. Most were nothing ("d
for today, m for tomorrow"). Six research pages, in all five languages, called liblouis 3.39.0
"the current release"; GitHub's record of the project's releases gives 3.39.0 as published on
2026-09-01 and the newest, with 3.38.0 on 06-01 and 3.37.0 on 03-03 — a release a quarter. Each
page now gives the date instead. Section 36 fails on "the current release" or "the latest
version" and their Spanish, Portuguese, Arabic and Hindi forms. When liblouis 3.40.0 appears the
measurements themselves will need running again on it; the pages will stay true meanwhile.

## One language, one way of saying it (17 October 2026)

`verify/maintain/terms.py` counts competing forms in each of the other four languages. The
Portuguese pages are written in the Portuguese of Portugal throughout (sítio 119 times and site
never, registo, ficheiro, descarregar, prima), the Spanish ones address the reader as tú in all
37 instructions and never as usted, the Hindi pages spell हिन्दी 160 times and ब्रेल 770, and the
Arabic ones برايل 639. What the other side of each count holds is practice words ("você" is a word
to practise on four letter pages), a quoted phrase (मैं तुमसे प्यार करता हूँ among the phrases) or a
sentence about spelling itself — with one exception: the Portuguese research page headed a
section "Verifique você mesmo", the only place the section addressed the reader as você. It reads
"Verifique por si" now. The tool prints its contexts so that a person can tell these apart.

## Quotations and rule citations against their sources (18 October 2026)

`verify/maintain/quote_sources.py` looks for every quotation of six words or more, word for word,
at the pages its page links to. Of thirteen: ten are found at their sources today (Duxbury's
copy of the 1829 book, BANA, Britannica, APH, Paths to Literacy, the ULB record of the 1981
study, NFB); one is a chapter title of Alice, which the site publishes itself; one is the EU
directive, which EUR-Lex will not serve to a program — Article 56a reads, in the UK's official
copy at legislation.gov.uk, "must also be expressed in Braille format on the packaging"; and
one is the rulebook, a PDF. Britannica sometimes refuses a script, so a run can count one fewer.

`verify/maintain/pdf_text.py` was made able to read that rulebook: bracketed strings as well as
Chrome's hexadecimal ones, ranges in a font's ToUnicode map, and lines kept to their own page.
(Until now it had read neither the rulebook nor the older English charts. On 13 October this file
and the helpers' README said it had read the charts; they had been read by a one-off script, and
the tool reads them only now — finding, as that script did, "3-4-5-6" in one string on one line.)
`verify/maintain/rule_citations.py <the rulebook PDF>` then finds the rulebook's 514 numbered
sections, and every rule number an English page cites — 47 — is a section of it. Read one by
one beside the rule's own words, 46 say what the rule says. The one that did not: the study of
running text, and eleven rows of `expected-context-differences.tsv`, gave rule 7.6.15 for
writing the apostrophe in ’Tis and ’em. 7.6.15 is the positional fallback for software without
a list, and by it a mark at the start of a word is an opening quote. The apostrophe is 7.6.6,
whose example is “’Tis late.”, and 7.6.14, a list of words such as 'tis. The quotation from 7.6.13
on that page is exact (page 87 of the 2024 edition).

## The sign pages against the ADA Standards (18 October 2026)

The 2010 ADA Standards were read from the Access Board's own copy (access-board.gov/ada/,
section 703, 11,500 characters). Every measurement on the five `/signs/` pages and on
`/learn/braille-on-signs-and-packaging/`, in inches, millimetres, percent and degrees, is a
figure §703 gives: 105 matched as written, and the rest are the same figures written another way
(55% where the standard says 55 percent, 45 degrees for 45 degree). Two more hits of the count
were not measurements at all but the words "703.3.1 in full". Every clause
number in the pages' tables sits beside the clause it names (703.2.4 is character proportions,
703.2.6 stroke thickness, 703.3 says braille "shall be contracted (Grade 2)", 703.3.1 gives the
domed shape and the table). The capital-letters page puts 703.2.2 and 703.3.1 in its own words
and keeps both. Its cell counts were worked out again on the engine and match: STAFF ONLY is
12 cells in capitals and 8 as the standard allows, because ff, a lower groupsign, may not end a
word, and staff is written st-a-f-f. Nothing on these pages needed changing.

The page estimator's two citations of BANA's Braille Formats (2016) were read at BANA's own
copy of section 1 (brailleauthority.org): §1.7.1 gives 25 lines and "a maximum of 40 cells per
line", the two defaults the tool starts from; §1.6.1 says a volume should end at a logical break —
"the end of a unit, part, chapter, or section" — that keeping to this matters more than uniform
volume size, and that an agency may set the maximum. The page says each of these.

**Not read.** Search Console and Bing Webmaster need the owner's sign-in, which the browser these
checks drive does not have; the comparison of click rates on the pages retitled on 2 October,
due about the 9th, has not been made.

## Arabic mathematics against Mada (18 October 2026)

Mada's Arabic braille portal (braille.mada.org.qa) teaches mathematics in twelve lessons, each
with worked examples, in English and in Arabic, and keeps two tables: the Grade 1 reference and a
mathematics reference. The examples were taken from both versions of the lessons: 47 from the
English pages and 24 more, sentences with Arabic words among them, from the six-dot examples of
the Arabic pages. Examples in the eight-dot computer table, in Latin letters, and the general
fractions, whose layout is lost in text, were left aside. The engine wrote 21 of the 71 as Mada
does; it wrote no sign of arithmetic, comparison or currency. It now writes and reads back all 71
(`verify-languages.js` section 2 holds them).

Mada does not agree with itself in two places. Its Grade 1 table and its mathematics reference give
less than and greater than as 6-35 and 4-35; its fifth lesson gives 45-26 and 45-35, and says the
new Arabic braille uses them in text without clashing with the letters (dot 6 alone is the shadda).
liblouis's Arabic table, whose copyright is Mada's, follows the lesson, and so does the engine. The
mathematics reference gives the comma in a number as dot 5; the second lesson and the Grade 1
table give dot 2, as liblouis and the engine do. `/ar/numbers/` says both.

Fifty signs from the two tables are written, each checked against liblouis alone and between two
digits. Three are written but not read back as signs (the ellipsis, ≠ and √), because their cells
are also three full stops, the Arabic comma and a quotation mark, and the comma and ش. Two faults
turned up on the way. A character the engine does not write, standing between a number and a
letter on a digit's cell, took the letter sign away: ٣,ا was ⠼⠉⠁, which reads 31. And dot numbers
in an Arabic sentence were drawn backwards after an Arabic word, because the Unicode bidirectional
algorithm takes a digit after an Arabic letter for an Arabic-Indic one and a hyphen does not join
those: 38 runs on three pages, measured in Chrome by where each character was drawn. The stylesheet
now isolates each `.dn` run left to right (`verify-pages.js` section 33).

The same fault was then looked for in the other engines: a number, a character the engine does not
write, then a letter on a digit's cell. English, Spanish and Portuguese all lost the letter sign
(3☃b was ⠼⠉⠃, 32); Hindi has no letter sign to lose, which `/hi/numbers/` already says. All three
are fixed and `verify-languages.js` tries 188 such strings in four engines. Written old against new
over every book, word list and page in five languages, one line changed, and it is raw entity text
on an English page read by the Spanish engine. Two smaller things came with it: every engine wrote
U+FEFF as a space (JavaScript's `\s` matches it), and the left-out notice of the translator, name and
bracelet tools showed an invisible character as nothing. U+FEFF is now written as nothing and named;
the notice gives an unseen character's code point (`verify-pages.js` section 16).

Not settled, because it needs the standards' own text: "3.b" in Spanish and Portuguese, and "3,b"
in Spanish, are written so that they read 3.2 and 3,2; liblouis writes them the same way except
Portuguese "3,b", where it puts dot 5 before the b.

## Sections 3 and 4 of the rulebook (18 October 2026)

`ueb-rulebook-examples.tsv` had nothing from section 3 (general symbols) or section 4 (accents, Greek
letters, eng and schwa, the sharp s): it was built from examples the engine already wrote. The
rulebook PDF was read again with the five glyphs its braille font leaves unmapped (codes 11 to 15 of
font 105: dots 3, 4, 5, 6 and 1-2) taken from the appendix's own lines of braille order. Appendix 3,
the Symbols List, gives each symbol's cells and the rule or the Guidelines for Technical Material
section it comes from. 59 worked examples of sections 3.3, 3.10, 3.11, 3.15, 3.17, 3.18, 4.5 and
4.6 were copied by hand from that text with the print spacing as printed; the engine wrote 37. It
writes all 59 now and they are in the file; 57 read back (the double prime after a letter, and μ,
which comes back as the micro sign µ, are marked `no`). The symbols are the rulebook's cells; the
decoder reads δ ε ν σ τ as Greek only where they do not follow a letter, because after one they are
the final contractions ound, ance, sion, less and ount. Over the three books and the four English
lists nothing is written or read differently.

The 39 examples of 4.1 to 4.3 followed (now 1,830 rows). The engine had no letter with a macron, breve or caron; the
letters are now derived from their Unicode decomposition into one base letter and one 4.2 modifier, a derivation that
reproduces all 139 accented letters already in the table, plus ł and ħ from the rulebook's examples. Text is composed
(NFC) before it is written in English, Spanish, Portuguese and Arabic, so a separately typed accent is not left out.
Rule 6.6, the numeric space: the decoder read ⠐⠁ to ⠐⠚ inside a number as letters or contractions (3bdejjj); it now reads
them first. The writer takes a no-break, narrow no-break or thin space between two digits as numeric and any other space as
ordinary, the rule's own default where the space is not clearly numeric. `verify-rulebook.js` writes the five 6.6 examples
and 3.17's phone number with no-break spaces and reads them back; they are not in the examples file, whose print is as
printed, because the PDF does not say which space its print used.
Sections 3 to 8 were then extracted whole and each pair either matched exactly (the print is then right; 56 added) or
read by hand. Two gaps came out. One-character fractions (6.2.1) were left out; they are written ⠼ numerator ⠌
denominator and read back. And hyphen-joined letters (5.9.1): the engine now chooses between an indicator per letter,
one word indicator from some segment on, and the word indicator with a terminator before a contracted segment, by
fewest cells with no contraction lost; a tie keeps the letters one by one, as t-n, {k-p} and br-r-r in 10.1, 2.6.3
and 10.12.14 do. 5.9.1's own un-e-mo-tion-al, also a tie, takes the word indicator in the rulebook, and the engine
writes the form it prints as the alternative; it is not in the file. The decoder holds ⠰⠰ to a space or ⠰⠄ (5.3.2).
Still not handled: the rulebook's 6.2.1 writes "4 500 000 [spaces in print]" with numeric spaces; the engine takes only
a no-break or thin space as numeric, since rooms 101 102 103 has the same shape. And the grade 1 passage indicator
across words (5.4.2, H-o C-h-i M-i-n-h).
Left aside: the grave accent shown alone, which Appendix 3 lists as ⠨⠡ (the engine's) and 4.2.6 writes as ⠘⠡; the PDF
does not say which print character the example uses.

Two faults came with it: an accented letter in a grade 2 word that might read as a shortform word
threw ("Im Frühling"), and an accented vowel was not a vowel to rule 10.9.3. The rest of section 3
and section 4.1 to 4.3 still has examples that are not in the file: laid out in columns, depending on
the numeric space (6.6), or on symbols the Guidelines define; they need the same reading by hand.

## Kept from Google's index (18 October 2026)

Measured that day: Google had 458 pages indexed and 62 only discovered, 13 of those among the site's most
original pages (the six /signs/ pages, /learn/what-is-braille/, /learn/perkins-brailler/, two studies, three
contraction pages), never crawled although the home page, crawled on 13 October, links to two of them and the
footer of 237 pages to /signs/. By family over the last 28 days the 344 pages made one to a letter, digit or
contraction earned 12% of Google's 413 impressions; over Bing's period, under 1% of 10,428 impressions and 7%
of 228 clicks; and 13% of Copilot's 4,084 citations. The owner decided to keep 343 of them (all but
/numbers/1-to-100/) out of Google's index alone: each carries `<meta name="googlebot" content="noindex">`, none
a robots noindex, and all stay in a sitemap for the engines that read it (since 19 October,
`sitemap-templates.xml`). `verify-pages.js` section 30 holds
the set. Indexing was requested in Search Console for the ten original pages the same day; /signs/dimensions/
was crawled within the hour and reported "Crawled - currently not indexed". To undo: delete the tag and the check.

## Not counted, the drill's first screen, two sitemaps (19 October 2026)

Three things the audit of 18 October found, each measured before it was changed.

**A visit made while checking the site was counted.** The analytics script is no longer in a `src`
attribute: a loader in each page's head reads a note in local storage, `bc-not-counted`, and fetches
the script only when the note is not there. The note is kept by a button on `/privacy/`, or by any
address that ends in `#not-counted` (heard on load and on a change of hash, since adding a hash to the
page already open does not reload it); `#counted` takes it away, and pressing the button also sets
Google's own `ga-disable-` switch for the page. In a fresh Chrome, with Google's addresses refused:
the script was put in the page on `/` and not on `/about/#not-counted`, `/`, `/hi/` or `/practice/`
after it; the button turned it back on and off; `/privacy/#not-counted` typed onto the open page kept
the note and the button showed it. `verify-pages.js` section 25 holds the loader, the name of the note
in both scripts, and what `/privacy/` says; seven faults put in on purpose were each reported.

**The drill moved the page.** At 375 pixels on a slowed connection `/practice/` measured a layout shift
of 0.128: its box held one line until the script drew 565 pixels of drill. The drill's first screen is
always the same (letters, cell to name), so `build-practice-skeleton.js` runs `practice.js` against a
stand-in page, takes what it draws first and makes it inert: spans for buttons, the answers and the
score kept but not shown, no dots raised, no ids or names, `aria-hidden`. Its height matched the drawn
drill's at every width tried from 320 to 1400 pixels (blank answers did not: at 320 to 340 the score
runs to two lines). The shift is now 0 at 375 and at 1100. Under `scripting: none` the theme takes it
away. `verify-pages.js` builds it again and compares; a change to the drill's markup is reported.

**The sitemap listed pages Google is asked not to index.** Google: "Include the URLs in your sitemap
that you want to see in Google's search results" (build-sitemap, updated 2026-07-08). `sitemap.xml`
now lists the 101 pages Google may index and `sitemap-templates.xml` the 343 kept from Google alone,
each entry moved as it was; `robots.txt` names both, so every engine still finds them. `lastmod.py`
dates both, and section 30 holds each page to the right one.

## The small-screen header (22 October 2026)

Looked at for the first time at tablet width, the header showed the menu button beside the theme switch
rather than at the far edge. Measured at 375, 768 and 880 pixels, in English and Arabic: the stylesheet of
1 September already gave the menu `flex-basis: 100%` and the button an auto margin, but the header's row
never wrapped. So the closed menu sat in the row as 126 empty pixels (at 375) that took the button's space
and squeezed the logo out of its mark (107 pixels wide instead of 129), and the open menu was a column
beside the logo that made the header 279 pixels tall. Under 56rem the row now wraps, with no gap between its
lines, and the logo's line is the header's height (3.75rem), so the logo and buttons stand still with the
menu open, closed, or shown without a script; the closed header is 61 pixels, as before, and the desktop
header is unchanged. Reflow, targets, no-script and focus were asked of all 444 pages again (focus: only the
known link on `/dots/`). `verify-pages.js` section 26 holds the three rules; with the wrap taken out it fails.

## Two pages that said least of their own (19 October 2026)

Of the pages Google may index, `/ar/punctuation/` (248 words, 58% its own by `unique_text.py`) and
`/hi/phrases/` (61%) were the thinnest. Neither was padded; each was given what it lacked.

`/ar/punctuation/` had no sentence. `build-ar-punctuation.js` adds the examples of the fifth lesson of
eight-dot braille on Mada's Arabic Braille portal, written in six dots by the engine (each is also what
liblouis's ar-ar-g1 writes, and each reads back), and the lesson's eight marks in eight dots, written by
liblouis's `ar-ar-comp8`, which writes all seven of the lesson's examples as the lesson prints them. The
lesson says the comma, semicolon, colon and mathematical comma "stayed as they are" in six dots with dot 7
added. That holds for the two commas (5 to 5-7, 2 to 2-7). The colon is two cells in six dots, as Mada's
own second mathematics lesson writes it (5, then 2), and one cell in eight (2-5-7); the semicolon is two
(5, then 2-3) and becomes 5-6-7, which is neither of its cells with dot 7. 591 words, 82% its own.

`/hi/phrases/` promised that each phrase shows one rule of the code and did not say which.
`build-hi-phrases.js` says it for each, with the cells, after asking the engine for every order it
describes: the halant before its consonant (स्त, न्य, प्य), ृ as two cells, क्ष as one, the nukta before
its letter in both of its Unicode spellings, the anusvara and chandrabindu after the vowel sign. The
rules are those of `/hi/halant/`, `/hi/matra/` and `/hi/nukta/`. 741 words, 81% its own.

`verify-languages.js` section 11 builds both again and compares; a fault put into each was reported.
`/hi/name/` and `/hi/decoder/` (62%, 67%) were left: what they share is the block of cards to the
other Hindi pages, and the tool is the page.

## Published (11 October 2026)

`build-checks.js --write` writes `/checks/` — what each check asserts and how to run it — and copies
twenty-one files to `/checks/verify/`: the seven suites, the two measuring scripts, the liblouis
fetcher, the seven files of expected differences, the ledger of numbers and the script that writes
it, `chart-pdfs.json` and this README. Five files are
withheld and the page says why: `corpus.txt` (google-10000-english: educational and research use;
commercial use advised against without an LDC licence), `wider-list.txt` (hermitdave/FrequencyWords,
CC BY-SA 4.0, raw subtitle vocabulary) and the three files drawn from the Rules of Unified English
Braille, which is CC BY-NC-ND 2.5 Australia (read from page ii of the 2024 edition). The page gives
each source, and for the two word lists the SHA-256 of the lines joined by a line feed.

`verify-pages.js` section 13 fails if a published copy differs from the file here, so run
`build-checks.js --write` after changing anything in this folder. `/checks/verify/*` is served with
`X-Robots-Tag: noindex`; the page is the thing to index.
