diff --git a/README.md b/README.md index 4cb0174..2950517 100644 --- a/README.md +++ b/README.md @@ -469,7 +469,7 @@ The AIO firmware enables motion automatically for original Wii Remotes with an e - **Nunchuk stick travel:** the parser reads the 16-byte extension calibration at `A40020` before activating MotionPlus. It validates both checksum bytes and the X/Y min/center/max ordering, then scales each side of each axis independently into the full normalized range, clamps overshoot, and inverts Y. It preserves the notched gate rather than expanding diagonals into square corners. Unreadable/invalid calibration uses documented nominal travel (center 128, ±96), not the full 0–255 byte range. Replacement Nunchuks reload their own calibration. Existing left-stick profile tuning applies; saved right-stick tuning is not copied or overwritten. - **Motion:** ordinary remotes use continuous `0x31`; MotionPlus and Nunchuk use combined `0x35` reports. The parser checks primary/backup accelerometer factory calibration and both fast/slow gyro calibration blocks, including checksums. Invalid calibration disables only the affected sensor rather than inventing readings. MotionPlus selects sensitivity independently for each axis; factory-only zero correction can retain temperature-dependent gyro drift. - **Nunchuk passthrough:** MotionPlus and Nunchuk samples alternate. Held Nunchuk controls persist across gyro packets, moved C/Z bits are decoded, and the remote's accelerometer remains the motion source. Attaching or removing a Nunchuk triggers serialized extension discovery, updates Studio's layout, and clears detached stick/C/Z state. Extension identifiers accept the upstream-compatible type suffix rather than requiring a vendor-specific prefix; initializing extensions get bounded reply-paced retries. Periodic discovery of an inactive external MotionPlus attached alone and Classic Controller MotionPlus passthrough remain unimplemented. Existing ordinary Classic Controller, Wii U Pro, Balance Board and uDraw paths remain separate. -- **Accelerometer swing → button:** in Studio's Macro section, **Wii Remote swing** selects a final output button, Low/Medium/High sensitivity, and an optional held modifier. Disabled by default in every profile. This works on original remotes without MotionPlus or a sensor bar, with either aiming source and even when gyro output is disabled. A fresh, sustained acceleration burst generates one 80 ms press; ordinary gravity-only tilt is rejected. The detector requires 120 ms of settled samples and at least 250 ms after the previous swing to rearm. High sensitivity needs less force but is more susceptible to accidental movement. Modifier release, stale samples, disconnects, profile changes, reserved profile hotkeys and active macros cancel gesture output; physical button holds remain intact. For Zelda on Switch, choose **Y** (logical `west`; use the Switch preview for Nintendo button labels). +- **Accelerometer swing → button:** in Studio's Macro section, **Wii Remote swing** selects a final output button, Low/Medium/High sensitivity, and an optional held modifier. Disabled by default in every profile. This works on original remotes without MotionPlus or a sensor bar, with either aiming source and even when gyro output is disabled. A fresh, sustained acceleration burst generates one 80 ms press; ordinary gravity-only tilt is rejected. Initially, settle the remote for 120 ms to arm it. Subsequent strokes require only a 20 ms lower-force gap and at least 200 ms between presses, so back-and-forth swings do not require full stops. Early rebounds are discarded rather than replayed when the cooldown expires. High sensitivity needs less force but is more susceptible to accidental movement. Modifier release, stale samples, disconnects, profile changes, reserved profile hotkeys and active macros cancel gesture output; physical button holds remain intact. For Zelda on Switch, choose **Y** (logical `west`; use the Switch preview for Nintendo button labels). - **Experimental IR aiming:** `SWITCH_PICO_WII_IR_GYRO=ON` enables camera-derived aiming instead of the mutually exclusive IR mouse experiment. Hold **1 + 2 for two seconds** to switch sources: one rumble pulse selects physical gyro, two select IR. Physical gyro is the connection default. With a Nunchuk, hold **C + 1** to reposition without sending aim motion; 1 alone remains usable. IR horizontal motion uses Nintendo **gyro Z**, matching a captured physical MotionPlus yaw reference; gyro X remains neutral. Vertical output uses gyro Y with **1.5x pitch gain**, conservatively tuned against a physical-gyro capture without changing horizontal gain, camera geometry or filtering. The real accelerometer is retained. Tracking loss stops IR aiming rather than silently switching to physical gyro. Horizontal aiming is user-confirmed in-game; the increased vertical gain still requires in-game qualification. - **IR tracking:** gyro mode follows relative camera-frame movement of one to four persistent spots, without requiring both ends of the bar or expiring a continuously visible single spot after 80 ms. Mutual nearest matches tolerate camera-slot changes; conflicting motion, large jumps, stale reports and complete visibility loss rebaseline rather than replay missing movement. The IR mouse experiment retains its two-end bar model. Relative single-spot tracking cannot distinguish remote roll from pointing motion, so keep the remote upright for aiming. - **Hardware verification:** Remote Plus `B8:AE:6E:21:8F:C2` reconnected after flashing and delivered 100 real USB `0x30` reports with changing accelerometer and gyro values on all axes. Identify dispatched rumble without losing the link. All 72 profile records, names/aliases, active selections, nine pairings and adapter configuration generation 21 / CRC `b58672ac` were unchanged. The subsequent Studio fix detects the attached Nunchuk with live stick and gyro data and renders the matching layout and vertical lock. Native tests cover integrated/external detection, calibration/error boundaries, hotplug ordering, interleaved controls, orientation changes and stale-request rejection. External MotionPlus combinations still need physical qualification. diff --git a/src/firmware/input/wii_swing.cpp b/src/firmware/input/wii_swing.cpp index b14547d..866cc02 100644 --- a/src/firmware/input/wii_swing.cpp +++ b/src/firmware/input/wii_swing.cpp @@ -5,7 +5,8 @@ constexpr uint32_t kStaleMs = 150; constexpr uint32_t kMaximumSampleGapMs = 50; constexpr uint32_t kSettleMs = 120; constexpr uint32_t kPulseMs = 80; -constexpr uint32_t kRearmMs = 250; +constexpr uint32_t kRearmMs = 200; +constexpr uint32_t kReleaseMs = 20; constexpr uint32_t kEvidenceMs = 10; constexpr int32_t kGravity = 4096; constexpr int64_t square(int32_t value) { @@ -55,13 +56,16 @@ bool WiiSwingDetector::update(const WiiAccelerometerSample& sample, magnitude_squared > square(kGravity * 3 / 4) && magnitude_squared < square(kGravity * 5 / 4); - if (settled) { + // Startup still needs a settled reference. Between strokes, a short + // lower-force interval is enough; continuous swinging need not stop. + const bool released = dynamic_squared < square(thresholds[sensitivity] / 2) || + !force_excursion; + if (fired_ ? released : settled) { if (!quiet_) { quiet_ = true; quiet_since_ms_ = sample_ms_; } - if (sample_ms_ - quiet_since_ms_ >= kSettleMs && - (!fired_ || sample_ms_ - fired_ms_ >= kRearmMs)) { + if (sample_ms_ - quiet_since_ms_ >= (fired_ ? kReleaseMs : kSettleMs)) { armed_ = true; } } else { @@ -72,9 +76,13 @@ bool WiiSwingDetector::update(const WiiAccelerometerSample& sample, candidate_ = true; candidate_since_ms_ = sample_ms_; } else if (sample_ms_ - candidate_since_ms_ >= kEvidenceMs) { - pulsing_ = true; - fired_ = true; - fired_ms_ = now_ms; + // Consume even an early rebound. It must fall below the release + // threshold again, rather than firing late when cooldown expires. + if (!fired_ || now_ms - fired_ms_ >= kRearmMs) { + pulsing_ = true; + fired_ = true; + fired_ms_ = now_ms; + } armed_ = false; candidate_ = false; quiet_ = false; diff --git a/src/firmware/input/wii_swing.h b/src/firmware/input/wii_swing.h index b84b121..d31ab8b 100644 --- a/src/firmware/input/wii_swing.h +++ b/src/firmware/input/wii_swing.h @@ -16,8 +16,9 @@ struct WiiAccelerometerSample { class WiiSwingDetector { public: void reset(); - // Returns an 80ms button pulse. Fresh observations must settle before - // arming/rearming; repeated reads cannot extend a pulse or build evidence. + // Returns an 80ms button pulse. Startup needs settled observations; + // subsequent strokes need a brief force release and a 200ms cooldown. + // Repeated reads cannot extend a pulse or build evidence. bool update(const WiiAccelerometerSample& sample, uint32_t now_ms, uint8_t sensitivity, bool allowed); diff --git a/src/switch_pico_bridge/web/profile_editor.js b/src/switch_pico_bridge/web/profile_editor.js index 3e128e0..2b9ed1e 100644 --- a/src/switch_pico_bridge/web/profile_editor.js +++ b/src/switch_pico_bridge/web/profile_editor.js @@ -2161,7 +2161,7 @@ function renderMacro() { -
Triggers one ~80 ms press per deliberate swing and must settle before rearming. High sensitivity triggers more easily but is more susceptible to accidental shakes. The optional held modifier is not consumed by the gesture and remains active for normal mapping.
`; +Triggers one ~80 ms press per deliberate stroke. Settle once to arm; back-and-forth strokes can then repeat after brief lower-force gaps, with at least 200 ms between presses. High sensitivity triggers more easily but is more susceptible to accidental shakes. The optional held modifier is not consumed by the gesture and remains active for normal mapping.
`; elements.macroControls.innerHTML = `