Skip to content

Eval harness: headless engine CLI, 200-sentence zh/en corpus tooling, baseline numbers - #1

Merged
lmanchu merged 3 commits into
masterfrom
eval-harness
Sep 9, 2026
Merged

lmanchu merged 3 commits into
masterfrom
eval-harness

Conversation

@lmanchu

@lmanchu lmanchu commented Sep 9, 2026

Copy link
Copy Markdown
Owner

P0.5 of the plan: measure before building.

  • Source/Engine/CMakeLists.txt: standalone cmake -S Source/Engine now resolves brew GoogleTest (find_package(GTest CONFIG), FetchContent fallback); 124 engine tests pass headlessly.
  • tools/eval/mixime-eval: C++ CLI over the real engine — keys (standard-layout key sequence → composed sentence, mirrors KeyHandler's compose order), readings (Bopomofo readings → sentence), keyseq (Chinese sentence → readings + key sequence via the layout's own keySequenceFromSyllable).
  • tools/eval/build_corpus.py + run_eval.py: build a private 200-sentence mixed zh/en corpus (kept outside the repo; only a 10-row synthetic fixture is committed) and report F1 (English retention), F2 (homophone accuracy), latency.

Baseline (upstream f5ba010, standard layout): F1 0.0% (by design — every letter is a valid Bopomofo key), F2 95.3% char-level, ~2.4 ms/sentence. See tools/eval/BASELINE.md.

Finding worth knowing before P1: tone 1 has no key, so a tone-1 syllable only composes on space/Enter; the harness emits a space after every syllable.

🤖 Generated with Claude Code

lmanchu and others added 3 commits September 9, 2026 08:00
Source/Engine/CMakeLists.txt referenced GTest::gtest_main but never called
find_package/FetchContent when configured standalone (cmake -S Source/Engine),
only Source/CMakeLists.txt did. Move GTest resolution (prefer brew-installed
GTest via find_package(GTest CONFIG), fall back to FetchContent) above the
add_subdirectory(gramambular2)/add_subdirectory(Mandarin) calls so their
GTest::gtest_main links resolve too. No test code changed; all 124 engine
tests still pass.

Add tools/eval/mixime-eval, a headless CLI that drives the real engine
(BopomofoReadingBuffer + ReadingGrid + McBopomofoLM) for eval scripts,
mirroring KeyHandler.mm's isValidKey->combineKey->composeReading->
insertReading->walk order rather than reimplementing engine logic:
  - keys: raw ASCII standard-layout key sequence -> composed text
  - readings: space-separated Bopomofo readings -> composed text (for F2)
  - keyseq: CJK sentence -> readings + standard-layout keys, via
    McBopomofoLM::getReading() (longest match, no keyboard map reimplemented)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
