switch-pico/SWITCH_FAMILY_HD_RUMBLE_PLAN.md

30 KiB

Switch-family native HD-rumble implementation handoff

Status: implemented; hardware qualification incomplete. This handoff now records the implementation and remaining acceptance work. The roadmap entry is in ADAPTER_PARITY_PLAN.md under “Native Switch-family HD rumble — Implemented, qualification incomplete”.

Goal

Deliver native Nintendo rumble commands to qualified Switch-family Bluetooth controllers without collapsing left/right and low/high-band information to conventional motor magnitudes. This is a separate backend from DualSense PCM synthesis.

Start with a genuine original Switch Pro Controller, then original standalone Joy-Con L/R. Keep third-party Switch-mode controllers on their existing tested fallback until individually qualified. Do not implement Joy-Con logical pairing or assume Switch 2/NSO controllers share the same capabilities.

Current project state and coordination

  • Branch at handoff: feature/controller-profiles.
  • 44a474e: roadmap plan for native Switch-family HD rumble.
  • 3c2d9fa: Set B profiles, catalog migration, recording, optimized transport and native DualSense/XInput work.
  • Other transport qualification/artifact work may still be in progress. Coordinate with the active agent before editing shared files; do not reset, stash, or overwrite its changes. Re-read current code rather than relying on line numbers here.
  • Standard AIO/XInput builds use 300 MHz/1.3 V and optimized CYW43 transport. DualSense now defaults to its physically accepted 32-frame PCM stream with explicit AudioControl initialization and full controls; compact 64-frame mode is an unqualified experiment. Nintendo native output requires explicit approval of the stable physical Bluetooth identity; unapproved devices keep compatibility output.
  • Profiles are schema 6 / 384 bytes; catalog 2 keeps a 512-byte record stride and two 128 KiB arenas. Preserve migration, identity keys, names, active indices and atomic publication.
  • Adapter configuration is now schema 3 / 232 bytes: up to 16 physical Nintendo approvals, independent of profiles. Old schemas 1/2 migrate with no approvals and preserve their existing settings. No controller is approved merely by its name, VID/PID or parser.
  • Preserve Bluetooth bonds, calibration, the UART wire protocol and the private src/firmware/platform/pico/switch2_wake_config.h. Do not expose that file's contents or change the configured wake identity.

Important timing qualification caveat

Do not generalize single-controller DualSense results to mixed-controller loads. An earlier 32-frame/93.75-packet-per-second run passed roughly 65 seconds with one controller, but a later Switch Pro + DualSense test with continuous USB motion reads recorded 80 skipped audio slots over 16.6 seconds, despite receiving all 2,050 USB commands with no command drops or send failures. Maximum permission wait was 17,180 us and the eight outgoing ACL credits were observed exhausted. That led to an earlier 64-frame default based on transport counters. Subsequent physical tests rejected the compact 64-frame format and accepted the 32-frame full-control format with explicit audio initialization, strong separated output and clean stops. Preserve that current choice; mixed-radio physical fidelity is still unqualified.

Nintendo uses no PCM stream. Its small commands still contend for radio scheduling and HCI credits; payload byte rate alone did not predict the measured mixed-controller limit.

Implemented behavior and current evidence

  • input/switch_native_output.* owns four bounded, generation-tagged queues. USB publication uses an IRQ-safe BTstack wake rather than taking the radio async-context lock. Encoding waits for L2CAP can-send permission. A prepared schedule that becomes obsolete is discarded and resynchronized, not replayed.
  • usb/switch/switch_native_haptics.* preserves independent sides/bands and all representable 1/2/3-substep forms. Safe synchronized unity can retain raw bytes. Profile changes revoke unmodified provenance. XInput parsing clears reused HD/raw state rather than accidentally inheriting Nintendo data.
  • Safe amplitude codes stop at 100 (decoder LUT index 228 / Q15 17867). Q15 is not a wire amplitude. Frequencies clamp to indices 1..127. Exact one-packet forms are preferred, then bounded two-packet prefixes/baselines. Unrepresentable scaled sequences retain their time slots and choose legal commands minimizing abs(Q15 error) + 128 * abs(frequency-index error), with lower-command ties and exact silence. This is documented quantization, not a promise of lossless or perceptually equivalent arbitrary scaling.
  • Mono selects the dominant contribution per band before rounding, with left ties. Unequal side counts use the larger count and hold the shorter side's final sample; that temporal quantization is reported. No Joy-Con pairing.
  • Parser counters and effective rumble are per physical device. Queued subcommands refresh rumble and LED state at actual submission. Native ownership cancels duration/delayed/refresh compatibility timers; detach retires state without writing to a dead connection.
  • Local feedback overrides output without freezing the host timeline. Switch commands expire after 50 ms. Unchanged held states coalesce while extending that watchdog; active states retain a 40 ms refresh. XInput holds use left-low 160 Hz / right-high 320 Hz until explicit stop.
  • Read-only management operation 0x43, diagnostic schema 2, returns four 80-byte rows. It separates received, HCI-completed, coalesced and dropped commands. Latency percentiles are 250-us histogram upper bounds for actual submissions; coalesced holds are excluded. No physical-onset claim.

Opt-in commands:

uv run switch-pico-config profiles list
uv run switch-pico-config config native-rumble approve --identity N --yes
uv run switch-pico-config config native-rumble status --json
uv run switch-pico-config config native-rumble revoke --identity N

--identity N uses the physical row from profiles list. The separate native-rumble list command lists persisted approval indices; revocation by --approval N works even after a controller leaves the profile catalog.

Qualification checkpoint

  • Software: 260 repository tests pass, including independent absolute packet vectors, scaled/relative codec cases, actual patched parser/queue tests, and 17 owner lifecycle/credit/coalescing scenarios. All five final firmware variants build; AIO, feasibility and UART artifacts are refreshed.
  • Persistent data: all 24 profiles, active indices, aliases and names matched the pre-migration hardware checkpoint. Configuration migrated generation 9 → 10; explicit approval of the attached Pro produced 11. Profile schema/catalog, bonds and wake identity were not changed.
  • Pro-only radio: genuine Pro reply firmware bytes 03 48; a controlled pre-coalescing 125-Hz run completed all 1,025 commands, with 1,025 submitted reports, zero new drops, and zero congestion attempts.
  • Optimized Pro hardware: the reconnected Pro received 513 commands for separated left/right 160-Hz and 320-Hz pulses. Eight state changes completed, 505 unchanged holds coalesced, and 56 reports were submitted including refreshes, with zero loss/congestion. The user confirmed all four pulses worked and stopped cleanly. Worst observed submission latency was 1,031 us. A subsequent 125-Hz run changed amplitude every command: all 1,025 commands completed as 1,025 reports, with zero coalescing, loss or congestion. The diagnostic maximum reached 1,576 us; neither figure is actuator onset.
  • Mixed radio before coalescing: can-send-driven Pro isolation with DualSense connected completed 195 of 513 commands and dropped 318; the idle DualSense PCM stream skipped 30 slots. Stopping its PCM stream but keeping its input connection completed 276/513 and dropped 237. These failures must not be relabeled as successful fidelity qualification.
  • Optimized mixed holds: concurrent USB writers delivered 2,049 commands per controller at approximately 125 Hz each. Pro completed 65 state changes, coalesced 1,984 holds and lost none; DualSense accepted all 2,049 commands with no audio skips/send failures. An earlier sequential-writer test ran only about 62 Hz per controller and must not be cited as a 125-Hz result.
  • Optimized mixed distinct updates: changing amplitude every 8 ms on both controllers completed 404/1,025 Pro commands and superseded 621; DualSense accepted all host commands but skipped 46 audio slots. This remains a failed high-rate qualification, not evidence of lossless mixed HD rumble.
  • Remaining checks: resolve mixed distinct-update saturation, then qualify approval revocation/resume, LEDs, scaling and stateful XInput on hardware. Pro approval survived firmware reboot and reconnect.
  • Unavailable evidence: no real-console USB/BT rumble capture corpus, original Joy-Con L/R qualification, four-controller hardware result or instrumented actuator onset has been obtained. Do not infer these from synthetic vectors or HCI acceptance.

Active transport experiments

Native rumble remains enabled at the user's request. A reported controller power-off after a test is accepted as a failure observation; its cause is not established. Pro approval was briefly revoked at configuration generation 12, then explicitly restored at 13. Do not revoke it as part of further experiments.

Passive HCI observations found both links in master role, with Pro in sniff mode at interval 24 (15 ms) and DualSense active. During saturation, the Pro held four or five of the eight outgoing ACL buffers. Those eight buffers are distinct from the three controller-to-host incoming flow-control credits. Remote feature responses were Pro bff8cbfecbef7b87 and DualSense bf3a8dfedbff7b87; the local HCI packet-type mask was 0xcc18.

