feat(input): touch hit facts — bounds hits resolved by the host, carried per contact - #209
Merged
Conversation
…ied per contact The gesture layer used to ASK where a finger landed (an ink hitTest query on the down edge) and the answer had holes: ink misses on row gaps forced every list to hand the framework its own screen geometry (VirtualList.touchRect — an app feeding the runtime numbers the runtime already owns). Hits are now FACTS the host delivers, and they resolve by layout box, not ink: - spec: op 42 hitTestBounds (the bounds-only hit twin — pure layout containers claim their box, UIKit semantics) and prop 32 hitPass (a node's own box never claims, descendants still tested — pointer-events:none, self-only). Frame contract: argument 4 `hits`, parallel to `touches`. - core: draw::hit_test grows a bounds mode sharing the entire walk (paint order, clips, transforms, opacity culling); Ui::touch_hits keeps the 8-slot contact-id -> hit-at-down capture table — a NEW contact is resolved ONCE against the committed frame the user was looking at and carried until it lifts. The framework marks its overlay/portal layer hitPass so bounds facts resolve THROUGH empty overlay space to app content (found the hard way: the OSK journey went dead when the full-screen layer swallowed every contact). - hosts: vita frame_with_input resolves via the core table and passes arg 4; wasm exports ui_hit_test_bounds; the sim and the golden oracle carry a TS twin of the table (framework/src/touch.ts createTouchHitFacts). - framework: TouchContact/GestureContact grow `hit` (the down fact); resolveTouchHit (input.ts) is the one touch-hit authority — fact first, then op 42, then op 27 (stale hosts), then region rects. VirtualList drops touchRect entirely: rows resolve by node identity, and a row-gap tap is a separator tap (it doesn't press). Devtools replay drops live hits so replayed contacts re-resolve deterministically through the query path. The guest now issues ZERO hit queries on the live touch path. Validated: core 110 (bounds/carry/decode/hitPass new), contract byte-equal, gesture+touch 23, virtual-list 16 (incl. the query-fallback case), im-sim 11 (the OSK tap journey), canonical chain, 52 web goldens byte-exact, Vita3K e2e 52/52 byte-exact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
doodlewind
marked this pull request as ready for review
August 3, 2026 07:45
This was referenced Aug 3, 2026
doodlewind
added a commit
that referenced
this pull request
Aug 3, 2026
…ardware bug (#213) Every touch on real Vita hardware resolved to ONE node — the TextField's Portal host — killing list pans and field taps while the OSK (modally exempt) kept working. The host is an explicit full-screen absolute box in the overlay layer; #209 marked the overlay ROOT hitPass but every portal host inside it was another full-screen pure container that still claimed every bounds hit. Why no rig caught it: the pocket-youtube sim's vendored wasm was STALE (predating op 42), so its journeys silently exercised the ink-query FALLBACK, not the fact path; the Vita3K suite's only touch spec drove OSK keys — overlay content, immune. The on-device svc diagnostic (every touch-down reporting x,y,hit) showed the constant node id across the whole screen; rebuilding the vendor wasm reproduced it in the sim in one probe. - Portal host + the TextField dock wrapper: hitPass (pure plumbing never claims; a modal scrim is portal CONTENT and keeps blocking). - tests/portal-hit.test.ts: the REAL-core mount test mock hosts cannot express — a mounted empty-chrome Portal must not swallow bounds hits (fails without the fix, registered in the chain). - golden-specs: im-rowtap made permanent — app-layer tap-through-the-stack coverage on BOTH oracles (web + Vita3K), the hole this bug lived in. Validated: canonical chain 0 fail, 54 web goldens, im-rowtap + im-touch Vita3K byte-exact. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 5, 2026
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.
Phase 2 of the touch program, change A (design context: the gesture layer review that closed #177).
What changes
The guest no longer asks where a finger landed — the host tells it.
hitTest(op 27) on the down edgeframe()arg 4, parallel totouches)VirtualList.touchRectgeometry workaroundhitTestBounds): a finger in a row gap still owns the list;touchRectdeletedhitPassprop (32): the layer's own box never claims, its portal content still doesdraw::hit_testbounds mode (shares the whole walk),Ui::touch_hits8-slot capture table + testsframe_with_input, wasmui_hit_test_bounds, sim/golden-oracle TS table twinTouchContact.hit/GestureContact.hit,resolveTouchHitfallback chain (fact → op 42 → op 27 → rects), devtools replay re-resolves deterministicallyThe bug the design caught
First full run: every OSK tap journey went dead — the framework's full-screen overlay layer, a pure layout box, swallowed every bounds hit. That's the honest cost of bounds semantics, and
hitPassis its principled fix (pointer-events:none, self-only, typed and local). The im-sim OSK journey now pins it forever.Validation
core 110 · contract byte-equal · gesture+touch 23 · virtual-list 16 · im-sim 11 · canonical chain · 52 web goldens byte-exact · Vita3K e2e 52/52 byte-exact — the fact channel changes zero pinned behavior; it removes the query and the workaround.
🤖 Generated with Claude Code