Motion controls: the full six-axis state as Eden builds it (follow-ups to #1114) #1237
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1237
Loading…
Reference in a new issue
No description provided.
Delete branch "hid/motion-followups"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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
Nice set of follow-ups. Here's what I checked and found to hold up:
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.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.+= 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_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.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_ACCELis 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.
@ -90,0 +123,4 @@const float vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;const float e[3] = {az * vx - ax * vz, ay * vz - az * vy, ax * vy - ay * vx};memcpy(m->real_error, e, sizeof e);if (gain <= 1 && hos_motion_len(e) >= 0.05f)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_ACCELstaying 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 nowrun_dtadds 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 spasses 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.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.
@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
HOS_MOTION_DEBUGlog attached.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,
pitchstarts at 0 in whatever pose the pad is in. The tilt pull is off while rolled, and the anchor (tilt - pitch / sv) then keeps that offset once the pad comes level. A possible fix: startpitchfrom the measured tilt whenupis first set (e.g.sv * asin(-up[2])), and anchor the pull to the absolute tilt. I'm happy to retest on the T3 Pro.@Link-22 @hckrman101 thanks, both of you. Everything is in the latest commit on this PR:
pitchstarts at the pad's tilt from flat,sv * atan2(-up[2], up[1]). If the pad is rolled at that moment, it starts at the nose's angle above level,sv * asin(-up[2]), as you suggested, and the tilt pull settles it once the pad is level. The pull's anchor is that tilt itself (tilt_acc = tilt), not the current pitch, so a start held tilted no longer offsets the aim for good. A new test reproduces your start 3: a pose with the nose up 0.6 rad, reached rolled 40° before the filter starts, then rolled level. It now aims the same as reaching that pose from a flat start (-0.6). On main it lands near 0 (+0.12).HOS_MOTION_STILL_ACCELis now 0.0173 G summed over the three axes, 0.01 G per axis, the same convention as the gyro's. The noisy-sensor test now adds 5 mG of accelerometer noise per axis on top of the 0.0067 rad/s gyro noise., truefrom your #1114 note).test_motion.pypasses with gcc and clang.run_all.pypasses apart from the same two failures main has (test_output_resolution.py, andtest_nup_read_name.py, which needszstandard). Link-22, a retest of the start-tilted case would be great.@ -90,0 +123,4 @@const float vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;const float e[3] = {az * vx - ax * vz, ay * vz - az * vy, ax * vy - ay * vx};memcpy(m->real_error, e, sizeof e);if (gain <= 1 && hos_motion_len(e) >= 0.05f)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).
Reads much better, thanks.
Re-checked
73a34a6. My three earlier points are all fixed (replied in the threads).test_motion.pypasses on the Linux CI job; the only failure istest_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
pitchatsv * tiltand takingtilt_acc = tilton the first level reading lines the pull target up with the aim, so nothing jumps after the start. The rolled start viaasinis continuous, and theatan2is 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: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.
@ -124,4 +182,4 @@}const int still = m->gvar < HOS_MOTION_STILL_GYRO * HOS_MOTION_STILL_GYRO &&m->avar < HOS_MOTION_STILL_ACCEL * HOS_MOTION_STILL_ACCEL && m->amean > 0.9f && m->amean < 1.1f;m->still_t = still ? m->still_t + dt : 0;Here sensitivity and invert stop being a speed and become an offset at rest:
tilt_acc * svputs 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."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).
@hckrman101 good catch, thanks. Fixed in the latest commit:
tilt_ref, the pad's tilt from flat at that moment). The aim is-tilt_ref + (tilt - tilt_ref) * sv. At rest in that hold, the aim is the pad's real tilt at any setting. With the defaults, it's the real tilt everywhere, as a Switch reports it. A new test checks a 0.7 rad hold: at 200% it aims at 0.7, not near vertical, and a further 0.1 rad moves the aim by 0.2. With Invert Y it also aims at 0.7, not mirrored, and the same 0.1 rad moves the aim 0.1 the other way.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?
test_motion.pypasses with gcc and clang.run_all.pyshows the same two failures as main and nothing else.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_refis 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).@ -127,3 +185,3 @@m->still_t = still ? m->still_t + dt : 0;if (m->still_t >= HOS_MOTION_STILL_S && dt > 0) {const float k = dt / ((m->learned_t < 2 ? 0.25f : 3.0f) + dt); /* (fast for the first 2 s still, then slowly) */const float k = dt / ((m->learned_t < 2 ? 0.25f : 3.0f) + dt);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_refis 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.Nit:
tilt_accstarts from the wrappedatan2, buttilt_refmay 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) * svis then off by π or so. Starting it asm->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,asinvsatan2, but that's rare enough to ignore.)@hckrman101 both fixed in the latest commit:
tilt_refis now taken again once the pad has been set down and then picked up. The pad counts as set down while it's still on a table. It counts as picked up once it's held steady in the hand for 0.5 s, meaning the gyro's spread is under a hand's (0.05 rad/s) and it isn't turning, so a smooth sweep doesn't count as a hold. The new hold is eased in over about 0.3 s. At the defaults the aim doesn't move (it changes by-(1 + sv)per radian of ref). At other settings the resting aim eases to the real tilt in the new hold. I added the test you asked for: a flat start on the desk, then lifted to 0.7 rad with a hand's tremor and held. At 200% it rests at 0.7 (on main this gives -1.4), and with Invert Y it rests at 0.7 too (main gives a mirrored +0.7). A further 0.1 rad then moves the aim 0.2, or 0.1 the other way with the invert.tilt_accnow starts astilt_ref + wrap(tilt - tilt_ref).test_motion.pypasses with gcc and clang.run_all.pyshows only the two failures main also has.@wyatt-poole @hckrman101 retested
775ca01on 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.
@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:
sdl_driver.cppmaps them.motion_input.cpp: kp 0.3, ki 0.005, the same quaternion integration and the sameGetOrientationsigns. It also includes the reset from the accelerometer when the filter loses track.The tests now check:
test_motion.pypasses with gcc and clang.run_all.pyshows only main's two failures.The code is attributed in
hos_motion.h(yuzu GPL-2.0-or-later, Eden GPL-3.0-or-later; this project is GPL-3.0).Two things I can't check from here:
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, gyroX, -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, andGetOrientation's quaternion swap and row signs all match, and the rows land indirection[3][3]in the order Eden'sSixAxisSensorState::orientationuses. 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.gyroon the moving frame, so they don't catch this.Also:
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.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.
This scales the rate that goes into Mahony, so the integrated tilt no longer agrees with the accelerometer at any
sv != 1. Thekp * e + ki * Ifeedback 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/rotationas 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.@hckrman101 thanks for checking the port against Eden. Both points are fixed:
d68db5b): the filter now runs on the real, unscaled rates, exactly as Eden does. The orientation that's handed out is a second quaternion, integrated from the scaled rates plus the real filter's correction term. It loses the gyro's drift but keeps a scaled or inverted move. At 100% the two quaternions are identical.out.gyroandrotationare scaled as before. New test, as you suggested: 0.5 rad nose up, held 3 s. At 200% it matches the matrix of a real 1.0 rad tilt, and with Invert Y it matches a real -0.5. Both fail on the previous head.8f9b8e1, merged main): the Motion controls row keeps main'shn_gyroand is preset-exempt ashn_gyro, false, true}.run_all.pyon Linux: 161 passed. The only failure isnx2/test_nup_read_name.py(needszstandard).test_output_resolution.pypasses again on main.@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(befored68db5b), on the T3 Pro, Linux, menu defaults:I haven't run the full comparison yet. I'll build
8f9b8e1and come back with the rest: start poses, lying back, and 200% / Invert Y held still on a target for a few seconds.@hckrman101 @Link-22 one more change, in
0b221fc, and the in-game result:TAB_MOTION, a hidden tab), so the sliders, mouse and wheel all work as before. The tab strip keeps Controls highlighted, 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. The readme table now lists the page.run_all.pyon Linux passes apart fromnx2/test_nup_read_name.py, which needszstandard.@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):
Motion deadzone: there's a new row on the Motion page with the console's three zero-drift modes:
Any rate under the threshold counts as no movement. If the pad jitters when held still, High should steady it. Low goes the other way and keeps the smallest movements. It's option 23 (
HOS_OPT_GYRO_DEADZONE), and there's a test that Low lets a 0.005 turns/s turn through and High drops 0.008.What I ruled out: I also checked whether the accelerometer feeding the gravity correction could cause the jitter. In the simulation, with 10 mG of accelerometer noise per axis and the gyro in use, the matrix moves by about 0.0003 rad, which is far too small to see. So I didn't add any smoothing there and kept Eden's filter as it is. The jitter you saw (and Eden's) is more likely the gyro's own noise, or a hand's tremor, crossing the deadzone threshold now and then. The deadzone is what addresses that.
When you do the full run on
8f9b8e1or later, it would be great to try High on the target hold too.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.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 thehn_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_DEADZONEis 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:
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.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.@ -49,7 +49,9 @@ enum MenuTab {TAB_GRAPHICS = 0,Nit:
TAB_MOTION = 3equalsTAB_COUNT, which is what makestab_selected[TAB_COUNT + 1]and thet < TAB_COUNTloops work. #1270 makes it four tabs, and then they would be the same index as its fourth tab. Defining it asTAB_MOTION = TAB_COUNT(after the enum) would keep it one past the strip.rsis the scaled rate in the pad's own frame, where the split into turn and tilt uses the realup.quat_outis 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 toquat_outas 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, sincecis 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 toquat_outby left-multiplying (a world-frame rotation), pluscas 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).@hckrman101 thanks, all three are fixed:
11d915c): this works as you suggested. The real rate goes to the world through the real orientation. There, the vertical part (the turn) is scaled byshand the rest (the tilt) bysv. It then comes back to the pad throughquat_out, so a scaled turn while tilted turnsquat_outabout its own vertical. At 100%,quatandquat_outstay identical. Your test is in: 0.5 rad nose up at 200%, then a quarter turn about the vertical at 200%. The world's up seen from the pad (the third column) still matches a real 1.0 rad tilt, with a dot product of 1 within 2e-3. Before the fix it was 0.54.cf9c774):TAB_MOTION = TAB_COUNT, andtab_selectedis sizedTAB_MOTION + 1, so a tab added beforeTAB_COUNT(#1270) moves the Motion page along with it.cf9c774, main atbbdc370):tx_deadzoneand main'stx_audio_outputare both kept. On the Motion page, Esc goes back to Controls before it touches main's pending Restart/Quit confirm. The Motion row opens its page right after the Cheats row's test in main's confirm chain.run_all.pyon Linux: 165 passed. The only failure isnx2/test_nup_read_name.py, which needszstandard.test_motion.pypasses with gcc and clang.Fixed in
11d915c. I read the new block:dranddqare both world to pad, sodr^T gis the real rate in the world, the vertical part getssh, the horizontal part getssv, anddq * wtakes it into the handed-out pad frame, so a scaled turn goes aboutquat_out's own vertical. At 100% it reduces tog. Thanks.@ -49,7 +49,9 @@ enum MenuTab {TAB_GRAPHICS = 0,Fixed in
cf9c774,TAB_MOTION = TAB_COUNTwithtab_selected[TAB_MOTION + 1]. Thanks.Re-checked
1ad8124. Everything from my last review is fixed:11d915cis right.dranddqboth map world to pad, sodr^T gis the real rate in the world, the vertical part getssh, the rest getssv, anddq * whands it toquat_out. At 100% it reduces tog, so nothing changes by default. The new test (0.5 rad at 200%, then a quarter turn about the vertical) checks the third column against a real 1.0 rad tilt.mergeable_stateis clean now, and main's Restart/Quit confirm comes before the Motion back handling as asked.HOS_OPT_GYRO_DEADZONEis answered in vk_replay.cpp, and nothing else on main uses 23 (main's stick dead zone is 11).CI is green on every job, Windows included, and
test_motion.pyis covered. One nit, not blocking: the new test only runs 200% and not Invert Y (the -0.5 case I mentioned), though the code path is the same sign flip onsvandsh. Good to merge from my side.This landed in the v2.0.12 branch (merged there for testing; it reaches
mainwith that release), so closing. Thanks!Reopened: closing this was a mistake on my side. It was tested in the v2.0.12 branch, and it will be merged into main here, so it shows as merged. Sorry for the noise.