Skip to content

Bounded first page pads with shorter-prefix characters instead of exact readings for common syllables #17

Open

Description

@laubonghaudoi

Status (2026-10-19)

The user-visible defect is fixed and live; open for oracle certification. #18 landed the bounded-page fix, and it has been in production since 2026-10-16 (dabf60c0). On main and on the live demo, all nine syllables below now show the same page 0 on the bounded and complete paths, with no shorter-prefix padding (for example fan → 分 份 婚 訓 粉 瞓, siu → 小 少 消 笑 燒 銷). The table below is the historical 2026-10-10 reproduction. Neither the pre-fix build nor the current build shows the duplicate 分 it lists, so compare captures against current output rather than against the table.

Remaining: Done-when item 1 (TypeDuck-HK librime v1.1.2 page-size-6 captures for the nine inputs) and the ratchet part of item 4. hou already matches the first six rows of the checked-in v1.1.2 fixtures (captured at page size 50), so eight captures remain.

Blocked by: None. #20 landed in 039708e2 on 2026-10-20. Assert the captures on the shipped compiled assets (table.bin plus prism), not only through cantonese_parity, which builds its engine from the source dictionary. #20 added the compiled-asset harness for this: typeduck_v112_unpatched_case and leading_candidate_texts in crates/yune-rime-api/tests/yune_web.rs, and the compiled engine in cantonese_parity. The oracle rows #31 needs can be captured in the same Windows session. The former "blocks #8" link was removed because item 5 is met.

Context

For the TypeDuck Jyutping profile the engine computes only the first page of candidates on each keystroke (the bounded refresh), and materializes the complete list only when the user pages forward. The shipped schema also sets combine_candidates: true, which merges rows that share the same text, and prefix_fallback: true, which supplies characters for a shorter prefix when the page would otherwise be short.

Problem

On a fresh profile with no learned rows, the bounded first page differs from the complete list's first page for about one in five common syllables. The bounded path truncates the exact-match rows to the page size first, then collapses duplicate texts, and fills the gap with prefix-fallback rows. The complete path keeps the next exact readings. Measured on 2026-10-10 against the shipped jyut6ping3_mobile assets through the CLI (fan typed alone versus fan{Page_Down}{Page_Up}), 9 of 41 syllables differ:

Input Bounded page 0 (what users see) Complete page 0
fan 分 份 分 婚 化 花 分 份 分 婚 訓 粉
hou 好 號 好 豪 毫 可 好 號 好 豪 毫 浩
man 民 問 民 文 聞 馬 民 問 民 文 聞 蚊
lei 你 理 利 呢 嚟 哩 你 理 利 呢 嚟 離
zung 中 中 仲 總 鐘 就 中 中 仲 總 鐘 終
dou 都 都 到 道 度 多 都 都 到 道 度 導
siu 小 小 少 消 笑 時 小 小 少 消 笑 燒
wui 會 會 回 匯 滙 護 會 會 回 匯 滙 迴
wan 運 雲 穩 揾 混 話 運 雲 穩 揾 混 温

The replaced characters are readings of a shorter prefix (faa, ho, maa, and so on), not of the typed syllable. Which page is correct must be pinned by the oracle (the reference TypeDuck-HK librime v1.1.2 build with the same schema), but a prefix-fallback row displacing an exact reading of the full syllable is not what either the complete path or librime produces, so the bounded page is the defect.

Mechanism, in crates/yune-core/src/translator/mod.rs: the bounded exact path calls order_lookup_candidate_refs(&mut selected, limit) (line 5471), which truncates to limit; combine_duplicate_text_candidates_with_full_input_anchor (called at line 5552) then collapses same-text rows with no backfill; the prefix-fallback insertion that follows fills the shortfall. The complete path never truncates before collapsing. The same under-fill is what stops #8's learned-phrase shortcut: with a learned row present, the bounded page for fan lands the learned row's anchor on a fallback row.

Approach

  • In the bounded exact path, collapse duplicates before truncating, or fetch limit + number of collapsed rows and truncate after the collapse, so the window always holds limit distinct exact rows when the complete list has them.
  • Do not touch the complete path, the prefix-fallback ordering, or merge_userdb_candidates.
  • Pin the behavior two ways: oracle fixtures for the nine inputs above captured from TypeDuck-HK librime v1.1.2 with the same schema (non-circular, per docs/conventions.md section 7), and a self-consistency sweep test on the production path (compact table plus prism) asserting bounded page 0 equals forced-complete page 0 for a list of at least the 41 syllables used here.

Done when

  • Oracle fixtures for the nine inputs exist and the bounded page matches them.
  • The sweep self-consistency test passes on the production path and fails on current main.
  • Existing Jyutping, Luna, and Cangjie parity fixtures pass unchanged.
  • Per-key latency for the swept syllables stays within the existing browser latency gate; the signed native ratchet is re-run by a maintainer before close.
  • Retyping a learned multi-syllable phrase stalls the engine for seconds #8's user-confirmed three-case comparison (cungfukhokzaap → 重複學習 via real commit/reload, explicitly seeded coengjamzitsyujapcaksi → 長音節輸入測試, and duplicate-heavy learned fan) shows identical pages on both paths, which unblocks Retyping a learned multi-syllable phrase stalls the engine for seconds #8.

