Skip to content

Haptic Feedback

Parent Documents:

  • KN-86-Pi-Zero-Build-Specification.md (hardware spec, GPIO budget, BOM — supersedes the archived Pico-era KN-86-Modern-Build-Specification.md)
  • KN-86-Prototype-Architecture.md (Pi Zero hardware, audio subsystem)
  • KN-86-UI-Design-System.md (interaction patterns, sound design)
  • KN-86-Cartridge-Grammar-Spec.md (stdlib API, cartridge authoring surface)
  • KN-86-PCM-Voice-Bark-Addendum.md (companion sensory channel — audio barks)

The KN-86 Deckline sells its fiction through multi-sensory feedback. Amber text on the screen carries narrative. The Kailh Choc switches provide the “Deckline Chatter” — audible clicks as the operator works the grid. The YM2149 PSG punctuates state changes with tones, noise, and (per the bark addendum) short PCM voice stings. What’s missing is the fourth channel: the hands.

Modern devices that feel authoritative — high-end game controllers, the iPhone, mechanical watches — all use haptic feedback as a signal layer. When you press the home button on an iPhone, the click you feel isn’t a real button; it’s an LRA pulse perfectly timed to the press detection. The illusion is total because the physical sensation matches the visual and audio feedback at the millisecond level.

The KN-86 has an opportunity to use haptics as a third narrative channel, coordinated with the display and the PSG. A subtle tick on every keypress reinforces that the device responded. A sharp double-pulse on EVAL makes the commit feel weighty. A sustained buzz when ICE catches you, paired with the “BREACH” bark and the on-screen alert, turns a moment of gameplay into a moment of physical panic. Three signals arriving simultaneously — hand, ear, eye — is how you make a $600 prototype feel like a $6,000 industrial device.

Design constraint: Haptics supplement, never replace, the existing feedback. The device must work identically with haptics disabled (bad actuator, low battery conservation mode, operator preference). Every haptic event has an audio and/or visual counterpart that carries the same information.


Two actuator technologies are viable. The decision between them shapes everything downstream.

2A. Option A — ERM (Eccentric Rotating Mass)

Section titled “2A. Option A — ERM (Eccentric Rotating Mass)”

A tiny DC motor with an off-center weight. Same technology as 1990s pagers and most low-end phone vibrations.

PropertyValue
Size~10mm diameter, ~3mm thick (coin-type)
DriveSingle GPIO through a transistor + flyback diode
Voltage3V typical
Current80–100mA when active
Startup latency20–50ms to reach rated vibration
Stop latency50–100ms to spin down
Cost$0.50–1.50 per unit
Pins required1 GPIO
Driver ICNone (discrete transistor)

Strengths: Dirt cheap. Trivial circuit. One GPIO pin. Available from any supplier in quantity.

Weaknesses: The spin-up/spin-down lag is the killer. You cannot produce a crisp, short tick — everything becomes a “buzz” because the motor is still accelerating when you want it to stop. Haptic events under ~80ms in duration are impossible. Feel is mushy and toy-like.

Appropriate use cases: Sustained alerts (ICE detection, mission failure, low battery), where the vibration is meant to last 300ms+ anyway. Inappropriate for per-keypress feedback.

2B. Option B — LRA (Linear Resonant Actuator) + Driver IC

Section titled “2B. Option B — LRA (Linear Resonant Actuator) + Driver IC”

A voice-coil driving a mass on a spring. Same technology as iPhone Taptic Engine, PS5 DualSense, Apple Watch. Drives a precise frequency, produces sharp pulses with millisecond accuracy.

PropertyValue
Size~10mm × 3.4mm coin LRA (e.g., G1040003D from Jinlong)
DriveI2C-controlled haptic driver IC (DRV2605L or similar)
Voltage3V typical, AC resonant drive
Current60–80mA peak during pulse
Startup latency<10ms (driver IC handles auto-resonance)
Stop latency<10ms (active braking in driver)
Cost$3–5 per unit (LRA + driver IC)
Pins required2 GPIO (I2C SDA + SCL)
Driver ICTI DRV2605L ($2.50) — 123 built-in effects, I2C interface, library support

Strengths: Precise, crisp pulses. Can produce a 10ms “tick” that feels like a button click. Driver IC has a built-in effect library (sharp click, double click, triple click, soft bump, ramp up, long buzz, etc.) — you send an effect ID over I2C and the driver handles the waveform. Zero CPU overhead during playback.

Weaknesses: Costs ~5× more. Needs two GPIO pins (I2C). Driver IC adds an I2C device to the system bus. Slightly more complex firmware integration.

