Two-thumb experimental build (upstream v4.1.2 merged, installs side by side) - #141
Merged
Conversation
…ck harness (#135) Research outcome for "make two-thumb typing work WITH the native gesture decoder". Findings in docs/TWO_THUMB_TEMPORAL_ALIGNMENT.md. The AOSP decoder models TWO per-pointer tracks (MAX_POINTER_COUNT_G == 2) and can spell a word by alternating between them. We have never used the second one: InputPointers.appendAll hardcodes pointer id 0, so every merged multi-part trail lands in track 0 as one long glide with a synthetic connector. Measured against the real AOSP ProximityInfoState on the host (new harness): - track membership is decided purely by pointer id; shifting or overlapping timestamps moves zero points between tracks, which falsifies "temporally shift the strokes so the library sees them as simultaneous" as a mechanism; - deliberate overlap drives speed rates negative (impossible from real input), i.e. it actively corrupts the features fed to the closed weighting policy; - global time monotonicity IS required: refreshSpeedRates walks raw indices across the pointer boundary, so per-stroke clocks that restart go negative. Bug fixed: if no point carries id 0, Suggest::initializeSearch early-returns and the gesture produces zero suggestions. Reachable in ordinary two-thumb use (thumb A down, thumb B down, thumb A lifts, thumb B swipes on with id 1). Any id >= 2 reaches no track at all. PointerIdNormalizer renumbers raw MotionEvent pointer ids in first-seen order at the BatchInputArbiter seam; it is the identity mapping for normal input and only repairs the broken cases. - PointerIdNormalizer + 7 JVM unit tests - two_pointer_track_test.cpp: tunable pointer-id / time-policy / tap-promotion knobs and a printed sweep table; runs in CI with the existing native suite - native host suite 83/83; JVM suite unchanged vs baseline (only the 4 documented Windows-only ParserTest failures) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…135) Acts on the research in docs/TWO_THUMB_TEMPORAL_ALIGNMENT.md: the native decoder keeps two per-pointer tracks and we have only ever used one, because InputPointers.appendAll hardcoded pointer id 0 for the whole merged trail. StrokeAligner is the merge seam WordComposer.setBatchInputPointers now delegates to. Two modes: CONNECTOR (default) everything on track 0 — the historical behaviour, reproduced byte-for-byte including the 25/60 ms base re-timing. DUAL_POINTER prior fragments on track 0, the in-flight stroke on track 1, so the decoder's own two-pointer search runs and there is no synthetic connector to hallucinate letters across. The class enforces the four constraints the research established: track 0 is always non-empty (an empty track 0 makes Suggest::initializeSearch return zero suggestions), only ids 0 and 1 are emitted, timestamps stay globally monotonic, and a given raw index never changes track mid-gesture. IdealPrefixTrailBuilder is ported from the stranded #99/B7b branch and is now reachable at runtime instead of via a side-by-side build type. It traces the composing prefix through key centres and promotes a one-letter prefix to a four-point micro-stroke — the "turn taps into small swipes" half of the original hypothesis, which the research found sound. It iterates code points and returns null when any letter is not on the keyboard, so a hole in the synthetic path can never replace the raw trail. Four prefs on Two-Thumb Typing → Recognition, all defaulting to today's behaviour; the mode picker is hidden when no native gesture lib is present because the Java fallback ignores pointer ids entirely. Review fixes folded in: - CONNECTOR pins the timing knobs to 25/60 so tuning them in dual mode and switching back cannot silently change "one joined trail". - a current stroke that is ITSELF multi-pointer keeps its own ids instead of being flattened onto track 1, preserving genuine simultaneous two-thumb structure (new InputPointers.appendAllPreservingIds). - IdealPrefixTrailBuilder no longer silently drops unmappable letters. Tests: 31 new (StrokeAligner 19, IdealPrefixTrailBuilder 10, settings 2), including a proof that DUAL_POINTER changes pointer ids only — never geometry or timing — so the Java fallback engine cannot regress. Full JVM suite 356 tests with only the 4 documented Windows-only ParserTest failures; native host suite 83/83; assembleOfflineliteDebugNoMinify builds. Not yet verified on device: whether two tracks SCORE better than one merged trail is decided by the closed weighting policy and needs the A/B in §6a. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…build Merges the new dev (which now carries LeanBitLab v4.1.2) into the two-thumb work. Only CHANGELOG.md conflicted; the code auto-merged, since the two-thumb edits are additive at the points upstream touched. Adds an 'experimental' build type with applicationIdSuffix .exp and versionNameSuffix -exp, plus a src/experimental resource override for english_ime_name. It installs alongside the normal debug build instead of replacing it, and shows up as 'LeanTypeDual EXP' in the input-method picker. Experimental input changes have to be A/B'd against a working daily driver, and that is impossible if installing one uninstalls the other. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
This was referenced Aug 20, 2026
Closed
…ental # Conflicts: # CHANGELOG.md
Reported as 'IDK if it does anything'. Two stacked reasons, neither visible from the UI: 1. The recognition settings synthesise points for the native gesture decoder, and .exp is a separate package with its own files/ directory, so it had no user-supplied libjni_latinime.so and sHaveNativeGestureLib was false. The mode picker hid itself, while PREF_STROKE_IDEAL_PREFIX was NOT gated and fed synthesised points to the Java fallback engine, which scores a single trail and ignores pointer ids - producing nonsense words. 2. Even with the library, StrokeAligner is only reached from WordComposer.setBatchInputPointers during multi-part composition, which is armed by the spacing mode (manual spacing, or a non-zero combining grace). At the default spacing mode the code never runs, so changing the mode picker legitimately does nothing. Gates the whole recognition group on a loaded gesture library, and shows an explanation above it when the spacing mode leaves it inert, instead of rendering settings that silently cannot take effect. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
Merging dev after the release bump put the #135/#141 entries under [0.3.0], but 0.3.0 was tagged from main without them. Moved them to [Unreleased] and rewrote them to match what device testing found: both experimental recognition modes are off by default and not currently recommended, because with a user-supplied gesture library they produce incorrect words (#144). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Warning
DUAL_POINTERproduces nonsense in real use and should not be presented as a working feature. Typing "ambulance" repeatedly with it enabled yields garbage. Root cause in #144: the research measured the in-repo AOSP decoder, but the decoder that actually runs on any device where this setting is visible is a user-supplied closed library. Merge for the packaging, the bug fix and the settings gating — not for the dual-pointer mode, which stays off by default.Merges the new
dev(LeanBitLab v4.1.2, #137) into the two-thumb work from #136, and makes it installable side by side so it can be A/B tested.Supersedes #136 — same work, plus the upstream merge, the experimental packaging, and two rounds of fixes from actual device use.
Side-by-side packaging
New
experimentalbuild type:applicationIdSuffix = ".exp"→com.asafmah.leantypedual.expversionNameSuffix = "-exp"→0.2.0-expsrc/experimental/resoverridesenglish_ime_name→ "LeanTypeDual EXP"Installing it does not replace the daily driver. Experimental input changes can only be judged against a keyboard that works, and that's impossible if installing one uninstalls the other. The label override matters for the same reason — without it the two are indistinguishable in the picker.
Verified on device: both packages installed, both listed, active IME untouched.
What device use actually found
Three separate problems, all invisible from the UI, none caught by tests:
1. The recognition settings were inert and one was harmful.
.expis a separate package with its ownfiles/, so it had no user-suppliedlibjni_latinime.soandsHaveNativeGestureLibwas false. The mode picker hid itself — butPREF_STROKE_IDEAL_PREFIXwas not gated, so its synthesised key-centre points went into the Java fallback engine, which scores a single trail and ignores pointer ids. That produced nonsense words. Now the whole group is gated on a loaded library.2. Even with the library, the mode could not run.
StrokeAligneris only reached fromWordComposer.setBatchInputPointersduring multi-part composition, which is armed bymGestureManualSpacing || mCombiningGraceMs > 0— both at defaults. So changing the mode picker legitimately did nothing. The screen now says "Not active at this spacing mode" with the reason, instead of showing settings that structurally cannot take effect.3. With everything correctly armed,
DUAL_POINTERstill produces nonsense. See #144. This is the finding that matters: the two-track premise came fromapp/src/main/jni/, butsHaveNativeGestureLibis true only when a user-supplied or system library is loaded — so the binary being measured was never the binary being used.What still stands
0returned zero suggestions, reachable in ordinary two-thumb use. Real Java-side defect, fixed, unaffected by DUAL_POINTER produces nonsense: the research measured the in-repo AOSP decoder, not the user-supplied library that actually runs #144.The merge
Only
CHANGELOG.mdconflicted across both dev merges; the code auto-merged, as #136 predicted.Verification
364 tests, 4 failed— the documented Windows-onlyParserTestbaseline, confirmed withtools/check_test_results.py(chore(tools): gate test-result analysis instead of trusting it #139).Recommendation
Merge for the packaging, the zero-suggestions fix and the settings honesty. Treat
DUAL_POINTERas a parked experiment behind its default-off setting until #144 establishes what the loaded library actually does with a second track.