Skip to content

Repository files navigation

BounceBench

CI Release License: MIT

Turn logic-analyzer switch captures into auditable debounce evidence.

Choosing DEBOUNCE = 5 because it is common is easy. Showing what a particular switch actually did—and whether a candidate filter would emit one clean transition—is harder. BounceBench reads real digital CSV captures, reconstructs their edges, replays explicit defer and eager policies, and writes deterministic JSON, CSV, and script-free HTML evidence.

It is for embedded developers, keyboard builders, hardware testers, and anyone who has one-channel 0/1 captures but needs a reviewable interval decision. It is an offline evidence tool: it does not capture hardware, flash firmware, or send data anywhere.

60-second quick start

Python 3.11 or newer is required. v0.1.0 is distributed from GitHub Releases, not PyPI.

python -m pip install https://github.com/KanadeK/bouncebench/releases/download/v0.1.0/bouncebench-0.1.0-py3-none-any.whl
bouncebench demo --out bouncebench-demo

Expected terminal result:

status: ready
defer: 500 us
eager: 500 us
report: .../bouncebench-demo/report

The command creates three actual CSV inputs, analyzes them through the normal pipeline, and writes:

  • bouncebench-demo/report/report.html — human decision and evidence tables;
  • bouncebench-demo/report/report.json — complete machine-readable evidence;
  • bouncebench-demo/report/evaluations.csv — one row per trace/policy/interval.

The destination must not already exist. Open report.html in any browser to inspect the result; no server or JavaScript is required.

What happens to a capture

case.json + digital CSVs
        -> strict boundary validation
        -> adjacent samples collapsed into raw edges
        -> bounce duration and final dwell measured
        -> every interval replayed under defer and eager
        -> smallest candidate passing every trace, or inconclusive
        -> report.json + evaluations.csv + report.html

An evaluation passes only when it emits exactly one output transition, ends in the declared final state, and the capture observes that final raw state for at least the candidate interval. “Passing” therefore means observed passing on the supplied captures, not universal reliability.

Analyze your own data

Create case.json beside or above its trace files:

{
  "schema_version": 1,
  "name": "panel switch sample",
  "csv": {
    "time_column": "Time [s]",
    "signal_column": "Button",
    "time_unit": "s"
  },
  "candidate_intervals_us": [500, 1000, 2000, 5000],
  "traces": [
    {
      "id": "press-01",
      "file": "press-01.csv",
      "expected_initial": 1,
      "expected_final": 0
    }
  ]
}

The referenced CSV may contain unrelated columns; the two named columns are what matter:

Time [s],Button,note
0.000000,1,start
0.000100,0,first edge
0.000240,1,bounce
0.000430,0,settled
0.005430,0,end

Then validate and analyze:

bouncebench validate case.json
bouncebench analyze case.json --out switch-report

validate reads every referenced CSV but writes nothing. analyze accepts only a new output path, so it cannot silently replace an earlier report.

Reading the decision

  • ready (exit 0) means the smallest reported defer interval passed every supplied trace with enough final-dwell evidence.
  • inconclusive (exit 1) is valid analysis, and all three reports are still written. Either no candidate produced one clean transition on every trace or the capture tail was too short to prove it.
  • A boundary or usage error (exit 2) writes no requested analysis directory and prints [CODE], the precise problem, and one repair: action.

The eager result is shown as a latency trade-off. BounceBench v0.1.0 treats the eager lockout as half-open: it emits the first eligible edge immediately, ignores raw changes during the interval, and reconciles the current raw state at expiry. A reconciliation emission starts a new lockout; otherwise an edge exactly at expiry is eligible. Confirm that this matches the target firmware before applying the number.

CLI

bouncebench --help
bouncebench --version
bouncebench demo --out NEW_DIRECTORY
bouncebench validate CASE.json
bouncebench analyze CASE.json --out NEW_DIRECTORY

Committed examples cover all three outcomes:

bouncebench analyze examples/ready/case.json --out ready-report
bouncebench analyze examples/inconclusive/case.json --out inconclusive-report
bouncebench validate examples/invalid/case.json

The second command intentionally exits 1 after producing evidence. The third intentionally exits 2 with [TRACE_SIGNAL] because 0.4 is not digital.

Input rules

  • JSON fields are strict; unknown keys fail instead of being ignored.
  • Trace paths must be relative and remain inside the case directory.
  • Times are finite, non-negative decimals and strictly increase after half-up conversion to signed 64-bit integer nanoseconds.
  • time_unit is exactly s, ms, us, or ns.
  • Signal cells are exactly 0 or 1 after surrounding whitespace is removed.
  • Each CSV contains one actuation: its first and last states match the declared different initial/final states and at least one transition occurs.
  • Candidate intervals are unique positive integer microseconds in ascending order.

Saleae's automation API can export raw digital CSV, and other logic-analyzer or oscilloscope exports work after mapping their exact headers and time unit in the manifest. BounceBench does not assume a vendor-specific column layout.

Common failures

[TRACE_COLUMNS] means a declared header is absent. Copy the exact CSV header text into csv.time_column or csv.signal_column.

[TRACE_TIME_ORDER] means two timestamps became equal or reversed after nanosecond conversion. Re-export with enough precision and a strictly increasing time column.

[OUTPUT_EXISTS] protects an existing file, directory, or symlink. Choose a new --out path or move the old result aside.

inconclusive is not malformed input. Read the finding in report.html: extend the capture tail when proof is short, or add larger candidates after inspecting extra emitted transitions. The full repair table is in docs/repair.md.

Known limitations

  • Only digital 0/1 input is supported; there is no analog thresholding.
  • Each CSV is one channel and one actuation; there is no keyboard-matrix, per-row, per-key, or global coupling model.
  • BounceBench neither captures signals nor generates or flashes firmware.
  • Results cover supplied traces only; they do not establish electrical safety, component health, switch lifetime, or every future actuation.
  • Candidate intervals are explicit integer microseconds rather than an unbounded optimizer.
  • Firmware scan cadence and implementation details can differ from the replay contract, especially for eager behavior.

Why another debounce project?

Runtime libraries such as Bounce2 apply a chosen filter; logic analyzers capture and display signals; QMK documents firmware algorithms. BounceBench fills the step between those tools: measured traces → replayed candidate behavior → auditable cross-trace decision. It is not a live input filter, capture application, latency tester, firmware library, or file-format converter. The bounded candidate review and six closest README comparisons are recorded in docs/research.md.

QMK is cited only for the high-level eager/defer distinction. BounceBench's exact v0.1.0 event contract is documented and tested independently in docs/spec.md.

Development and verification

The runtime uses only Python's standard library. Development dependencies are locked with uv.

uv sync --locked --dev
uv run --no-sync python scripts/check.py

That single gate runs formatting, lint, strict types, unit/integration tests, coverage, all documented outcome paths, package build and content checks, and a clean wheel installation followed by real installed-CLI runs. CI invokes the same command.

License

MIT © 2026 KanadeK. See LICENSE.

About

Turn measured switch traces into auditable debounce evidence.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages