Repository navigation
Motion controls: the full six-axis state as Eden builds it (follow-ups to #1114) - #1237
wyatt-poole wants to merge 12 commits into
Conversation
…ampling) - The tilt pull follows the tilt unwrapped since its anchor and scales that, so at a sensitivity that is not a whole number the target no longer jumps by about pi once the pad turns past half a turn. - Changing Motion sensitivity Y or Invert motion Y re-anchors the pull: the aim stays where it is rather than sliding to the new scale with the pad held still. - Stillness reads the accelerometer's direction as well as its size: a pad turning at a steady rate is no longer still, so the turn is not learned as bias. The gyro threshold is 0.015 rad/s over the three axes (about 0.009 on each), room for noisier third-party sensors, and a pad that has not read still for 30 s says so in the log once. - The delta time handed to the game is capped at 100 ms, as the filter caps its own: a stalled pad's next reading is no jump. - Sampling numbers move a whole history on at each switch between live and at rest, so no two entries ever share one. - Tests: a sensitivity and an invert change while held tilted, a half-turn and more at 50%, a 0.0067 rad/s noisy sensor learning its bias on a table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
hckrman101
left a comment
There was a problem hiding this comment.
Nice set of follow-ups. Here's what I checked and found to hold up:
- Tilt pull: accumulating
wrap(tilt - tilt_last)from an anchor ofpitch / svmakes the target agree with the gyro integral at any sensitivity, so the jump at half a turn is gone. While the pad is rolled or shaken,tilt_lastisn't updated, and the first level reading afterwards picks up the whole change, so the old "corrected once it's level again" behaviour is kept. - Re-anchoring on a new
sv: this covers both sensitivity Y and the invert, since the invert flips the sign ofsv. The new tests fail on main as they should: at 2x the aim would slide to -1.0, and at 3.5 rad / 50% the wrapped target lands well away from -1.75. - Sampling numbers: I walked through live -> rest -> live. With
+= HOS_MOTION_HISTORYon the way to rest andsix_sampling + HOS_MOTION_HISTORY - h[0].non the way back, the oldest entry of the new run is always above the newest of the old one.motion_readingsstays continuous across a pad switch, soh[]is consecutive and the reasoning holds. - dt cap and threshold: the
dt_nscap matchesHOS_MOTION_DT_MAX. The gyro threshold numbers check out (0.015/sqrt(3) is about 0.0087 per axis, and the old 0.01 is about 0.0058). The noisy-sensor test fails stillness on main and passes here, and the 0.03 tremor in the hand test still stays well above the new threshold. - CI:
test_motion.pypasses on both runners. The only red check istest_output_resolution.py, which fails on main too, so it isn't this PR's fault. No conflicts with main, and no overlap with other open PRs (#1213 touchespad_sdl.c/nn_hid.c, but only in the rumble code).
There's one thing I'd like looked at before merging. The accelerometer test now uses the variance of the whole vector, summed over three axes, but HOS_MOTION_STILL_ACCEL is still 0.01. For isotropic noise, that makes the accelerometer side about sqrt(3) stricter: roughly 5.8 mG per axis now, against 10 mG for the radial component before. So the PR loosens the gyro threshold for noisier third-party pads and tightens the accelerometer one, which works against that goal. The tests can't show it, because the simulated accelerometer noise is 1e-5 m/s^2, which is effectively zero. Details inline.
There's also a smaller point on how much the vector variance actually catches, and a nit on the new 30 s log line.
| m->avec[i] += d * k; | ||
| av += d * d; | ||
| } | ||
| m->avar += (av - m->avar) * k; |
There was a problem hiding this comment.
Since this now sums the variance over three axes, isotropic accelerometer noise reads about sqrt(3) bigger than it did on the magnitude. Combined with HOS_MOTION_STILL_ACCEL staying at 0.01, a pad needs under about 5.8 mG per axis to count as still, where it used to need under 10 mG radial. That runs against the noisier-sensor motivation. Two options: scale the threshold by sqrt(3) to match the gyro side's convention, or add realistic accelerometer noise (a few mG) to the noisy-sensor test so this case is covered at all. Right now run_dt adds 1e-5 m/s^2.
Separately, about how much this catches: a steady turn about gravity (yaw) doesn't change the accelerometer vector at all, so it still reads as still. A steady pitch or roll only shows up once the EMA lag omega * 0.2 s passes 0.01, which is around 3 deg/s. So the header comment "a pad turning at a steady rate is not still" overstates it a bit; hand tremor on the gyro is still what keeps aiming from being learned as bias. There's also no test for the steady-turn case this change is meant to fix. Not a blocker, but it would be good to make the comment match and/or add that test.
There was a problem hiding this comment.
Thanks, both parts are covered now: 0.0173 matches the gyro side's convention, the noisy-sensor test runs at 5 mG RMS per axis (8.7 mG summed, well under the threshold), and the comment and the steady-pitch test match what the vector actually catches. The ~5 deg/s figure checks out with the new threshold (0.0173 / 0.2 s).
| rx_option(HOS_OPT_GYRO_SENS_Y, 100) / 100.0f, rx_option(HOS_OPT_GYRO_INV_X, 0), | ||
| rx_option(HOS_OPT_GYRO_INV_Y, 0)) && !fresh) | ||
| return; | ||
| if (!motion_warned && motion.learned_t <= 0 && now - motion_start_ns > 30000000000LL) { |
There was a problem hiding this comment.
Nit: anyone who picks up the pad at launch and plays for 30 s without ever setting it down will hit this, because a hand never reads still by design. In that common case the "its sensor is noisier than expected" part points people the wrong way when they read logs. Maybe reword it so the table hint comes first and the noisy-sensor explanation is only offered as a possibility.
There was a problem hiding this comment.
Reads much better, thanks.
|
@wyatt-poole one more for this list. I posted it on #1114 just after it was merged, so it may have been missed: my retest of 76fc99b, with a The vertical aim depends on the pad's pose when the filter starts. If Motion controls starts while the pad is rolled and pitched, the vertical aim stays about 0.9 rad off from a level start, with the same hold afterwards. That happens at game start, on toggling the setting, and on switching pads. It feels almost inverted in game. Starting with the pad flat on the desk fixes it. From the log, |
…xed#1237 - The vertical aim is absolute: it starts at the pad's tilt from flat (at the nose's angle above level while rolled), and the tilt pull's anchor is that tilt itself, so how the pad is held when the filter starts no longer offsets the aim for good (Link-22: a start held rolled and pitched aimed about 0.9 rad off). A new sensitivity or invert rescales the aim to match the tilt at the new scale. - HOS_MOTION_STILL_ACCEL is 0.0173 G summed over the three axes (0.01 G on each), like the gyro's, so the accelerometer side is no stricter than before; the noisy-sensor test adds 5 mG of accelerometer noise. - The stillness comment says what the accelerometer's vector catches (a steady pitch or roll past about 5 degrees a second, not a turn about gravity), with a test for a steady, noiseless pitch. - The 30 s log line leads with setting the pad down. - Motion controls' row is preset-exempt like the four under it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
|
@Link-22 @hckrman101 thanks, both of you. Everything is in the latest commit on this PR:
|
hckrman101
left a comment
There was a problem hiding this comment.
Re-checked 73a34a6. My three earlier points are all fixed (replied in the threads). test_motion.py passes on the Linux CI job; the only failure is test_output_resolution.py, the same as on main. Windows is still running.
The "measured from flat" change fixes Link-22's start-pose bug, and the mechanics look right to me. Starting pitch at sv * tilt and taking tilt_acc = tilt on the first level reading lines the pull target up with the aim, so nothing jumps after the start. The rolled start via asin is continuous, and the atan2 is only evaluated under the roll gate, so it never sees the degenerate case. Resetting on a toggle or pad switch now lands on the same aim as continuing would. The new start-3 test is a good reproduction.
One thing I'd like settled before approving: Motion sensitivity Y and Invert motion Y now scale an absolute angle, so they move the resting aim as well as the speed. Link-22's normal hold is about 0.7 rad from flat (up ≈ (0.02, 0.76, -0.65)). That gives:
- 100%: aim ≈ 0.7 rad, the "fine" case
- 200%: ≈ 1.4 rad, about 81°, right at vertical
- 300%: ≈ 2.1 rad, past vertical
- Invert Y at 100%: ≈ -0.7 rad, the mirror of the normal hold
His table suggests the game does care where the matrix sits in absolute terms (+0.09 felt "almost inverted" while -0.8/-0.9 felt fine). If so, high Y sensitivity or Invert Y may feel as wrong as the start-pose bug did. Before #1237 that couldn't happen, because the aim started at 0. Could Link-22 try 200% and Invert Y on this head? If those feel off, one option is to apply sensitivity and invert only to the deviation from a fixed reference hold, rather than to the angle from flat.
Smaller: the readme and the header (hos_motion.h:14) still say "held flat or upright". By your own note, upright now aims about 90° up, so either drop "upright" for the vertical aim or mention the trade-off there.
| m->pitch = hos_motion_wrap(sv * (fabsf(up[0]) < HOS_MOTION_TILT_ROLL ? atan2f(-up[2], up[1]) : asinf(e))); | ||
| m->tilt_sv = sv; | ||
| } else if (sv != m->tilt_sv && m->tilt_sv != 0) { | ||
| m->pitch = hos_motion_wrap(m->tilt_ready ? m->tilt_acc * sv : m->pitch / m->tilt_sv * sv); |
There was a problem hiding this comment.
Here sensitivity and invert stop being a speed and become an offset at rest: tilt_acc * sv puts a normal ~0.7 rad hold at ~1.4 rad at 200% and at -0.7 with Invert Y. That might be fine, but given Link-22 saw +0.09 feel inverted, it is worth a retest at 200% and with Invert Y on. If it is off, scale only the deviation from a reference hold.
| @@ -273,7 +273,8 @@ and the right one closes the menu. Your choices are saved in `hookshot_options.i | |||
| - **Motion controls:** the controller's gyroscope aims where the game uses motion, as on the Switch. It works with | |||
| Switch Pro controllers and pads in Switch mode, DualSense and DualShock 4 (through SDL), held flat or upright. Its row | |||
There was a problem hiding this comment.
"held flat or upright" no longer holds for the vertical aim: upright is about 90° up now. Maybe drop it or note the trade-off (same in the header at hos_motion.h:14).
…its angle The vertical aim is the pad's real tilt from flat, as a Switch reports it. Motion sensitivity Y and Invert motion Y now scale only the tilt's change from where the filter started (tilt_ref): at rest in that hold the aim is the real tilt at any setting, and with the defaults it is the real tilt everywhere. Scaling the whole angle from flat moved a normal 0.7 rad hold to about vertical at 200% and mirrored it with the invert (hckrman101). Test: a 0.7 rad hold at 200% and inverted. The readme and the header no longer say the vertical aim works held upright: the turn does, the tilt is from flat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
|
@hckrman101 good catch, thanks. Fixed in the latest commit:
A correction to my upright note above: since the game recenters the aim from the matrix each time it aims, an upright hold should behave like a real Switch controller held upright, not aim at the sky. That's my reading, though, and I haven't tested it. @Link-22 when you retest, could you try 200% and Invert Y as well as the start-tilted case?
|
hckrman101
left a comment
There was a problem hiding this comment.
Thanks, the maths checks out. With the defaults the aim is exactly -tilt, the gyro term (rates[0] * sv) matches the pull target's slope, Invert Y flips only the change around the start hold, and a settings change rescales consistently in both branches. The readme and header wording is fixed too.
There's one case left, inline: tilt_ref is whatever pose the filter starts in, so the fix only holds if motion starts in the playing hold. There's also a small wrap nit. CI is the same two failures main has (test_output_resolution.py, test_program_cache_cleanup.py).
| setting rescales the change. With the default (sv = -1) it is the real tilt everywhere. */ | ||
| if (started) { | ||
| const float e = -up[2] > 1 ? 1 : -up[2] < -1 ? -1 : -up[2]; | ||
| m->tilt_ref = fabsf(up[0]) < HOS_MOTION_TILT_ROLL ? atan2f(-up[2], up[1]) : asinf(e); |
There was a problem hiding this comment.
This fixes the 200% and Invert Y rest only when the filter starts in the hold you play in. When it starts with the pad lying flat on the desk (at launch, or after a reconnect while it's set down, which the 30 s log line now tells people to do), tilt_ref is 0 and we're back to the old numbers: picking it up to a 0.7 rad hold aims -(0.7 * 2) = -1.4 at 200%, and +0.7 (mirrored) with Invert Y. So at any setting other than 100%, the resting aim depends on the start pose again, which is the same class of bug as Link-22's.
Could you add a test for a flat start, then a lift to 0.7 at 200% and with Invert Y? One fix could be to re-take tilt_ref (blended in, so the aim doesn't jump) once the pad has been held steady for a moment after leaving a still/on-the-table state; the bias learner already tells those apart. Otherwise the readme should at least say that the Y sensitivity and invert assume motion starts in your playing hold.
| if (!m->tilt_ready) m->tilt0 = hos_motion_wrap(tilt - m->pitch / sv), m->tilt_ready = 1; | ||
| const float target = hos_motion_wrap(hos_motion_wrap(tilt - m->tilt0) * sv); | ||
| if (!m->tilt_ready) { /* (the tilt from flat itself: what the aim is measured from) */ | ||
| m->tilt_acc = tilt, m->tilt_ready = 1; |
There was a problem hiding this comment.
Nit: tilt_acc starts from the wrapped atan2, but tilt_ref may sit on the other branch. Near face-down (around ±π) they can differ by 2π, and at a non-integer scale (150%, 50%) (tilt_acc - tilt_ref) * sv is then off by π or so. Starting it as m->tilt_ref + hos_motion_wrap(tilt - m->tilt_ref) keeps it on the same branch. (A rolled start held past vertical has a similar mismatch, asin vs atan2, but that's rare enough to ignore.)
tilt_ref (the hold Motion sensitivity Y and Invert motion Y scale the tilt's change from) was taken only when the filter started, so a start with the pad lying on the desk left them measured from flat: lifted to a 0.7 rad hold, the aim rested at -1.4 at 200% and mirrored with the invert (hckrman101). A pad that was set down (still on a table) and is then held steady in the hand (the gyro's spread a hand's, not turning) for 0.5 s has that hold eased in as tilt_ref over about 0.3 s. At the defaults this moves nothing. Also: the pull's tilt_acc starts on tilt_ref's branch (near face-down the wrapped atan2 could be a turn away), and the stillness window starts from a small variance, so a pad set down is still in about 1.3 s instead of 2.2 s. Test: a flat start lifted to 0.7 rad at 200% and with the invert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
|
@hckrman101 both fixed in the latest commit:
|
|
@wyatt-poole @hckrman101 retested 775ca01 on the T3 Pro (Switch Pro mode, Linux, built on the v2.0.10 AppImage). Everything works:
Thanks for the quick turnaround on this. From my side #1237 is good to merge. |
Replaces the partial state (a yaw rate in the y slot and a tilt matrix, the rest neutral) and the cases it needed for how the pad is held when motion starts. The game now gets what Eden, the emulator it aims right with, gives it: acceleration (G) and angular velocity (turns a second) in the Switch's frame, the rotation summed, and the orientation matrix from Mahony's filter, following Eden's motion_input.cpp and sdl_driver.cpp (yuzu GPL-2.0-or-later, Eden GPL-3.0-or-later). The game does its own aiming and recentering, so the start pose does not matter. Kept: the bias learned on a table (stillness of both sensors), the console's 0.007 turns/s zero-drift threshold (Eden's standard), the per-reading history and sampling numbers, three six-axis handles, and Motion sensitivity X/Y and Invert motion X/Y, which scale the angular velocity's part about the world's up (turn) and the rest (tilt) before it is used; at 100% the state is Eden's. Tests: the frame and units, the rotation sum, the same orientation for a start flat-then-tilted and a start in that pose (also rolled and pitched), bias learning (small, large, noisy) and what is not learned, the threshold, the sensitivities and inverts, repeats and stalls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
|
@Link-22 @hckrman101 I've changed approach here. Instead of adding another case for each way the pad can be held, this PR now sends the game the full six-axis state, built the way Eden builds it. Link-22 said Eden aims right with this game, and a real Switch works however you hold the pad (in bed, at a desk, anywhere), because the game recenters on the camera every time it aims. What changed:
The tests now check:
The code is attributed in Two things I can't check from here:
|
hckrman101
left a comment
There was a problem hiding this comment.
Went through the new approach against Eden's motion_input.cpp / sdl_driver.cpp. The port looks faithful: the SDL to Switch mapping (accel -X, Z, -Y, gyro X, -Z, Y, G and turns/s), the clamps (7 G, 5 turns/s), the Mahony step (rate swap, kp 0.3 / ki 0.005, the 35/10 accelerometer-only weights, windup at 0.05), the 0.1 s stall skip, ResetOrientation / SetOrientationFromAccelerometer, and GetOrientation's quaternion swap and row signs all match, and the rows land in direction[3][3] in the order Eden's SixAxisSensorState::orientation uses. Rest state and initial quaternion match too. The old tilt_ref / pull / rolled-gate code is gone cleanly, and the bias learning and stillness test carried over. Starting accelerometer-only with the 35x weight is what makes the start pose irrelevant, so that part makes sense to me.
One real problem, inline: Motion sensitivity Y and Invert Y are applied to the rate before the filter, so the gravity correction undoes them once the pad is held still. A 1D run of the same loop (kp 0.3, ki 0.005 per reading, 200 Hz), tilting 0.5 rad and then holding still: at 200% the matrix tilt reaches ~0.95 and then settles back to ~0.54 within about a second. With Invert Y it goes to -0.39 and then swings over to +0.46, the non-inverted side, within 1-2 s. If the game reads the tilt from the matrix (the old partial state suggests it does), a held aim drifts at any Y setting other than 100%, and Invert Y ends up the wrong way round at rest. X is fine since gravity doesn't correct heading. The sensitivity tests only check out.gyro on the moving frame, so they don't catch this.
Also:
- Not mergeable: the conflict is
gpu/vk/vk_menu.cpp. Main reworkedRow(help +hint,restart,preset_exempt), and the Motion controls row is now..., hn_gyro},. The preset-exempt fix needs to becomehn_gyro, false, true},after a rebase. The readme paragraph didn't change on main, so it should merge as is. - Link-22's retest was on 775ca01, before this change. @Link-22, once it's rebased, could you run the comparison with Eden on the T3 Pro? Especially 200% and Invert Y with the pad held still on a target for a few seconds, plus the start poses again.
Not approving yet, because of the conflict and the Y sensitivity/invert drift.
| { | ||
| const float sh = sens_h * (inv_h ? -1.0f : 1.0f), sv = sens_v * (inv_v ? -1.0f : 1.0f); | ||
| const float t = g[0] * m->up[0] + g[1] * m->up[1] + g[2] * m->up[2]; | ||
| for (int i = 0; i < 3; i++) g[i] = sh * t * m->up[i] + sv * (g[i] - t * m->up[i]); |
There was a problem hiding this comment.
This scales the rate that goes into Mahony, so the integrated tilt no longer agrees with the accelerometer at any sv != 1. The kp * e + ki * I feedback then pulls the matrix back to the real tilt: at 200% a 0.5 rad tilt peaks near 1.0 and sags back to ~0.5 within a second or so of holding still. With Invert Y it crosses over to +0.5 (not inverted) in 1-2 s. The integral term, summed per reading, speeds this up and makes it depend on the pad's report rate.
One way out: run the filter on the unscaled rate (so it stays Eden's), and build what you hand out from a second quaternion. Integrate that one from the scaled rate plus the real filter's correction term, so it still loses gyro drift but keeps the scaled or inverted movement. Scale out.gyro/rotation as now. It would be good to have a test that tilts 0.5 rad at 200% and with Invert Y, holds 3 s, and checks the matrix's tilt from the start pose stays near 1.0 and -0.5.
There was a problem hiding this comment.
Fixed in d68db5b: the filter runs on the real rates and the handed-out matrix comes from the second quaternion, with a test for the 3 s hold at 200% and Invert Y. Thanks. One follow-up on the second quaternion in the new review.
…ntation Scaling the rate before Mahony's filter let its gravity correction undo Motion sensitivity Y and Invert motion Y once the pad was held still: at 200% a 0.5 rad tilt sagged back to 0.5, and inverted it swung over to the non-inverted side (hckrman101). The filter now runs on the real rates (Eden's own), and the orientation handed out is a second quaternion integrated from the scaled rates plus the real filter's correction term, so it loses the gyro's drift but keeps a scaled or inverted move. At 100% the two are the same. Test: 0.5 rad at 200% held 3 s matches a real 1.0 rad tilt, and inverted a real -0.5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
Conflict in gpu/vk/vk_menu.cpp: main reworked Row (help, hint, restart, preset_exempt); the Motion controls row keeps main's hn_gyro and is preset-exempt (hn_gyro, false, true). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
|
@hckrman101 thanks for checking the port against Eden. Both points are fixed:
@Link-22 this head is ready for the comparison with Eden on the T3 Pro: 200% and Invert Y with the pad held still on a target for a few seconds, plus the start poses. |
|
@wyatt-poole @hckrman101 a first, short result on the Eden-style approach. I only had time for a quick try of 5e2f904 (before d68db5b), on the T3 Pro, Linux, menu defaults:
I haven't run the full comparison yet. I'll build 8f9b8e1 and come back with the rest: start poses, lying back, and 200% / Invert Y held still on a target for a few seconds. |
…s tab The five motion rows (Motion controls, Motion sensitivity X and Y, Invert motion X and Y) move off the Controls tab onto a page of their own, opened by a Motion row just above Controls, the way the Controls row opens the bindings page. The page is drawn and driven like a tab (TAB_MOTION, not on the strip): the tab strip shows Controls, Esc or a right click goes back to the Controls tab, and Q/E or the bumpers move on from Controls. The options file's keys are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
|
@hckrman101 @Link-22 one more change, in 0b221fc, and the in-game result:
|
…t modes) The rate under which the pad counts as still was fixed at Eden's standard 0.007 turns a second. The Motion page now has a Motion deadzone row with the console's three zero-drift modes: Low (tight, 0.002), Standard (0.007, the default and the console's own) and High (loose, 0.01), for a pad that jitters when held still on a target (Link-22) or, the other way, to keep the smallest movements. Option 23 (HOS_OPT_GYRO_DEADZONE); the filter takes it as HosMotion.threshold. Test: Low lets a 0.005 turns/s turn through, High drops 0.008. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
|
@Link-22 thanks for the quick check, and for comparing against Eden. A note from my side: I got around to testing the Eden-style build on my 8BitDo Ultimate 2 (Switch mode, Windows). I didn't notice any jitter, but I was mostly checking the motion itself and looking for drift, so I didn't spend long holding it dead still on a target. Your report is probably the better data point there. What I changed for it (latest commit):
When you do the full run on 8f9b8e1 or later, it would be great to try High on the target hold too. |
hckrman101
left a comment
There was a problem hiding this comment.
Thanks, the two points from my last review are done: the filter now runs on the real rates and only the handed-out quaternion carries the scaling (d68db5b), and the hn_gyro, false, true} row is right. The Motion page, the deadzone row and the option id also look good: 23 is free on main, HOS_OPT_GYRO_DEADZONE is answered in vk_replay.cpp, and the row is saved with the others. #1270's 27/28 don't collide with it. The deadzone is a good place to take Link-22's jitter report.
Two things are still open:
- Conflict with main. The branch is 25 commits behind and
gpu/vk/vk_menu.cppconflicts in three places (checked with a trial merge; every other file merges cleanly):tx_audio_outputnext totx_deadzone, the Esc handling (main'sif (press[5] && pending)goes first, then your Motion back, thenrestart_ask()), and the Enter handling (keep main'sconfirm_selectedblock, then add youris_motion_rowbranch after the Cheats one). Heads-up: #1270 also changes the tab count to four, which will hitTAB_MOTION = 3andtab_selected[TAB_COUNT + 1], so whichever lands second needs to adjust. - The handed-out quaternion and turning while tilted (inline). I think it is a real problem at sens/invert other than 100%, but I haven't run it.
Nit: the comment in vk_api.h above HOS_OPT_DLSS_JITTER_* still says "18 to 22 are the motion rows'"; it is 18 to 23 now. The readme describes the Motion row but has no bullet saying what the deadzone is for.
CI is green on 1ece02e. Nothing blocks except the conflict and point 2.
| } else { | ||
| hos_motion_rate(m, gs, rs); | ||
| for (int i = 0; i < 3; i++) rs[i] += c[i]; | ||
| hos_motion_integrate(m->quat_out, rs, dt); |
There was a problem hiding this comment.
rs is the scaled rate in the pad's own frame, where the split into turn and tilt uses the real up. quat_out is not at the real pose once sv != 1 (0.5 rad real tilt is 1.0 at 200%, or -0.5 inverted), so a pure turn about the real up is applied to quat_out as a rotation about an axis that is off its vertical by that extra tilt. By my reading, turning left or right while holding a tilted aim at 200% (or Invert Y) makes the vertical aim bob by up to about that offset, and nothing pulls it back, since c is zero while the real filter agrees with gravity. At 100% the two are equal, so the current tests and Link-22's hold-still runs wouldn't show it.
One way: scale in the world frame. Take the rate in the world frame with the real orientation (w = R_real * g), split it into the vertical part (turn, sh) and the horizontal part (tilt, sv), and apply that to quat_out by left-multiplying (a world-frame rotation), plus c as now. A test would be: tilt 0.5 rad at 200%, then turn 90 degrees about the vertical at the same hold, and check the matrix's tilt from the start pose stays at 1.0 (and -0.5 with Invert Y).
| TAB_CONTROLS = 2, | ||
| TAB_COUNT = 3 | ||
| TAB_COUNT = 3, | ||
| TAB_MOTION = 3 // (hookshot: not a tab of the strip, the Motion page that the Controls tab's Motion row opens) |
There was a problem hiding this comment.
Nit: TAB_MOTION = 3 equals TAB_COUNT, which is what makes tab_selected[TAB_COUNT + 1] and the t < TAB_COUNT loops work. #1270 makes it four tabs, and then they would be the same index as its fourth tab. Defining it as TAB_MOTION = TAB_COUNT (after the enum) would keep it one past the strip.
Conflicts in gpu/vk/vk_menu.cpp with main's Audio output row (NotRexed#1240) and the Restart/Quit confirm (NotRexed#1266, NotRexed#1255): tx_deadzone and tx_audio_output both kept; Esc on the Motion page goes back before the pending confirm is cancelled; the Motion row opens its page after the Cheats row's test in main's confirm chain. TAB_MOTION is now TAB_COUNT itself (one past the strip), and tab_selected is sized by it, so a tab added before TAB_COUNT (NotRexed#1270) moves the Motion page along instead of colliding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
…ments The handed-out orientation took the rate split by the accelerometer's up in the pad's frame, so at a sensitivity or invert other than 100% a turn while tilted turned quat_out about an axis off its own vertical and tipped it (hckrman101). The real rate is now taken to the world by the real orientation, its vertical part scaled by sh and the rest by sv, and brought back to the pad by quat_out, so quat_out turns about its own vertical. At 100% the two are still the same. Test: 0.5 rad nose up at 200%, then a quarter turn at 200%: the world's up seen from the pad still matches a real 1.0 rad tilt (0.54 before). vk_api.h: the motion options' comments name all six (18 to 23). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
|
@hckrman101 thanks, all three are fixed:
|
Conflicts with NotRexed#1298 (stick dead zone row) and the wider Motion sensitivity X (10% to 600%) on main: tx_deadzone and tx_stick_dead_zone both kept; the motion rows stay on the Motion page with X's new 600% top; the readme's Motion row lists X's range. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
Follow-ups to #1114, found in a second review of the merged code (none of them reported in game yet):
wrap(wrap(tilt - tilt0) * sv). That target jumps by about pi once the pad turns past half a turn from its anchor. The pull now accumulates the tilt unwrapped since the anchor and scales that.svnow re-anchors the pull.delta_timehanded to the game is capped at 100 ms, the same cap the filter uses for its own dt.Tests cover each of these: a sensitivity change and an invert change while held tilted, a pitch of 3.5 rad at 50%, and a 0.0067 rad/s noisy sensor learning its bias on a table.
test_motion.pypasses with gcc and clang.run_all.pypasses apart fromtest_output_resolution.py, which already fails on main before #1114, andnx2/test_nup_read_name.py, which needszstandard.🤖 Generated with Claude Code
https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW