feat(native-usb): add 32-frame HD gameplay rumble and USB flight diagnostics
Preserve native frequency/amplitude timelines for DualSense PCM output, remove the unsupported 64-frame path, and retain compatibility rumble for other controllers. Use the validated 300 MHz sampling phase and restrict non-bondable Classic discovery autoconnect to previously paired peers. Buffer native-hub UART stdout and release USB IRQs around port-reset callbacks. Add observer liveness and pre-SETUP root-response observations without changing recovery behavior. Cover transport and haptics boundaries. Record the user-accepted 0.108 trial: controls remained responsive and rumble felt fine. Instrumentation changes timing; the disconnect root cause and long-term reliability remain unqualified. Validation: 624 tests, 11 affected firmware/probe builds, and on-device concurrent USB and HD-auto-start checks. Private captures, generated images, and unrelated working-tree files are intentionally excluded.
This commit is contained in:
parent
ae1ca51b9a
commit
80b788099b
46 changed files with 2873 additions and 412 deletions
|
|
@ -4,6 +4,25 @@
|
|||
|
||||
Native Nintendo HD-rumble decoding and stateful XInput motor strengths feed the DualSense Bluetooth PCM backend. Standard AIO/XInput builds enable the qualified 300 MHz/1.3 V transport by default and auto-arm the first eligible DualSense that becomes ready, in any slot. One native stream is selected at a time; other controllers retain compatibility rumble. The deterministic transport fixture remains available. Console gameplay was user-tested; precise actuator-onset latency and perceptual equivalence to Nintendo hardware are not claimed.
|
||||
|
||||
Native Joy-Con USB `GAMEPAD`/`DUALSENSE` translation was introduced in 0.103,
|
||||
which regressed Switch input and was rolled back. The 0.105 candidate corrects
|
||||
the measured 300 MHz USB sampling offset and prevents unpaired Classic discovery
|
||||
from competing with HD startup. The 0.106 candidate additionally buffers native-hub
|
||||
UART stdout and releases USB IRQs around child-reset callbacks, addressing a
|
||||
captured foreground-reply delay. PC protocol and PCM results do not establish
|
||||
Switch-side controller recognition or physical input/actuator latency.
|
||||
The user confirmed initial game response on 0.106, then loss of response after
|
||||
setting the controller down. Bluetooth reports continued while USB input and
|
||||
address-routing progress stopped. Sustained Switch input remains unqualified;
|
||||
0.107 adds observer-progress diagnostics and 0.108 records root-response
|
||||
observations, not a recovery fix. Both retain enabled 32-frame HD. On the
|
||||
installed 0.108 image the DualSense stream auto-started in gameplay mode;
|
||||
6,686 packets had been sent with no skips or send failures at the PC snapshot.
|
||||
That counter check issued no motor commands and does not qualify physical rumble.
|
||||
The user subsequently reported no freeze and satisfactory rumble on a Switch
|
||||
trial and accepted 0.108 for now. This does not establish the disconnect's root
|
||||
cause, long-term reliability, or physical latency.
|
||||
|
||||
The user observed 1–2 seconds of gameplay-to-haptics delay in OMP session `01a06fa9-cdc7-72de-ac0e-7de08c355f06`. Both a DS5Dongle-style 0x39 stream and a short 0x32 stream failed after continuous silence, latest-state replacement and can-send callbacks were tried. Do not repeat those changes as newly discovered fixes or attribute the observed delay to profile feedback.
|
||||
|
||||
Recovered source and retained object identify three concrete issues:
|
||||
|
|
@ -34,22 +53,22 @@ and 96, with the opposite channel zero and no compatibility-selector reports
|
|||
during the tones. The user confirmed strong, distinct sides and clean stops at
|
||||
peak 96/127. The gain curve was not increased to obtain this result.
|
||||
|
||||
The compact 64-frame/buffer-16 candidate felt worse despite zero skipped packets.
|
||||
It is retained only as an explicit, physically unqualified experiment. That
|
||||
comparison changed the control header and buffer field as well as frame count;
|
||||
it does not establish that batching alone caused the difference. The accepted
|
||||
The user reports that **64-frame transport does not work**. Its generator and
|
||||
build selection have been removed; it is not a fallback or a candidate to retry.
|
||||
Earlier compact-format comparisons changed the control header and buffer field
|
||||
as well as frame count, so they do not isolate batching as the cause. The accepted
|
||||
32-frame trial had ten skipped slots across its strong-pulse run and no send
|
||||
failures, so mixed-radio and long-duration qualification remain outstanding.
|
||||
Earlier results below are historical transport measurements, not approval of
|
||||
the current or rejected formats' physical fidelity.
|
||||
failures; mixed-radio and long-duration qualification remain outstanding.
|
||||
Earlier results below are historical measurements, not approval of a rejected
|
||||
format's physical fidelity.
|
||||
|
||||
## Implementation contract
|
||||
|
||||
1. AIO/XInput defaults enable `SWITCH_PICO_HAPTICS_EXPERIMENT`, `SWITCH_PICO_HD_RUMBLE`, packet-level CYW43 reads and bounded HCI credit batching at 300 MHz/1.3 V. UART is unchanged. Preserve wake identity, pairing storage and USB modes. Incoming flow control and FIFO capacities remain unchanged; the controller's advertised outgoing capacity is eight ACL packets on this hardware.
|
||||
2. One selected Sony DualSense/DualSense Edge, Bluetooth Classic, sufficient negotiated MTU. Auto-arm chooses the first eligible ready controller, not necessarily slot 0, and later controllers do not steal an active stream. The fixture requires explicit start. Idle native output remains silent. Other devices use compatibility output unless explicitly approved for the separate Nintendo-native backend described in `SWITCH_FAMILY_HD_RUMBLE_PLAN.md`.
|
||||
3. Report 0x32 plus A2 remains a 143-byte L2CAP SDU. The first report is state-only: sequence/tag byte 0x10, sized state block 0x90/63, and valid flag0 0x80 to write AudioControl with default route/MicSelect. Other state validity flags stay clear: no volume, preamp, mute, trigger or LED change. It carries no PCM. Default subsequent controls are `{0x91,7,0xfe,0,0,0,0,0xff,counter}`, followed by `{0x92,64}` and one 64-byte PCM block (32 stereo frames). The data counter begins at zero after initialization and advances by one. The 0xff field is a reference parameter, not an established millisecond duration. Explicit 64-frame mode retains compact controls `{0x91,3,0x62,16,counter}`, two blocks under 0xd2, and a counter advancing by two; it is not the accepted default. Padding and Bluetooth CRC remain deterministic. No speaker/microphone stream, USB audio endpoint, Opus or resampler is added.
|
||||
4. At 3 kHz, 32/64 stereo frames require 93.75/46.875 reports/s. Absolute rational deadlines preserve fractional time and skip obsolete packets after stalls rather than burst-replaying them. Timer wakeups account for SDK +1 tick. Can-send permission and audio deadlines remain separate; flags are armed before requests and synchronous callbacks cannot recursively generate a stream.
|
||||
5. The default deterministic fixture is 576 reports over 6.144 seconds: 96 priming slots, four cycles of left 100 Hz / silence / right 200 Hz / silence (24 reports = 256 ms per phase), then 96 trailing-silence reports. The state-only initialization occupies the first priming slot and counts as one report, with zero PCM frames. Explicit 64-frame mode preserves the same timeline with 288 total reports, 48 priming/trailing slots and 12 reports per phase. Peak remains 32/127, not full-strength rumble or a calibrated physical-force percentage. Gameplay has no one-second priming pattern and uses timestamped Switch commands. Stop restores compatibility output; disconnect cancels without stale-pointer use.
|
||||
3. Report 0x32 plus A2 remains a 143-byte L2CAP SDU. The first report is state-only: sequence/tag byte 0x10, sized state block 0x90/63, and valid flag0 0x80 to write AudioControl with default route/MicSelect. Other state validity flags stay clear: no volume, preamp, mute, trigger or LED change. It carries no PCM. Subsequent controls are `{0x91,7,0xfe,0,0,0,0,0xff,counter}`, followed by `{0x92,64}` and one 64-byte PCM block (**32 stereo frames**, not 64 frames). The data counter begins at zero after initialization and advances by one. The 0xff field is a reference parameter, not an established millisecond duration. Padding and Bluetooth CRC remain deterministic. No speaker/microphone stream, USB audio endpoint, Opus or resampler is added.
|
||||
4. At 3 kHz, 32 stereo frames require 93.75 reports/s. Absolute rational deadlines preserve fractional time and skip obsolete packets after stalls rather than burst-replaying them. Timer wakeups account for SDK +1 tick. Can-send permission and audio deadlines remain separate; flags are armed before requests and synchronous callbacks cannot recursively generate a stream.
|
||||
5. The deterministic fixture is 576 reports over 6.144 seconds: 96 priming slots, four cycles of left 100 Hz / silence / right 200 Hz / silence (24 reports = 256 ms per phase), then 96 trailing-silence reports. The state-only initialization occupies the first priming slot and counts as one report, with zero PCM frames. Peak remains 32/127, not full-strength rumble or a calibrated physical-force percentage. Gameplay has no one-second priming pattern and uses timestamped host commands. Stop restores compatibility output; disconnect cancels without stale-pointer use.
|
||||
6. No historical PCM FIFO. Generate only the current due block when transmission is permitted; bounded control mailbox across cores. Record packet counts, skipped blocks, failed sends, synchronous callbacks, generation cost, send gaps, lateness, request wait and first-tone timestamps. HCI submission is not physical actuator onset.
|
||||
7. Host `haptics-experiment start`, `gameplay`, `status`, `stop`, and `profile` retain USB management framing. AIO builds enable these operations; explicitly disabled/UART builds do not. Operation 0x40 uses schema 5 and transport profiling uses schema 3. Both fixture and gameplay diagnostics report the actual configured frame count; host metadata derives packet counts and timing from it. Update firmware and host tools together.
|
||||
8. Regression coverage must include synchronous callback delivery, rational clock and late wakeups, reference packet interpretation, finite completion/stop, disconnect/reconnect and compatibility restoration. Native probes cannot prove controller acceptance or physical latency.
|
||||
|
|
@ -71,11 +90,11 @@ cmake --build build-hd-rumble
|
|||
|
||||
Load `build-hd-rumble/switch-pico.elf` or `.uf2`, or use the standard `build.py --aio` entry point and `firmware/switch-pico-aio.uf2`. Both use the optimized native path. Reconnect a DualSense with PS if needed. Manual selection uses `haptics-experiment gameplay --slot N` after stopping any active run; CLI slots are zero-based, so the second controller is `--slot 1`. Manual arming does not persist across power cycles. `stop` disarms the native stream and restores compatibility output. A new eligible connection may auto-arm; there is no periodic re-arm that defeats an explicit stop.
|
||||
|
||||
The decoder preserves each actuator's one-to-three ordered substeps and frequency indices. Amplitudes become linear Q0.15 values via precomputed lookups; compatibility magnitudes retain their previous mapping. Profile strong/weak scales apply to the low/high bands of both actuators without discarding substeps.
|
||||
The original Switch decoder preserves each actuator's one-to-three ordered substeps and frequency indices. Amplitudes become linear Q0.15 values via precomputed lookups; compatibility magnitudes retain their previous mapping. Profile strong/weak scales apply to the low/high bands of both actuators without discarding substeps.
|
||||
|
||||
The synthesizer has independent left/right low/high phase accumulators. Frequencies are `40 * 2^(index/32)` and `80 * 2^(index/32)` Hz. Each Switch command occupies an 8 ms window, split into 24/12/8 PCM samples per substep for counts 1/2/3. New reports supersede unplayed old substeps; identical compressed words hold final state rather than replaying deltas. Each Switch-updated side expires after 50 ms, matching the existing conservative timeout policy.
|
||||
|
||||
Standard gameplay uses 10.667 ms causal lookback. The explicitly selected, unqualified 64-frame experiment uses 21.333 ms. Fixed 16-entry cross-core and synthesis histories contain commands, not PCM. Overflow is counted and obsolete sample intervals are not replayed. XInput holds use a distinct persistent command: strong/low magnitude drives the left 160 Hz band, weak/high magnitude drives the right 320 Hz band, until a new command or zero stop. They do not fake refreshes to evade the 50 ms Switch watchdog. Retained XInput state is seeded once per native run, including manual re-arming after compatibility output.
|
||||
Gameplay uses 10.667 ms causal lookback. Fixed 16-entry cross-core and synthesis histories contain commands, not PCM. Overflow is counted and obsolete sample intervals are not replayed. XInput holds use a distinct persistent command: strong/low magnitude drives the left 160 Hz band, weak/high magnitude drives the right 320 Hz band, until a new command or zero stop. They do not fake refreshes to evade the 50 ms Switch watchdog. Retained XInput state is seeded once per native run, including manual re-arming after compatibility output.
|
||||
|
||||
Native gameplay uses balanced **2x low/high gain after profile scaling**, followed by a gentle **0.8-power curve** on the combined amplitude. This lifts quiet and medium effects while retaining their low/high ratio. The curve is a 257-entry lookup with integer interpolation, not per-sample floating-point math. Combined weights are capped at 65535 to avoid overflow and clipping. Zero remains zero. The amplitude curve does not alter carrier frequencies or local-confirmation gain; packet timing follows the transport configuration above. This response replaced the initial 1.5x and low-band-only experiments after user comparison.
|
||||
|
||||
|
|
@ -83,6 +102,48 @@ Local confirmation remains a transient overlay and resumes the current host stat
|
|||
|
||||
The gameplay stream continues with silence while idle. It stops on disconnect, explicit stop, or a stalled send-permission watchdog. Existing LED feedback can drain without switching the controller out of native haptics. Continuous idle streaming trades power for avoiding repeated audio-mode startup.
|
||||
|
||||
### Native Joy-Con USB input
|
||||
|
||||
Native Output Report `0x01` preserves both 10-bit frequency codes and both
|
||||
10-bit amplitudes in each five-byte sample. It does not quantize frequencies
|
||||
through the original Switch's seven-bit representation. The frequency model is
|
||||
`10 * 2^((code - 1) / 96)` Hz; active bands accept codes 1–670, and a zero-amplitude
|
||||
band may retain any ten-bit code. Raw amplitudes 0–1023 are normalized to Q0.15
|
||||
after low/strong and high/weak profile gains on both actuators.
|
||||
|
||||
Each native substep occupies **16 PCM frames (5.333 ms)**, rather than dividing
|
||||
the compatibility scheduler's 12 ms approximation or the original Switch's
|
||||
8 ms window. The final sample holds only until 50 ms after original USB receipt.
|
||||
Count-zero HOLD and malformed reports do not refresh that deadline.
|
||||
|
||||
Instances A-R/A-L/B-R/B-L map to their assigned source's right/left actuators.
|
||||
Only the selected DualSense uses PCM; Wii and other/unselected controllers keep
|
||||
bounded conventional output. Pending, draining and restoring HD ownership never
|
||||
permits compatibility fallback. Cancellation and mailbox admission share an
|
||||
atomic backend-to-haptics lock boundary; no driver or timer callback runs there.
|
||||
Source epochs discard retired host work without restarting a still-selected
|
||||
physical connection or disturbing another pair.
|
||||
|
||||
Built-in cues use side-local PCM overlays. Cue completion requires a successful
|
||||
PCM block intersecting the actual shortened cue interval—not mailbox acceptance,
|
||||
a state-only setup packet or a late silent packet. Already submitted lookback
|
||||
samples cannot be recalled. Local/profile feedback remains higher priority.
|
||||
The internal PCM-coverage timestamp does not change USB diagnostic schema 5.
|
||||
|
||||
Native-hub HD builds require 300 MHz, packet-level reads and HCI-credit batching.
|
||||
The software USB observer compiles for 25 cycles/bit with default phase **12**;
|
||||
240 MHz compatibility builds retain 20 cycles/bit and phase 4. The offset is
|
||||
relative to a software edge timestamp: it cannot be scaled as a bit-period
|
||||
fraction. Phase 5 in 0.103 failed child controls even with HD stopped; phase 12
|
||||
passed the loaded comparison without changing the receiver instruction path.
|
||||
Clock mismatch fails detached, and attach-last startup remains intact.
|
||||
|
||||
Closed-window Classic discovery now skips unpaired peers before paging.
|
||||
Stored peers and explicit pairing remain supported; incoming and BLE paths are
|
||||
unchanged. This avoids the measured startup competition without longer
|
||||
send-permission timeouts, extra ACL credits, 64-frame packets or automatic retries.
|
||||
See `README.md` for the comparison and qualification limits.
|
||||
|
||||
## Building and running
|
||||
|
||||
Use the repository's provisioned Pico SDK/toolchain environment. These CMake commands only build; they do not flash the adapter:
|
||||
|
|
@ -116,7 +177,7 @@ USB vendor operation 0x40: OUT `{action, slot}` (0=stop, 1=finite fixture, 2=gam
|
|||
|
||||
- Seventeen little-endian u32 fields: run_id, connection_generation, start_us, generated_packets, sent_packets, skipped_packets, send_failures, can_send_requests, synchronous_callbacks, max_generate_us, max_send_gap_us, max_lateness_us, max_request_wait_us, first_tone_due_us, first_tone_sent_us, last_sent_us, elapsed_us.
|
||||
- Four u8 fields: state, slot, last_error, reserved (zero).
|
||||
- Byte 72: mode (0=fixture, 1=gameplay); byte 73: actual stereo frames per packet (32 or 64); byte 74: whether the last successfully submitted PCM packet was nonzero (0/1); byte 75: zero reserved. Nonzero PCM is firmware output evidence, not measured actuator motion.
|
||||
- Byte 72: mode (0=fixture, 1=gameplay); byte 73: actual stereo frames per packet (32 in current firmware; historical images may report 64); byte 74: whether the last successfully submitted PCM packet was nonzero (0/1); byte 75: zero reserved. Host decoding of historical metadata does not enable 64-frame output. Nonzero PCM is firmware output evidence, not measured actuator motion.
|
||||
- Little-endian u32 at 76: `host_updates`; at 80: `dropped_updates`.
|
||||
- State: idle=0, pending=1, running=2, completed=3, stopped=4, disconnected=5, unsupported=6, error=7. Disabled build reports unsupported.
|
||||
- Microsecond timestamps are low 32 bits of Pico uptime; use unsigned modular differences for this bounded experiment. Host receipt time is not a hardware onset measurement.
|
||||
|
|
@ -156,7 +217,7 @@ An explicit `SWITCH_PICO_SYS_CLOCK_MHZ=400` plus `SWITCH_PICO_OVERCLOCK_MV=1400`
|
|||
|
||||
## Verification and acceptance
|
||||
|
||||
Build normal and opt-in firmware, run focused regressions, verify time-critical symbols and retained wake configuration. Exercise the actual USB CLI and connected controller. Capture live counters during the finite run. The current two-block format requires 46.875 reports/s and nominal HID+A2 traffic of 6,703.125 bytes/s, before L2CAP/HCI/radio overhead. Acceptance is 288/288 submissions with zero skipped slots and failures, repeated runs, bounded stop and continuing controller input.
|
||||
Build affected firmware, run focused regressions, verify time-critical symbols and retain wake configuration. Hardware tests require authorization: then exercise the actual USB CLI and controller and capture live counters. The 32-frame format requires 93.75 reports/s and nominal HID+A2 traffic of 13,406.25 bytes/s before L2CAP/HCI/radio overhead. Cadence acceptance is 576/576 submissions with zero skipped slots and failures, repeated runs, bounded stop and continuing controller input; historical results below do not supersede that criterion.
|
||||
|
||||
Physical acceptance requires correlating first-tone scheduling/sending with actuator onset using an accelerometer/contact microphone or a synchronized observation. Record controller model/firmware, packet gaps and CPU generation time. A successful `l2cap_send` is not an acknowledgement of playback. Only a measured low-latency result permits integration with Nintendo's per-side, per-band timeline.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue