Skip to content

Commit 573b528

Browse files
committed
fix(macronizer): single-accented words take the best-ranked reading (M-023k)
Germanis read 'full sister' (germana) in the popup because getAccents' single-distinct-accented fast path attached entries[0] — the first row in macrons.txt file order — without running the case-aware ranking the multi-accented path uses. The wordlist has 4 lemmas for germanis (germana, Germani, Germanus, germanus) all macronizing to germa_ni_s. Rank every entry unconditionally; the single-accented case keeps its one spelling but takes lemma/tag from the best-ranked candidate. Capitalized Germanis → Germani 'the Germans (people)' (casedist + RFTagger agree). Text output unchanged. Mirrored in upstream latin-macronizer-wasm src/core/Tokenization.ts. New popup e2e test; 9 popup + 3 macronizer e2e + unit/IPA/gloss/census green.
1 parent 583eb55 commit 573b528

4 files changed

Lines changed: 106 additions & 20 deletions

File tree

wiktionary_pron/docs/ISSUES.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,3 +1211,25 @@ analysis-lemma stratum.
12111211

12121212
**Status: FIXED.** 8 popup + 19 macronizer e2e + 22 unit + 81 IPA + 2069
12131213
golden + 348 census, all green.
1214+
1215+
## M-023k — Germanis read "full sister" instead of "the Germans" (2026-08-12) ✅
1216+
1217+
**Symptom.** The popup's reading #1 for a capitalized `Germanis` was
1218+
`germana · noun · pl · fem · abl · full sister`. In Caesar it means "the
1219+
Germans" (dative/abl. pl. of the proper noun Germani).
1220+
1221+
**Root cause.** The wordlist has 4 lemmas for `germanis` (germana, Germani,
1222+
Germanus, germanus) but all macronize to the SAME form `germa_ni_s`.
1223+
`getAccents`' single-distinct-accented fast path attached `entries[0]` (the
1224+
first row in macrons.txt file order = germana) without running the
1225+
case-aware (casedist/tagdist/lemdist) ranking the multi-accented path uses.
1226+
1227+
**Fix.** Rank every entry unconditionally; the single-accented case keeps the
1228+
one spelling but takes its lemma/tag from the best-ranked candidate
1229+
(capitalized Germanis → Germani, matching both case and RFTagger's tag).
1230+
Text output unchanged. Mirrored in upstream latin-macronizer-wasm
1231+
src/core/Tokenization.ts.
1232+
1233+
**Status: FIXED.** New popup e2e test (Germanis → "The Germans", not "full
1234+
sister"); 9 popup + macronizer e2e + 22 unit + 81 IPA + 2069 golden + 348
1235+
census, all green.

wiktionary_pron/docs/LESSONS.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,3 +733,30 @@ Catilinam I, Vergil Aeneid I, Ovid Metamorphoses I.
733733
- **Lesson.** A test that evals production build code inherits every file dependency that code has. If any of those files are gitignored, the test can only ever be green on the author's machine. CI is the truth-teller: check what a fresh checkout actually has.
734734
- **Fix.** `utils/build_ls_fixture.cjs` extracts the ~95 Lewis & Short entries the 2067 golden rows can *reach* (fixpoint over the eval'd defs, growing by missed-key base-families → 253KB) into **committed** `utils/ls_golden_fixture.json`. The test always resolves through the fixture (`makeDefs(true)`), giving identical semantics in CI and locally; when `utils/ext_tmp/` IS present, a **drift guard** re-runs against the full dump and errors with "run `npm run build:gloss-fixture`" if the fixture stops reproducing it. Regenerate after golden edits: `npm run build:gloss-fixture`, commit the fixture in the same commit.
735735
- **Lesson.** Two-source divergence is the failure mode to kill: making the committed fixture the *authoritative* source (not "full data locally, fixture in CI") is what prevents "passes locally, red in CI". The drift guard only exists to catch the fixture going stale locally, before it ships.
736+
737+
## The single-accented fast path skipped the case-aware ranking — Germanis read "full sister" (2026-08-12)
738+
739+
- **Symptom.** In the popup, `Germanis` (capitalized, "the Germans" in Caesar) showed
740+
reading #1 as `germana · noun · pl · fem · abl · full sister`.
741+
- **Root cause.** The wordlist has FOUR lemmas for `germanis``germana` (full
742+
sister), `Germani` (the Germans), `Germanus`, `germanus` — but they ALL macronize
743+
to the same form `germa_ni_s`. `getAccents` in `dist/core/Tokenization.js` had a
744+
fast path for "every entry shares one accented form" that attached
745+
`entries[0]` — the first row in **macrons.txt file order**, which is `germana`
746+
WITHOUT running the (casedist/tagdist/lemdist) ranking the multi-accented path
747+
uses. The token's own best lemma was `Germani`; the ranking would have picked it
748+
(casedist 0 for the title-case lemma vs 1 for germana); the fast path bypassed both.
749+
- **Lesson.** Any per-form analysis with a "fast path when the answer looks
750+
unambiguous" must still run the disambiguation — the *macronization* was
751+
unambiguous (one spelling), but the *reading's identity* (lemma/tag) was not.
752+
"Same output spelling" ≠ "same reading".
753+
- **Fix.** Rank every entry unconditionally; the single-accented case keeps
754+
`accented = [the one form]` but attaches the **best-ranked** candidate's
755+
lemma/tag. Text output unchanged (same spelling); only the popup's lemma/gloss
756+
column changes. Capitalized `Germanis` → `Germani · noun · pl · masc · abl ·
757+
"The Germans (people)"`. Mirrored in the upstream `latin-macronizer-wasm`
758+
`src/core/Tokenization.ts` so the next engine sync doesn't lose it.
759+
- **Lesson.** `macronizer/dist/` is the compiled engine; the site copy can be AHEAD
760+
or BEHIND the upstream `latin-macronizer-wasm` repo. Before editing, diff the two
761+
to learn which is the source of truth — do NOT `npm run build` + sync blindly, or
762+
you'll clobber whichever side carries the newer fixes.

wiktionary_pron/e2e/popup-check.spec.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,35 @@ test("Caesar BG I stragglers gloss in the popup — idem, iamque, plerumque", as
185185
}
186186
});
187187

188+
test("capitalized proper noun Germanis reads the Germans, not 'full sister'", async () => {
189+
// M-023k: the wordlist has 4 lemmas for germanis (germana "full sister",
190+
// Germani "the Germans", Germanus, germanus) but ALL macronize to the same
191+
// form germa_ni_s. The single-accented fast path used to attach entries[0]
192+
// (file order → germana "full sister") without ranking. The ranking is
193+
// case-aware (casedist): a CAPITALIZED Germanis must resolve to the title-case
194+
// Germani → "the Germans (people)", which also matches RFTagger's tag.
195+
const page = sharedPage;
196+
test.setTimeout(300_000);
197+
await page.goto(PAGE);
198+
await expect(page.locator("#macronize_btn")).toBeEnabled({ timeout: 240_000 });
199+
200+
await page.fill("#text_to_macronize", "Germanis");
201+
await page.click("#macronize_btn");
202+
const span = page.locator("#resultText .ipa").first();
203+
await expect(span).toBeVisible({ timeout: 120_000 });
204+
await span.hover();
205+
206+
const defCells = page.locator(".word-popup table.readings td.r-def");
207+
await expect(defCells.first()).toBeVisible({ timeout: 10_000 });
208+
const defs = (await defCells.allTextContents()).join(" | ");
209+
expect(defs).toContain("The Germans");
210+
expect(defs).not.toContain("full sister");
211+
212+
// the lemma column of the active row carries Germani
213+
const activeLemma = page.locator(".word-popup table.readings tr.active td.r-lemma");
214+
await expect(activeLemma).toContainText("Germani", { timeout: 5000 });
215+
});
216+
188217
test("prose shows no grey placeholders; numbered verse scans after line-number strip", async () => {
189218
const page = sharedPage;
190219
test.setTimeout(300_000);

wiktionary_pron/macronizer/dist/core/Tokenization.js

Lines changed: 28 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)