FAILURE MAP
← Case archive

FA-31681 / Keyboard interactions / Open access

Per-switch keyboard scan debounce: Debounced release is published as a press · case 01

The event trace violates the edge name rule and produces incorrect keyboard state or command output.

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

ROOT CAUSE

Debounced release is published as a press.

VERIFIED REPAIR

Use the contract transition `'down' if value else 'up'` at the edge name fault site; preserve the other state transitions.

Unsuccessful approach: The partial repair changes this transition to 'down', 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 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,'up' if value else 'down',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', 'up', 13]], ['A']]][[[['A', 'down', 13]], ['A']]]Failed
key-debounce scenario 2[[[['A', 'up', 13], ['A', 'down', 17]], []]][[[['A', 'down', 13], ['A', 'up', 17]], []]]Failed
key-debounce scenario 3[[[['A', 'up', 15]], ['A']]][[[['A', 'down', 15]], ['A']]]Failed
key-debounce scenario 4[[[['A', 'up', 13], ['A', 'down', 17], ['B', 'up', 17]], ['B']]][[[['A', 'down', 13], ['A', 'up', 17], ['B', 'down', 17]], ['B']]]Failed
key-debounce scenario 5[[[], []]][[[], []]]Passed
key-debounce scenario 6[[[['A', 'up', 23], ['B', 'up', 23], ['A', 'down', 27]], ['B']]][[[['A', 'down', 23], ['B', 'down', 23], ['A', 'up', 27]], ['B']]]Failed
key-debounce scenario 7[[[['A', 'up', 13]], ['A']]][[[['A', 'down', 13]], ['A']]]Failed

SHA-256 / cc1403422d0dbf724b7de36f1f1bc619b327b1e8fe090f42148fbb5628d7d709

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',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', 'down', 17]], []]][[[['A', 'down', 13], ['A', 'up', 17]], []]]Failed
key-debounce scenario 3[[[['A', 'down', 15]], ['A']]][[[['A', 'down', 15]], ['A']]]Passed
key-debounce scenario 4[[[['A', 'down', 13], ['A', 'down', 17], ['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', 'down', 27]], ['B']]][[[['A', 'down', 23], ['B', 'down', 23], ['A', 'up', 27]], ['B']]]Failed
key-debounce scenario 7[[[['A', 'down', 13]], ['A']]][[[['A', 'down', 13]], ['A']]]Passed

SHA-256 / 0cd1f973e1153725b8200c4d5bf76129590a2e64d96b0bf17f10621895e88177

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

Case digest / 47627a67b8ab4340325675ac5be04ab358b0733660dae605fafee1c251b1924c