# Pre-registration — `runs/2026-09-10-phayan-romanization`

```
run_id:       2026-09-10-phayan-romanization
product:       phayan
tier:          standard
written_at:   2026-09-11T00:20:00Z
revision:      1 — before the adversary
```

## 1. Question

When two independent sources write the same Thai proper name in Latin letters, what exactly do they
disagree about, and which feature of the Thai spelling predicts the disagreement?

Population: Thai proper names of places, at two levels of observation.

- **Frame E**, entities: an OpenStreetMap object inside Thailand that carries `name:th`, `name:en`
  and a `wikidata` tag, joined by that tag to a Wikidata item.
- **Frame W**, words: a Thai word in Phayan's own corpus that also appears in the Wiktionary
  extract, so that two independently produced romanizations of the same word exist.

The two frames are reported separately and are never pooled.

## 2. Method

A session that has never seen this run must be able to execute the text below. It is written for
that reader: the re-execution replicator receives this section and nothing else.

### 2.1 The two axes

Every compared pair is classified into exactly one **kind** in §2.4, applied in the order written,
first match winning. Every entity additionally carries a set of **Thai features** in §2.5, computed
mechanically from the Thai string. The finding is the cross-tabulation of kind by feature.

### 2.2 The frame

`frame_e` is drawn by iterating the 77 published ISO 3166-2:TH province codes and, for each, asking
Overpass for the objects inside that province area carrying all three tags:

```
[out:json][timeout:90];
area["ISO3166-2"="<CODE>"]->.p;
(node["name:th"]["name:en"]["wikidata"](area.p);
 way["name:th"]["name:en"]["wikidata"](area.p););
out tags;
```

plus one reconciliation query over the whole country:

```
[out:json][timeout:180];
area["ISO3166-1"="TH"][admin_level=2]->.th;
(node["name:th"]["name:en"]["wikidata"](area.th);
 way["name:th"]["name:en"]["wikidata"](area.th););
out tags;
```

User-Agent on every request: `article-research/1.0 (phayan romanization divergence run; contact
info@zava-solutions.com)`. Serial requests, at most one in flight, at least 3 seconds between two
requests to the same host. On 429 or 503, honour `Retry-After` and retry up to three times; on
timeout or 504, retry once after 30 seconds. The measured national count before this run was 5 594,
recorded in `data/feasibility/FEASIBILITY.md`; a national total that disagrees with the sum of the
provinces by more than 10 per cent is a finding about the frame and is written into the dataset, not
smoothed over.

### 2.3 The sample, fixed before the first fetch

Every object returned by either query is collected, deduplicated by `(type, id)`, and sorted
ascending by `type` then `id` as strings. **Every fifth object in that order is taken.** With the
5 594 measured, that is about 1 119 entities. The rule is written here so the draw cannot be chosen
after seeing which entities differ.

`frame_w` is every word in Phayan's corpus (`apps/api/src/content/seed/kaikki-enriched.jsonl`) whose
`word` also has an entry in the Wiktionary extract with a `romanization` field. No sampling: the
whole overlap is taken.

### 2.4 The classifier — the instrument

Applied to a pair `(L_osm, L_wd)`, the English name from OpenStreetMap and the English label from
Wikidata, and to the pair `(T_osm, T_wd)`, the Thai names from the same two sources.

`norm(s)`: Unicode NFC, trim, collapse runs of whitespace to one space.

`PREFIXES`, stripped repeatedly from the front of a Thai name, in this order: `บ้าน`, `วัด`,
`สถานีรถไฟ`, `สถานี`, `เทศบาลเมือง`, `เทศบาลตำบล`, `เทศบาลนคร`, `เทศบาล`,
`องค์การบริหารส่วนตำบล`, `องค์การบริหารส่วนจังหวัด`, `ตำบล`, `อำเภอ`, `จังหวัด`, `แขวง`, `เขต`,
`ถนน`.

`DISAMBIGUATORS`, matched case-insensitively against trailing words: `railway station`,
`train station`, `railway halt`, `station`, `bts station`, `mrt station`, `bus terminal`,
`intersection`, `junction`, `airport`, `bridge`, `temple`, `wat`, `market`, `hospital`,
`university`, `school`, `municipality`.

`CONSONANT_PAIRS`: `bbp`, `ddt`, `kkh`, `tth`, `pph`, `st`, `chj`, `fph`, `ngn`, `lr`, `wv`.

Categories, in this order, **first match wins**:

1. `thai_forms_differ` — `strip_prefix(norm(T_osm)) != strip_prefix(norm(T_wd))`.
2. `identical` — `norm(L_osm) == norm(L_wd)`.
3. `whitespace_or_case` — equal after lowercasing and deleting every space.
4. `disambiguator` — one token list is a prefix of the other and every extra trailing token comes
   from `DISAMBIGUATORS`.
5. `translation_or_exonym` — the two token lists share no common token, and one is not a prefix of
   the other. A different name, not a different spelling of the same name.
6. Otherwise, compare token by token and classify on the **first differing token pair**, in this
   order:
   - `tone_written` — equal once all combining marks in U+0300–U+036F are removed and both are
     decomposed, and one of the two carried a mark;
   - `vowel_length` — equal once any run of two or more of the same vowel letter is collapsed to one;
   - `consonant` — the first character at which the two tokens differ forms a pair in
     `CONSONANT_PAIRS`;
   - `word_carried_over` — the token lists have different lengths;
   - `other_spelling` — none of the above.