Appropriate use cases: Everything ERM is bad at — per-keypress feedback, EVAL confirmations, subtle state transitions, rhythmic patterns. Also works for sustained buzzes (effects 47–52 in the DRV2605L library are long ramps and buzzes).

The LRA path is the right call for a device whose entire identity is “premium handheld with tactile authenticity.” The extra $3 per unit is trivial in a $600 prototype budget and a production device targeting a prosumer price point. The crispness is non-negotiable — a mushy ERM buzz would undermine the Kailh Choc click that is the device’s most-advertised tactile feature.

For prototype validation, an ERM can be used as a cost-free fallback if the LRA path hits a blocker. But the production spec should target LRA.

The Pi Zero 2 W has abundant GPIO (40 header pins, 26 of them general-purpose). The haptic driver’s I2C bus is free in pin count — no peripheral needs to be sacrificed. If additional I2C peripherals are added later (IMU, RTC, external EEPROM), they share the bus; each additional device is free in pin count.

Recommendation: Bring up LRA + DRV2605L on the Pi Zero 2 W I2C1 bus. Validate the tactile experience. No pin-budget decision is blocking this path.


The LRA should be mounted on the inside of the base shell, directly under the palm rest / switch plate. The operator’s hands rest on the switch plate while operating — any vibration coupled through the plate is felt in both hands simultaneously. This is the same approach used by laptop trackpads (LRA under the deck) and game controllers (LRAs in the grip areas).

ConsiderationDetail
Mount typeDouble-sided adhesive pad (3M VHB) directly to the underside of the switch PCB mounting plate
IsolationFoam gasket around the LRA prevents rattling against the shell
OrientationCoin LRAs are omnidirectional; orientation doesn’t matter
Distance from display>30mm (prevent display controller interference)
Distance from speaker>40mm (prevent acoustic coupling that would muddy both outputs)

LRA peak current (~80mA) is well within the Pi Zero 2 W’s power budget. No additional power regulation needed. The DRV2605L handles its own supply filtering. Add a 10µF decoupling capacitor at the driver IC per datasheet.

On the Pi Zero 2 W:

  • I2C1 SDA → DRV2605L SDA (GPIO 2)
  • I2C1 SCL → DRV2605L SCL (GPIO 3)
  • 3.3V → DRV2605L VDD
  • GND → DRV2605L GND
  • DRV2605L OUT+/OUT- → LRA terminals
  • IN/TRIG pin tied to VDD (use I2C mode, not GPIO trigger mode)

That’s the complete circuit. Two signal pins, two power pins, two output pins, one cap.


/* ---- Haptic Feedback ---- */
/* Built-in effect IDs — a curated subset of DRV2605L library effects,
* mapped to semantic KN-86 interaction moments. Cartridge authors use
* these semantic names rather than raw effect numbers. */
typedef enum {
HAPTIC_NONE = 0,
HAPTIC_KEY_TICK = 1, /* Soft click, ~10ms — per-keypress feedback */
HAPTIC_CONFIRM = 2, /* Sharp click, ~15ms — EVAL commit */
HAPTIC_DENY = 3, /* Double bump, ~40ms — invalid action */
HAPTIC_DRILL = 4, /* Descending ramp, ~60ms — CAR into child */
HAPTIC_EMERGE = 5, /* Ascending ramp, ~60ms — BACK out of depth */
HAPTIC_ALERT = 6, /* Triple tick, ~80ms — attention required */
HAPTIC_WARNING = 7, /* Long buzz, ~300ms — critical state */
HAPTIC_FAIL = 8, /* Sustained pulse, ~500ms — mission failed / ICE breach */
HAPTIC_SUCCESS = 9, /* Rising triple-pulse, ~150ms — mission complete */
HAPTIC_LINK_ESTABLISH = 10, /* Soft sustained, ~200ms — link protocol handshake */
HAPTIC_CART_INSERT = 11, /* Sharp tap, ~20ms — cartridge detected */
HAPTIC_LAMBDA_RECORD = 12, /* Slow pulse, ~40ms — macro recording state */
HAPTIC_LAMBDA_PLAYBACK = 13, /* Per-key soft tick — macro replay feedback */
} HapticEffect;
/* Trigger a haptic effect. Non-blocking — returns immediately, effect
* plays out asynchronously via driver IC. */
void stdlib_haptic(SystemState *state, HapticEffect effect);
/* Trigger a raw DRV2605L effect ID (1-123). For advanced authors who
* want access to the full driver library. */
void stdlib_haptic_raw(SystemState *state, uint8_t effect_id);
/* Stop any currently playing haptic effect. */
void stdlib_haptic_stop(SystemState *state);
/* Check if haptic hardware is present and initialized. Returns false
* on devices without haptic actuators or when initialization failed. */
bool stdlib_haptic_available(SystemState *state);

