FAILURE MAP
← Case archive

FA-31651 / Keyboard interactions / Open access

Per-switch keyboard scan debounce: Contact bounce fails to restart the candidate timer or stable samples continually restart it · case 01

The event trace violates the candidate change rule and produces incorrect keyboard state or command output.

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

ROOT CAUSE

Contact bounce fails to restart the candidate timer or stable samples continually restart it.

VERIFIED REPAIR

Use the contract transition `if key not in candidate or candidate[key]!=value:` at the candidate change fault site; preserve the other state transitions.

Unsuccessful approach: The partial repair changes this transition to if key not in candidate or candidate[key]==value:, which still violates the model contract on the explicit regression traces.

Case contract

Case [threshold,scans]. Each scan is [time,sorted_pressed_keys] and times increase. A raw transition starts a candidate timer; a candidate becomes stable after continuously present for threshold time including exact boundary. Each scan considers every known or raw key. Emit [key,down_or_up,time] on stable transitions; return emissions plus stable pressed keys. Missing keys are raw up. 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):
        threshold,scans=c
        stable={}; candidate={}; since={}; output=[]
        for now,pressed in scans:
            raw=set(pressed)
            keys=sorted(set(stable)|set(candidate)|raw)
            for key in keys:
                value=key in raw
                if key not in candidate:
                    candidate[key]=value
                    since[key]=now
                if now-since[key]>=threshold and stable.get(key,False)!=value:
                    stable[key]=value
                    output.append([key,'down' if value else 'up',now])
        return [output,sorted(k for k,v in stable.items() if v)]
    return [run(c) for c in cases]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('key-debounce scenario 0', solve([[3, []]] * N), [[[], []]] * N)
check('key-debounce scenario 1', solve([[3, [[10, ['A']], [12, ['A']], [13, ['A']], [14, ['A']]]]] * N), [[[['A', 'down', 13]], ['A']]] * N)
check('key-debounce scenario 2', solve([[3, [[10, ['A']], [13, ['A']], [14, []], [17, []]]]] * N), [[[['A', 'down', 13], ['A', 'up', 17]], []]] * N)
check('key-debounce scenario 3', solve([[3, [[10, ['A']], [11, []], [12, ['A']], [14, ['A']], [15, ['A']]]]] * N), [[[['A', 'down', 15]], ['A']]] * N)
check('key-debounce scenario 4', solve([[3, [[10, ['A']], [13, ['A']], [14, ['B']], [17, ['B']]]]] * N), [[[['A', 'down', 13], ['A', 'up', 17], ['B', 'down', 17]], ['B']]] * N)
check('key-debounce scenario 5', solve([[3, [[10, ['A']], [11, []], [14, []]]]] * N), [[[], []]] * N)
check('key-debounce scenario 6', solve([[3, [[10, []], [20, ['A', 'B']], [23, ['A', 'B']], [24, ['B']], [27, ['B']]]]] * N), [[[['A', 'down', 23], ['B', 'down', 23], ['A', 'up', 27]], ['B']]] * N)
check('key-debounce scenario 7', solve([[3, [[10, ['A']], [13, ['A']], [14, []], [15, ['A']], [18, ['A']]]]] * N), [[[['A', 'down', 13]], ['A']]] * 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
key-debounce scenario 0[[[], []]][[[], []]]Passed
key-debounce scenario 1[[[['A', 'down', 13]], ['A']]][[[['A', 'down', 13]], ['A']]]Passed
key-debounce scenario 2[[[['A', 'down', 13], ['A', 'up', 14]], []]][[[['A', 'down', 13], ['A', 'up', 17]], []]]Failed
key-debounce scenario 3[[[['A', 'down', 14]], ['A']]][[[['A', 'down', 15]], ['A']]]Failed
key-debounce scenario 4[[[['A', 'down', 13], ['A', 'up', 14], ['B', 'down', 17]], ['B']]][[[['A', 'down', 13], ['A', 'up', 17], ['B', 'down', 17]], ['B']]]Failed
key-debounce scenario 5[[[], []]][[[], []]]Passed
key-debounce scenario 6[[[['A', 'down', 23], ['B', 'down', 23], ['A', 'up', 24]], ['B']]][[[['A', 'down', 23], ['B', 'down', 23], ['A', 'up', 27]], ['B']]]Failed
key-debounce scenario 7[[[['A', 'down', 13], ['A', 'up', 14], ['A', 'down', 15]], ['A']]][[[['A', 'down', 13]], ['A']]]Failed

SHA-256 / 9f65b9d75a86f46397c8e08610d20e555dafcce5ea5d9c31ad21ff5c205be161

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):
        threshold,scans=c
        stable={}; candidate={}; since={}; output=[]
        for now,pressed in scans:
            raw=set(pressed)
            keys=sorted(set(stable)|set(candidate)|raw)
            for key in keys:
                value=key in raw
                if key not in candidate or candidate[key]==value:
                    candidate[key]=value
                    since[key]=now
                if now-since[key]>=threshold and stable.get(key,False)!=value:
                    stable[key]=value
                    output.append([key,'down' if value else 'up',now])
        return [output,sorted(k for k,v in stable.items() if v)]
    return [run(c) for c in cases]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('key-debounce scenario 0', solve([[3, []]] * N), [[[], []]] * N)