build_corpus.py filters ~/Dev/mixime-private/corpus_candidates.txt (private,
mostly AI-generated summaries/Slack text) down to 200 short, zh/en-mixed
chat-style sentences, normalizes each to CJK+ASCII-letters+spaces only,
segments into zh/en spans, and gets each zh segment's Bopomofo readings and
standard-layout keys from `mixime-eval --mode keyseq` (no keyboard map or
dictionary reimplemented). The private corpus never enters this repo
(.gitignore's *.private.* rule); only a synthetic-only 10-row sample lives
at tools/eval/fixtures/sample10.tsv.

run_eval.py measures two baseline metrics against the stock engine:
  F1 (English segment retention via mode `keys`): 0/385 tokens = 0.0%,
    confirming the engine has no English-awareness yet (P1's gap to close).
  F2 (homophone/candidate accuracy via mode `readings`): 4369/4586 = 95.3%,
    the unmodified Viterbi walk's accuracy given correct readings.
Both average ~2.2-2.5ms/sentence. Full numbers, commands, and data
provenance (sha256) in tools/eval/BASELINE.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Same numbers within normal timing jitter (F1 0.0%, F2 95.3%); re-ran as
part of verifying the P0.5 acceptance commands end to end.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Claude Code Review Failed

The automated Claude review encountered an error and could not complete. You can:

  • Check the workflow logs for details
  • Trigger a manual review by commenting @claude on this PR
  • The review will be retried automatically on the next push

This does not affect the PR approval process.

@lmanchu
lmanchu merged commit eb71aba into master Sep 9, 2026
4 of 9 checks passed
@lmanchu
lmanchu deleted the eval-harness branch September 9, 2026 00:26
lmanchu pushed a commit that referenced this pull request Sep 11, 2026
…eval, isolation)

1. Rebuild Source/Data/latin-words.txt entirely from SCOWL/ESDB (size<=70,
   American spelling, variant level <=1) instead of macOS's 1934 web2 word
   list with SCOWL only as a frequency-tier annotation. web2 had no
   inflected forms at all; the new list carries them directly and every
   word now has a rank tier (was ~39%). tools/lexicon/build_lexicon.py
   rewritten to build both the word set and its tiers from a local SCOWL
   repo checkout in one pass; tools/lexicon/scowl-size-tiers.tsv (the old
   annotation-only file) is removed. 141,697 words (was 204,217): 80,067
   added, 142,587 removed, 61,630 kept -- ACKNOWLEDGEMENTS.md and
   README.markdown updated to match. P1 regression: F1 app-path retention
   holds at 349/385 = 90.6%, pure-Chinese ON=OFF still identical character
   for character; a sweep of every tone-1 single-syllable reading in
   data.txt found exactly 2 (of 138 with a key sequence >= 3 letters)
   changed Rule-B ambiguity status ("ful"/ㄑㄧㄠ newly ambiguous,
   "tji"/ㄔㄨㄛ no longer is) -- neither is used by any existing test, and
   Rule B never overrides the default Chinese candidate on its own.

2. Learn from Latin runs the user actually types and commits (Enter/
   space/punctuation), not only from an explicit Tab/candidate-window
   completion accept -- new Preferences.latinLearnTypedWords (default
   on). KeyHandler's _commitMixedScriptLatinRun: gains a mayLearn flag so
   Tab landing there (no completion was on offer) never counts as
   evidence the user meant to finish the word, and
   MixedScriptTracker::latinRunAlreadyRemembered() prevents double-
   counting a run a completion accept already wrote. Esc/Backspace never
   reach the commit path, so a canceled run is never learned.

3. Tab (and the completion tooltip/Shift+Tab candidate window) must not
   grow an already-finished word into a longer dictionary entry sharing
   its prefix -- new KeyHandler _isAlreadyCompleteWord:lexicon: gate
   (word is itself a known word whose rank is at least as good as the
   best longer completion), applied at the pending-run, committed-node,
   and tooltip call sites. "acer" (tech-seed) stays "acer", not
   "acerbic"; "the" stays "the". Rule-B alternates are exempt so e.g.
   "coo" can still complete into "cook" on a second Tab.
   testR5_TabOnARuleARunDoesNotWriteTheUserLexicon restored to its
   original semantics; testTabDoesNotExtendACompleteWord added.

4. LatinCompletionKeyHandlerTests' eval now classifies "never completable"
   tokens into proper-noun/abbreviation, inflected-form, and other, and
   adds a "with history" pass that replays eval200 in corpus order,
   teaching each row's eligible words (mirroring fix #2) before scoring
   the next row. Inflected-form count is 0 in both passes, confirming
   fix #1's dictionary switch. BASELINE.md's P3 section replaced with
   both tables.

5. KeyHandlerBopomofoTests (upstream, mixedScript-unaware) now forces
   mixedScriptEnabled/latinCompletionEnabled/latinLearnTypedWords off in
   setUp and restores the real values in tearDown, so it no longer
   inherits whatever this machine's live plist happens to have (it was
   failing testInvalidBpmf whenever MixedScriptEnabled=1 on disk). Full
   suite verified green with the real pref both true and false, and the
   real plist ends each run unchanged.

6. New testing-only LanguageModelManager.resetLatinLexiconForTesting()
   (and LatinLexicon::reset()) reloads the process-wide Latin lexicon
   fresh before every KeyHandler-level test, fixing the cross-test
   pollution from REVERIFY-P1-2026-09-10.md's R12 (an accepted word in
   one test used to silently change another test's top completion).
   Waits for any load already in flight before resetting to avoid a
   data race with the test host app's own startup load. Word-choice
   workaround comments from the pollution era removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
