Two bugs made the search jump land in the wrong place:
1. Double-counted visual lines. The visual line at which logical line li
starts is exactly VisualsBefore(li); the code computed li +
VisualsBefore(li), i.e. 2x the intended depth with the all-ones
pre-shape estimates. The MaxScroll clamp masked it on small files;
long files landed far off. Now uses VisualsBefore(li) (fallback li).
2. Truncation vs non-integer line height. Line height is 16.8dp, so
floor(V*lh) sits just above the target line's top and the window
decomposition floors to the line above it. The target now rounds UP:
ceil(V*lh) is always in [V*lh, (V+1)*lh), so the viewport top
decomposes to exactly V. The line-height source now also prefers the
shaped GlyphLayout.LineHeight like scrollVisualDecompose/MaxScroll.
3. Estimate settle. On long wrapped files the lines above the target are
still estimated at 1 visual line when the jump happens, so the landing
can be short. The scroll now arms a bounded settle (SettleByte /
SettleScroll / SettlePasses on FindState); after each layout-feedback
wrap-count correction the logic goroutine re-runs the target and
re-scrolls until it converges, is exhausted (4 passes), or the user /
the MaxScroll clamp moves the viewport (which cancels it). Edits,
query changes, close, and file open disarm the settle.
Tests: round-trip scroll-target on a 2000-line wrapped file, the
no-wrap identity, settle correction/convergence, and settle cancellation
on user scroll.