KB-M1
calibration
Synthetic keystroke streams with known ground truth, replayed through the engine. Every figure below is asserted against a value derived by hand, not observed and blessed. Three of these guard correctness traps that produce plausible but wrong numbers: a repeated key cannot overlap itself, modifiers are never keystrokes, and a keyup lost to a focus change must not poison every measurement after it.
All pass — 187 of 187
| Check | Detail | Expected | Actual |
|---|---|---|---|
| IKI → WPM identity — pass | wpmFromIki(120) | 100.0000 | 100.0000 |
| IKI → WPM identity — pass | wpmFromIki(200) | 60.0000 | 60.0000 |
| Rollover: discrete — pass | no overlap anywhere | 0.0000 | 0.0000 |
| Mean IKI — pass | keys 200 ms apart | 200.0000 | 200.0000 |
| Hold time — pass | each key held 80 ms | 80.0000 | 80.0000 |
| Rollover: full — pass | 3 of 4 keystrokes roll | 0.7500 | 0.7500 |
| Mean overlap — pass | held 120 ms, 100 ms apart | 20.0000 | 20.0000 |
| Rollover: repeated key — pass | 'll' cannot overlap | 0.0000 | 0.0000 |
| Rollover: modifiers — pass | Shift excluded | 0.0000 | 0.0000 |
| Keystroke count — pass | Shift is not a keystroke | 1.0000 | 1.0000 |
| Rollover: stuck key — pass | blur clears held keys | 0.0000 | 0.0000 |
| KSPC — pass | 6 keystrokes, 4 final chars | 1.5000 | 1.5000 |
| Corrections — pass | one backspace | 1.0000 | 1.0000 |
| Accuracy — pass | 4 of 5 non-correction strokes | 0.8000 | 0.8000 |
| Alternation benefit — pass | alternating 60 ms faster | 60.0000 | 60.0000 |
| Idle: wall clock — pass | start to finish | 10200.0000 | 10200.0000 |
| Idle: active time — pass | gap capped at 3 s | 3200.0000 | 3200.0000 |
| Idle: paused time — pass | the excluded 7 s | 7000.0000 | 7000.0000 |
| Idle: speed uses active — pass | wpm from active, not wall clock | true | true |
| Blur: no time charged — pass | 60 s hidden costs nothing | 100.0000 | 100.0000 |
| Final char: run closes — pass | cursor reached the end | true | true |
| Final char: reopens — pass | backspace revives it | true | true |
| Final char: reports reopened — pass | so the caller can cancel the save | true | true |
| Final char: refinishes — pass | corrected and complete | true | true |
| Final char: accuracy — pass | 2 of 3 printable correct | 0.6667 | 0.6667 |
| Soft kb: keydown rejected — pass | 229 carries no timing | true | true |
| Soft kb: detected — pass | fallback path armed | true | true |
| Soft kb: text recorded — pass | two characters | 2.0000 | 2.0000 |
| Soft kb: timing survives — pass | 150 ms apart | 150.0000 | 150.0000 |
| Soft kb: flagged — pass | rollover is not measurable | true | true |
| Caps Lock: detected — pass | read off any keystroke | true | true |
| Integrity: paste flagged — pass | never silently dropped | true | true |
| Integrity: impossible gaps — pass | 5 ms is below the motor floor | 1.0000 | 1.0000 |
| Timed: remaining — pass | 15 s budget, 1 s typed | 14000.0000 | 14000.0000 |
| Timed: text end does not finish it — pass | budget decides | true | true |
| Gate: fast but sloppy fails — pass | 12 wpm at 0.96 | false | false |
| Gate: accurate but slow fails — pass | speed still required | false | false |
| Gate: both cleared passes — pass | true | true | |
| Unlock: first lesson open — pass | true | true | |
| Unlock: third lesson shut — pass | one ahead only | false | false |
| Unlock: pass opens the next — pass | true | true | |
| Unlock: earlier stays open — pass | revision is never blocked | true | true |
| Next lesson: skips the passed one — pass | true | true | |
| Placement: never regresses — pass | a bad day cannot close material | true | true |
| Drill: stays in pool — pass | pool "fj" | 0.0000 | 0.0000 |
| Drill: non-empty — pass | true | true | |
| Drill: reproducible — pass | same lesson and attempt, same text | true | true |
| Drill: retry differs — pass | so it cannot be memorised | true | true |
| Revive: rejects junk — pass | falls back to no placement | true | true |
| Revive: rejects null — pass | true | true | |
| Revive: keeps valid rows — pass | one of two survives | 1.0000 | 1.0000 |
| Revive: clamps placement — pass | 999 is not a stage | true | true |
| Habit: streak crosses months — pass | 30 Jan → 1 Feb | 3.0000 | 3.0000 |
| Habit: days practised — pass | three distinct days | 3.0000 | 3.0000 |
| Habit: grace for today — pass | yesterday still counts | 1.0000 | 1.0000 |
| Habit: gap breaks streak — pass | only today counts | 1.0000 | 1.0000 |
| Habit: longest remembered — pass | the earlier pair | 2.0000 | 2.0000 |
| Habit: same day merges — pass | one day, two runs | 1.0000 | 1.0000 |
| Habit: daily total sums — pass | two minutes | 120000.0000 | 120000.0000 |
| Habit: empty history — pass | no streak, no crash | 0.0000 | 0.0000 |
| Guide: f is left index — pass | true | true | |
| Guide: j is right index — pass | true | true | |
| Guide: capital needs shift — pass | A is Shift+KeyA | true | true |
| Guide: space is a key — pass | true | true | |
| Guide: covers the alphabet — pass | 26 lowercase letters resolve | true | true |
| Columns: ten of them — pass | one per finger | 10.0000 | 10.0000 |
| Columns: thirty keys — pass | no key in two columns | 30.0000 | 30.0000 |
| Columns: cover the alphabet — pass | every letter has a finger | true | true |
| Columns: eight home keys — pass | the anchors, stretches excluded | 8.0000 | 8.0000 |
| Columns: two stretch columns — pass | the inner reaches | 2.0000 | 2.0000 |
| Columns: QAZ on US QWERTY — pass | names are built from the layout | true | true |
| Columns: every key resolves back — pass | columnOf is total over the pool | true | true |
| Columns: sweep is a round trip — pass | d e d c d — three returns in five | true | true |
| Reverse: every prompt distinct — pass | thirty keys, thirty descriptions | 30.0000 | 30.0000 |
| Reverse: gives nothing away — pass | no character appears in the prompt | true | true |
| Reach: names the anchor — pass | E is one up from D | true | true |
| Recall: correct promotes — pass | box 0 → 1 | 1.0000 | 1.0000 |
| Recall: slow does not promote — pass | worked out, not retrieved | 0.0000 | 0.0000 |
| Recall: miss drops two — pass | a slip is not amnesia | 2.0000 | 2.0000 |
| Recall: miss floors at zero — pass | 0.0000 | 0.0000 | |
| Recall: cap limits promotion — pass | cued reps stop at 3 | 3.0000 | 3.0000 |
| Recall: cap never demotes — pass | box 5 stays 5 under a cap of 3 | 5.0000 | 5.0000 |
| Recall: miss leaves latency alone — pass | a wrong key times nothing | 420.0000 | 420.0000 |
| Recall: automatic needs speed — pass | top box at 1200 ms is known, not automatic | true | true |
| Recall: automatic at the top — pass | true | true | |
| Recall: unseen is not shaky — pass | never asked ≠ got it wrong | true | true |
| Decay: mastered key floors at one — pass | forgotten becomes slow, not unknown | 1.0000 | 1.0000 |
| Decay: unmastered key falls to zero — pass | 0.0000 | 0.0000 | |
| Decay: recent key untouched — pass | one day is not a lapse | 5.0000 | 5.0000 |
| Key revive: rejects junk — pass | true | true | |
| Key revive: keeps valid rows — pass | one of two | 1.0000 | 1.0000 |
| Key revive: clamps the box — pass | 99 is not a box | 5.0000 | 5.0000 |
| Key revive: drops bad clear stamps — pass | 1.0000 | 1.0000 | |
| Schedule: terminates — pass | 0.0000 | 0.0000 | |
| Schedule: secures every key — pass | 12 in the pool | 12.0000 | 12.0000 |
| Schedule: no immediate repeats — pass | answers cannot come from inertia | 0.0000 | 0.0000 |
| Schedule: two clean reps per key — pass | 24 prompts for 12 keys | true | true |
| Schedule: misses buy more reps — pass | the missed key outnumbers a clean one | true | true |
| Schedule: capped for a stuck typist — pass | ends rather than holding them there | true | true |
| Schedule: nothing secured on all-wrong — pass | 0.0000 | 0.0000 | |
| Sweep: two columns, two passes — pass | 5 strokes each | 20.0000 | 20.0000 |
| Sweep: starts at home — pass | true | true | |
| Sweep: names the column once — pass | true | true | |
| Sweep: a miss does not advance — pass | the same reach is asked again | true | true |
| Sweep: advances on success — pass | true | true | |
| Homing: impossibly fast rep discarded — pass | hands never lifted | 2.0000 | 2.0000 |
| Homing: median ignores it — pass | 650.0000 | 650.0000 | |
| Homing: best ignores it — pass | 600.0000 | 600.0000 | |
| Homing: slow rep counted but not clean — pass | found, not arrived at | true | true |
| Homing: six clean arrivals pass — pass | true | true | |
| Homing: an abandoned rep is not a homing time — pass | 48 s is an interruption, not a measurement | 450.0000 | 450.0000 |
| Homing: and is counted separately — pass | 1.0000 | 1.0000 | |
| Homing: best is unaffected — pass | 400.0000 | 400.0000 | |
| Align: omission is one omission — pass | 'helo' | 1.0000 | 1.0000 |
| Align: and not a run of substitutions — pass | 0.0000 | 0.0000 | |
| Align: doubled letter is an insertion — pass | 'helllo' | 1.0000 | 1.0000 |
| Align: a wrong key is a substitution — pass | c → b | 1.0000 | 1.0000 |
| Align: nothing else moves — pass | insert+omit would cost the same | 0.0000 | 0.0000 |
| Align: identical text is all matches — pass | true | true | |
| Align: empty is empty — pass | 0.0000 | 0.0000 | |
| Align: abandoned run is not 500 omissions — pass | target clipped to what was attempted | true | true |
| Realign: positional model miscounts — pass | 'helo' scores 3 of 4 right | 3.0000 | 3.0000 |
| Realign: every stroke was correct — pass | the typist hit no wrong key | 4.0000 | 4.0000 |
| Realign: one omission — pass | 1.0000 | 1.0000 | |
| Realign: no substitutions — pass | 0.0000 | 0.0000 | |
| Realign: the 'o' is re-attributed — pass | was charged to l, belongs to o | 1.0000 | 1.0000 |
| Realign: reports the damage — pass | one stroke mis-attributed | 1.0000 | 1.0000 |
| Realign: aligned accuracy — pass | 4 matched of 5 operations | 0.8000 | 0.8000 |
| Realign: the omitted char is named — pass | 1.0000 | 1.0000 | |
| Realign: corrections are not insertions — pass | typed, deleted, retyped | 0.0000 | 0.0000 |
| Realign: the survivors are clean — pass | 2.0000 | 2.0000 | |
| Pearson: perfect positive — pass | 1.0000 | 1.0000 | |
| Pearson: perfect negative — pass | -1.0000 | -1.0000 | |
| Pearson: a flat series has no correlation — pass | zero variance is not r = 0 | true | true |
| Pearson: refuses two points — pass | true | true | |
| Detrend: shared trend inflates raw r — pass | both rise, r = 1 | 1.0000 | 1.0000 |
| Detrend: and vanishes once removed — pass | no relationship beyond time | true | true |
| Detrend: residuals sum to zero — pass | 0.0000 | -0.0000 | |
| Slope: recovers a known gradient — pass | series rises 10 per step | 10.0000 | 10.0000 |
| Slope: unit gradient — pass | 1.0000 | 1.0000 | |
| Slope: flat series — pass | 0.0000 | 0.0000 | |
| Loop: silent below the threshold — pass | 11 runs is not a finding | true | true |
| Loop: confirms a real relationship — pass | true | true | |
| Loop: reports a negative — pass | faster runs had less rollover | true | true |
| Loop: says so in words — pass | the verdict names the contradiction | true | true |
| Loop: excludes runs too short to measure — pass | true | true | |
| Loop: the weakness is noticed — pass | top pairs were th, er | true | true |
| Loop: ranked by time lost, not slowness — pass | the costliest pair leads | true | true |
| Loop: the plan targets it — pass | targets th er | true | true |
| Loop: the drill text contains it — pass | diagnosis reaches the keyboard | true | true |
| Loop: the transfer test is the measured one — pass | drill pace is not a typing speed | true | true |
| Loop: invents nothing for an even typist — pass | no pair is slower than the rest | 0.0000 | 0.0000 |
| Loop: and falls back honestly — pass | ordinary text, not a fabricated drill | true | true |
| Loop: headroom is real, not aspirational — pass | your own 20th percentile, not a target | true | true |
| Loop: one dropped letter used to poison the matrix — pass | positional model logged 496 confusions | true | true |
| Loop: and now logs none — pass | an omission is not a substitution | 0.0000 | 0.0000 |
| Roll call: order — pass | the eight home keys, left to right | true | true |
| Roll call: a wrong key holds its place — pass | Q at position one — a miss, not a reset | true | true |
| Roll call: eight correct presses end it — pass | done on the final home key | true | true |
| Roll call: the miss survives the run — pass | one wrong key is still on record | 1.0000 | 1.0000 |
| Roll call: inert when done — pass | a ninth press changes nothing | true | true |
| Walk: only the thirty count — pass | Enter is not part of the map | 0.0000 | 0.0000 |
| Walk: two of three is not a column — pass | Q and A without Z | 0.0000 | 0.0000 |
| Walk: home, up and down make one — pass | Z completes QAZ | 1.0000 | 1.0000 |
| Walk: the whole board — pass | thirty keys, ten columns | 10.0000 | 10.0000 |
| Coach: no runs, no evidence — pass | an empty history sends nothing | true | true |
| Coach: recent speed is the median — pass | runs at 40, 60, 50 wpm | 50.0000 | 50.0000 |
| Coach: costliest pair leads the pack — pass | er loses the most total time | true | true |
| Coach: targets are letters only, ranked — pass | er in re an | true | true |
| Coach: passage separated from the read — pass | tagged block comes out whole | true | true |
| Coach: no tag, no passage — pass | the read survives untouched | true | true |
| Coach: untypeable passage rejected — pass | an em dash is not on the board | true | true |
| Coach: whitespace flattened — pass | a passage is one line on the instrument | true | true |
| Features: mechanics reads the costliest pair — pass | the er data travels in the prompt | true | true |
| Features: mechanics stays silent without misses — pass | no pairs, no theory | true | true |
| Features: the week is exactly seven days — pass | the contract says seven lines | true | true |
| Features: debrief carries today's number — pass | 42 wpm against a median of 38 | true | true |
| Features: debrief admits a missing baseline — pass | day one is not judged against nothing | true | true |
| Features: mnemonics pick the lost, not the never-asked — pass | c struggles, f is fine, q was never prompted | true | true |
| Features: mnemonics name finger and anchor — pass | c is the middle finger's drop from d | true | true |
| Features: mnemonics cap at eight keys — pass | twelve lost keys, eight cues | 8.0000 | 8.0000 |
| Features: mnemonics rank the most lost first — pass | an unknown key outranks a slow one | true | true |
| Features: no cues for nothing — pass | an empty list is null, not an empty prompt | true | true |
| Features: topic is flattened and capped — pass | whitespace collapses, 200 chars become 80 | true | true |
| Features: topic passage is passage-only — pass | the contract forbids text outside the tags | true | true |
| Features: question is capped, emptiness rejected — pass | 400 chars at most, silence is not a question | true | true |
| Features: the answer must come from the pack — pass | question and evidence travel together | true | true |
KB-M1 · Form 08/08 · Rev AThe same fixtures run headless — npm test