A pair that matches no category through 6 ends at `other_spelling` and its two forms are written into
the dataset verbatim, so the category can be inspected rather than trusted.

### 2.5 The Thai features, computed from the Thai string

Computed on `norm(T_wd)`, the Wikidata Thai label, and only for entities whose kind is not
`thai_forms_differ`. Each is a boolean or a small enum, computed by a program, never by hand:

```
syllable_count            integer, counted from the vowel and final-consonant structure
has_silent_ha             a leading ห before a single low-class consonant
has_cluster               an initial consonant pair on the published cluster list
has_long_vowel            a long vowel form present
has_tone_mark             one of ่ ้ ๊ ๋
has_homophone_letter      a letter from one of the published same-sound sets
first_consonant_class     mid | high | low
has_admin_prefix          one of PREFIXES present at the front
has_thai_digit            a character in U+0E50–U+0E59
pali_loan                 the Wiktionary extract marks the entry as a loan from Pali or Sanskrit,
                          else null
```

The published cluster list, the same-sound sets and the vowel tables are transcribed from the
Royal Institute's rule document into `data/tools/thai-tables.json` and hashed into `DATASET.json`
before the first measurement.

### 2.6 Arm 2 — the derived systems

A program applies to each Thai string, independently:

- `rtgs` — the Royal Thai General System, from the Royal Institute's published table;
- `ala_lc` — the ALA-LC table as published by the Library of Congress;
- `paiboon` — the Paiboon system, from the table Phayan already carries.

A derived form is compared with the crowd forms by the same classifier, and its own disagreement is
reported as a separate kind series. **It is never pooled with the crowd series**, because one side
here is a program's output and the other is a person's choice.

### 2.7 Arm 3 — the word level

For each word of `frame_w`: Phayan's `paiboon`, Phayan's `romanizedRI`, the Wiktionary
`romanization`, and the three derived forms of §2.6. Every pair among those that exist is classified
by §2.4, and the word's own features from §2.5 are attached.

**Phayan's `romanizedRI` is Phayan's implementation of the Royal Institute system and not the Royal
Institute's output.** It is used as one comparator among several, never as the authority.

### 2.8 Calibration, before the first real comparison

Twelve fixtures are built by hand in `data/fixtures/`, each a pair of Latin forms with a
known-correct category written down from the published tables and the definitions in §2.4: two for
each of `whitespace_or_case`, `disambiguator`, `vowel_length`, `tone_written`, `consonant` and
`word_carried_over`. The classifier must score twelve of twelve on the Latin side.

For the derivation, twelve Thai strings with their published `rtgs` and `ala_lc` outputs are taken
from the worked examples in the two rule documents and must be reproduced exactly. A derivation that
cannot reproduce a published example of its own system is not an instrument.

Both results are written into `DATASET.json` as `calibration`. A run that fails either **does not
proceed**: it records `METHOD_BROKEN` with the failing fixture named.

### 2.9 Repetitions

Each entity is fetched once from Overpass and once from the OSM API for its last editor. A
pre-registered 10 per cent subsample, taken as every tenth entity in the same fixed order, is
re-fetched immediately after the main pass on the same day, and the two fetches are compared. Any
entity whose `name:en` or `wikidata` value changed between them is dropped and counted.

### 2.10 What is discarded, and on what rule

- An object whose `name:th` contains no character in U+0E00–U+0E7F.
- An object whose `name:en` is empty after `norm`.
- A duplicate `wikidata` value within the draw; the first in the fixed order is kept.
- An entity whose Wikidata item has no English label is **kept and reported**: it is a fact about
  the sources, and it is never folded into `identical`.
- An entity whose Wikidata item is a redirect, or whose fetch fails after the retry policy, is
  recorded as `fetch_failed` and counted, never as a "no".
- Rows the classifier cannot reach because the Thai feature extractor returned a parse error are
  reported separately and never folded into a category.
- Nothing else. Frame E and frame W are never pooled.

### 2.11 The numerator of every share, and its denominator

The denominator is written here for every share, before anything is fetched. This is the joint the
previous run broke.

| Share | Numerator | Denominator |
|---|---|---|
| `share_identical` | kind is `identical` | **D1** — entities of frame E where both sources carry a Latin form AND `strip_prefix(T_osm) == strip_prefix(T_wd)` |
| `share_convention` | kind is `whitespace_or_case` or `disambiguator` | D1 |
| `share_real` | kind is one of `translation_or_exonym`, `tone_written`, `vowel_length`, `consonant`, `word_carried_over`, `other_spelling` | D1 |
| `share_thai_differ` | kind is `thai_forms_differ` | **D2** — all drawn entities of frame E whose two Thai forms could be read |
| `share_by_kind[k]` | kind is `k` | D1 for each kind in §2.4 |
| `share_word_pairs` | word pairs agreeing | **D3** — frame W pairs where both forms exist and a derived form could be produced |
| `share_agree_with_rtgs` | the crowd form equals the derived `rtgs` form | entities of D1 where an `rtgs` form was produced |

