FAILURE MAP
← Case archive

FA-31156 / Keyboard interactions / Open access

Per-key double tap recognizer: Tap accounting replaces its cumulative count or uses remaining held keys · case 01

The event trace violates the tap count rule and produces incorrect keyboard state or command output.

Verified by executionVariant 1 · 11 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Tap accounting replaces its cumulative count or uses remaining held keys.

VERIFIED REPAIR

Use the contract transition `taps+=1` at the tap count fault site; preserve the other state transitions.

Unsuccessful approach: The partial repair changes this transition to taps+=len(held), which still violates the model contract on the explicit regression traces.

Case contract

Case [window,events] with nondecreasing timestamps and events [kind,key,time]. Down repeats do not count. A matched up produces tap and may complete a double if the prior release of the same key is within the inclusive window. Completing a double consumes that prior release. Cancel clears that key, blur clears all state. Return doubles [key,time], held keys and remembered release times. Inputs are finite ordered event traces; return the stated deterministic state. Batch entries are independent. N varies the number of independent input transactions.

Why this case matters

Controlled keyboard event processing model for debugging application event logic.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(cases):
    def run(c):
        window,events=c
        held=set(); last={}; doubles=[]; taps=0
        for kind,key,now in events:
            if kind=='down': held.add(key)
            elif kind=='up' and key in held:
                held.discard(key)
                taps=1
                previous=last.get(key)
                if previous is not None and now-previous<=window:
                    doubles.append([key,now])
                    last.pop(key,None)
                else:
                    last[key]=now
            elif kind=='cancel':
                held.discard(key); last.pop(key,None)
            elif kind=='blur':
                held.clear(); last.clear()
        return [doubles,sorted(held),sorted([[k,v] for k,v in last.items()]),taps]
    return [run(c) for c in cases]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('double-tap scenario 0', solve([[4, []]] * N), [[[], [], [], 0]] * N)
check('double-tap scenario 1', solve([[4, [['down', 'A', 0], ['up', 'B', 1]]]] * N), [[[], ['A'], [], 0]] * N)
check('double-tap scenario 2', solve([[4, [['up', 'A', 1]]]] * N), [[[], [], [], 0]] * N)
check('double-tap scenario 3', solve([[4, [['down', 'A', 0], ['up', 'A', 0], ['down', 'A', 2], ['up', 'A', 4]]]] * N), [[[['A', 4]], [], [], 2]] * N)
check('double-tap scenario 4', solve([[4, [['down', 'A', 10], ['up', 'A', 11], ['down', 'B', 12], ['up', 'B', 13]]]] * N), [[[], [], [['A', 11], ['B', 13]], 2]] * N)
check('double-tap scenario 5', solve([[4, [['down', 'A', 1], ['down', 'A', 1], ['up', 'A', 2]]]] * N), [[[], [], [['A', 2]], 1]] * N)
check('double-tap scenario 6', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'A', 10], ['up', 'A', 11], ['down', 'A', 12], ['up', 'A', 13]]]] * N), [[[['A', 13]], [], [], 3]] * N)
check('double-tap scenario 7', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'A', 2], ['up', 'A', 3], ['down', 'A', 4], ['up', 'A', 5]]]] * N), [[[['A', 3]], [], [['A', 5]], 3]] * N)
check('double-tap scenario 8', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'B', 2], ['up', 'B', 3], ['down', 'A', 3], ['up', 'A', 4]]]] * N), [[[['A', 4]], [], [['B', 3]], 3]] * N)
check('double-tap scenario 9', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'B', 2], ['blur', '', 3]]]] * N), [[[], [], [], 1]] * N)
check('double-tap scenario 10', solve([[4, [['down', 'A', 0], ['down', 'B', 1], ['up', 'A', 2]]]] * N), [[[], ['B'], [['A', 2]], 1]] * N)
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 fixtureActualExpectedOutcome
double-tap scenario 0[[[], [], [], 0]][[[], [], [], 0]]Passed
double-tap scenario 1[[[], ['A'], [], 0]][[[], ['A'], [], 0]]Passed
double-tap scenario 2[[[], [], [], 0]][[[], [], [], 0]]Passed
double-tap scenario 3[[[['A', 4]], [], [], 1]][[[['A', 4]], [], [], 2]]Failed
double-tap scenario 4[[[], [], [['A', 11], ['B', 13]], 1]][[[], [], [['A', 11], ['B', 13]], 2]]Failed
double-tap scenario 5[[[], [], [['A', 2]], 1]][[[], [], [['A', 2]], 1]]Passed
double-tap scenario 6[[[['A', 13]], [], [], 1]][[[['A', 13]], [], [], 3]]Failed
double-tap scenario 7[[[['A', 3]], [], [['A', 5]], 1]][[[['A', 3]], [], [['A', 5]], 3]]Failed
double-tap scenario 8[[[['A', 4]], [], [['B', 3]], 1]][[[['A', 4]], [], [['B', 3]], 3]]Failed
double-tap scenario 9[[[], [], [], 1]][[[], [], [], 1]]Passed
double-tap scenario 10[[[], ['B'], [['A', 2]], 1]][[[], ['B'], [['A', 2]], 1]]Passed