4B. Firmware-Level Haptic Events (Not Cartridge-Triggered)

Section titled “4B. Firmware-Level Haptic Events (Not Cartridge-Triggered)”

The nOSh runtime fires haptic events for universal interactions. Cartridges cannot suppress these. They must be identical across all cartridges so muscle memory is preserved.

EventEffectRationale
Any keypressHAPTIC_KEY_TICK (if enabled in SYS menu)Confirms press was registered
EVAL pressHAPTIC_CONFIRM (overrides KEY_TICK)EVAL is always significant
BACK with non-empty nav stackHAPTIC_EMERGENavigation feedback
SYS menu openHAPTIC_KEY_TICKConsistent with general UI
SYS hold → force abortHAPTIC_WARNINGEmergency action should feel heavy
LAMBDA record startHAPTIC_LAMBDA_RECORDMode change feedback
LAMBDA record stopSame (second pulse)Mode change feedback
Cartridge insert detectedHAPTIC_CART_INSERTConfirms physical action
Mission board refreshHAPTIC_ALERTNew contracts available
Mission complete (success)HAPTIC_SUCCESSMajor win moment
Mission complete (failure)HAPTIC_FAILMajor loss moment
Link session establishedHAPTIC_LINK_ESTABLISHConnection feedback

Cartridges use stdlib_haptic() for module-specific moments. Guidelines:

  • Co-occur with audio events. Every haptic should have a matching PSG tone or bark. Never a silent buzz.
  • Reserve intense effects. HAPTIC_FAIL and HAPTIC_WARNING are for genuine gameplay consequences, not minor state changes.
  • Budget: ~5–10 cartridge-triggered haptics per 30-minute session. More than this causes fatigue and dilutes meaning.
  • Never block on haptics. Triggering a haptic is fire-and-forget; game logic proceeds immediately.

Example usage:

CELL_ON_EVAL(network_node) {
cell_network_node *self = (cell_network_node *)_self;
if (self->compromised) {
/* Three-channel celebration: bark + haptic + text */
stdlib_bark_play(g_state, "CLEAN");
stdlib_haptic(g_state, HAPTIC_SUCCESS);
nosh_print(g_state, 0, 12, "> EXTRACTED. NO TRACE.");
} else if (ice_triggered(self)) {
/* Failure mode */
stdlib_bark_play(g_state, "BREACH");
stdlib_haptic(g_state, HAPTIC_FAIL);
nosh_print(g_state, 0, 12, "> ICE DETECTED. BURN IMMINENT.");
} else {
stdlib_haptic(g_state, HAPTIC_DENY);
stdlib_sfx_error();
}
}

The SYS menu gains a Haptic submenu with three settings:

SettingValuesDefault
Haptic MasterOFF / ONON
Keypress HapticsOFF / SUBTLE / NORMALSUBTLE
Event HapticsOFF / ONON

Settings persist in DeckState. “Keypress Haptics OFF” disables HAPTIC_KEY_TICK on every key but keeps HAPTIC_CONFIRM on EVAL (EVAL is always felt). “Haptic Master OFF” disables everything including cartridge-triggered effects.


The haptic and bark systems are designed to work in coordination. The bark addendum’s design philosophy — “three channels delivering the same moment” — extends naturally to haptics as the physical channel.

Synchronization principles:

  1. Trigger together. Cartridge code should fire both simultaneously when both are warranted: stdlib_bark_play("BREACH"); stdlib_haptic(HAPTIC_FAIL);
  2. Latency matching. Both systems are non-blocking and fire on the same frame. Any sub-10ms skew is imperceptible.
  3. Intensity matching. A subtle bark (“CLEAN”) pairs with HAPTIC_SUCCESS (sharp triple-pulse). An intense bark (“BURNED”) pairs with HAPTIC_FAIL (sustained pulse). Mismatches (loud bark + weak haptic, or vice versa) feel uncanny.
  4. Graceful degradation. If haptics are disabled (user preference or hardware failure), barks still play. If audio is muted, haptics still fire. Each channel carries enough information on its own.

Combined stdlib helper:

