feat(humanize): add coordinate/duration/swipe-path jitter module
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import random
|
||||
|
||||
|
||||
from tools.humanize import (
|
||||
HumanizeConfig,
|
||||
jitter_duration,
|
||||
jitter_point,
|
||||
load_humanize_config,
|
||||
set_rng,
|
||||
swipe_waypoints,
|
||||
)
|
||||
|
||||
|
||||
def test_jitter_point_stays_within_radius():
|
||||
rng = random.Random(42)
|
||||
for _ in range(500):
|
||||
x, y = jitter_point(100, 200, radius=5.0, rng=rng)
|
||||
assert math.hypot(x - 100, y - 200) <= 5.0
|
||||
|
||||
|
||||
def test_jitter_point_centered_on_input():
|
||||
rng = random.Random(0)
|
||||
xs = [jitter_point(0, 0, radius=4.0, rng=rng)[0] for _ in range(4000)]
|
||||
assert abs(sum(xs) / len(xs)) < 0.3
|
||||
|
||||
|
||||
def test_jitter_duration_within_spread():
|
||||
rng = random.Random(3)
|
||||
for _ in range(200):
|
||||
v = jitter_duration(1000, spread=0.15, rng=rng)
|
||||
assert 850 <= v <= 1150
|
||||
|
||||
|
||||
def test_jitter_duration_never_zero():
|
||||
rng = random.Random(9)
|
||||
for _ in range(100):
|
||||
assert jitter_duration(1, spread=0.5, rng=rng) >= 1
|
||||
|
||||
|
||||
def test_swipe_waypoints_endpoints_exact_interior_offset():
|
||||
rng = random.Random(7)
|
||||
pts = swipe_waypoints((0, 0), (100, 0), curvature=0.2, n=4, rng=rng)
|
||||
assert pts[0] == (0, 0)
|
||||
assert pts[-1] == (100, 0)
|
||||
assert len(pts) == 6 # start + 4 interior + end
|
||||
assert any(abs(p[1]) > 1e-6 for p in pts[1:-1])
|
||||
|
||||
|
||||
def test_swipe_waypoints_degenerate_short_path_returns_two_points():
|
||||
rng = random.Random(1)
|
||||
pts = swipe_waypoints((5, 5), (5, 5), curvature=0.2, n=8, rng=rng)
|
||||
assert pts == [(5, 5), (5, 5)]
|
||||
|
||||
|
||||
def test_load_config_defaults_when_unset():
|
||||
cfg = load_humanize_config(env={})
|
||||
assert cfg == HumanizeConfig()
|
||||
|
||||
|
||||
def test_load_config_disabled():
|
||||
cfg = load_humanize_config(env={"APEX_HUMANIZE_ENABLED": "false"})
|
||||
assert cfg.enabled is False
|
||||
|
||||
|
||||
def test_load_config_overrides():
|
||||
cfg = load_humanize_config(
|
||||
env={
|
||||
"APEX_HUMANIZE_TAP_RADIUS_PX": "9",
|
||||
"APEX_HUMANIZE_SWIPE_CURVATURE": "0.3",
|
||||
"APEX_HUMANIZE_SWIPE_WAYPOINTS": "12",
|
||||
"APEX_HUMANIZE_DURATION_SPREAD": "0.2",
|
||||
}
|
||||
)
|
||||
assert cfg.tap_radius_px == 9.0
|
||||
assert cfg.swipe_curvature == 0.3
|
||||
assert cfg.swipe_waypoints == 12
|
||||
assert cfg.duration_spread == 0.2
|
||||
|
||||
|
||||
def test_set_rng_makes_output_reproducible():
|
||||
set_rng(random.Random(99))
|
||||
a = jitter_point(0, 0, radius=5.0, rng=random.Random(99))
|
||||
set_rng(None) # reset to default rng
|
||||
assert isinstance(a, tuple) and len(a) == 2
|
||||
Reference in New Issue
Block a user