**Every crowd share additionally carries the count of distinct contributors**, hashed, computed from
the OSM API's last editor for each object in the numerator and in the denominator. The contributor,
not the entity, is the real sample here.

### 2.12 Statistics

Wilson 95 per cent intervals on every share. A table, not a chart, below twenty points, and every
figure carries the table beneath it. No share is published below n = 30; between 30 and 300 the
share is published with its raw counts beside it. A percentage from fewer than twenty units always
travels with its counts.

### 2.13 Output

- `data/frames.json` — the frame manifest: the queries, their timestamps, the national count, the
  province sum, the draw rule, the resulting sizes.
- `data/entities.jsonl` — one row per entity, appended the moment it is produced, never rewritten:
  `entity_id` (the `wikidata` Q-id), `osm_type`, `osm_id`, `osm_url`, `t_osm`, `t_wd`, `l_osm`,
  `l_wd`, the German and French labels where present, `kind`, the §2.5 features, `contributor_hash`,
  `fetched_at`, `http_status`, `fetch_failed`.
- `data/words.jsonl` — one row per frame W word.
- `data/derived.jsonl` — one row per `(thai_string, system)`.
- `data/fixtures/` and `data/calibration.json`.
- `DATASET.json` and `DATASET.csv`, created with `"state": "IN_PROGRESS"` before the first fetch.
- `data/tools.json` — every tool version, read by running the tool.

**No contributor is identified in any published file.** The OSM user name is stored only as a
SHA-256 with a per-run salt, and the salt never leaves the session's memory: it is generated at the
start of the run, used to compute the hashes, and never written to disk anywhere. A published
dataset must not expose the people who wrote the names.

## 3. Expectation

Direction, not a point.

- After the conventions are removed, **more than 60 per cent of D1 entities are `identical`**. The
  belief that Thai romanisation is chaotic is largely wrong in this frame.
- The single largest non-identical kind is **`disambiguator`**, not a spelling difference.
- Among the genuine differences, the ones that carry **vowel length** and **`word_carried_over`**
  will be over-represented relative to their share of the frame.
- The crowd sources will agree with `rtgs` in more cases than with `paiboon`, because RTGS is the
  system Thai institutions actually use for place names.
- **The contributors will be few.** Two editors wrote 12 of 25 objects in the feasibility probe, so
  the expectation is that a small number of accounts wrote a large share of the Latin names in this
  frame — which would make "two sources" a weaker independence claim than it looks.

## 4. Falsifier

Any of these, evaluated by a script over `DATASET.json`:

- `share_identical` over D1 is **below 60 per cent**: the expectation is refuted and the article
  says so in the sentence carrying the number.
- `share_real` over D1 is **below 1 per cent**: the phenomenon is too rare to characterise by kind
  and the article becomes one about naming convention instead of spelling.
- Every derived `rtgs` form agrees with every crowd form: arm 2 discriminates nothing and the arm
  is dropped with the reason recorded.
- The distinct-contributor count for D1 is **above 30 per cent of the entity count**: the frame is
  not dominated by a few editors, the expectation about authorship is refuted, and the independence
  claim is stronger than predicted.

## 5. Abandonment condition

- The twelve classifier fixtures do not score twelve of twelve, or the twelve published derivation
  examples are not reproduced → `METHOD_BROKEN`, with the failing fixture named.
- Neither the Royal Institute rule document nor the Library of Congress table nor any mirror of
  either is reachable → arm 2 has no authority → `NOT_MEASURABLE`.
- After the whole documented fallback chain, fewer than 300 entities join to a Latin pair → `NOT_MEASURABLE`.
- The classifier disagrees with its independent derivation on more than 20 per cent of a
  pre-registered 100-entity subsample → `METHOD_BROKEN`, because the instrument is not reproducible
  and no share it produces can be defended.

## 6. Estimate

- **What the run needs from Jakub:** nothing. Every input is fetched from a public source by an
  anonymous client. No account, no phone, no credential, no payment.
- Machine time: 6 to 9 hours, dominated by politeness pacing against Overpass and the Wikidata API.
- Money: **0 USD** for data. Model time is the whole cost. Extrapolated from the only calibration
  point available — the Viallo metadata run at 22.71 USD for six sessions and 103 minutes of model
  time — this run's ten child sessions, three of them on Opus, are estimated at **30 to 60 USD**.
  The estimate will be wrong and is recorded as an estimate.
- Claims expected in the ledger, by type: **M** — every share, every count, the calibration result
  and the derivation error rate. **S** — what the Royal Institute and the Library of Congress
  prescribe, quoted from their documents. **D** — none; this run uses no first-party behavioural
  data. **U** — none permitted.

## 7. Adversary dispositions

Not yet filled. Written after the adversary returns and before Gate B renders.

## 8. Amendments

Append-only. Empty.

--------------------------------------------------------------------------------

*(The placeholder above is superseded. Revision 2 of this document begins here. Sections 1 and 2
above are unchanged and remain the record of what was written before the adversary; every change
below is an amendment under section 8 and is dated. Nothing here was written after a result was
seen: no share had been computed when this was written, and no draw had been taken.)*

## 7. Adversary dispositions

