← Back to visualizerFirmware 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
| Setting | Value | Purpose | Notes |
|---|
| COMBO_TERM | 30ms | Window for both combo keys to register | 25-30ms sweet spot for fast typists. Lower = fewer misfires, harder to trigger. |
| COMBO_HOLD_TERM | 150ms | Hold threshold for modifier combos | 125-200ms range. Only affects bare modifier keycodes via COMBO_MUST_HOLD_MODS. |
| COMBO_MUST_HOLD_MODS | enabled | Modifier combos require hold, not tap | KEYCODE_IS_MOD auto-detects bare modifiers. Command keycodes (LGUI(KC_C)) fire on tap. |
| COMBO_STRICT_TIMER | enabled | Timer starts from first key press only | Prevents second-key-press from resetting the combo window. |
| COMBO_ONLY_FROM_LAYER | 0 | Combos always reference base layer keycodes | Combos 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.
| Pair | Frequency | Risk | Assignment | Note |
|---|
| O+U | ~1.5% | high | Avoided | Highest-risk pair — intentionally left unassigned |
| H+A | ~1.2% | safe | Right Cmd | Hold requirement (150ms) prevents tap misfires during fast 'ha' rolls |
| R+T | ~0.3% | medium | Copy | Monitor during daily use at 30ms COMBO_TERM |
| L+D | ~0.3% | safe | Escape | Proven safe with COMBO_STRICT_TIMER |
| F+O | ~0.3% | medium | Quick Open | Cross-finger roll, monitoring recommended |
| E+I | ~0.3% | medium | App Switch | Right hand pair, moderate frequency |
| T+S | ~0.1% | safe | Left Cmd | Low bigram frequency |
| B+L | ~0.1% | safe | Close Window | Low bigram frequency |
| A+E | ~0.02% | safe | Right Ctrl | Very low frequency |
| N+R | ~0.05% | safe | Undo | Very low frequency |
| X+M | ~0% | safe | Paste | 'xm' essentially nonexistent in English |
| M+C | ~0.02% | safe | Cut | Very low frequency |
| C+V | ~0% | safe | Redo | 'cv' does not occur in English |
| Q+X | ~0% | safe | Save | 'qx' does not occur in English |
Research conducted 2026-02-27 · QMK firmware24 branch · ZSA Voyager