Drag-down on +z front-face cubies still inverted direction after the
face-anchor fix. Root cause was a dual-reference-frame mismatch in
chooseRotationAxis:
curAngle = (drag · pixelsAxis) * signMul ← measured along pixelsAxis
signMul = sign(motionScreen · drag) ← measured against drag
When pixelsAxis and the drag vector are anti-aligned (e.g. dragAxis='y'
where pixelsAxis points screen-UP since world +y projects to screen -y,
but the user drags screen-DOWN), the two reference frames disagree and
sign(curAngle) ends up opposite sign(motionScreen · drag) — so positive
rotation moves the cubie opposite the user's drag.
Fix: compare motionScreen against pixelsAxis (= screenDirs[dragAxisIdx])
so both halves of the curAngle formula share the same reference frame.
Adds 24 correctness tests asserting sign(curAngle) == sign(motionScreen ·
drag) for every face × cardinal drag — fails on the previous code, passes
after the fix. Also removes the temporary RUBIK_DEBUG diagnostic logs.
Drag-to-rotate sent edge and corner cubies the opposite direction at tilted
camera angles. Root cause: motionWorld = R̂ × hitWorldPos picks up an in-plane
component of P (β·F̂) for non-center cubies; under projection this can
dominate motionScreen and flip signMul.
Anchor the cross product to the face-normal axis only — drops the leakage
so signMul is identical for every cubie on a face.
Adds tests/gesture-math.test.js with face-invariance assertions across
centers, edges, and corners on all 6 faces × 4 cardinal drags.