The adversary returned `ADVERSARY.md` (16 objections, 2026-09-11T00:58Z) before any measurement. It
had no access to any result because none existed. Each objection is answered below. Read together
with section 8, which holds the amended text.

### A-1 Frame W does not exist as the method describes it — **accepted**

The objection is correct and the failure would have been silent: the extract has no top-level
`romanization` key, so a literal implementation selects zero words and reports an empty arm without
an error. Section 2.3 now names the exact selector. Measured on this machine before the amendment:
22 333 `sounds[]` blocks carry `raw_tags == ["Paiboon"]` and 22 333 carry `["Royal Institute"]`, and
these are the only two Latin romanizations the extract holds. Frame W was built from them and holds
17 396 rows.

### A-2 The classifier misroutes the phenomenon it exists to catch — **accepted**

The objection names the aspiration contrast, which is the most common Thai romanization difference,
and it is right that the pre-registered `CONSONANT_PAIRS` test cannot see it: in `thai` against
`tai` the first differing index is the `h`, not a consonant pair. The implementation had already
been rewritten for this reason before the adversary returned, and the adversary's own table of seven
pairs is now part of the smoke test. See A-08.

### A-3 Frame E leaks entities, characters and whole provinces — **accepted**

The objection is correct and it is the most consequential one. The query asked only for nodes and
ways, so the whole administrative-boundary layer was absent, and because the national reconciliation
query had the same omission the 10 per cent tripwire could never fire. The adversary measured 1 142
qualifying relations in Thailand, 17 per cent of the joined population. Both queries now include
`relation`. Five province files fetched under the old query are kept in
`data/frame_e/superseded/` and are not used.

### A-4 The two sources are one source — **accepted in part**

Accepted as a confound that must be measured rather than argued away. The adversarial claim is that
a Wikidata English label often descends from the English Wikipedia article title, so agreement
measures copying and disagreement measures which copy is stale. This cannot be removed from the
design, but it can be quantified: a third series is pre-registered in §2.11,
`share_wikipedia_copy`, over the entities where the Wikidata item has an English Wikipedia sitelink,
comparing the sitelink title against the Wikidata English label. Where that share is high, every
`share_identical` in frame E is reported beside it. Rejected only in part: the objection's stronger
form, that the frame should be dropped, is not accepted, because the composition question in A-10 is
still answerable and still informative even if the two Latin forms share an ancestor.

### A-5 The join is not entity identity — **accepted**

Correct, and the objection identifies why the instrument cannot catch it: `เทศบาลเมือง` and `อำเภอ`
are both in `PREFIXES`, so a town node pointing at the item for its own municipality strips to the
same string and stays in D1. The amendment records the Wikidata `P31` value and the OpenStreetMap
`place` or `boundary` tag for every entity and reports every headline share split by whether the two
agree. The prefix-stripping rule is not changed, because changing it would move the frame after the
adversary saw it; the split is reported instead.

### A-6 The design conditions on the thing it is measuring — **accepted**

Correct. §2.5 computed the Thai features only for entities whose kind is not `thai_forms_differ`,
which removes a downstream consequence of the independent variable from the population before the
association is estimated. The amendment computes the features for D2 as well and publishes the
feature marginals for the excluded rows beside the included ones, so the reader can see whether the
exclusion is harmless.

### A-7 The frame is one city — **accepted**

Correct, and it invalidates the reconciliation rule as written. Bangkok alone holds about 55 per cent
of the national count, while a mid-sized province is under 3 per cent, so losing an entire province
passes the 10 per cent check. The amendment adds a per-province reconciliation: a province whose
count is zero is a hard failure and not a datum, and every headline share is reported for Bangkok
and for the rest of the country separately.

### A-8 The comparison is between systems, not between people — **accepted**

This is the second statement of the defect found independently while building frame W: Phayan's
`paiboon` and `romanizedRI` are both read out of the same Wiktionary extract by
`scripts/data/enrich-kaikki.py`, and 2 176 of 2 228 rows are byte-identical to it. The arm is
restated as one source and two systems, widened to the whole frame W, and partitioned into
within-system pairs and cross-system pairs, reported as separate series with the cross-system series
labelled as an expected difference. See A-14 for the derived-system counterpart.

### A-9 The finding is defined at a grain the instrument cannot deliver — **accepted in part**

Accepted. The kind is decided on one differing syllable while every §2.5 feature is computed over
the whole name, so the features converge on the marginal distribution as names get longer, and
`syllable_count` is a confounder for every other feature in its own list. The remedy the adversary
proposes, computing each feature at the syllable where the two Latin forms differ, needs a syllable
alignment the frames do not carry and was not pre-registered. The amendment does the next best thing
and states the residue as a limit: the cross-tabulation is reported stratified by `syllable_count`,
and `syllable_count` is never reported as a predictor.

### A-10 The finding cannot be published under the method's own publication rule — **accepted, and it changes the article**

Accepted, and it is the objection that decides the article's shape. The announced finding was a
ten-by-ten cross-tabulation; the projection is about 37 real differences in a draw of 1 119, which
under §2.12's n = 30 floor leaves almost every cell unpublishable. The amendment splits the question
across the two frames that were always reported separately:

- **Frame E answers the composition question.** How much of the disagreement between two sources is
  naming convention and how much is real. This is well powered: the denominator is the whole draw.
- **Frame W answers the mechanism question.** Which feature of the Thai spelling predicts a system
  difference. This is well powered too: 17 396 word pairs against 37.

The ten-by-ten table is not the headline and is published only as counts. No share below n = 30 is
published, exactly as §2.12 requires; the cells below the floor are shown as raw counts and are
labelled as such.

### A-11 The calibration is silent where the classifier fails — **accepted**

Correct. The pre-registered twelve fixtures covered six categories and omitted the four largest
buckets. The amendment requires two fixtures for **every** category in §2.4, including `identical`,
`thai_forms_differ`, `translation_or_exonym` and `other_spelling`, so the set is twenty-two rather
than twelve. The objection's second point, that the author wrote both the fixtures and the
definitions, is accepted and is answered elsewhere: the fixtures are derived from the published
tables and the Royal Institute's own examples, which the author did not write, and every fixture
names the published line it rests on.

### A-12 The frame is selected on the outcome — **accepted, and it is unfixable**

Correct. An OpenStreetMap object acquires a `wikidata` tag most often through name matching, so an
entity whose two sources disagree is less likely to carry the tag, and `share_real` is biased
downward by an unknown amount. There is no repair available inside this design, because the
comparison requires the join. It is therefore stated in the article in the sentence that carries
`share_real`, carried into the ledger's `limits` field on every affected row, and named again in the
abandonment discussion. Reporting it is the whole of the remedy.

### A-13 The contributor-hashing rule costs reproducibility and buys nothing — **accepted in part**

Accepted that the salted, unwritten hash makes the contributor count uncheckable, which is the one
check that defends against the single-editor confound. Accepted that the protection is weak, because
OpenStreetMap user names are public under the ODbL. Rejected in part: publishing the salt would
publish the names, which §2.13 forbids. The amendment publishes the **count** of distinct
contributors and the **distribution** of objects per contributor, neither of which identifies anyone,
and stops writing the per-entity hash into any published file. The count becomes checkable from the
distribution without any name or hash being published. The salt requirement is withdrawn as
unnecessary once nothing hashed is published.

### A-14 The three derived systems are not three independent comparators — **accepted**

Correct: RTGS and ALA-LC agree on most of the consonant table and differ mainly on vowel diacritics,
so three series would read as three pieces of evidence and are one. The amendment publishes the
pairwise agreement among the derived systems on the same Thai strings, so the reader can see how
much independent information the series carry.

### A-15 The re-fetch protocol tests the source that does not move — **accepted**

Correct. Only Overpass was re-fetched, so `l_wd` was not reproducible. The amendment requests
`lastrevid` from `wbgetentities` in the same response at no extra cost, stores it per entity, and
includes the Wikidata label in the 10 per cent repeat fetch.

### A-16 German and French labels are collected and never used — **accepted**

Correct, and the objection's own evidence makes the field worth keeping rather than deleting: it
found a case where the German label follows the OpenStreetMap form and the English label does not,
and the next case the reverse. The amendment pre-registers it as a named series with its own
denominator rather than leaving it unspoken for: `share_german_tracks_osm` over entities where a
German label exists.

## 8. Amendments

Append-only. Each entry names the section it changes, what it said, what it says now, and why. The
reason is either an adversary objection by number or a defect found while building the instrument.

**AM-01 — §2.4, the `consonant` category.** *Was:* the first differing character must form a pair in
`CONSONANT_PAIRS`, a set whose members are inconsistent in length. *Now:* each side is converted to a
sequence of consonant graphemes, one symbol per grapheme, and every differing position must be one of
the published alternations `K/k, T/t/s, P/p/f/b, C/j, N/n, d/t, s/t, l/r, w/v`. *Why:* the
pre-registered test cannot see the aspiration contrast, which is the commonest difference there is
(A-2). The implementation is `tools/classify.py`; the smoke set scores 16 of 16.

**AM-02 — §2.4, the category list.** *Was:* ten categories, with no place for a difference that is
only a hyphen. *Now:* eleven, adding `syllable_hyphen` between `whitespace_or_case` and
`disambiguator`. *Why:* the Royal Institute's own rule inserts a hyphen to mark a syllable break, so
`krungthep` against `krung-thep` is a published convention and not a different name; the
pre-registered list routed it to `translation_or_exonym`.

**AM-03 — §2.2, the frame E query.** *Was:* `node` and `way` only. *Now:* `node`, `way` and
`relation`, in both the province query and the national reconciliation query. *Why:* A-3. The five
province files fetched under the old query are kept in `data/frame_e/superseded/` and are not used.

**AM-04 — §2.2, the endpoint.** *Was:* unnamed. *Now:* `https://z.overpass-api.de/api/interpreter`,
recorded in the frame manifest. *Why:* `overpass-api.de` drops the TLS handshake for this client
(`SSL_ERROR_SYSCALL` after the ClientHello) while the same instance answers on the `z.` frontend, and
two other mirrors time out or do not carry the world database. The endpoint is a change of address,
not of data.

