Repository navigation
feat(neuro): key spec 3, fold Hant to Hans and merge Han runs - #1366
Conversation
A-EDev
left a comment
There was a problem hiding this comment.
Thanks, this covers everything from #1341: the fold, merged runs with a break list per language, ICU4J in tests, Chinese golden cases, the migration, freq/zh.txt and the Hans broad topics. One thing needs to change before I merge, plus a few smaller points.
Needs a change: decide the fold once per video, not per string
NeuroText.simplified skips the fold when the string it gets has kana. But the title, tags, channel name, description and what the viewer types are folded separately, so Japanese is not unchanged. I ran these on the branch with IcuWordBreaker:
鬼滅の刃 無限城編 予告gives the key無限, and鬼滅 無限城編 予告gives无限. The same word becomes two keys depending on whether the title has kana.- Blocking
鬼滅stores鬼灭, so鬼滅 アニメ 最新話is no longer hidden (isBlockedByTextreturns false; it is hidden on main).addBlockedTopicfolds whatever the viewer's language is. - The migration keeps kanji keys for
languageProfile == "ja", but nothing at runtime checks the profile, so their new kanji-only titles and tags still fold. - Three kanji-only entries in
stop/ja.txt(私達, 貴方, 貴方方) fold when the list loads and stop matching Japanese titles.
What I'd like:
- Decide the fold once per video (title, channel name and tags together) and use that decision for every string of that video.
- Match blocked keywords with both sides fully folded, ignoring kana, since that's only text matching and not keys.
- Make the migration and the runtime agree on Japanese viewers (both check the profile, or neither does), and don't Han-fold the
ja/kolists when they load.
The Japanese part I add later needs the same per-video decision, so it's worth getting right now.
Smaller points
- IDF cap: the 28% growth meets an LFU cap at 20,000 entries. Single characters always survive and young candidates are dropped first, so a heavy Chinese viewer may rarely reach 3 documents for a name. Could candidates get some protection from the cap, or could you show it doesn't matter in practice?
- A promoted run keeps its words: when
李永乐is promoted,永乐still looks like it is learned on its own. English phrases take their words. Is that intended? - Character-only fold:
乾隆皇帝gives the merged candidate干隆. It only affects keys and the display keeps the spelling, so a short exception list is enough if you want to fix it. Not blocking. - Nightly note: since today, every PR with a change users can notice adds one file in
.github/nightly-notes/(see the README there). This one would be2026-10-20-neuro-key-spec-3.mdwith an## Improvedline about recommendations for Chinese titles (#1341). The Checks job validates it.
Timing stays as discussed: I'll merge right after the next release.
- Title, channel name and tags decide together; kana in any of them keeps Han unfolded for the whole video - Blocking matches with both sides fully folded, ignoring kana - Migration and runtime share NeuroTokenizer.hanFolds - ja/ko stop lists, frequency tables and run breaks load without the Han fold - Add the nightly note
|
Updated, and merged current main. Fold decision
Known limit: the decision only sees kana. A Japanese title written in kanji alone ( Smaller points
Tests: Side note, not for this PR: |
A-EDev
left a comment
There was a problem hiding this comment.
Thanks, this does it. I ran my earlier checks again on 084a0ebce: blocking 鬼滅 now hides 鬼滅 アニメ 最新話, a kanji-only title with kana in a tag keeps 無限, a ja viewer folds nothing, and 私達 is back in the Japanese stop list. Your answers on the IDF cap, promoted runs keeping their words and 乾隆 all make sense; leaving them as they are is fine.
Two things I'll pick up myself with the Japanese part:
NeuroChannelKnowledge.profileFromChannelTagsstill folds each channel tag on its own, so a kanji-only tag like鬼滅becomes鬼灭and splits into single characters. It needs the same once-per-channel decision.- The golden only holds single strings, so the per-video kana rule needs a title + tags case for the Desktop port.
I filed the description bug you found as #1395. Merging now.
Summary
Key spec 3 for Chinese, as agreed in #1341. Keys change, so this is one bump (
NeuroKeySpec.VERSION2 to 3, schema 21).NeuroText.fold(resources/neuro/fold/hant-hans.txt, 2965 characters, 1:1, from OpenCCTSCharacters.txt, generated byscripts/neuro/gen_hant_hans.py). The fold is decided once per video: title, channel name and tags decide together, and kana in any of them keeps Han unfolded in every field of that video. The title's own spelling stays the display form.Chunks.merged): adjacent singles, a word before them (大谷 + 翔 + 平) or a word after a lone character (李 + 永乐). Runs end at the language's break characters (resources/neuro/runbreak/zh.txt), are 2 to 5 characters and contain no stop or format word. A language without a break list merges nothing.NeuroPhrases.isMergedRunpromotes a candidate to a key at 3 documents and at least half of the documents holding its rarest single character.NeuroTokenizer(wordBreaker)andNeuroText.words(folded, breaker)take the breaker as a parameter. Tests use ICU4J (testImplementation), so the golden splits Chinese like a device.NeuroFormats), video-structure stop words (scripts/neuro/add/zh.txt),freq/zh.txt(gen_wordfreq.py, then pruned to words ICU keeps whole byNeuroSpecificityTestwithNEURO_WRITE_FREQ_ZH=1).REJECTION_BROAD_TOPICSgets 新闻, 音乐, 游戏, 科技, 电影, 教育, 美食, 旅游 (Hans). They are also inunstemmed.txt; no key changes.NeuroKeyMigration.toKeySpec3): stored keys fold and merge, document frequency is summed, blocked topics fold, the Hant spelling is kept as the display form. Japanese viewers (languageProfile == "ja") are not folded, in the migration and at runtime (NeuroTokenizer.hanFolds).鬼滅hides鬼滅 アニメ 最新話. A blocked topic is stored the way the viewer's keys are (Hans, unchanged forja).zhlists fold Han when they load; thejaandkostop lists, frequency tables and run breaks keep their kanji.mergedandsingles). A JVM test covers the Chinese channel-name path (TVBS新聞gives新闻).NeuroHanFoldDecisionTestcovers the per-video decision, description keywords, blocking and Japanese viewers..github/nightly-notes/2026-10-20-neuro-key-spec-3.md.Related issue
Part of #1341.
Change type
Validation
./gradlew :app:assembleGithubDebug./gradlew :app:testGithubDebugUnitTest :app:testFossDebugUnitTest: 4303 and 4260 tests, 1 failed in each:TranslationFormatStringsTest, becausevalues-rusection_top_artists_inis%1$(a Weblate commit on main, not touched here). Run with-Duser.language=en; two tests depend on the host locale. Includes the benchmark tests../gradlew ktlintCheck spotlessCheck,:app:compileGithubDebugAndroidTestKotlinDevice checks (Pixel 6, Android 16,
NeuroTokenizercalled from the built APK throughapp_process):chunks,modelPairs,formats,mergedandsinglesas ICU4J (all 119 probed lines, titles and channel names, match).topical/zh.txtreturn themselves (folded) fromtokenizeChannelName.NeuroTokenizer.hanFoldOfgivesNONEwhen the channel name or a tag has kana andALWAYSotherwise;鬼滅 無限城編 予告reads無限underNONEand无限underALWAYS.javiewer hashanFolds == false:鬼滅stays and周杰倫 演唱會merges nothing. The default viewer folds to鬼灭and merges周杰伦and演唱会.stopList("ja")holds私達,貴方and貴方方.Measurements
I asked an AI assistant to help collect the data and run the tests and measurements.
Corpus: 654 real titles from YouTube search (zh-TW and zh-CN queries, 425 with Hant characters) and 128 English titles. These are not a viewer's history.
Cost, JVM with ICU4J, median per call (spec 2 / spec 3), second of two runs. The runs differ by up to 7 µs for zh
chunkand 5 µs for zhfeatures, and by under 2 µs for the other calls. Spec 2 runs first in each pair, so JIT warm-up favours spec 3 slightly:chunkextractFeaturesdocumentTermsplantFilterchunkallocates about 24% more for Chinese (20.6 / 25.5 KB), the same for English.NeuroText.foldon non-ASCII text takes about 360 ns more with the Hant fold (0.85 to 1.07 us / 1.23 to 1.43 us).Pixel 6,
chunkmedian over two rounds (spec 2 / spec 3): zh about 63 to 70 / 62 to 63 µs, en about 19.4 to 19.5 / 18.8 to 19.0 µs. The phone read slower than in the first measurement (zh spec 2 was about 50 µs), so compare the two columns within one run.IDF table after 654 zh titles: 6832 / 8804 entries (+28%: 1040 single characters, 1991 merged candidates). English is unchanged.
IDF_MAX_KEYSis 20,000, so a Chinese history reaches the cap sooner, and candidates seen once can be capped out before they reach 3 documents. A simulation with the cap scaled down (4000/3200, an AI assistant ran it) leaves candidates at a count of 3 or more about as often as other words: 83.7% against 84.7%.Promotion on this corpus (3 documents):
No promoted candidate is longer than 5. I read the 67 promoted at 0.5/5 myself: about 80% are names or terms (周杰伦, 李永乐, 台积电, 黄仁勋, 原神, 零基础), about 20% are fragments across a word (旅游全, 首精选, 科学小). This is my judgement, not labelled data, and the thresholds are not tuned beyond this corpus.
Folding: distinct title words across the zh-TW and zh-CN groups 2817 to 2482 (-12%), 247 more words match across spellings.
Risk and compatibility
Key spec 3: brain sync pauses against peers on spec 2 until Desktop ships 3 (as discussed in [Bug]: FlowNeuro mishandles scripts written without spaces (Chinese, Japanese, Thai): names are dropped or split, channel names give no signal #1341).
NeuroContentStorevectors on spec 2 are dropped and refill.Schema 21 migration; a copy of the brain is kept before it (
keepCopyBeforeMigration). Folding is not reversible: a stored Hant key becomes Hans, its spelling is kept as a display form.Removed from
topical/zh.txt: 心理學, 紀錄片, 摩托車, 腳踏車, 自行車, 行銷. Folded to Hans, ICU4J splits them, so they could never match. They were checked in Hant on the device in feat(neuro): split channel names into words and add Chinese topic words #1350.Hans keys follow the ICU dictionary of the Hans spelling, which differs from Hant for some words.
The fold only sees kana. A Japanese title with no kana in its title, channel name or tags folds like Chinese for a non-
javiewer (for example鬼滅 無限城編 予告). Telling it apart needs more than the video's own text.A
javiewer folds nothing, Chinese videos included.New test dependency: ICU4J 77.1 (
testImplementationonly; it was already in the Gradle cache). The OpenCC attribution is added toassets/licenses/notices.txt.The change does not introduce secrets, private data, or unexpected telemetry.
No new user-facing text.
Dependency changes are limited to the test-only ICU4J.
No Room schema change.
Breaking changes are listed above (key spec 3 sync pause).