SHA-256 / 97c9c7ad6022e8751fa25276dae9856ccff607a7563b4c34eec90a4b3aefb48a

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(cases):
    def run(c):
        window,events=c
        held=set(); last={}; doubles=[]; taps=0
        for kind,key,now in events:
            if kind=='down': held.add(key)
            elif kind=='up' and key in held:
                held.discard(key)
                taps+=len(held)
                previous=last.get(key)
                if previous is not None and now-previous<=window:
                    doubles.append([key,now])
                    last.pop(key,None)
                else:
                    last[key]=now
            elif kind=='cancel':
                held.discard(key); last.pop(key,None)
            elif kind=='blur':
                held.clear(); last.clear()
        return [doubles,sorted(held),sorted([[k,v] for k,v in last.items()]),taps]
    return [run(c) for c in cases]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('double-tap scenario 0', solve([[4, []]] * N), [[[], [], [], 0]] * N)
check('double-tap scenario 1', solve([[4, [['down', 'A', 0], ['up', 'B', 1]]]] * N), [[[], ['A'], [], 0]] * N)
check('double-tap scenario 2', solve([[4, [['up', 'A', 1]]]] * N), [[[], [], [], 0]] * N)
check('double-tap scenario 3', solve([[4, [['down', 'A', 0], ['up', 'A', 0], ['down', 'A', 2], ['up', 'A', 4]]]] * N), [[[['A', 4]], [], [], 2]] * N)
check('double-tap scenario 4', solve([[4, [['down', 'A', 10], ['up', 'A', 11], ['down', 'B', 12], ['up', 'B', 13]]]] * N), [[[], [], [['A', 11], ['B', 13]], 2]] * N)
check('double-tap scenario 5', solve([[4, [['down', 'A', 1], ['down', 'A', 1], ['up', 'A', 2]]]] * N), [[[], [], [['A', 2]], 1]] * N)
check('double-tap scenario 6', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'A', 10], ['up', 'A', 11], ['down', 'A', 12], ['up', 'A', 13]]]] * N), [[[['A', 13]], [], [], 3]] * N)
check('double-tap scenario 7', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'A', 2], ['up', 'A', 3], ['down', 'A', 4], ['up', 'A', 5]]]] * N), [[[['A', 3]], [], [['A', 5]], 3]] * N)
check('double-tap scenario 8', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'B', 2], ['up', 'B', 3], ['down', 'A', 3], ['up', 'A', 4]]]] * N), [[[['A', 4]], [], [['B', 3]], 3]] * N)
check('double-tap scenario 9', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'B', 2], ['blur', '', 3]]]] * N), [[[], [], [], 1]] * N)
check('double-tap scenario 10', solve([[4, [['down', 'A', 0], ['down', 'B', 1], ['up', 'A', 2]]]] * N), [[[], ['B'], [['A', 2]], 1]] * N)
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 fixtureActualExpectedOutcome
double-tap scenario 0[[[], [], [], 0]][[[], [], [], 0]]Passed
double-tap scenario 1[[[], ['A'], [], 0]][[[], ['A'], [], 0]]Passed
double-tap scenario 2[[[], [], [], 0]][[[], [], [], 0]]Passed
double-tap scenario 3[[[['A', 4]], [], [], 0]][[[['A', 4]], [], [], 2]]Failed
double-tap scenario 4[[[], [], [['A', 11], ['B', 13]], 0]][[[], [], [['A', 11], ['B', 13]], 2]]Failed
double-tap scenario 5[[[], [], [['A', 2]], 0]][[[], [], [['A', 2]], 1]]Failed
double-tap scenario 6[[[['A', 13]], [], [], 0]][[[['A', 13]], [], [], 3]]Failed
double-tap scenario 7[[[['A', 3]], [], [['A', 5]], 0]][[[['A', 3]], [], [['A', 5]], 3]]Failed
double-tap scenario 8[[[['A', 4]], [], [['B', 3]], 0]][[[['A', 4]], [], [['B', 3]], 3]]Failed
double-tap scenario 9[[[], [], [], 0]][[[], [], [], 1]]Failed
double-tap scenario 10[[[], ['B'], [['A', 2]], 1]][[[], ['B'], [['A', 2]], 1]]Passed