Experiment Commands per controller Pro completed / dropped DS audio skips
Distinct nonzero effects, normal policy 1,025 404 / 621 46
Distinct nonzero effects, Pro no-sniff policy, receive bound 1 1,025 882 / 143 2
Distinct nonzero effects, Pro no-sniff policy, receive bound 2 1,025 947 / 78 5
Changing zero-amplitude commands, normal policy, receive bound 1 513 214 / 298 21
Changing zero-amplitude commands, Pro no-sniff policy, receive bound 1 513 491 / 21 1
Changing zero-amplitude commands, Pro no-sniff policy, receive bound 4 1,025 988 / 36 4

The zero-amplitude runs each coalesced one command; the other commands exercise the 11-byte native transport without driving actuators. They do not prove nonzero-effect fidelity. Exit-sniff requests alone failed to keep the Pro active; that unproven automatic-wake code has been removed. The no-sniff policy is an experimental per-link setting, not a qualified production default.

The diagnostic builds supported same-boot receive bounds 1/2/4 and incoming credit thresholds 2/3 while retaining the existing credit timer and three advertised receive buffers. These experiments did not establish a lossless mixed high-rate configuration.

Temporary build-aio/link_probe.cpp, link_probe_config.h, link_probe_credit.patch, and SWITCH_PICO_LINK_PROBE hooks were removed when packaging the deadline-aware scheduler. Production settings remain one received packet per service turn, two-credit batching, normal link policy, and 300 MHz/1.3 V. Native approvals remain enabled.

For historical reproduction, diagnostic operation 0x44 (schema 2, 468-byte payload) captured link snapshots, selected HCI events, disconnect reasons and control completion state. Its OUT payload <HH> supported:

  • connected Pro handle + policy 1 (role-switch only), 5 (normal role-switch plus sniff), or 0xffff (readback);
  • handle 0xffff + value 2/3 to set incoming-credit batching threshold;
  • handle 0xffff + value 0x101/0x102/0x104 to set receive bound 1/2/4.

The interrupted active-policy matrix is invalid across its reconnect. HCI recorded reason 0x22 (LMP response timeout) about 32.9 seconds after the active-mode transition; this is correlation, not proof of cause. Tests now abort on Pro connection-generation changes, changed link fingerprints, USB errors, or a changed/stopped DualSense native run.

A subsequent same-boot normal-policy matrix completed with stable connections. All cases received 513 commands per controller, but all failed the no-loss criterion:

Receive bound Credit threshold Pro completed / dropped DS skips
1 2 174 / 338 38
2 2 195 / 317 31
4 2 207 / 305 19
4 3 204 / 309 21
2 3 196 / 316 26
1 3 183 / 329 37
1 2, repeated baseline 177 / 335 40

All but the (4,3) trial coalesced one command. Threshold 3 reduced incoming credit-return command traffic, but did not materially improve delivery.

Temporary sniff negotiation controls also support Pro handle + 0x200 | N, where N is 8/12/16/24 slots of 0.625 ms. Attempt/timeout were 4/1. The Pro re-entered 15 ms sniff before deferred and immediate re-entry requests ran; those attempts received Command Disallowed. Briefly gating automatic sniff, requesting interval 12, then restoring policy 5 successfully negotiated 7.5 ms, but no healthy mixed-stream throughput result was obtained: the DualSense stream was in can-send timeout. Original interval 24 / policy 5 were restored and the connected DualSense native stream was re-armed. Do not promote interval-only tuning from command acceptance.

Further interval/window tests with attempt 1 / timeout 0 also failed: 7.5 ms completed 464 of 1,025 Pro commands, dropped 560 and skipped 76 DualSense audio slots; 5 ms completed 162 of 513 Pro commands, dropped 350 and skipped 52 slots. Each coalesced one command. Original timing was restored.

A host-side outstanding-packet quota protected DualSense only by sacrificing Pro delivery. At quotas 8/4/2/1, Pro completed 224/184/97/50 of 513 commands, with 22/8/0/0 DualSense skips. A repeated quota-2 trial completed 95 commands with zero DualSense skips; the repeated quota-8 baseline completed 207 with 27 skips. Quotas were restored and that experimental gate was removed.

Shared deadline-aware scheduler implementation