check('key-debounce scenario 1', solve([[3, [[10, ['A']], [12, ['A']], [13, ['A']], [14, ['A']]]]] * N), [[[['A', 'down', 13]], ['A']]] * N)
check('key-debounce scenario 2', solve([[3, [[10, ['A']], [13, ['A']], [14, []], [17, []]]]] * N), [[[['A', 'down', 13], ['A', 'up', 17]], []]] * N)
check('key-debounce scenario 3', solve([[3, [[10, ['A']], [11, []], [12, ['A']], [14, ['A']], [15, ['A']]]]] * N), [[[['A', 'down', 15]], ['A']]] * N)
check('key-debounce scenario 4', solve([[3, [[10, ['A']], [13, ['A']], [14, ['B']], [17, ['B']]]]] * N), [[[['A', 'down', 13], ['A', 'up', 17], ['B', 'down', 17]], ['B']]] * N)
check('key-debounce scenario 5', solve([[3, [[10, ['A']], [11, []], [14, []]]]] * N), [[[], []]] * N)
check('key-debounce scenario 6', solve([[3, [[10, []], [20, ['A', 'B']], [23, ['A', 'B']], [24, ['B']], [27, ['B']]]]] * N), [[[['A', 'down', 23], ['B', 'down', 23], ['A', 'up', 27]], ['B']]] * N)
check('key-debounce scenario 7', solve([[3, [[10, ['A']], [13, ['A']], [14, []], [15, ['A']], [18, ['A']]]]] * N), [[[['A', 'down', 13]], ['A']]] * 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
key-debounce scenario 0[[[], []]][[[], []]]Passed
key-debounce scenario 1[[[], []]][[[['A', 'down', 13]], ['A']]]Failed
key-debounce scenario 2[[[], []]][[[['A', 'down', 13], ['A', 'up', 17]], []]]Failed
key-debounce scenario 3[[[], []]][[[['A', 'down', 15]], ['A']]]Failed
key-debounce scenario 4[[[], []]][[[['A', 'down', 13], ['A', 'up', 17], ['B', 'down', 17]], ['B']]]Failed
key-debounce scenario 5[[[], []]][[[], []]]Passed
key-debounce scenario 6[[[], []]][[[['A', 'down', 23], ['B', 'down', 23], ['A', 'up', 27]], ['B']]]Failed
key-debounce scenario 7[[[], []]][[[['A', 'down', 13]], ['A']]]Failed

SHA-256 / 454ab2e3ea2735c0236f61b98bb5c92e94c55594c74303ae1f8cfd34b2585f31

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):
        threshold,scans=c
        stable={}; candidate={}; since={}; output=[]
        for now,pressed in scans:
            raw=set(pressed)
            keys=sorted(set(stable)|set(candidate)|raw)
            for key in keys:
                value=key in raw
                if key not in candidate or candidate[key]!=value:
                    candidate[key]=value
                    since[key]=now
                if now-since[key]>=threshold and stable.get(key,False)!=value:
                    stable[key]=value
                    output.append([key,'down' if value else 'up',now])
        return [output,sorted(k for k,v in stable.items() if v)]
    return [run(c) for c in cases]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('key-debounce scenario 0', solve([[3, []]] * N), [[[], []]] * N)
check('key-debounce scenario 1', solve([[3, [[10, ['A']], [12, ['A']], [13, ['A']], [14, ['A']]]]] * N), [[[['A', 'down', 13]], ['A']]] * N)
check('key-debounce scenario 2', solve([[3, [[10, ['A']], [13, ['A']], [14, []], [17, []]]]] * N), [[[['A', 'down', 13], ['A', 'up', 17]], []]] * N)
check('key-debounce scenario 3', solve([[3, [[10, ['A']], [11, []], [12, ['A']], [14, ['A']], [15, ['A']]]]] * N), [[[['A', 'down', 15]], ['A']]] * N)
check('key-debounce scenario 4', solve([[3, [[10, ['A']], [13, ['A']], [14, ['B']], [17, ['B']]]]] * N), [[[['A', 'down', 13], ['A', 'up', 17], ['B', 'down', 17]], ['B']]] * N)
check('key-debounce scenario 5', solve([[3, [[10, ['A']], [11, []], [14, []]]]] * N), [[[], []]] * N)
check('key-debounce scenario 6', solve([[3, [[10, []], [20, ['A', 'B']], [23, ['A', 'B']], [24, ['B']], [27, ['B']]]]] * N), [[[['A', 'down', 23], ['B', 'down', 23], ['A', 'up', 27]], ['B']]] * N)
check('key-debounce scenario 7', solve([[3, [[10, ['A']], [13, ['A']], [14, []], [15, ['A']], [18, ['A']]]]] * N), [[[['A', 'down', 13]], ['A']]] * 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
key-debounce scenario 0[[[], []]][[[], []]]Passed
key-debounce scenario 1[[[['A', 'down', 13]], ['A']]][[[['A', 'down', 13]], ['A']]]Passed
key-debounce scenario 2[[[['A', 'down', 13], ['A', 'up', 17]], []]][[[['A', 'down', 13], ['A', 'up', 17]], []]]Passed
key-debounce scenario 3[[[['A', 'down', 15]], ['A']]][[[['A', 'down', 15]], ['A']]]Passed
key-debounce scenario 4[[[['A', 'down', 13], ['A', 'up', 17], ['B', 'down', 17]], ['B']]][[[['A', 'down', 13], ['A', 'up', 17], ['B', 'down', 17]], ['B']]]Passed
key-debounce scenario 5[[[], []]][[[], []]]Passed
key-debounce scenario 6[[[['A', 'down', 23], ['B', 'down', 23], ['A', 'up', 27]], ['B']]][[[['A', 'down', 23], ['B', 'down', 23], ['A', 'up', 27]], ['B']]]Passed
key-debounce scenario 7[[[['A', 'down', 13]], ['A']]][[[['A', 'down', 13]], ['A']]]Passed

SHA-256 / a41cbbca932b1c6a67b6188ff4125f0e29dd181b7435e69222accfa8a0f3f434

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:42:04.251779+00:00.

Case digest / 059845aa06024bfa44b713225c9c2559cc6be31d301b5aaab5664a889a740bf2