← Back to visualizer

Firmware Research

Findings from 4 phases of QMK research — combo system, layer design, timing, and key repeat.

Build Phases

PHASE 1Foundationcomplete

Clean Graphite base layer that compiles, flashes, and lets the user type all alphas with zero lag.

Key Findings

  • Clean Graphite alphas — no LT() or MT() on any alpha position
  • Thumb cluster: MO(1), Space, Backspace, MO(2)
  • Combo timing defines (COMBO_TERM 30, COMBO_MUST_HOLD_MODS, COMBO_STRICT_TIMER) configured in config.h even with zero combos
  • Unsized combo_t key_combos[] with sizeof introspection replaces manual COMBO_COUNT define
  • Removed all mod-tap artifacts: PERMISSIVE_HOLD, TAPPING_TERM_PER_KEY, QUICK_TAP_TERM
  • ORYX_ENABLE kept for ZSA live-training compatibility

Risks & Notes

  • Empty combo array compilation — GCC handles zero-length arrays as extension (verified safe)
  • SERIAL_NUMBER kept from old layout for Oryx compatibility
PHASE 2Comboscomplete

Common commands and bare modifiers fire via natural two-key rolling combos on the base layer.

Key Findings

  • 18 combos total: 14 commands (tap), 3 modifiers (hold 150ms), 1 escape
  • COMBO_MUST_HOLD_MODS auto-distinguishes tap vs hold — KEYCODE_IS_MOD checks IS_MODIFIER_KEYCODE in QMK source
  • Command keycodes like LGUI(KC_C) have non-zero basic keycodes, so they fire instantly on tap within 30ms COMBO_TERM
  • All combo pairs use adjacent-column, different-finger keys — cross-referenced against Graphite bigram data
  • O+U pair (~1.5% bigram frequency) intentionally avoided as highest-risk collision
  • LED modifier indicators via rgb_matrix_indicators_advanced_user with get_mods() | get_weak_mods()
  • Gruvbox color palette: yellow for Cmd, aqua for Ctrl
  • Voyager has 52 addressable RGB LEDs via IS31FL3731 driver

Risks & Notes

  • R+T pair ('tr' ~0.3%) assigned to Copy — monitor during daily use
  • H+A pair ('ha' ~1.2%) safe for Cmd because hold requirement prevents tap misfires
  • LED colors set outside indicator callback get overwritten by animation frames
PHASE 3Layerscomplete

Full symbols/numbers layout and vim-style navigation accessible without leaving home position.

Key Findings

  • Layer 1 (symbols): brackets/parens/braces on left home row, numpad on right
  • Layer 2 (nav): vim h/j/k/l arrows on right home row, Home/End/PgUp/PgDn on bottom row
  • Convenience keys (Tab, Enter, Escape, Backspace) on left hand of nav layer for one-handed editing
  • COMBO_ONLY_FROM_LAYER 0 means combos work from any active layer — combo resolution always uses base layer keycodes
  • MO() key positions must be KC_TRANSPARENT on their target layer to prevent layer sticking
  • Layer LED indicators: orange (Gruvbox #fe8019) for symbols, blue (#83a598) for nav
  • Layer check runs before modifier check in LED indicator chain — layer context takes priority

Risks & Notes

  • Shifted keycodes (KC_LPRN etc.) internally register/unregister shift — don't hold physical shift simultaneously
  • LAYER_STATE_8BIT limits total layers to 0-7 (sufficient for this layout)
PHASE 4Polishcomplete

Daily-driver ready with key repeat and timing tuned from real usage.

Key Findings

  • QK_REP placed on both layer thumbs — nav layer left inner thumb, symbols layer right inner thumb
  • QK_REP auto-ignores MO keys, modifier keycodes, and itself — layer switching never overwrites repeat buffer
  • QK_REP remembers modifier state: Shift+A then REP sends Shift+A again
  • Can repeat combo outputs: Copy combo (R+T) then REP sends Cmd+C again
  • NO_ALT_REPEAT_KEY saves ~500 bytes firmware (QK_AREP not needed with dedicated nav layer)
  • Fixed build.sh to use QMK_HOME from qmk config instead of hardcoded path
  • Final firmware binary: 55KB, flashable via Keymapp

Risks & Notes

  • COMBO_TERM may need adjustment after extended daily use — tune in 2-3ms increments
  • Per-combo timing (COMBO_TERM_PER_COMBO) available if individual combos need different windows

Timing Configuration

SettingValuePurposeNotes
COMBO_TERM30msWindow for both combo keys to register25-30ms sweet spot for fast typists. Lower = fewer misfires, harder to trigger.
COMBO_HOLD_TERM150msHold threshold for modifier combos125-200ms range. Only affects bare modifier keycodes via COMBO_MUST_HOLD_MODS.
COMBO_MUST_HOLD_MODSenabledModifier combos require hold, not tapKEYCODE_IS_MOD auto-detects bare modifiers. Command keycodes (LGUI(KC_C)) fire on tap.
COMBO_STRICT_TIMERenabledTimer starts from first key press onlyPrevents second-key-press from resetting the combo window.
COMBO_ONLY_FROM_LAYER0Combos always reference base layer keycodesCombos work identically regardless of active layer.

Bigram Collision Analysis

Each combo pair cross-referenced against English bigram frequency on the Graphite layout. Pairs with >0.3% frequency are flagged.

PairFrequencyRiskAssignmentNote
O+U~1.5%highAvoidedHighest-risk pair — intentionally left unassigned
H+A~1.2%safeRight CmdHold requirement (150ms) prevents tap misfires during fast 'ha' rolls
R+T~0.3%mediumCopyMonitor during daily use at 30ms COMBO_TERM
L+D~0.3%safeEscapeProven safe with COMBO_STRICT_TIMER
F+O~0.3%mediumQuick OpenCross-finger roll, monitoring recommended
E+I~0.3%mediumApp SwitchRight hand pair, moderate frequency
T+S~0.1%safeLeft CmdLow bigram frequency
B+L~0.1%safeClose WindowLow bigram frequency
A+E~0.02%safeRight CtrlVery low frequency
N+R~0.05%safeUndoVery low frequency
X+M~0%safePaste'xm' essentially nonexistent in English
M+C~0.02%safeCutVery low frequency
C+V~0%safeRedo'cv' does not occur in English
Q+X~0%safeSave'qx' does not occur in English

Sources


Research conducted 2026-02-27 · QMK firmware24 branch · ZSA Voyager