The user selected a deadline-aware host scheduler rather than making an unqualified power-policy change permanent. input/native_output_scheduler.* now coordinates Nintendo and DualSense native senders:

  • Fixed four-client table, no payload/PCM FIFO or heap allocation.
  • Urgent real stop transitions first; earliest pending deadlines next; rotating equal-deadline ties; bounded waves of synchronous grants.
  • The last free controller ACL credit is held for an earlier announced periodic deadline. This is host admission, not reserved on-air time.
  • Future-reservation changes do not dispatch synchronously. A regression reproduced the former rollover race: announcing the next PCM interval dispatched later Pro work before the already-due PCM request was registered.
  • Every send/abort releases one grant, identified by connection generation. A stale completion cannot release a replacement connection's grant.
  • Generic LED FIFO handoffs preserve fixture/drain ownership, without retaining a notification that prevents the next PCM request.
  • Both writers retain their original packet formats, gains, command lifetimes and generation/expiry rules. No Bluetooth power-policy commands are added.

Software verification: 261 repository tests pass, including 11 dedicated arbiter scenarios and both native sender lifecycle suites. All five firmware variants build. High-rate radio qualification remains a separate failure: the corrected scheduler candidate completed 342/1,025 Pro commands, coalesced one, dropped 682, and skipped 48 DualSense audio slots under a zero-amplitude changing-command workload. Both connections stayed stable. A mixed held-effect run completed 33 state changes, coalesced 992 holds and dropped none out of 1,025 Pro commands, with 197 reports including refreshes; DualSense accepted all host commands and skipped one audio slot. These are measured limits, not a claim that host arbitration solves the remaining mixed-transport limit.

Final packaged-build check: all 24 profiles, active indices, aliases and names were preserved; adapter configuration remained generation 13 with Pro native approval enabled. A 16.6-second mixed held-effect run received 2,049 commands per controller, with zero Pro drops, 1,984 coalesced holds, stable connections, and one DualSense audio-slot skip. The user accepted this result as sufficient for the current setup. No further power-policy tuning was applied, and no lossless high-rate, Joy-Con, four-controller or captured-game qualification is implied by that acceptance.

Linux's current Nintendo driver also documents disconnect risk from excessive output traffic and uses input-report-aware throttling. This is corroborating timing evidence, not code incorporated into this project: https://github.com/torvalds/linux/blob/master/drivers/hid/hid-nintendo.c

Scheduling literature: applicable principles, not a drop-in port

User-supplied paper: Moonbeom Kim and Jeongyeup Paek, Multiconnection Scheduling With Fair Resource Management for Scalable Bluetooth Low-Energy Networks (EMBLEM, IEEE IoT Journal, 2026; online December 2025).

The full text was reviewed. EMBLEM operates in a modified central BLE link layer on nRF52840/Mynewt/NimBLE, using Bluetooth 5.3 connection subrating, anchor placement, event preemption/extension, measured event durations and guard resources. Our Pro/DualSense links are Bluetooth Classic BR/EDR; Classic sniff/subrating is not LE connection subrating. The CYW43439 firmware owns radio scheduling, and the HCI APIs used here do not expose equivalent anchor placement or event preemption. Its results therefore do not establish our achievable capacity or justify copying its BLE constants.

Useful next experiments/design directions:

  • Treat both native outputs as clients of one host-side admission scheduler. Use deadlines/age plus bounded per-link service, not independent writers consuming credits opportunistically. This cannot guarantee on-air scheduling.
  • Budget measured queue/credit occupancy and estimated exchange cost, not payload bytes alone. HCI completion delay is a proxy, not measured airtime.
  • Examine window duration and phase as well as interval. For Classic sniff, attempt/timeout and negotiated interval matter together; shortening only the interval can reserve too much service time for one link.
  • Retain guard margin for measured jitter/retransmissions. Exclude reconnects, stopped streams, and coalesced holds from inappropriate rate/latency estimates.
  • Adapt slowly using smoothed demand and hysteresis, rather than issuing radio-parameter updates on every 8 ms rumble change. EMBLEM's illustrated adaptation takes about four seconds, not a per-haptic-frame response.
  • The default application output periods share a 32 ms hyperperiod: four 8 ms Pro updates and three 32-frame/3-kHz DualSense reports. A small deadline/phase analysis is sufficient; the paper's 1,023-node bitmap tree is unnecessary for four slots. This application calendar is not a Bluetooth radio calendar.

Related sources:

Read these code paths first

