Commit Graph
166 Commits
Author SHA1 Message Date
HowGeGeandfanlusky 1671c3a467 fix(engine): recognize complete umlaut aliases in quanpin (#994)
* fix(engine): recognize complete umlaut aliases in quanpin

* refactor(engine): 移除九键输入

九键会话只有引擎公共 Session 门面在用,Server 与 TSF 都没有接入。删掉
NineKeySession、Session 的九键入口与 SessionSnapshot::nine_key_spellings,
以及对应测试和注释。

* fix(engine,server): ü 别名共用一条归一规则,入库与纠错不再产出查不到的键

- 新增 quanpin::is_jqxy_v_alias / canonical_umlaut_spelling,替换 canonical_syllable
  与 to_google_spelling 里各自手抄的别名表,normalize_umlaut_aliases 也改用它。
- 纠错表生成器与运行时生成式空间不再从 jqxy+v 别名派生纠错键(nvan -> jvan 之类),
  手误只纠到 u 的标准写法;重新生成 autocorrect_table.h。
- 个人词库新写入的拼音词、设置页导入的全拼按词库写法入库(xvan -> xuan、lue -> lve),
  否则查询归一后永远查不到;被替换的旧词保留原键,旧行仍可删除。
- 双拼音节集统一排除 jqxy+v 别名,round-trip 检查不再特判。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-16 20:48:33 +08:00
Jia Suiandfanlusky 93f95d1245 fix(engine): emoji / 颜文字查询精确命中优先于前缀命中 (#989)
* fix(engine): emoji 查询精确命中优先于前缀命中 (#988)

* fix(engine): 颜文字查询同样精确命中优先于前缀命中 (#988)

* fix(engine): emoji / 颜文字精确提级只认全拼,并合并为单次排序查询

- 双拼下原始码只按目录顺序参与前缀命中,不再精确提级:小鹤 xl 精确命中简拼键时会压过展开后的 xiang。
- 颜文字只有全拼精确命中提级,简拼精确命中不提级:单音节条目的简拼只有一个字母,打 k 时会整批排到前面。
- 两级查询合并为每个查询码一条 SQL(精确行在 LIMIT 之前),语句只 prepare 一次,循环抽到 local_database 的 query_ranked_entries 共用。
- server 回归测试不再断言 ku 多个精确条目之间的目录顺序。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-16 20:17:01 +08:00
chr331andfanlusky 1c3bd7cb83 feat(server,ui-html,windows): 支持自定义“输入状态切换”中的快捷键 (#985)
* fix(windows): 支持标点切换快捷键改绑与取消绑定

* fix(shortcut): 对齐虚拟键并处理语音与窗口快捷键冲突

* fix(ui-html): 绑定标点快捷键前提示应用快捷键占用

* fix(ui-html): 仅对已知快捷键冲突提示确认

* feat(shortcuts): 支持自定义输入状态切换快捷键

* fix: keep shortcut editor buttons on one row

* fix(shortcuts): 限定输入状态快捷键为 Ctrl 组合

* fix: clarify shortcut override and punctuation lock behavior

* perf(server,windows): 提前筛选输入状态快捷键

* fix(windows,server,ui-html): 修正输入状态快捷键的兼容与保存问题

- windows: 未协商 InputStateShortcuts 时回退到旧的 Ctrl+Shift+E KeyEvent,
  新 DLL 配旧 Server 时英文候选快捷键不再失效
- windows: 全角快捷键与标点快捷键一样可在待处理按键后排队,不再漏给应用
- windows: Server 不可用时排队的英文候选切换按 FUNCTION_CANCEL 离线回放,
  不再丢弃其后的按键
- server: 非字符串配置值不再以 "!invalid" 作为 binding 发给设置页
- server: 仅三个输入状态快捷键路径强制回传配置快照;去掉 worker 中与
  reader 重复的协商检查;抽出 InputStateShortcutViews 去除重复转换
- ui-html: 保存后 5 秒未收到快照则恢复编辑器并提示重试

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-16 03:31:19 +08:00
Jia Suiandfanlusky d414268b5f [Feat] U 模式重构为部件拆字反查 (#973)
* feat(engine,server,windows,ui-html,installer): U 模式重构为部件拆字反查 (#971)

把 U 模式从 Unicode 十六进制码位输入改成部件拆字反查:Shift+U 后输入各
组成部分的拼音连写(晶 = ririri)即可出字,候选上反显注音。

- 引擎:新增 local_modes/radical_query(前缀匹配、字频降序),数据表
  engine/radical/decomposition.txt 与生成脚本;LocalInputMode::Unicode
  改为 Radical;删除 unicode_query。
- Server:g_radical_mode_triggered + IsRadicalCompositionActive + 候选分发,
  用 cpp-pinyin 反显注音;删除 Unicode 码位路径。
- 前端 TSF:删除 U 模式的十六进制/数字特例与影子预判,按普通组合处理。
- 配置:utility.unicode_mode 改为 utility.radical_mode。
- 部署:assets 登记 radical_decomposition,安装器与 CI 拷表。

数据来源 mirtlecn/rime-radical-pinyin (GPL-3.0),授权见 engine/radical/NOTICE.md。

* feat(server): U 模式反显拼音改为带调风格(jīng)

* fix(server,installer): 修复 U 模式 CI(拆字表打包夹具与 clang-format)

* fix(engine,server): 修正 U 模式按键错位、重复候选与配置迁移

- Server 在 U 模式不再吞掉撇号、分号、斜杠、大写字母等键:TSF 已把它们记进按键缓冲,
  吞掉又不重同步会让两侧串和光标错位。照常插入,含非 a-z 的码查不出候选
- 拆字查询全码命中排在仅前缀命中之前,同一个字的几种拆法只出一次
- 升级合并把旧键 utility.unicode_mode 的值带到 utility.radical_mode,读取时也兜底旧键
- 生成脚本只收汉字,重建拆字表去掉 abc 码下的 51 行数字、字母与符号
- 开着 U 模式时在后台预热拆字表与 cpp-pinyin 词典,避开首个按键的冷加载
- Server 按会话的 RuntimePaths 查拆字表,删掉走全局数据目录的便捷重载
- cpp-pinyin 初始化合并到 utils/hanzi_pinyin,设置页导入注音共用;U 模式与快捷短语的收键分支合并

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-16 01:01:18 +08:00
Jia Suiandfanlusky 2f7335be3e 统计增加首词命中率 (#963)
* feat(engine): webview 契约给统计概览增加首词命中率字段并支持 null 分支

* feat(server,ui-html): 统计新增首词命中率(决策点记录、stats v2 迁移与设置页展示)

* fix(server): 首词命中计数补日语标点排除与并发迁移复查

* fix(ui-html): 统计卡片改用自适应列宽,避免长数字被省略号截断

* fix(server,installer): 修复首词命中率随附测试在 CI 的失败

三条 server 用例是首词命中率改动新加、但从未跑绿:RawDb 在迁移用例里对尚不存在的库开只读写连接,补上 SQLITE_OPEN_CREATE;09-19 写了一次次选,按契约它是一次真实测量(0%),断言却要求 nullopt,改为断言 0.0,并把 nullopt 覆盖移到迁移用例(total==0 的那天)。安装器 preserve-user-data 从 stats_pipe.cpp 取库名,而 EnsureStatsStoreOpen 连同 stats.db 字面量已挪到 stats_candidate_capture.cpp,改到新位置。

* fix(server,ui-html): 首词命中率写库移出按键路径,修正迁移与统计口径

- 选词计数改由后台 SerialTaskRunner 写库,标点收尾与选词路径不再同步等 SQLite 锁
- 迁移以表结构为准,不再被版本号挡住;缺列时在 IMMEDIATE 事务里逐列补齐
- 标点在日期入口上收尾时按实际上屏的首条候选计数
- 五笔四码唯一自动上屏不计入;多段造词任一段非首位即不算命中
- 只有选词计数的日线不算打字日,不影响天数、日均与连续天数
- 卡片复用 formatFirstWordRatio 并展示累计命中率,概览复用 DailyRow::FirstWordRatio

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-15 22:09:50 +08:00
Jia Suiandfanlusky 456b23ed94 feat(engine,server): 五笔支持 T 模式与 V 模式 (#942)
* feat(engine,server): 五笔支持 T 模式与 V 模式

五笔方案此前被排除在 Shift+T 日期时间模式与 V 模式之外。根因是方案层就把
raw 归一成小写 a–y、丢弃数字与运算符,Server 的形状判定读不到大写 T/V 前缀。

- 引擎 WubiScheme 新增 raw_input_with_cases_:保留大小写与 T/V 模式的非字母键
  (数字、运算符、: /),但按码字域丢掉越域字母(默认配置下 z)。查询码仍由
  normalize_wubi_code 派生,普通码下两者长度相等——Server 用原样串长度数四码。
- Server:T 触发新增独立谓词 date_time_scheme(chinese_scheme || Wubi),不并入
  chinese_scheme 本身;FormatVModeWorkerPayload 对五笔返回 UppercaseOnly。
- 自动上屏 / 顶字两个谓词新增 special_mode_active 前置:T 模式关键词归一后可能与
  合法四码重合(Tdate → tdat),不挡会在第四个字母上结束模式。
- 引擎与 Server 测试、README 与两份配置模板文案同步。

* fix(engine,server,ui-html): 五笔 T/V 模式由宿主标注字面输入

原实现让五笔原样串不再裁到码长,普通码下原样串可以比码长(混输关、z 通配时 wqazb 对 wqaz),
Server 读回后光标、退格和上屏都会错位;守卫也只认 T 模式的全小写主体,TdatE 仍会顶出 tdat。

- 引擎:InputSession::set_wubi_literal_input 随 pending 序列一起下发,只作用于同一次设置。
  普通码的原样串就是带大小写的码,与查询码逐位对应、一起截断;字面输入原样保留,
  不查码表与拼音,也不算完整四码。退格从原样串重新派生码,字母键两份视图取同一个字母。
- Server:ApplyCompositionEditKey 按 IsWubiLiteralComposition(T 模式触发标记或 V 模式形状)
  标注字面输入;两个五笔上屏策略恢复原签名,不再需要 special_mode_active。
- 设置页 T/V 模式说明补上五笔。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-15 02:08:36 +08:00
Jia Suiandfanlusky 1499027f04 fix(engine,server): 光标落在拼音首单元内部不再收起候选窗 (#940)
* fix(engine,server): 光标落在拼音首单元内部不再收起候选窗

quantized_prefix_end() 把光标 floor 到完整单元边界后,prefix_end==0 同时表示「真串首」与「首单元内部」。后者被 Server 的 IsCaretPrefixEmpty 当成串首,候选窗整窗收起(全拼 nihaoya 选你好后剩 ya 左移即消失)。

floor 结果为 0 且 caret>0(首单元内部)时改返回 raw.size(),落回整串解码;prefix_end()==0 当且仅当 caret==0,只有真串首才隐藏。segment_raw_boundaries() 与 #187「前缀只切完整单元」的契约不变。

* fix(engine,server): 首单元内部的光标向上取整到首单元末尾

上一提交让首单元内部的光标落回整串解码,prefix_end() 因此报告整串已解码,
与实际光标位置不符:Server 选词时 caret_prefix_selection 为假,不复位组合
光标,造词后剩余串带着旧光标位置;首单元与后续单元的取整方向也不一致。

改为向上取整到首单元末尾(只有一个单元时即整串)。prefix_end() == 0 仍当且
仅当 caret == 0,前缀选词走正常的光标复位路径。同步更新引擎与
input_key_policy.h 中描述旧规则的注释和相应测试。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-15 01:37:58 +08:00
Lu Fan(SonnyCalcr) e46294308d fix: CS2 / Dota 2 / 三角洲行动等游戏里的输入法兼容(加载、候选、翻页、独占全屏、启动崩溃) (#925)
* fix(installer): TSF DLL 改装到系统目录,CS2 等 Trusted Mode 游戏里可以切出输入法

CS2 的 Trusted Mode 只放行位于系统目录且有代码签名的第三方 in-proc DLL,
装在 Program Files 下的 DLL 会被拒绝加载,游戏里切不出输入法。
x64 DLL 改装到 System32\IME\MetasequoiaIME\msime_v*,x86 装到 SysWOW64 下同名目录,
语言配置图标随之放到两处根目录;升级时清掉旧版留在 Program Files 的版本目录。

* fix(windows): CS2、Dota 2 不理会 UI-less 声明,照常显示候选窗

起源 2 引擎的 Valve 游戏以 UI-less 激活 TSF,但只替白名单里的输入法绘制候选。
我们据声明收起了自己的候选窗,游戏又不画,结果只剩左上角的系统默认 IME 小框,只能盲打。
对 cs2.exe、dota2.exe 忽略 UI-less,照常由 Server 绘制候选窗。

* fix(server): 前台为 D3D 独占全屏时不弹候选窗与光标状态提示

独占全屏下别的进程的窗口画不上去,弹出去反而可能把游戏踢出独占模式(闪黑、切分辨率、卡死)。
在 UI 线程真正显示前用 SHQueryUserNotificationState 判一次(300ms 缓存),独占全屏就收起候选窗,
并清掉已渲染可见标记,数字键与空格选词不再等待不会到来的绘制。

* fix(engine,server,windows): 游戏自绘候选时后台改动的候选列表实时同步给宿主

英雄联盟等 UI-less 宿主自己画候选。云候选、AI 候选、模型重排、英文混排在按键回复之后才改
候选列表,却从不通知 DLL,游戏还画着上一键读到的旧列表,数字键和空格却按新列表上屏,
于是「选的和上屏的不一样」。

- 契约新增 Worker opcode 35 UiLessCandidatesChanged,载荷与 UiLessComposition 同形,
  由 uiless_page.h 统一格式化与解析,并附候选页代号;旧 DLL 读选中位时在第三个制表符处停下,兼容。
- Server 在上述后台改动之后把新页推给候选归属的客户端。
- DLL 收到后回到 TSF 线程核对焦点与组合纪元,更新候选 UI 元素并通知宿主重读。推送与按键回复
  走不同管道,按代号只接受不旧于当前的页,避免旧页盖掉新页。
- 从共享内存重读到不同的页时高亮回到第一项,与 Server 每次重建都选中首项一致,
  不再保留旧高亮造成「高亮这个、空格上屏那个」。

* feat(windows,server,ui-html,installer): 游戏兼容开关,CS2 / Dota 2 独占全屏打字不再黑屏

CS2、Dota 2 只替内置白名单(按输入法登记名称全等比对)里的输入法处理输入法消息。不在名单里时
组合落到 Windows 默认组合窗 CiceroUIWndFrame,左上角弹出编码小框;独占全屏下这个窗口一出现
游戏就被踢出全屏,表现为一打字就黑屏。清风输入法未开兼容时同样如此。

- 设置页帮助页新增「游戏兼容(CS2 / Dota 2)」:开启后把水杉在系统里登记的名称换成白名单里的
  名字(默认「拼音输入法」),游戏会自己画候选。需要管理员权限:设置进程以
  --apply-game-compat-name 提权重启自身,写 HKLM 记录值并改语言配置的 Description。
- TSF DLL 注册时以记录值为准,安装/升级重新注册不再把用户选的名称冲掉;卸载时删除记录值。
- 开启时不再对 cs2.exe / dota2.exe 忽略「宿主自己画候选」的声明,避免两个候选框。
- Server 的独占全屏判断覆盖进出独占模式时的 QUNS_BUSY 间隙,不在切换途中弹候选窗。
- TSF 诊断日志在组合开始/结束与 BeginUIElement 时记录宿主窗口状态(只有窗口与标志,没有文本)。

* fix(windows): 后台刷新回调持有加载器引用,宿主卸载 TSF DLL 后不再崩溃

当前输入法是水杉时,CS2 启动途中会加载再卸载 TSF DLL。诊断日志的线程池回调
FlushTsfDiagnosticLogs 正在 Sleep(250),只靠 DllAddRef 撑着:那只回答 DllCanUnloadNow,
挡不住宿主直接卸载。DLL 卸载后 SDL3 映射进同一段地址,回调醒来跑进 SDL3 的代码,
游戏崩溃(0xc0000005,崩溃转储里卸载模块的地址范围与崩溃地址吻合)。

- 诊断日志与统计上报的线程池回调改为用 GetModuleHandleEx 取加载器引用,
  回调里 FreeLibraryWhenCallbackReturns,函数完全返回后才放手。
- 析构函数把 DllRelease 挪到最后,不在等待 IPC 线程退出之前就让 DLL 可卸载。

* fix(windows): 游戏自绘候选时不再把 Server 的 HTML 候选页当成候选交给游戏

CS2 开了游戏兼容后打一个码,游戏里的候选只有一项且是乱码。宿主要等 BeginUIElement 才说
自己画候选,而那时组合首键已经不带 UI-less 标记发给了 Server;Server 往共享内存写的是给
自己候选窗用的 HTML 页(辅助码、译文 span),DLL 的候选元素从共享内存回读后整串交给了游戏。

- 开了游戏兼容名称的 cs2.exe / dota2.exe 在 ActivateEx 就认定由游戏画候选,每个按键都带标记。
- 候选会话里收到过 Server 的 UI-less 页之后才从共享内存回读,之前一律不读。

* fix(windows): 游戏自绘候选时翻页能刷新,部分上屏后不再出现乱码候选

三角洲行动(虚幻引擎,以 TF_TMF_UIELEMENTENABLEDONLY 激活)里按 = / - / PageUp / PageDown
翻页,Server 已翻到第 N 页,游戏却还画着第一页;接着按空格上屏的是第 N 页的首项(多为单字),
剩下的拼音继续组合时候选框里出现乱码。

- 翻页、移高亮等键走异步的 WM_AsyncServerCandidateKey,以前把回复当作「只是确认」丢掉;
  UI-less 下这份回复就是新的候选页,现在应用并通知宿主重读。
- UI-less 的 _SetText 拿不到 Server 的候选页时不再退回本地引擎的占位列表——那不是候选,
  宿主会画成一条乱码。部分上屏后从共享内存回读 Server 写好的新页;_ClearList 同时清掉上次
  的页记录,页文本相同也会重新载入。
2026-10-13 21:58:15 +08:00
Lu Fan(SonnyCalcr) 119c439d7a fix(engine,server): 日文候选不再写进拼音词库,升级回放跳过已残留的日文词 (#907)
日文 provider 出的候选没有设置 scheme,默认是 Quanpin。在日文模式下选非首选
候选时,Server 按拼音调频,用罗马音作键(pan、amarini)写进拼音日志。msime.db
里没有这些行,UPDATE 改不到任何东西,但 update_pinyin_weight 照样记了日志;
升级回放时 apply_pinyin 找不到行就插入,日文词于是出现在全拼候选里。

- 日文候选标上 SchemeType::JapaneseRomaji
- Server 的选词调频、置顶、造词与云候选落库跳过日文候选
- update_pinyin_weight 没改到行时不写日志
- 回放跳过、打开日志时清掉自动学习来的带假名拼音条目;手动加的词不动

Fixes #869
2026-10-13 12:54:29 +08:00
Jia Suiandfanlusky 60c2b26698 fix(engine,server): 全拼纠错收窄「多字」纠错并让开关真正关闭纠错 (#889)
* fix(engine,server): 全拼多字纠错收窄为仅 QWERTY 邻键插入

多字(插入)纠错只保留「误插 QWERTY 邻键」:生成器 insertion_variants 不再生成重复相邻键(双击),生成式空间不再生成任意字母插入(远键插入)。autocorrect_table.h 随生成器重新生成。

* fix(engine): 全拼纠错开关关闭时真正不再改写切分

纠错开关关掉后真正不纠错:把手误拼写别名层从常开改为跟掩码走(ü 归一 jv->ju / nue->nve 仍常开);两个早于掩码可知的切分入口 QuanpinScheme::build_request 与 QuanpinDictionary::get_or_compute_segments 显式带上掩码,切分缓存键随之隔离。

* fix(engine,server): 纠错开关关闭时辅助码、写回与排序上下文也按字面切分

关掉全拼纠错后,只有方案的 build_request 和查询自身切分不再跑手误别名层,
其余几条路径仍用默认(带别名)的切分,和查询落在不同的键上:

- 辅助码路径自己切底串作为显式切分交给词典,haoyonga 仍被切成 hao'yong'ga;
- 调频、造词、删词、建 SQL、校验去掉分隔符后重切,开关关掉时选中 hao'yong'a
  的词会去更新 hao'yong'ga,调频静默失败;
- 排序 / 固定位置的上下文 key(引擎与 server 两处)仍按别名切,laing 与 liang
  的固定位置互相串。

改动:
- cut_pinyin_by_mode 的 include_correction_aliases 改为必填,每个调用点显式决定;
  双拼词典、设置页词库校验和离线工具传 true,保持原行为。
- 新增 quanpin::correction_aliases_enabled,方案、词典、辅助码与排序 key 共用
  (只看换位、邻键两位),修正只带删除 / 插入位的掩码在方案与词典两层判断不一致。
- 词典按最近一次 query 的掩码记录别名开关,写回路径统一走 cut_for_write。
- 会话改为在换方案、改开关时 bind_quanpin_scheme 一次,不再每次 refresh 下发。
- 方案只存 bool;jv -> ju 放回纠错表,去掉合并表。
- 在 CI 实际执行的 test_input_session 中补回归用例(写回、辅助码、固定位置、
  方案掩码),每条都确认回退修复后会失败。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-12 20:41:23 +08:00
Jia Suiandfanlusky fc90cb1f2c fix(engine): 纯简拼读法否决权,纠错不再劫持 nbn 等简拼候选 (#881)
* fix(engine): 纯简拼读法否决权,纠错不再劫持 nbn 等简拼候选

纯简拼形输入(切分全是单字母声母)若词库 jp 列有该输入读法,纠错不得接管:
nbn 是「能不能」、nqn 是「女青年」、bqng 是「并且能够」,纠错却把它们改写成
字母更近的错串候选(难/帮),顶掉真实命中的简拼词。

输入形状分不干净——sahng 也全是合法声母却必须保留乱序纠错;唯一分得开的是
词库有没有这个简拼读法,故判定落在同时持有 DB 与纠错掩码的查询层。

- 新增 quanpin::has_pure_jianpin_reading(jp 列 LIMIT 1 查询)
- QuanpinDictionary::effective_autocorrect_types 统一 query_exact 与
  insert_word_to_series_cache 的有效掩码,避免 series 缓存键分歧
- 非纯简拼(shng=[sh,n,g]、shbng、sahng/shangg/sjhang)纠错不变

* fix(engine): 纯简拼整词改为排在纠错前面,不再整个关掉纠错

原方案在词库有纯简拼读法时把纠错掩码置 0,漏了元音的真手误(bng 想打 bang)
也随之纠不了,而且只有词库层知道纠错被关,预编辑与选词消耗仍按原掩码判断。

改为不动掩码:纠错照常进行,纠错分支里字面结果中的纯简拼整词
(literal_jianpin_pinyin_match:两个及以上单字母声母盖住整串,逐个是正读首字母,
a/e/o 不算)和混合简拼一样放进领衔组。纠错读法排在后面,仍可选、仍带纠错标记。
判定直接看已查回的字面结果,不新增 SQL;掩码不变,缓存键也不变,
因此删掉 has_pure_jianpin_reading 与 effective_autocorrect_types。

测试补上预编辑(n'b'n)、选前缀单字只消耗自己的声母、bng 仍保留「帮」。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-12 17:23:30 +08:00
時崎 ギャラクシーandfanlusky add122fcc6 feat: 候选词翻译新增 AI 辅助提供方,收敛为单一 translation.provider (#876)
* feat(engine): 翻译凭据测试新增 translation.ai 服务类型

* feat(server): 候选词翻译新增 AI 辅助提供方,收敛为单一 translation.provider

* feat(ui-html): 候选词翻译新增 AI 辅助提供方并跟随单一 provider

* fix(server): 将 ai_translation 加入编译目标与测试套件

* fix(server): 在 AiTranslation 命名空间导出 ParseTranslations 供测试调用

* fix(server): 解析模型返回时剥除 Markdown 代码块标记

* fix(server): AI 候选词翻译校验占位 Token、收紧解析,翻译配置改读快照

- AI 翻译的 Token 改用 IsUsableSecret 校验,出厂模板的占位符不再被当成可用凭据
- 解析模型回复时,合法 JSON 但键不对、数组长度不符或残缺 JSON 一律作废,不再把原文当译文显示和写进释义库
- 从旧布尔推导出腾讯云时保留 tencent_tmt.enabled 门禁;在设置页明确选择时才打开,升级不会替用户向腾讯云发送候选词
- 候选词翻译读取的 provider 与各服务配置合成一份加锁快照,配置重载时整份替换,候选线程和翻译 worker 不再直接读会被改写的全局字符串;每个任务只解析当前服务的配置
- provider 与三个旧布尔一次原子写入;旧的 niutrans/custom enabled 开关改为切换 provider
- AI 联想与 AI 翻译共用 ChatCompletions 传输层,翻译超时与 AI 联想一致改为 8 秒
- 设置页凭据测试与运行时共用 AiTranslation::FromConfiguredTranslation,provider 白名单只保留 IsValidTranslationProvider 一处

* fix(ui-html): 候选词翻译的 AI 配置提示随 AI 辅助页实时更新

AI 辅助页改服务商、地址、Token 或模型后,翻译区的提示和摘要立刻刷新,不用重开设置窗口;可用性判断与服务端一致,占位 Token 不算已配置。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-12 16:47:04 +08:00
Jia Sui d495ea2ad0 fix(engine,server,installer): octagram 加成收敛为单一注入,校正量纲与参数,修复〔万象〕同音乱码 2026-10-12 13:12:21 +08:00
fanlusky b434240489 feat(engine): 模糊读音按词频与原读音混排,整句词格收模糊读音
开模糊音时模糊读音的候选不再整组垫在末尾:同组内按「权重 × 0.2」与敲出读音的词库行
归并,min 出「民 明 名 命 敏…」而不是先翻完 min 的生僻字。词格的跨度查询同时收模糊变体,
每个模糊音节扣 1.0(log10),lin'yi'bian 解出「另一边」;整句 canonical 记真实读音,
造词不写错音。切分与纠错的裁决打分不带模糊读音,避免字面读法借模糊分压过纠错。
双拼同步。eval_quanpin_sentence 新增 fuzzy=<规则位> 参数。
2026-10-12 03:44:25 +08:00
fanlusky cf07b77df6 feat(engine): 等音节数字面切分按整句打分裁决,预编辑跟随首选
最少音节切分平局时固定取较短首音节,切分又在查词之前定死,yibananzhao 只会切成
yi'ba'nan'zhao 出「一把难找」,「一般按照」「这个方案很好」这类 n/ng 边界歧义的整句没有机会。

- arbitrate_literal_segmentations:合法输入的等音节数字面切分各解一遍词格,路径分最高的领衔,
  同分仍取原切分;输方分差在 3.0 以内时它的整句占第 2 位,更远不出。任一切分在词库里有整词时
  不介入(单词级仍按词频),整句来源全关时与原来一致,用户调过频的读音不让出领衔
- 合法输入纠错的原读法改为领衔的字面切分:纠错胜出时第 2 位是得分最高的字面读法
- 首选是另一种等音节数字面切分时预编辑跟着它分(yi'ban'an'zhao、fang'an)
- 胜出切分的前缀词替换原切分前缀区间扫出的同名行,选「一般」按 yi'ban 消耗
- 词格跨度查询与最佳路径加缓存,随 reset_cache 清空;普通输入每键 p90 也随之下降
- 新增 eval_quanpin_sentence 与句子级评测集 tests/data/quanpin_sentence_cases.tsv
2026-10-12 03:05:44 +08:00
fanlusky 7ed4f2e0fd fix(engine): 回溯切分判定合法全拼,合法输入选词不再按纠错切分消耗
is_complete_pinyin_input 用贪心最长匹配判定合法性:zaijinuta 取了 jin 就剩下切不动的
uta,合法的 zai'ji'nu'ta 被当成手误。查询侧用回溯切分认它合法,预编辑和选词却去走纠错:
预编辑显示成 zai'ji'nut'a,选「再」按 zaij -> zai 的纠错切分吞掉 j,只剩 inuta。

- is_complete_pinyin_part 与 looks_like_jianpin_spelling 改用回溯切分,与查询侧同一个算法
- corrected_reading_raw_length:读音是原始字母前缀、整串合法(可带一个末尾声母)时按读音长度消耗
2026-10-12 03:01:57 +08:00
Lu Fan(SonnyCalcr) 035b2ad779 chore(engine): 辅助码表补充「瑠」「弔」 (#856) 2026-10-12 01:07:15 +08:00
Lu Fan(SonnyCalcr) 715028e1b9 feat(server): AI 联想上下文跳过密码框、换应用清空、过滤类凭证串 (#845)
AI 联想会把最近的上屏文本作为上下文发到云端,以前不区分输入框,也跨应用累积。

- 密码 / PIN 输入框:TSF 在一段输入的首键上读 InputScope(IS_PASSWORD、IS_NUMERIC_PIN 等),
  按键包带上 FanyImePipeFlags::SensitiveField;Server 收到后不发 AI 请求,上屏也不进上下文
- 只在 AI 联想开启时才检测:Server 通过新的 worker opcode AiContextUploadChanged(34)
  告诉 TSF 开关状态,关着时按键路径只多一次 atomic load
- 上下文按焦点会话归属:client_id 或 activation_epoch 变了就清空,不再把 A 应用里打的字带进
  B 应用的请求
- 发送前去掉像凭证的 ASCII 串:不含空白、长度 >= 8 且混了字母 / 数字 / 符号中两类以上
2026-10-11 21:58:34 +08:00
沐雪清泽andmushiyuki 93c7b8121d fix(engine): consume Japanese nn as a single nasal (#669)
Co-authored-by: mushiyuki <248113054+muxueqingze@users.noreply.github.com>
2026-10-11 20:39:55 +09:00
Lu Fan(SonnyCalcr) 649747f8cd feat(server): 神经网络整句效果优先模型改为按需下载 (#838)
效果优先模型(sentence-model-desktop.safetensors,约 25 MB)不再随安装包分发,
改为和八股文模型一样在设置页下载后才能使用。

- engine:SentenceAssociationOptions 新增 neural_desktop_model 路径,留空即模型缺席、
  静默不重排;词典不再从资源目录固定加载效果档。load_file 按 UTF-8 打开路径,
  数据目录在非 ASCII 用户目录下也能读到模型
- server:新增 settings/neural_model,下载到 <数据目录>\models\neural-desktop\,
  下载与导入都按 neural-model/lock.json 的字节数和 SHA256 校验后原子落位;
  会话层只在开关打开时解析模型路径
- ui-html:设置页增加下载 / 导入 / 删除 / 浏览器下载,模型未就绪时开关置灰
- installer:不再打包效果档模型;旧版装进数据目录根下的那份在覆盖安装与卸载时清掉

原有配置项 sentence_neural_desktop 保持不变,用户的开关状态升级后保留。
2026-10-11 18:08:33 +08:00
chr331 be66c8c128 test(engine): 补全混拼边界与双拼回归 2026-10-11 14:59:31 +08:00
chr331 36ab259c43 fix(engine): 保留原样混拼候选优先级 2026-10-11 13:56:14 +08:00
Lu Fan(SonnyCalcr) 85edddc988 feat(server): 设置页支持把水杉设为默认输入法 (#802)
帮助页新增「默认输入法」卡片:显示当前是否为默认,提供「设为默认」和
「系统设置」两个按钮。设置走 input.dll 的 InstallLayoutOrTip /
SetDefaultLayoutOrTip,完成后按 HKCU International\User Profile 复核;
EnumEnabledLayoutOrTip 在 Windows 10/11 上对 TSF 输入法返回空列表,
因此状态直接读注册表。只在用户点击时执行,不在启动或升级时改写。

新增 defaultImeRequest / defaultImeResponse WebView 契约并同步 ui-html。

同时在帮助页加入 Wiki 与 FAQ 链接,在关于页加入官网 msime.app。
2026-10-11 02:22:01 +08:00
Ouchihao 777fb2aef4 fix(engine): protect repeated-initial jianpin 2026-10-10 17:05:09 +08:00
Ouchihao 29c8917e8c fix(engine): preserve leading-initial mixed jianpin 2026-10-10 14:46:13 +08:00
AkaneIwasaandfanlusky 568d07f513 feat(windows): 添加中日英三语轮换 (#717)
* feat(windows): 添加中日英三语轮换

* fix(windows): 三语轮换改为快捷键开关并修复两端状态不同步

- 开关移到「快捷键 → 中英文切换」下的「允许中日英循环切换」,配置键改为
  [keybindings] trilingual_cycle;输入模式恢复为中文/日文,二者互不影响
- 循环请求带显式标记位和请求方自身的中英/日文状态,Server 据此计算目标语言;
  普通 VK_SHIFT 仍是中英切换,不再被误当成循环
- Server 开关在请求途中被关闭时仍回复,按中英切换处理,TSF 只丢弃后续排队按键,
  不再超时重连
- 已发出的循环回复未被应用(含 Superseded)时走完整重连,避免日文状态两端不一致
- 循环切换补上光标处状态提示和标点同步
- 切换语言时复用暂存的另一语言会话,不在按键路径上重建引擎和打开 msime.db
- 配置读取的语言与开关改为原子量;TrilingualCycleChanged 的能力过滤移到发送方
- 中日切换不再重发 TrilingualCycleChanged;结束组合被拒绝时不计入统计

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-09 15:44:41 +08:00
時崎 ギャラクシー aa5c632ea9 Merge pull request #711 from Tokisaki-Galaxy/feat/llm-model-fetch
feat: AI 辅助新增获取模型、测试连接与 Custom 提供商
2026-10-08 23:13:01 +08:00
normaltank c2bd59d448 fix(server): 翻页扩展时保留混输候选并同步 Emoji 末尾排序 2026-10-08 22:10:13 +08:00
fanlusky 9a08042586 fix(engine): 日语预测紧跟整读音转换,不再被前缀片段压后
完整读音时的候选顺序改为:最佳整句路径与整读音精确词条在前,前缀预测随后,
其余整句结果(次优路径、假名兜底、只覆盖前缀的词条)殿后。预测只取读音严格
长于输入的词条,不再重复扫描精确词条;预测权重不高于其上方的转换,避免按权
重重排时把顺序翻回去。测试补齐头文件、清理目录不再抛异常,并新增前缀片段回归。
2026-10-08 16:48:39 +08:00
mushiyuki 4234f6deb2 fix(engine): prefer Japanese conversions over prefix predictions 2026-10-08 11:45:44 +08:00
luojiyin ecbc932009 perf(engine): reuse int8 weights across eight rows 2026-10-06 09:24:24 +08:00
Lu Fan(SonnyCalcr) 53ee180ec3 feat(engine): 直接辅助码句中四码可用第二位辅码大写结束 (#630)
直接辅助码的四码在句中原来只能补 / 结束(uiab/),现在也可以把第二位辅码敲成大写(uiaB)。
设置页「直接辅助码句中四码标记」两个勾选框可多选、至少留一个,默认只勾 /。

- 引擎:拼写图新增 Aux2Upper(非缩写)。开着大写标记时大写字母只能当第二位辅码,
  走不通再退回不分大小写的旧规则,第一位辅码敲大写(uiAuiQ)照常可用。/ 的收键受勾选控制。
- 协议:DirectHelpcodeChanged 载荷改为 "0"/"1"/"2"(2 = 开着但 / 不是编码键),
  TSF 据此决定吃不吃 /。
- 配置:新增 helpcode.shuangpin_direct_helpcode_slash / _uppercase,同步写入出厂模板、
  工作配置和两个设置宿主的读写映射。
2026-10-05 20:43:02 +08:00
Lu Fan(SonnyCalcr) 3c79876205 Merge pull request #629 from fanlusky/feat/v-mode
feat: 新增 V 模式,数字转中文与算式计算
2026-10-05 17:51:21 +08:00
fanlusky e72820ef46 perf(engine): 全拼前缀不再解注定被丢弃的 Google 整句
query_series 对每个前缀走 query_single_path,词库查不到时会解一条 Google
兜底整句,随后又因只对完整输入出整句而被删掉,还打乱解码器的增量状态。前缀
改走只查词库的 query_prefix_path:非空结果照原样写入 cache_,查不到的记进
单独的 prefix_miss_cache_,不往 cache_ 写空结果,这串之后作为完整输入时仍
补得上兜底整句。

逐键对照新旧引擎 3960 个快照(三档整句设置、退格、缓存冷热、先长串后前缀)
的全部候选词、来源与读音,完全一致。
2026-10-05 13:02:46 +08:00
fanlusky 352c7e2b12 perf(engine): 混排未开启时不再每键复制候选列表
update_mixed_candidates 无条件把候选按值传进 CandidateQueries::mixed,Server
自己混排、candidates() 也只在混排开启时才读这份副本。未开启时直接清空返回;
混排开关、方案与固定位置每次变化都会重新调用本函数,不会读到旧列表。
2026-10-05 13:02:46 +08:00
Lu Fan(SonnyCalcr) 1da1c1bcd6 fix(engine): 辅助码串上的 Ctrl+方向与 Ctrl+Backspace 按单元编辑 (#627)
双拼串里带句中辅助码(反引号段 / 大写段)或开着直接辅助码时,segment_raw_boundaries
直接返回空,Server 只能退回逐字符移动和删除。

现在按请求带着的辅助码段布局切单元:每个音节和挂在它后面的辅码段各算一个单元,
Ctrl+Backspace 可以只删辅码,Ctrl+方向也能停在音节与辅码之间。布局对不上原串时仍
不给边界、退回逐字符。直接辅助码没解析出辅码时请求原样放行,沿用普通双拼切分。

光标前缀重算与按段编辑共用同一套边界,带辅助码的串也随之按光标前缀出候选。
2026-10-05 04:19:23 +08:00
Lu Fan(SonnyCalcr) b391fd616b feat: T 模式支持输入指定的日期和时间 (#626)
Shift+T 后面除了 rq / sj / xq 唤醒词,还可以直接写数字:

- YYYYMMDD、YYYY/M/D、M/D(年份取今年)出那一天的日期格式,星期按那一天算
- YYYY/M 出年月格式(新增 date:ym_* 五种)
- h:mm、h:mm:ss 出时间格式,写了秒才给带秒的格式
- YYYYMMDDhh[:mm[:ss]] 出日期加时间的格式

指定的日期时间沿用唤醒词的格式 ID,学到的顺序、置顶、固定位置跟着走。

输入形状放在 engine/contracts/date_time_input.h,TSF、Server、引擎共用:
数字、/ 和 : 只在还能接成某种形状时才进输入串,接不上的数字仍是选词键,
/ 和 : 仍是标点,Shift+数字始终选词。小键盘数字同样可以输入。

Refs #531
2026-10-05 03:42:12 +08:00
Lu Fan(SonnyCalcr) ae353d142c feat(server): 语言栏右键菜单新增输入方案二级菜单 (#625)
在托盘语言栏菜单第二项加入「输入方案」,悬停展开全拼/双拼/五笔二级菜单,
当前方案打勾,点击即切换(与设置页走同一条 SetConfiguredInputScheme 路径)。

- D2D:TrayMenuPresenter 用 Popup 实现子菜单,展开时宿主窗口向有空间的一侧加宽,收起时还原
- WebView2:页面通过 inputSchemeSubmenu 请求宿主加宽窗口,再由 PlaceInputSchemeSubmenu 摆放;
  changeInputScheme 切换方案
- 契约:messages.json 新增 changeInputScheme / inputSchemeSubmenu,并重新生成 ui-html/webview2/shared
2026-10-05 02:44:59 +08:00
Lu Fan(SonnyCalcr) 8a524aa498 feat(engine): 全拼纠错扩展到多字/漏字,贵档读法按整句分领衔 (#623)
* feat(engine): 合法输入纠错扩展到多字、漏字,纠错读音按原始区间消耗字母

jiuzheeyang 切成 jiu'zhe'e'yang 全合法,多打的 e 原本纠不回「就这样」;多字纠错的
读音又与原始字母不等长,buuhui 选「不会」后会剩下一个 i。

- legal_input_correction_cuts(原 legal_input_transposition_cuts)认换位、多字、漏字
  三类静态表读法,不认邻键与生成式空间;末尾音节按漏字读出的(nia -> nian)视为
  还没打完,跳过;k-best 先取 9 条再过滤,长句里纠正不被别处的读法挤掉
- 改变字母数的纠正代价 3.0(换位 2.0);字面切分本身是真词时不认改变字母数的读法,
  少一个音节的句子在语言模型里天然分高(邪恶 -> 些、答案 -> 但)
- 新增 correction_cut_for_reading / corrected_reading_raw_length:选词按纠错切分
  记录的原始区间消耗字母,预编辑也按它分隔(jiu'zhee'yang)
- AutocorrectCut 恢复 has_generated,供合法输入路径排除生成式读法

* fix(engine): 被裁掉的合法输入纠错读法不再误标原读法的候选

合法输入上的纠错读法登记进了全局纠错标记表,即使裁决时被丢掉也照样参与按读音字母
的标记匹配:xiee 按多一个 e 读成 xie 被丢掉后,邪恶后面的前缀单字 写/些/血 读音
也是 xie,全被标成纠错。

改为不登记,只在 arbitrate_legal_corrections 里给真正用上的那条纠错读法的整读音
候选打标,标记随结果进缓存。

* feat(engine): 结构性贵档纠错读法可按整句分领衔

shiideya 的换位读法 shi + ide->die(权重 10,是爹呀)比多字读法 shii->shi(权重 12,
是的呀)便宜一档。#592 只在同档内做上下文消解,贵档读法一律排在最后,「是的呀」
连首页都进不了。

- 上下文消解把前 2 条贵档读法也解码,不要求词库有整词;总分 = 路径分 - 手误代价,
  代价按纠错表权重换算(权重 x 0.2:换位 2.0、漏字 2.2、多字 2.4)
- 只收由结构性手误(换位、漏字、多字)构成的贵档读法:邻键只比漏字贵 0.4 分,
  常用词的语言模型优势会把 zhowei 的「周围」挤成「作为」
- AutocorrectCutSegment 记录每段的纠正权重,用来区分纠正类型
- 预编辑跟随领衔读法分隔也覆盖切不成合法音节的输入(shii'de'ya)
- eval_quanpin_autocorrect 新增 --word-lattice,量产品默认开词格时的排序

评测(--word-lattice,300 样本,seed 42)各档 R@1/R@3 不低于改动前:mixed R@1
79.7/76.7/78.3/70.3 -> 82.3/81.7/81.3/78.0,insertion 83.3/74.7/72.7/52.0 ->
91.3/89.3/90.0/83.7,deletion 持平或上升,ambiguous 持平。

* fix(engine): 纠错上下文消解在任一整句来源开启时生效

上下文消解只认 word_lattice 开关。只开万象重排、不显示 Trigram 整句时(词格在内部
照样解码),整段被跳过,shiideya 退回静态排序,「是爹呀」又领衔。

- 新增 SentenceAssociationOptions::any_sentence_source,门控改看它;全关时仍是纯
  静态排序,#592 的「关联关闭 = 静态路径」不变
- 贵档胜者没有整词也没有整句行时(重排模型未载入),由它的最长前缀词领衔,不让
  主切的行顶回首位
2026-10-05 01:41:37 +08:00
Lu Fan(SonnyCalcr) adde5c188c feat(engine): 合法输入的换位手误按整句打分纠错 (#622)
ziazheliya、jioa、liazheli、nia 能整串切成合法音节(zi'a'zhe'li'ya、ji'o'a),
纠错入口按「切不成合法音节」设闸,原本完全不进纠错。

- legal_input_transposition_cuts 列出合法输入上的换位读法:纠正段须盖住字面
  切分里被挤出的非首位零声母音节(排除 dao'lu 读成「大楼」这类跨边界重切),
  另有罕见音节小表(lia->lai、dia->dai),只用于多音节输入
- arbitrate_legal_corrections 按噪声信道整句打分:词格最佳路径分 + 每处换位
  -2.0,不看整句候选显示开关;纠错领衔时原读法固定第 2 位,原读法领衔时纠错
  在 3.0 分差内占第 2 位,否则不出
- 字面整词保住首位:候选里有读音字母与输入相同的词库词时,改写字母的读法
  (换位纠错,以及已有的辅音复制别名 yongan -> 勇敢)最多排第 2
- 纠错领衔时预编辑按领衔读法分隔(zia'zhe'li'ya)
2026-10-04 23:54:18 +08:00
Lu Fan(SonnyCalcr) 5208753ce8 feat: 句中辅助码新增大写字母触发方式 (#620)
* feat(engine): add uppercase trigger for shuangpin mid-sentence helpcode

With the uppercase trigger on, an uppercase letter right after a complete
two-key syllable opens a mid-sentence helpcode block by itself, as if the
backtick had been typed before it: ulpbXihfa is ulpb`xihfa, and a following
uppercase letter is still the second code (ulpbXYih is ulpb`xYih). The raw
input keeps the typed letters; the block rules live in
contracts/mid_sentence_helpcode.h so TSF, Server and engine parse alike,
including the semicolon final, which no longer counts uppercase blocks.

The scheme now hands the parsed layout (source index and decorations) to the
request, so selection advance and preedit no longer re-parse the raw input.
Adds the MidSentenceHelpcodeUppercaseChanged worker opcode (31).

* feat(windows): count uppercase helpcode blocks before backtick and semicolon

With MidSentenceHelpcodeUppercaseChanged on, TSF uses the shared block rules
when deciding whether '`' opens a block and whether ';' is the ing final, in
both the synchronous and the deferred key classification.

* feat(server): wire the mid-sentence helpcode uppercase trigger

New helpcode.shuangpin_mid_sentence_helpcode_uppercase (default off) in both
config templates, both settings hosts and the settings page as a third
trigger checkbox. It takes effect only with mid-sentence helpcode on and
direct helpcode off; the Server applies it to the semicolon final rule and
broadcasts MidSentenceHelpcodeUppercaseChanged to TSF.
2026-10-04 21:00:41 +08:00
Lu Fan(SonnyCalcr) 22fb1e2569 Merge pull request #619 from fanlusky/feat/shuangpin-direct-helpcode
feat: 双拼直接辅助码(万象式,无需引导键)
2026-10-04 19:32:10 +08:00
luojiyin 7e2d9990a5 perf(engine): use AVX2 for vocabulary projection
Pack active hidden rows before the tied vocabulary projection.

Reuse the existing int8 AVX2 kernel when the CPU supports it.
Keep the original scalar path for other CPUs and float weights.
The projection and complete scoring both run faster with locked models.
2026-10-04 16:16:30 +08:00
fanlusky a219e499f6 fix(engine): 全拼纠错上下文接管让位于用户在主切读音上的选择
加载 sc.lm 时词格路径分里的词库权重只剩 dictionary_tiebreak(≤0.07)和
reading_prior,调频改的权重翻不过 1.0 的接管边际:banzhng 的用户每次都选
班长,办证系照样每次领衔,调频在这组词上被架空。

接管前加一道判据:主切键在用户日志里有 upsert,且主切整键首位权重不低于
胜出切分首位(即用户已把主切读音选到前面),就不接管、走按权重排序的静态
合并。出货词频本身仍交给上下文裁决;日志只在边际达标后查一次。

测试用手写 ARPA 作 sc.lm,让 LM 稳定偏向办证,覆盖无用户记录照常接管、
用户选过班长不再接管、用户记录未越过胜者照常接管三种情形。
2026-10-04 14:35:50 +08:00
Jia Sui 7e1ae10da2 docs(engine): 更正生成式空间可达性论断,删除对用户不可见的错误结论 2026-10-04 05:21:45 +08:00
Jia Sui 3a0d95e143 test(engine,server): 对齐生成式纠错空间的类型位可达性断言 2026-10-04 04:25:25 +08:00
Jia Sui bbebd34d22 docs(engine): 补上下文重排切分与用例打分的前提说明
R2 的分层判据是 canonical_pinyin == winner_key。原注释只说明了「整句与词格行
同键」,没解释为什么整键档**必然**是 winner_key。而 query_single_cut_keyed 在精确
键查空时会退到前缀区间扫描,那类行的 canonical 是别的读音(gun'qi 捞出 gun'qiu),
放宽判据就会静默把同档整词沉到末尾——正是本任务在修的那类回归。补上这条不变量:
decode_pool 由 query_exact_segmentations_keyed_flat 只收精确键构造,词库里没有整词的
切分进不了池,这条路在胜出切分上不可达。

测试侧补上「fixture 无 sc.lm ⇒ 启发式打分 ⇒ 用例确定」这个前提(同组 ranking fixture
已有,新用例依赖同一前提却没写)。
2026-10-04 03:45:53 +08:00
Jia Sui e1331ff1dc fix(engine): 恢复上下文重排的「整键在前、前缀在后」分层
胜出切分接管后用 query_series(raw_input, winner_key, winner_segments) 取它的结果,
但该函数的 count 从整键递减到 1,返回的不只是整键行,还有首音节的全部单字;而
rest(班长、搬账这些同键位整词)在这之后才append。

结果是办证/辩证/整句/班/办/半/般…/班长——胜出切分的单字插到同键位整词之前,把它们
整组挤出首页,PR 描述里「同键位纠错读法整组下移」并不成立。

按 canonical_pinyin == winner_key 把前缀行摘出来,恢复 merge_alternative_segmentations
的既有分层(merged_full 在前、前缀作为 remaining 追加):整键块、rest、前缀块。分类
键选 canonical_pinyin 而非 pinyin——后者承载用户键入码(可能是简拼);整句与词格行
同样落在整键块内,因为 append_ime_fallback 用完整 segmentation、词格 select_distinct
用 path->key 填 canonical_pinyin。

补判别性用例:原 fixture 缺 tbl_1_b,前缀这条路根本没被走到。已实测——撤掉本修复
时该用例失败(Same-tier whole words must not be pushed behind the winner cut's
single-character prefixes.),装回后通过。
2026-10-04 03:45:53 +08:00
Jia Sui 044352d49a fix(engine): 上下文重排的预过滤基准改用正读整键首位权重
1/100 预过滤的基准取 result.front(),注释声称与merge_alternative_segmentations
的 primary_top_weight 同源,实际不是:merge 取的是 primary_full(query_single_path
的整键结果),无整键行时为 0;而这里的 result 是 query_series 的完整输出。

正读在词库里没有整词时,result.front() 是一个前缀单字——真实词库里单字是 1e6+
的语料计数——于是 5000*100 < 1e6,正常备选在解码之前就被筛掉。而这恰恰是重排价值
最高的场景:正读最弱、词格只能用单字拼出路径时,正是上下文裁决该出手的时候。

改为复用 merge 的同一调用(query_single_path + 同一实参),走缓存不额外查库,
两处门槛从此不会各自漂移。

该缺陷无法用行为测试判别:正读无整词时 merge 的merged_full 只剩备选整键行,
备选本就居首,重排发生与否结果相同;构造分歧需要「词格胜者≠ 权重排序首位」,
而启发式 ln(w)+3.0×音节数 下同音节数时权重恒占优。故作为一致性修复保留。
2026-10-04 03:45:53 +08:00
Jia Sui 7d83aed411 docs(engine): 说明 wake 谓词依赖的跨表不变量
wake_ 的谓词按模型判空槽、不比键,成立的前提是 pending_ 与 active_keys_ 不可能
同时持有同一个模型:待办是在 worker 取走的那一刻才从表里 erase 的,在那之前同一
模型的键只挂在 active_keys_ 上。

这个不变量此前没有文字说明。谁哪天在 order_for 里改成「在算的也照样排一份」
(在 42aeda08 之前看着像无害的加固),谓词会把正在算的那批重排一遍——正是该字段
引入前要消灭的重复计算。纯注释,行为不变。
2026-10-04 03:45:53 +08:00