/* Fire a bark and matching haptic together. Convenience function. */
void stdlib_signal(SystemState *state, const char *bark_label, HapticEffect effect);

  1. DRV2605L alternatives: Are there smaller/cheaper haptic driver ICs worth considering? The DA7280 (Dialog Semi) and LC898302 (Sanyo) are alternatives. Is the DRV2605L’s effect library + I2C interface worth the cost premium?
  2. Power budget: LRA peak current is ~80mA. Combined with PSG audio (peak ~20mA), display, and CPU, does the Pi Zero 2 W’s power path have headroom at all operating states, including under USB-MSC update mode? If not, does the LRA need its own rail?
  3. Physical mounting validation: How do we verify the LRA doesn’t cause the display to vibrate visibly, or the speaker to rattle, during sustained effects (HAPTIC_WARNING, HAPTIC_FAIL)?
  1. I2C integration: The codebase has no existing I2C peripherals. Adding the DRV2605L means introducing an I2C driver. Use Linux i2c-dev ioctls directly, or abstract through the platform layer (nosh_platform.h) so emulator and device share an interface?
  2. Async effect playback: DRV2605L effects run for tens to hundreds of milliseconds. The stdlib_haptic() call must return immediately. How do we handle overlapping requests — queue them, cancel the previous, or drop the new?
  3. Emulator haptic simulation: On macOS/Linux desktop, there’s no haptic hardware. Should the emulator visualize haptic events (e.g., a brief screen tint, a text indicator in the debug overlay) to help cartridge authors verify their haptic timing matches their intent?
  1. Haptic budget per session: Is the proposed “5–10 cartridge-triggered haptics per 30 minutes” right? Should this be per-mission, per-phase, or time-based?
  2. Semantic effect mapping: The 13 proposed HapticEffect IDs map to interaction moments. Are these the right moments? Missing any? Too many?
  3. Cartridge-specific haptic vocabulary: Should cartridges be able to define custom haptic sequences (like they define custom PCM barks), or is the nOSh runtime-provided library sufficient?
  1. Tactile quality acceptance: What’s the standard for “good” haptics? Can a first-time user distinguish HAPTIC_CONFIRM from HAPTIC_DENY without seeing the screen? Is a blind user test part of QA?
  2. Durability testing: LRAs have finite life (rated ~1 million cycles typically). With per-keypress haptics enabled, how many months of heavy use before failure? Does this justify socketing the LRA for replacement?

RiskLikelihoodImpactMitigation
GPIO budget unresolvable in productionMediumHighShip haptics on prototype only. Use prototype validation to justify reshuffling production pins during PCB design. Acceptable fallback: production ships without haptics; haptics become a “revision 2” feature.
LRA feels different across unitsLowMediumSpecify a single part number with tight tolerances. QA samples multiple units per batch. DRV2605L auto-resonance detection handles minor variation automatically.
Per-keypress haptics annoy usersMediumMediumDefault to SUBTLE, not NORMAL. Expose clear SYS menu toggle. Ship with haptics on EVAL only by default; full-key haptics as opt-in.
Haptic + audio phase out of syncLowLowBoth systems fire from the same frame. Any real skew is under 16ms (one frame at 60fps), below perceptual threshold for these event types.
DRV2605L supply chain issueLowMediumSecond-source identified (DA7280). Driver abstraction layer allows swap without cartridge changes.
Haptic fatigue over long sessionsMediumLowBudget enforcement in cartridge reviews. Default settings err toward less haptic. Long sessions (>2 hours) show haptic usage stats in SYS menu.
Adhesive mount loosens over timeMediumMediumUse 3M VHB (automotive-grade). Include service note in repair docs. Alternative: mechanical bracket for production.
Battery drain from hapticsLowMediumEach haptic pulse is brief (<500ms) and low current. Estimated <1% additional drain under normal use. SYS menu includes battery-saver mode that disables keypress haptics.

  1. On the prototype, pressing EVAL produces a synchronized experience: Kailh Choc click (audible), PSG confirm tone (~10ms), haptic HAPTIC_CONFIRM pulse (~15ms), and on-screen feedback (next frame). A user describes the device as “feeling crisp” or “feeling expensive” unprompted.
  2. Triggering HAPTIC_FAIL alongside a “BREACH” bark during ICE detection produces an unmistakable, visceral “failure moment” — the operator feels the failure before reading the text.
  3. With all haptics disabled via SYS menu, the device functions identically in every respect except tactile output. No game state, no audio, no display element changes.
  4. Three blind users can distinguish HAPTIC_CONFIRM from HAPTIC_DENY with >80% accuracy after 30 seconds of familiarization.
  5. The LRA survives 100,000 keypress-triggered haptic pulses without performance degradation.
  6. The DRV2605L I2C bus does not interfere with any other system timing (display refresh, PSG audio, key matrix scan).
  7. Cartridge authors can add a haptic trigger to an existing handler with a single line of code: stdlib_haptic(g_state, HAPTIC_ALERT); — no direct driver interaction needed.