FAILURE MAP
← Case archive

FA-31656 / Keyboard interactions / Open access

Per-switch keyboard scan debounce: Candidate state records the opposite level or assumes every transition is a press · case 01

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

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

ROOT CAUSE

Candidate state records the opposite level or assumes every transition is a press.

VERIFIED REPAIR

Use the contract transition `candidate[key]=value` at the candidate value fault site; preserve the other state transitions.

Unsuccessful approach: The partial repair changes this transition to candidate[key]=True, 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]=not 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 / 18e08fafa5dd122cc9b534dbe4f316f9410eafc76a7b34751eb6ad3d2d665f5f

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]=True
                    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']]][[[['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], ['B', 'down', 17]], ['A', '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', '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 / c47ae6cd390bff3ea6f06d4b646c3ff58b03e305a4feea55dd34235a79529173

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

Case digest / b114bc0be3c46774c04bf319b5d77e21e8024c9a88e9d5c717e5db97f8536cdd