SHA-256 / 17fdae9b275c29a5d78d9f376fdd8d14273d7bdb3313e53d464a122010e50c87

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(cases):
    def run(c):
        window,events=c
        held=set(); last={}; doubles=[]; taps=0
        for kind,key,now in events:
            if kind=='down': held.add(key)
            elif kind=='up' and key in held:
                held.discard(key)
                taps+=1
                previous=last.get(key)
                if previous is not None and now-previous<=window:
                    doubles.append([key,now])
                    last.pop(key,None)
                else:
                    last[key]=now
            elif kind=='cancel':
                held.discard(key); last.pop(key,None)
            elif kind=='blur':
                held.clear(); last.clear()
        return [doubles,sorted(held),sorted([[k,v] for k,v in last.items()]),taps]
    return [run(c) for c in cases]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('double-tap scenario 0', solve([[4, []]] * N), [[[], [], [], 0]] * N)
check('double-tap scenario 1', solve([[4, [['down', 'A', 0], ['up', 'B', 1]]]] * N), [[[], ['A'], [], 0]] * N)
check('double-tap scenario 2', solve([[4, [['up', 'A', 1]]]] * N), [[[], [], [], 0]] * N)
check('double-tap scenario 3', solve([[4, [['down', 'A', 0], ['up', 'A', 0], ['down', 'A', 2], ['up', 'A', 4]]]] * N), [[[['A', 4]], [], [], 2]] * N)
check('double-tap scenario 4', solve([[4, [['down', 'A', 10], ['up', 'A', 11], ['down', 'B', 12], ['up', 'B', 13]]]] * N), [[[], [], [['A', 11], ['B', 13]], 2]] * N)
check('double-tap scenario 5', solve([[4, [['down', 'A', 1], ['down', 'A', 1], ['up', 'A', 2]]]] * N), [[[], [], [['A', 2]], 1]] * N)
check('double-tap scenario 6', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'A', 10], ['up', 'A', 11], ['down', 'A', 12], ['up', 'A', 13]]]] * N), [[[['A', 13]], [], [], 3]] * N)
check('double-tap scenario 7', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'A', 2], ['up', 'A', 3], ['down', 'A', 4], ['up', 'A', 5]]]] * N), [[[['A', 3]], [], [['A', 5]], 3]] * N)
check('double-tap scenario 8', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'B', 2], ['up', 'B', 3], ['down', 'A', 3], ['up', 'A', 4]]]] * N), [[[['A', 4]], [], [['B', 3]], 3]] * N)
check('double-tap scenario 9', solve([[4, [['down', 'A', 0], ['up', 'A', 1], ['down', 'B', 2], ['blur', '', 3]]]] * N), [[[], [], [], 1]] * N)
check('double-tap scenario 10', solve([[4, [['down', 'A', 0], ['down', 'B', 1], ['up', 'A', 2]]]] * N), [[[], ['B'], [['A', 2]], 1]] * N)
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 fixtureActualExpectedOutcome
double-tap scenario 0[[[], [], [], 0]][[[], [], [], 0]]Passed
double-tap scenario 1[[[], ['A'], [], 0]][[[], ['A'], [], 0]]Passed
double-tap scenario 2[[[], [], [], 0]][[[], [], [], 0]]Passed
double-tap scenario 3[[[['A', 4]], [], [], 2]][[[['A', 4]], [], [], 2]]Passed
double-tap scenario 4[[[], [], [['A', 11], ['B', 13]], 2]][[[], [], [['A', 11], ['B', 13]], 2]]Passed
double-tap scenario 5[[[], [], [['A', 2]], 1]][[[], [], [['A', 2]], 1]]Passed
double-tap scenario 6[[[['A', 13]], [], [], 3]][[[['A', 13]], [], [], 3]]Passed
double-tap scenario 7[[[['A', 3]], [], [['A', 5]], 3]][[[['A', 3]], [], [['A', 5]], 3]]Passed
double-tap scenario 8[[[['A', 4]], [], [['B', 3]], 3]][[[['A', 4]], [], [['B', 3]], 3]]Passed
double-tap scenario 9[[[], [], [], 1]][[[], [], [], 1]]Passed
double-tap scenario 10[[[], ['B'], [['A', 2]], 1]][[[], ['B'], [['A', 2]], 1]]Passed

SHA-256 / 996bacd31ad41881e54cbca4d0eab47d384d1b6adac1e7258fa4b3224482c7f1

Verification & scope

Offline stipulated event model, not a browser implementation or web standard conformance claim. 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:41:59.451739+00:00.

Case digest / 19b0b1ca0038b9e2fa78ca6390ca252f26447fe7da4b7ec2a12f5fb21a2f9cf1