Area Files / symbols Relevant facts
Switch host decoder src/firmware/usb/switch/switch_haptics.h/.cpp, SwitchHapticsDecoder, ControllerRumbleOutput, SwitchHapticsFrame Two sides with up to three substeps; original eight bytes now have explicit validity/unmodified provenance.
Intensity scaling src/firmware/profile/controller_profile_transform.cpp, controller_profile_scale_host_rumble Strong scales low band and weak scales high band on both sides. Q15 amplitudes are decoder-normalized values, not raw Nintendo amplitude codes.
Routing and lifetime src/firmware/input/bluepad32_input_backend.cpp Generation-tagged slots, compatibility mailbox, native submission, local/profile feedback, controller ready/disconnect events. Preserve slot isolation.
Native Nintendo owner/encoder src/firmware/input/switch_native_output.*, src/firmware/usb/switch/switch_native_haptics.* Per-physical opt-in, bounded queues, can-send-driven serialization, safe encoding and coalescing.
Existing Nintendo output patches/bluepad32-sdl3-imu.patch; generated build-aio/_deps/bluepad32-src/src/components/bluepad32/parser/uni_hid_parser_switch.c Modify the patch/build-local copy, not the upstream checkout or arbitrary SDK files.
Parser functions send_subcmd, switch_encode_rumble, switch_send_dual_rumble_now, switch_stop_rumble_now, set_led, fsm_enable_rumble Existing conventional path enables vibration with 0x48, uses fixed frequencies and a 40 ms refresh.
Transport src/firmware/input/haptics_transport_probe.*, src/firmware/platform/pico/cyw43_packet_transport.c, patches/btstack-credit-batch.patch Bounded receive fairness, packet-level reads, real per-handle credit accounting. Do not remove incoming flow control or invent extra controller credits.
Prior evidence HAPTICS_EXPERIMENT.md Distinguishes measured submission timing from physical actuator onset and preserves the accepted controlled-effect reference.

Parser ownership decisions

  1. Sequence state is per physical parser instance, shared by rumble/subcommands.
  2. Applicable subcommands carry the exact last-successful rumble bytes at send.
  3. Ownership transitions cancel compatibility timers and retire queued rumble.
  4. switch_encode_rumble() remains the conventional fixed-frequency fallback; independent native bands and compressed substeps use the new C++ encoder.
  5. Bluepad32 explicitly treats Joy-Cons as separate, horizontally mapped controllers. There is no existing two-Joy-Con logical pair to route stereo output into.

Model policy

  • Initial target: original genuine Pro Controller 057E:2009.
  • Next: original Joy-Con L 057E:2006, Joy-Con R 057E:2007.
  • A Pro-like name, VID/PID or parser type is not sufficient proof of full native compatibility; clones can present the same identity. Use qualified model/firmware evidence and conservative handling of ambiguous devices.
  • Preserve the 8BitDo Ultimate's tested enable/fixed-frequency/refresh behavior until that exact model passes native qualification.
  • Do not send DualSense 0x32 PCM packets to Nintendo devices.
  • Do not apply the DualSense 2x/0.8-power response curve to Nintendo actuators.
  • Switch 2 controllers and NSO/retro models require separate protocol and actuator qualification. Some Nintendo-family devices have no HD-rumble actuators.

Implementation sequence

1. Establish protocol truth with a genuine Pro Controller

Capture real console USB rumble words and Bluetooth reports for:

  • neutral and explicit stop;
  • each actuator independently;
  • low and high bands independently and together;
  • repeated words, absolute and relative commands;
  • all observed one/two/three-substep forms;
  • frequency sweeps and safe amplitude changes.

Validate accepted Bluetooth encodings, byte order, amplitude normalization and repeated-word behavior. The older public fixed-state tables do not prove every compressed command's behavior. Use independent golden data and real controller acceptance, not only decode(encode(x)) against the same implementation.

2. Preserve command information and add bounded delivery

Extend the existing rumble envelope with original eight-byte data and explicit validity/unmodified state while retaining the decoded, profile-scaled timeline. Conventional XInput/UART/local feedback must not accidentally acquire valid raw Nintendo bytes.

Use a fixed-capacity, generation-tagged Core-0-to-Core-1 command queue. Add one native output owner per physical Switch device, integrated with the existing parser's sender. No heap allocation, PCM FIFO or unbounded catch-up loop.

Nintendo rumble-only Bluetooth output is report 0x10: transaction byte, report ID, four-bit counter and eight rumble bytes, 11 bytes before L2CAP/HCI/radio overhead. At 125 reports/s that is about 1,375 payload bytes/s. If the original command can be sent directly, it need not inherit DualSense's PCM lookback. Measure actual queue/submission delay before making latency claims.

