FA-8231 / Direct manipulation / Open access
Pointer-capture ownership: A second touch steals an active drag · case 01
A second touch steals an active drag.
ROOT CAUSE
The second pointer operation uses `pointer` where the contract requires `pointer if owner is None else owner`.
VERIFIED REPAIR
Implement the second pointer operation as `pointer if owner is None else owner`.
Unsuccessful approach: Always retaining owner never captures the initial pointer.
Case contract
First pointer captures ownership; only its movement and release affect a drag; cancellation and capture loss clear ownership while preserving position.
Why this case matters
A deterministic model of pointer-capture ownership; this isolates one interface invariant without requiring a browser.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(owner, position, event, pointer, delta):
if event == 'down': return (pointer, position)
if event == 'move' and pointer == owner: return (owner, position + delta)
if event == 'up' and pointer == owner: return (None, position)
if event == 'cancel' and pointer == owner: return (None, position)
if event == 'lost': return (None, position)
return (owner, position)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('initial down', solve(None, 4, 'down', 1, 0), (1, 4))
check('second down', solve(1, 4, 'down', 2, 0), (1, 4))
check('other move', solve(1, 4, 'move', 2, 3), (1, 4))
check('owner move', solve(1, 4, 'move', 1, 3), (1, 7))
check('other up', solve(1, 4, 'up', 2, 0), (1, 4))
check('owner up', solve(1, 4, 'up', 1, 0), (None, 4))
check('cancel', solve(1, 4, 'cancel', 1, 0), (None, 4))
check('lost', solve(1, 4, 'lost', 2, 0), (None, 4))
check('parameterized pointer ownership', solve(N, N, 'move', N, N), (N,2*N))
for repetition in range(N):
check('repeat lost', solve(1, 4, 'lost', 2, 0), (None, 4))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| initial down | [1, 4] | [1, 4] | Passed |
| second down | [2, 4] | [1, 4] | Failed |
| other move | [1, 4] | [1, 4] | Passed |
| owner move | [1, 7] | [1, 7] | Passed |
| other up | [1, 4] | [1, 4] | Passed |
| owner up | [None, 4] | [None, 4] | Passed |
| cancel | [None, 4] | [None, 4] | Passed |
| lost | [None, 4] | [None, 4] | Passed |
| parameterized pointer ownership | [1, 2] | [1, 2] | Passed |
| repeat lost | [None, 4] | [None, 4] | Passed |
SHA-256 / 676eae6aea650a260e40b5871d6c7cd563a03ffcb4be5236d1797e16d077f64c
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(owner, position, event, pointer, delta):
if event == 'down': return (owner, position)
if event == 'move' and pointer == owner: return (owner, position + delta)
if event == 'up' and pointer == owner: return (None, position)
if event == 'cancel' and pointer == owner: return (None, position)
if event == 'lost': return (None, position)
return (owner, position)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('initial down', solve(None, 4, 'down', 1, 0), (1, 4))
check('second down', solve(1, 4, 'down', 2, 0), (1, 4))
check('other move', solve(1, 4, 'move', 2, 3), (1, 4))
check('owner move', solve(1, 4, 'move', 1, 3), (1, 7))
check('other up', solve(1, 4, 'up', 2, 0), (1, 4))
check('owner up', solve(1, 4, 'up', 1, 0), (None, 4))
check('cancel', solve(1, 4, 'cancel', 1, 0), (None, 4))
check('lost', solve(1, 4, 'lost', 2, 0), (None, 4))
check('parameterized pointer ownership', solve(N, N, 'move', N, N), (N,2*N))
for repetition in range(N):
check('repeat lost', solve(1, 4, 'lost', 2, 0), (None, 4))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| initial down | [None, 4] | [1, 4] | Failed |
| second down | [1, 4] | [1, 4] | Passed |
| other move | [1, 4] | [1, 4] | Passed |
| owner move | [1, 7] | [1, 7] | Passed |
| other up | [1, 4] | [1, 4] | Passed |
| owner up | [None, 4] | [None, 4] | Passed |
| cancel | [None, 4] | [None, 4] | Passed |
| lost | [None, 4] | [None, 4] | Passed |
| parameterized pointer ownership | [1, 2] | [1, 2] | Passed |
| repeat lost | [None, 4] | [None, 4] | Passed |
SHA-256 / 8d31798b1f605d0cecfeb036e8ef27da176dc7cffdfcb6792ffca44872d4d812
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(owner, position, event, pointer, delta):
if event == 'down': return (pointer if owner is None else owner, position)
if event == 'move' and pointer == owner: return (owner, position + delta)
if event == 'up' and pointer == owner: return (None, position)
if event == 'cancel' and pointer == owner: return (None, position)
if event == 'lost': return (None, position)
return (owner, position)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('initial down', solve(None, 4, 'down', 1, 0), (1, 4))
check('second down', solve(1, 4, 'down', 2, 0), (1, 4))
check('other move', solve(1, 4, 'move', 2, 3), (1, 4))
check('owner move', solve(1, 4, 'move', 1, 3), (1, 7))
check('other up', solve(1, 4, 'up', 2, 0), (1, 4))
check('owner up', solve(1, 4, 'up', 1, 0), (None, 4))
check('cancel', solve(1, 4, 'cancel', 1, 0), (None, 4))
check('lost', solve(1, 4, 'lost', 2, 0), (None, 4))
check('parameterized pointer ownership', solve(N, N, 'move', N, N), (N,2*N))
for repetition in range(N):
check('repeat lost', solve(1, 4, 'lost', 2, 0), (None, 4))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| initial down | [1, 4] | [1, 4] | Passed |
| second down | [1, 4] | [1, 4] | Passed |
| other move | [1, 4] | [1, 4] | Passed |
| owner move | [1, 7] | [1, 7] | Passed |
| other up | [1, 4] | [1, 4] | Passed |
| owner up | [None, 4] | [None, 4] | Passed |
| cancel | [None, 4] | [None, 4] | Passed |
| lost | [None, 4] | [None, 4] | Passed |
| parameterized pointer ownership | [1, 2] | [1, 2] | Passed |
| repeat lost | [None, 4] | [None, 4] | Passed |
SHA-256 / b57d2a364c43b5fad81f31ca1ee3ae6da82ece5397810eca5948879bc52cd522
Verification & scope
This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:38:19.559329+00:00.
Case digest / 4cc434d1eeae7e15aa5d43362c58e46db6f50ed47f66773cb48a6d5ffff46e3a