**AM-05 — §2.3, frame W.** *Was:* "an entry in the Wiktionary extract with a `romanization` field".
*Now:* a `sounds[]` block whose `raw_tags` contains `Paiboon` or `Royal Institute`; both must be
present; single-token headwords only. *Why:* A-1, and the phrase named a field that does not exist.
Frame W holds 17 396 rows.

**AM-06 — §2.7, the claim of the word arm.** *Was:* three forms from two sources. *Now:* one source,
two systems. *Why:* A-8. Phayan's `paiboon` and `romanizedRI` are copied out of the same extract;
2 176 of 2 228 rows are byte-identical to it. The comparison is Paiboon against Royal Institute and
is never described as two sources. The within-system and cross-system pairs are separate series.

**AM-07 — §2.6, arm 2.** *Was:* a program deriving `rtgs` and `ala_lc` for arbitrary Thai strings,
with `share_agree_with_rtgs` published from its output. *Now:* the published tables are used as a
difference classifier, and the generator is retained only as a calibration against the rule
documents' own worked examples, reported as an accuracy and never used to produce a share. *Why:*
twelve published examples cannot establish that a generator is correct on a thousand arbitrary place
names, and a share computed from it would be a number from a method nobody has shown to work.
`share_agree_with_rtgs` in §2.11 is replaced by `share_table_predicted`.

**AM-08 — §2.1 and §2.11, what each frame answers.** *Was:* the finding was the cross-tabulation of
kind by feature. *Now:* frame E answers the composition question and frame W answers the mechanism
question, and neither is underpowered. *Why:* A-10.

**AM-09 — §2.8, the calibration set.** *Was:* twelve Latin fixtures over six categories. *Now:*
twenty-two, two for every category in §2.4, each naming the published line it rests on. *Why:* A-11.

**AM-10 — §2.5, who gets features.** *Was:* features computed only where the kind is not
`thai_forms_differ`. *Now:* computed for every entity whose Thai could be read, with the marginals
for the excluded rows published beside the included ones. *Why:* A-6.

**AM-11 — §2.5, the grain.** *Now additionally:* every cross-tabulation is stratified by
`syllable_count`, and `syllable_count` is never reported as a predictor. *Why:* A-9. The residue,
that the features describe the whole name while the kind describes one syllable, is stated as a
limit and is not repaired.

**AM-12 — §2.11, new denominators.** Added: `share_wikipedia_copy` (A-4), `share_german_tracks_osm`
(A-16), the pairwise agreement among the derived systems (A-14), and every headline share split by
Bangkok against the rest of the country and by P31 agreement against disagreement (A-5, A-7).
Removed: `share_agree_with_rtgs`, replaced by `share_table_predicted`.

**AM-13 — §2.11 and §2.13, contributors.** *Was:* a per-entity salted SHA-256 with an unwritten salt.
*Now:* the count of distinct contributors and the distribution of objects per contributor are
published; no per-entity hash is written into any published file; no salt is kept, because nothing
hashed is published. *Why:* A-13.

**AM-14 — §2.9, the repeat fetch.** *Now additionally:* the Wikidata label and `lastrevid` are
re-fetched, and `lastrevid` is stored per entity. *Why:* A-15.

**AM-15 — §2.13, the collected-but-unused columns.** *Now additionally:* the Wikidata `P31` value and
the OpenStreetMap `place` or `boundary` tag per entity. The German label is no longer collected
without a use: it carries `share_german_tracks_osm`. *Why:* A-5, A-16.

**AM-16 — §2.10, relations.** *Now additionally:* an entity whose `wikidata` tag points at an item
whose `P31` disagrees with the OpenStreetMap `place` or `boundary` tag is **kept and reported**, and
every share is split by it. *Why:* A-5.

**AM-17 — §4, the falsifier.** *Was:* four conditions. *Now additionally:* a province whose count is
zero is a hard failure (A-7), and `share_wikipedia_copy` above 80 per cent refutes the independence
of the two crowd sources and is reported in the same sentence as every `share_identical` (A-4).

**AM-18 — §6, the estimate.** Unchanged in money: 0 USD for data. The frame E fetch is larger than
estimated because relations are now included and because Overpass is rate-limiting this client with
`429` and a 60-second `Retry-After` roughly every other request.

**AM-19 — §2.11, the definition of `share_table_predicted`.** AM-07 introduced the name and did not
define the computation, which would have left the arm undefined. *Definition, written before any
pair was classified:* for a pair the classifier placed in one of the spelling categories, take the
first differing consonant grapheme position under AM-01. The pair is **predicted** when the published
table contains at least one Thai letter that the table maps to both symbols — that is, when the two
renderings are both licensed readings of one Thai letter, so the difference is one the published
system itself can produce. The pair is **not predicted** otherwise. The numerator is the count of
predicted pairs, the denominator is every classified pair in the spelling categories for which a
derived lookup was possible. Reported for the Royal Institute table and, when transcribed, for the
ALA-LC table, as two separate series.