3. Implement scaling and state recovery, not just unity passthrough

Raw forwarding is a fast path only when profile gain is unity and the physical controller's command state is synchronized. For changed gain, encode the scaled low/high-band timeline using verified Nintendo encodings and safe amplitude limits.

  • Preserve exact silence and valid unity behavior.
  • Quantize against the supported Nintendo amplitude/frequency representation; do not treat normalized Q15 amplitudes as wire codes.
  • Retain all representable substeps.
  • If scaling makes a multi-step sequence impossible to encode in one word, explicitly qualify a bounded legal packet schedule or documented quantization policy. Do not silently replace it with peak/latest magnitude output.
  • After overflow, dropped history, feedback or reconnect, establish a legal absolute current-state/neutral baseline before forwarding commands that depend on prior state.
  • Never replay an obsolete vibration backlog to catch up.

4. Centralize LEDs, local feedback, stop and disconnect

The same per-device owner must serialize rumble-only reports and subcommands. Piggyback current effective rumble into applicable 0x01 reports rather than interrupting an effect with default/zero fields.

Local confirmation temporarily overrides host output, while the host timeline keeps advancing. After confirmation, resume the current host state, not an expired effect.

Preserve the existing 50 ms Switch-command expiry policy. Prioritize explicit stop. Cancel all parser rumble timers when transferring ownership or disconnecting; callbacks and queued commands from an old generation must never touch a replacement device. Preserve ordinary setup/calibration and subcommand replies.

For XInput input, use two-magnitude stateful effects with fixed native carriers and explicit zero/host-lifetime stop. XInput does not contain Nintendo frequency or substep detail.

5. Add explicit single-actuator and third-party behavior

A standalone Joy-Con has one actuator but is currently exposed as a standalone controller. Do not simply discard whichever host side is absent. Define a deterministic mono downmix preserving each band's dominant contribution, with a documented frequency/tie rule and safe amplitude limits. Spatial fidelity is necessarily lost.

Routing a stereo left/right pair belongs with a separate logical Joy-Con pairing feature; do not bundle that input-topology change into this task.

Third-party models remain on their proven compatibility policy unless their native behavior is independently qualified. Retain useful existing rumble rather than broadly enabling an unverified protocol.

6. Qualify and enable per model

Permanent regression cases should defend observable behavior:

  • independent golden packet/codec vectors and neutral values;
  • zero, unity and intermediate profile intensity;
  • band/actuator isolation and substep order;
  • per-device counter wrap and multiple devices;
  • LED/subcommand coexistence during effects;
  • local feedback resume, timeout and explicit stop;
  • queue pressure, loss/resynchronization, disconnect/reuse;
  • compatibility fallback and unsupported models.

Run existing relevant suites, then the complete repository suite:

uv run pytest -q tests/test_switch_haptics_native.py \
  tests/test_controller_profile_transform_native.py \
  tests/test_bluepad32_backend_lifecycle_native.py \
  tests/test_prepare_bluepad32.py tests/test_usb_output_driver_native.py
uv run pytest -q

Build affected AIO, XInput and UART configurations. build.py flashes by default; use CMake for build-only checks and coordinate hardware access before flashing.

Hardware acceptance: safe frequency/amplitude sweeps; actual captured game effects; one versus four controllers; mixed Nintendo/DualSense traffic; simultaneous motion/input, LEDs and profile writes; reconnect and stop. Report p50/p95/p99/worst host-receipt-to-HCI submission, lost/resynchronized commands and physical actuator onset where instrumentation exists. HCI acceptance is not proof of playback.

Enable native output only for models that pass qualification. Keep the fallback available for ambiguous/unsupported devices. Do not claim full Switch-family support from testing a single Pro Controller.

Non-goals

  • Nintendo controller firmware updating or modifying calibration flash.
  • Bond resets or wake-identity changes.
  • USB host support, audio/headphone output, NFC/IR or adaptive triggers.
  • New per-game detection, scripts or unbounded action layers.
  • Re-tuning DualSense gain or general overclock experiments as a side task.
  • Pretending unmeasured physical latency or cross-actuator force equivalence.

Protocol references

Treat community reverse-engineering references as evidence to validate, not an official guarantee. Preserve applicable source licenses and do not copy noncommercial reference implementations into this project without resolving their licensing.