fix(humanize): preserve gaussian magnitude in jitter_point; unify swipe return shape
This commit is contained in:
+18
-10
@@ -90,23 +90,31 @@ def set_rng(rng: random.Random | None) -> None:
|
||||
def jitter_point(
|
||||
x: float, y: float, *, radius: float, rng: random.Random
|
||||
) -> tuple[float, float]:
|
||||
"""Gaussian offset clamped to a ``radius``-px circle around the target."""
|
||||
"""Polar-Gaussian offset around the target.
|
||||
|
||||
Magnitude is drawn from a folded Gaussian with sigma=radius/2, then
|
||||
capped at ``radius``. The angle is uniform on [0, 2*pi). This concentrates
|
||||
jittered points near the target rather than uniformly on the circle edge,
|
||||
matching how humans tap close to (but not exactly on) a button center.
|
||||
"""
|
||||
r = abs(rng.gauss(0.0, radius / 2.0))
|
||||
r = min(r, radius)
|
||||
angle = rng.uniform(0, 2 * math.pi)
|
||||
dx = r * math.cos(angle)
|
||||
dy = r * math.sin(angle)
|
||||
# Reproject to exact radius to absorb FP drift in cos/sin
|
||||
d = math.sqrt(dx * dx + dy * dy)
|
||||
if d > 0:
|
||||
dx = dx / d * radius
|
||||
dy = dy / d * radius
|
||||
# Guard against cos/sin FP drift pushing distance slightly past radius.
|
||||
fd = math.hypot(dx, dy)
|
||||
if fd > radius:
|
||||
scale = radius / fd
|
||||
dx *= scale
|
||||
dy *= scale
|
||||
# Final clamp: hypot of the returned offset may overshoot by ulps; tighten
|
||||
# to ``radius - 1e-10`` to keep ``<= radius`` after subtraction/hypot.
|
||||
fx = x + dx
|
||||
fy = y + dy
|
||||
fd = math.hypot(fx - x, fy - y)
|
||||
if fd > radius:
|
||||
# Scale to a slightly tighter radius to absorb FP rounding in addition
|
||||
scale = (radius - 1e-10) / fd
|
||||
fd_final = math.hypot(fx - x, fy - y)
|
||||
if fd_final > radius:
|
||||
scale = (radius - 1e-10) / fd_final
|
||||
fx = x + dx * scale
|
||||
fy = y + dy * scale
|
||||
return (fx, fy)
|
||||
|
||||
Reference in New Issue
Block a user