**AM-20 — §2.11, `share_table_predicted` extended to vowel and tone differences.** AM-19 defined the
computation for a difference at a consonant grapheme, which leaves the vowel-length and tone
categories undefined because their consonant sequences are identical. *Extended, written before any
pair was classified:* a pair whose two forms differ by a tone mark is **never predicted**, because
the Royal Institute's table carries no tone marks at all, so a marked form is not something the
system can produce. A pair whose two forms reduce to the same vowel once a run of two or more of the
same vowel letter is collapsed to one **is predicted by construction**, because the published vowel
table maps the short and the long pattern of a vowel to a single roman letter. A pair whose consonant
grapheme sequences differ in length is **not predicted**. The three rules are applied in that order,
and `tools/table_predict.py` reports which rule decided each pair, so the reader can see the
instrument's reasoning rather than only its verdict.

**AM-21 — §2.2, the shape of the province query.** *Was:* each of the 77 province queries returned
`out tags`, the full tag set of every object. *Now:* the province queries return `out ids`, the
identity of each object and nothing else, and the national reconciliation query still returns
`out tags` and remains the sole source of the entities that are measured. *Why:* the province queries
exist only for A-7's per-province reconciliation, which needs membership and counts, not tags. A
single province response was 1.8 MB and the run was averaging six minutes per province under
Overpass's `429` pacing; the same reconciliation is available from a response of a few tens of
kilobytes. This changes the cost of the frame, not its contents: the measured population is still
every object the national query returns, and every object can still be attributed to a province
because the id lists carry the membership.

**AM-22 — §2.2, the source of the frame.** *Was:* the frame is fetched from Overpass by the province
and national queries of AM-03. *Now:* when Overpass is unavailable, the frame is taken from the
Geofabrik Thailand extract, `data/raw/thailand-latest.osm.pbf`, 327 209 245 bytes, sha256
`cfb890a8dde029490e0e9452c1c12d01974afe7c61295005d68505deac7bc82d`, downloaded 2026-09-11 and dated
2026-09-09 in its filename. The selection predicate is unchanged: an object carrying `name:th`,
`name:en` and `wikidata`. *Why:* both Overpass frontends began dropping the TLS handshake for this
client after the first province, and the one public mirror that still answered returned HTTP 200 with
an empty element list, which is the absence of data at that mirror and not the absence of objects in
Thailand. A snapshot dated two days before the run is a different source from a live query and is
recorded as such: the frame's date is now the extract's date, not the run's. The province
reconciliation of AM-16 is preserved by attributing each drawn object to a province from the boundary
relations in the same extract.

**AM-23 — §2.5, two features that the published sources on disk cannot produce.** Building the
extractor established that no file in `data/raw/` publishes a list of initial consonant clusters:
`thwiki-rtgs.txt`, `thwiki-rtgs.wiki` and `thwiki-rtgs-wikitext.json` are the same article and carry
only the consonant table, the vowel table and a history table, and the strings `ควบ`, `อักษรนำ` and
`อักษรควบ` do not occur in them; the three `orst.go.th` pages saved in `data/raw/` contain navigation
chrome and no article body. *Now:* `has_cluster` is reported as `null` for every entity, never as
`false`, and `syllable_count` is reported as `null` for the same reason, because its definition in
§2.5 is written in terms of the same structure. Two of the ten features are therefore **not
available**, and the article says so rather than reporting an absence as a value. *Consequence for
AM-11:* the cross-tabulation cannot be stratified by `syllable_count`. The stratifier becomes
`thai_length`, the count of characters in `norm(T_wd)` that fall in the Thai block U+0E00 to U+0E7F,
which is computable from the string alone and is monotone in the length of the name. The residue
AM-11 was written to expose, that the features describe the whole name while the kind describes one
syllable, is unchanged by the substitution and is still stated as a limit.

**AM-24 — §2.4, three alternations the amended pair set was still missing.** A smoke test over real
frame W rows, run before any share was computed, showed that the classifier put `gòk` against `kok`,
`dtaa` against `ta` and `bpaa` against `pa` into `other_spelling`. All three are the same alternation
the category exists to catch: Paiboon writes the unaspirated series with the voiced letters (ก as `g`,
ต as `dt`, ป as `bp`) where the Royal Institute writes `k`, `t`, `p`. *Now:* the digraph list gains
`dt` and `bp`, and the permitted alternation set gains `g/k`, `b/p`, `dt/t` and `bp/p`. *Why:* an
instrument that cannot see the commonest cross-system difference in its own frame would understate
that difference everywhere. The 22 fixtures were re-scored after the change and still score 22 of 22,
so the change widened the instrument without loosening the calibration.

**AM-25 — §2.10, a discard the first list did not carry.** Frame W contains 47 headwords that are a
single Thai character and 75 that are written with full stops, 122 rows in all, 0.7 per cent of the
frame. These are letter names and abbreviations, and their two romanizations differ by the convention
each system uses for naming a letter (`gɔɔ` against `ko`) rather than by how a word is spelt. *Now:*
a row whose headword is a single Thai character, or contains a full stop, is discarded and counted
under `letter_name_or_abbreviation`. *Why:* they are not words, and leaving them in would add a
constant to the difference share that has nothing to do with the question. The count is reported so
the discard is visible rather than silent.