How verified

cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo test -p yune-core --test cantonese_parity
cargo test -p yune-core --test upstream_luna_pinyin_parity
cargo test -p yune-rime-api --test yune_web
npm --prefix apps/yune-web/e2e run test:e2e:input-latency:public

Reproduce by hand: build cargo build --release -p yune-cli, then run the CLI twice against a fresh user directory with --schema jyut6ping3_mobile --shared-data-dir apps/yune-web/public/schema, once with --sequence "fan" and once with --sequence "fan{Page_Down}{Page_Up}", and compare the last event's candidates in the JSON output.

Background

Found while reviewing the attempt at #8 (comment of 2026-10-10), which hit this divergence on fan and stopped as instructed. The bounded refresh comes from the lazy-candidate work (M34) and the page-bounded TypeDuck refresh from the later performance milestones; see docs/history.md. No existing oracle fixture covers a syllable whose exact rows contain duplicate texts, which is why this never tripped a gate.

Activity

  1. added
    engineEngine core (crates/yune-core, yune-rime-api)
    parityBehavior or candidate order differs from the oracle
    on Oct 10, 2026
  2. github-actions commented on Oct 10, 2026

    @github-actions

    Thanks. Before this issue is picked up, please make sure it has a Type (Bug, Feature, or Task), Priority and Effort in the Fields panel (this check cannot read them). Labels describe the area and the kind of work; Type and Fields describe what it is and how it ranks.

  3. laubonghaudoi commented on Oct 12, 2026

    @laubonghaudoi
    MemberAuthor

    Landed on main: #18 (merge 0dc7354) keeps exact readings through the duplicate collapse, and the follow-up commit af629dc keeps the prediction row in the first-page merge. Verified before merge with cargo fmt, clippy -D warnings, and the full cargo test --workspace (28 targets, 1,188 passed, 0 failed, 13 ignored); the 41-syllable CLI sweep shows the bounded page equal to the complete page everywhere with the complete path unchanged.

    Still open on this issue, maintainer-owned: the TypeDuck v1.1.2 oracle rows for the nine syllables (recorded as the ignored blocked test typeduck_bounded_first_page_oracle_for_duplicate_text_syllables_is_blocked), the browser latency gate on an Emscripten host, and the signed Windows ratchet (its Track B row runs the new path).

  4. laubonghaudoi commented on Oct 13, 2026

    @laubonghaudoi
    MemberAuthor

    PR #22 adds the missing local browser latency sweep and records fresh verification. This issue remains open.

    The opt-in @issue17-sweep uses the existing production test’s exact 47 bare syllables / 155 keystrokes, jyut6ping3, page size 6, 250 ms cadence, 4× main-thread throttling and worker amplification. All 155 prefix diagnostics passed; every final page had six candidates; no page request occurred during measurement. Overall p95 was 65 ms, maximum 185 ms; the worst per-syllable p95 was 185 ms, within the unchanged 750/1000 ms ceilings.

    The real-asset bounded/complete sweep and #8’s learned-row comparisons (sikgaanei, ngohaihoenggongjan, fan) passed, along with all seven #8 Rust commands and the unchanged public browser latency gate. Raw sweep data stays outside Git and is bound to clean source 3a717929, the complete public artifact inventory, exact inputs, and toolchain. The verification receipt includes commands, hashes and failed-attempt provenance.

    This establishes local timing, page shape, and bounded/complete self-consistency. External oracle order is still unverified: typeduck_bounded_first_page_oracle_for_duplicate_text_syllables_is_blocked remains ignored until TypeDuck-HK/librime v1.1.2 page-size-6 rows exist for fan, hou, man, lei, zung, dou, siu, wui, and wan, followed by production-path comparisons.

    The source-bound Windows ratchet also remains pending: five complete runs and all 32 aggregate checks passing. Both #8 and #17 stay open. No deployment or WEB-06 work is included.

  5. laubonghaudoi commented on Oct 13, 2026

    @laubonghaudoi
    MemberAuthor

    PR #22’s natural-phrase fixture update has fresh source-bound results at 97810ddc: the unchanged opt-in sweep passed 47 syllables / 155 keys, p95 68 ms, max 197 ms, six-row final pages and zero timed requests. The existing eight-scenario public stress gate passed at p95 122 ms / max 157 ms.

    The learned page-comparison test now covers 重複學習, explicitly seeded 長音節輸入測試, and the retained learned fan regression; all bounded/complete pages matched. The longer phrase is an explicit fixture, not a fresh-translation or oracle assertion. Verification receipt preserves the exact inputs, commands, source bindings and raw-result hashes.

    Both #8 and #17 remain open. External TypeDuck v1.1.2 page-size-6 captures for the nine affected syllables, their production-path comparisons, and the source-bound Windows five-run / 32-check ratchet are still pending. WEB-06 remains deferred.

  6. laubonghaudoi commented on Oct 19, 2026

    @laubonghaudoi
    MemberAuthor

    Re-triaged on 2026-10-19:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    engineEngine core (crates/yune-core, yune-rime-api)parityBehavior or candidate order differs from the oracle

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions