The compression force, and which way it pushes

force_{side}_normal_n is a magnitude in newtons, recovered from the tactile image. It becomes a displacement through one constant: penetration = F / k with k = 2 N/mm (dexforce.STIFFNESS_N_PER_M — import it, do not retype it), and a direction: the gel normal in the sensor's own body frame.

from force_recovery.dexforce import gel_axis, STIFFNESS_N_PER_M
n_hat  = R_from_quat(pose[3:7]) @ gel_axis(task, side)   # world unit vector
target = pose[:3] + (force_n / STIFFNESS_N_PER_M) * n_hat

World vertical is not that direction, under either candidate axis — quoting only the active one would argue the point with the very axis in dispute. Over contact frames the gel normal sits a median 7.7°–25.1° from world −z on motherboard and 23.3°–43.2° on pushT, where 70–98% of 8,369 frames exceed 15°. Pushing "straight down" is wrong by at least the smaller of each pair.

Two candidate axes, and why the choice is not settled

The default is sensor-local −y: the Mini's sensing face is normal to the body's y. The calibration files also carry gel_axis_in_rigid (source="dual_ball"), which is normalize(gelball − refball) — the line between two calibration ball centres, from 3 poses. It never measured the gel surface; it is the normal only if the fixture held both balls along it. The two disagree by 21.2° (left) and 22.4° (right).

testangle from board normal
dual_ball / −y
corr(dF, v·n̂)
dual_ball / −y
image tilt R²
dual_ball / −y
sv₁/sv₂
left7.1° / 25.6°+0.085 / +0.053 (-y better on 3% of 31 eps)-0.151 / +0.0091.09
right18.1° / 7.7°+0.116 / +0.116 (-y better on 61% of 31 eps)-0.002 / +0.0061.04

Board normal: pressing >6 N on a level board (18,054 and 20,166 frames), the gel normal should point near world −z. corr(dF, v·n̂): pressing in raises force — uses no world frame and no table. Image tilt: R² of a single linear map from the pose-predicted tilt to the gel's own deformation gradient.

What the three tests actually say

They disagree, and the right sensor is the suspect. The left sensor's dual-ball axis wins both proxies; the right sensor's loses one and ties the other. The right calibration also reports depth_offset_mm = 0 where the left reports -5 — its ball centre was never backed off by a ball radius to reach the gel surface.

And this dataset cannot settle it. Two independent reasons. Kinematically the problem is ill-conditioned: sv₁/sv₂ is 1.09 and 1.04, so changing the axis by 60° barely moves any concentration score. And the gel's own deformation — a real signal, coherent to 10.6° between samples 7 contact rows apart — carries the geometry of whatever was touched (components, edges, connectors), not the sensor's tilt: R² ≈ 0 for both candidates. The contacted surface is not a known plane, so the most direct observable is confounded at the source.

What would settle it is a measurement, not more analysis: press the sensor flat on a known flat plate at ten-plus widely varied orientations. Kinematics becomes well-conditioned, and the image gives a null test — on a true plane the deformation gradient vanishes when the press is normal. That same run also answers whether the OptiTrack rigid body was redefined between calibration epochs, which the copied gel calibration cannot.

Cost of the current default: switching from dual_ball to −y moves force_*_target_pose by a median 0.57 mm (max 1.53) on motherboard and 0.30 mm (max 1.45) on pushT, because F/k is itself only a few mm. Force magnitudes and penetration are unaffected — they are scalars.