**AM-24, continued — the consonant test is now vowel-blind.** Adding the Paiboon digraphs was not
enough: `dtaa` against `ta` still fell to `other_spelling`, because the comparison ran over graphemes
with the vowels left in place and Paiboon doubles the long vowel as well as writing the stop as a
digraph. A pair that differs in both a consonant and a vowel reaches the consonant test having
already failed the vowel test, so the vowel must not also hide the consonant. *Now:*
`consonant_only_difference` compares the consonant graphemes alone. *Why:* the commonest cross-system
difference is exactly this pairing, and an instrument blind to it would report the phenomenon as
unstructured spelling noise. Fixtures re-scored after the change: 22 of 22, with the four mutants of
the falsification set still failing.

--------------------------------------------------------------------------------

### 7.1 Dispositions of the second adversary (`ADVERSARY-A2.md`, 2026-09-11T01:29Z)

The second pass returned before any share was computed and found five things. Three were blocking.
Every one of them was accepted and fixed as the adversary itself proposed; none was argued away.

**A2-1, `share_table_predicted` reads a table that pools the initial and the final column —
blocking, and it ends the arm.** Accepted in full. The objector is right that the Royal Institute's
table gives each letter one reading per position and never licenses writing one position two ways,
so once the columns are read separately the question "does the published system license this
difference" has only the answer "no" and the arm measures nothing. **Arm 2 is dropped.** §2.11's
`share_table_predicted` and the derived-form series of §2.6 are removed, and the reason is recorded
here and in `data/deviations.json`. §4 already anticipated this outcome: "every derived form agrees
with every crowd form: arm 2 discriminates nothing and the arm is dropped with the reason recorded."
The finding that the question is not well posed is itself reported in the article.

**A2-2, the exonym rule was an undeclared letter-overlap constant — blocking.** Accepted. The rule
is replaced by a token rule with no freedom in it: if the two token lists share no common token, the
pair is a different name — unless both lists are a single token and the two share at least half their
distinct letters, which is one name spelt two ways. `Bangkok` against `Krung Thep` and `Bangkok`
against `Krung Thep Maha Nakhon` now receive the same verdict, which the straddling constant did not
give them. Recorded as AM-26.

**A2-3, a modifier inside the name defeated the disambiguator rule — blocking.** Accepted. An extra
token that sits inside the name rather than at the end was reaching `word_carried_over`, which is the
numerator of `share_real`, so the naming convention of every international airport in the country was
being counted as romanisation chaos, in the direction that flatters the run's own headline.
*Now:* the rule is the token multiset difference, and every token on one side and not the other must
be a class word. A modifier class is added for the words that sit beside a class word, and the list
is written down rather than implicit. Recorded as AM-27.

**A2-4, the classifier and the predictor disagreed about the same pair — material.** Covered by the
drop of arm 2. There is no longer a second instrument to disagree with.

**A2-5, `Lampang` against `Lamphun` was a consonant difference — material.** Accepted. The vowel-blind
consonant test, which AM-24 introduced to catch Paiboon's `dt` and `bp`, also dropped the vowel that
is the only thing separating two different provinces. *Now:* the test runs on the sequence with vowel
**length** normalised and everything else intact, so a doubled vowel is not a difference but a
different vowel is. Recorded as AM-28. Two further fixtures were added from the adversary's own probe
pairs, F-25 and F-27. F-27 records why the objection cannot reach the bucket it feared: a wrong
`wikidata` tag points at a place with different Thai, so category 1 fires before any Latin
classification and the pair is excluded from D1 rather than reported as a spelling datum.

**Verification of the fixes.** Each of the five was checked by running the adversary's own probe
pairs, which is what each objection's "what it would take to settle it" asked for, rather than by
asking a third reviewer. The fixture set grew from 22 to 27 and scores 27 of 27, with six single-line
mutants of the instrument still failing it: the disambiguator rule removed, the syllable-hyphen rule
removed, the consonant test always false, the tone test always true, the vowel-length test removed,
and the exonym test always true.

**AM-26 — §2.4 category 5.** *Was:* a share of distinct Latin letters with an undeclared threshold of
0.34. *Now:* the pre-registered token rule, with one stated exception — two single-token lists that
share at least half their distinct letters are one name spelt two ways and not two different names.
*Why:* A2-2, and because the threshold decided the most-cited name in the population by 0.007 and was
recorded nowhere.

**AM-27 — §2.4 category 4.** *Was:* one token list is a prefix of the other and the extra trailing
tokens are class words, tested on the tail only. *Now:* the token multiset difference, with every
token on one side and not the other required to be a class word or a modifier; `MODIFIERS` is
`international`, `domestic`, `national`, `provincial`, `regional`, `main`. *Why:* A2-3.

**AM-28 — §2.4 category 6, the consonant test.** *Was:* the consonant graphemes with the vowels
removed. *Now:* the graphemes with vowel **length** normalised and the vowels left in place, so every
differing position must still be a permitted consonant pair. *Why:* A2-5, which showed the
vowel-blind form calling two different provinces one name spelt two ways. The Paiboon digraphs that
AM-24 exists to catch survive the change, because normalising length leaves `dt` against `t` and
`bp` against `p` as single differences.

**AM-29 — §2.6 and §2.11, arm 2 withdrawn.** *Was:* three derived systems, a generator calibrated on
the rule documents' own examples, and `share_table_predicted`. *Now:* the arm is gone; the tables are
used only in the calibration of §2.8 and in no published share. *Why:* A2-1.