lmanchu pushed a commit that referenced this pull request Sep 12, 2026
…eval, isolation)

1. Rebuild Source/Data/latin-words.txt entirely from SCOWL/ESDB (size<=70,
   American spelling, variant level <=1) instead of macOS's 1934 web2 word
   list with SCOWL only as a frequency-tier annotation. web2 had no
   inflected forms at all; the new list carries them directly and every
   word now has a rank tier (was ~39%). tools/lexicon/build_lexicon.py
   rewritten to build both the word set and its tiers from a local SCOWL
   repo checkout in one pass; tools/lexicon/scowl-size-tiers.tsv (the old
   annotation-only file) is removed. 141,697 words (was 204,217): 80,067
   added, 142,587 removed, 61,630 kept -- ACKNOWLEDGEMENTS.md and
   README.markdown updated to match. P1 regression: F1 app-path retention
   holds at 349/385 = 90.6%, pure-Chinese ON=OFF still identical character
   for character; a sweep of every tone-1 single-syllable reading in
   data.txt found exactly 2 (of 138 with a key sequence >= 3 letters)
   changed Rule-B ambiguity status ("ful"/ㄑㄧㄠ newly ambiguous,
   "tji"/ㄔㄨㄛ no longer is) -- neither is used by any existing test, and
   Rule B never overrides the default Chinese candidate on its own.

2. Learn from Latin runs the user actually types and commits (Enter/
   space/punctuation), not only from an explicit Tab/candidate-window
   completion accept -- new Preferences.latinLearnTypedWords (default
   on). KeyHandler's _commitMixedScriptLatinRun: gains a mayLearn flag so
   Tab landing there (no completion was on offer) never counts as
   evidence the user meant to finish the word, and
   MixedScriptTracker::latinRunAlreadyRemembered() prevents double-
   counting a run a completion accept already wrote. Esc/Backspace never
   reach the commit path, so a canceled run is never learned.

3. Tab (and the completion tooltip/Shift+Tab candidate window) must not
   grow an already-finished word into a longer dictionary entry sharing
   its prefix -- new KeyHandler _isAlreadyCompleteWord:lexicon: gate
   (word is itself a known word whose rank is at least as good as the
   best longer completion), applied at the pending-run, committed-node,
   and tooltip call sites. "acer" (tech-seed) stays "acer", not
   "acerbic"; "the" stays "the". Rule-B alternates are exempt so e.g.
   "coo" can still complete into "cook" on a second Tab.
   testR5_TabOnARuleARunDoesNotWriteTheUserLexicon restored to its
   original semantics; testTabDoesNotExtendACompleteWord added.

4. LatinCompletionKeyHandlerTests' eval now classifies "never completable"
   tokens into proper-noun/abbreviation, inflected-form, and other, and
   adds a "with history" pass that replays eval200 in corpus order,
   teaching each row's eligible words (mirroring fix #2) before scoring
   the next row. Inflected-form count is 0 in both passes, confirming
   fix #1's dictionary switch. BASELINE.md's P3 section replaced with
   both tables.

5. KeyHandlerBopomofoTests (upstream, mixedScript-unaware) now forces
   mixedScriptEnabled/latinCompletionEnabled/latinLearnTypedWords off in
   setUp and restores the real values in tearDown, so it no longer
   inherits whatever this machine's live plist happens to have (it was
   failing testInvalidBpmf whenever MixedScriptEnabled=1 on disk). Full
   suite verified green with the real pref both true and false, and the
   real plist ends each run unchanged.

6. New testing-only LanguageModelManager.resetLatinLexiconForTesting()
   (and LatinLexicon::reset()) reloads the process-wide Latin lexicon
   fresh before every KeyHandler-level test, fixing the cross-test
   pollution from REVERIFY-P1-2026-09-10.md's R12 (an accepted word in
   one test used to silently change another test's top completion).
   Waits for any load already in flight before resetting to avoid a
   data race with the test host app's own startup load. Word-choice
   workaround comments from the pollution era removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant