mkemoji wrote a row for every prefix of every alias, so that a query
matched as it was typed; the trie is a prefix index already, and with a
real emoji list those rows would be four times the aliases themselves.
Now emoji.dict has one row per alias, trienode() names a key's node, and
dictlookup walks the entries at and below it, the key's own first, up to
Maxkouho. Trie children are appended rather than pushed, so the walk
keeps the file's order and a bare digit still picks the superscript or
subscript it always did.
The hand-written symbol rows move to symbol.src; emoji.src is left to
the emoji. mkemoji reads both by default, or the files it is given.
The dictionary thread ran in imthread's own proc, so a lookup could
only start once the engine blocked, and it was a trie probe anyway; the
emoji and Hanja searches already called dictlookup directly. The
request/result channels, sequence numbers, staleness checks and the
second draw per Japanese key are gone; dictqjp fills the candidates
in place. Emit.dict and Lang.dictq only ever triggered lookups for
Vietnamese, which has no dictionary. dictlookup(Lang*, key, out, max)
returns the count. The Hanja lookup no longer pre-checks for a single
syllable; a reading either has an entry or it does not.
The hash map served a single exact-match lookup that the trie already
answers, at the price of a second container, a second file loader with
its own drift, and a Str-to-UTF-8 conversion on every chain probe. The
files are small (kanji.dict is 7.5k lines), so the trie holds both.
trieopen validates keys and each space-separated value word against Str
and reports path:line; trielookup takes the Str every caller holds and
treats a nil trie as an unloaded map; trienew/trieput exist for tests.
The overflow guards on growth, Trie.root (always 0) and the per-language
init loop written twice are gone.