diff --git a/GramAddict/core/physics/humanized_input.py b/GramAddict/core/physics/humanized_input.py index bf5ce4f..6d40573 100644 --- a/GramAddict/core/physics/humanized_input.py +++ b/GramAddict/core/physics/humanized_input.py @@ -48,19 +48,12 @@ def humanized_scroll(device, is_skip=False, resonance_score=None): start_x, start_y = body.get_scroll_start() end_x = start_x + random.gauss(0, w * 0.008) # Slight horizontal drift - do_correction = random.random() < correction_prob - if is_skip: - # Aggressive fast fling to skip quickly - if do_correction: - logger.debug(f"🪀 [Doomscroll] Correction (Prob: {correction_prob:.2f}) — Wait, what was that?") - distance = int(h * random.uniform(0.3, 0.5)) - duration = random.uniform(100, 150) - end_y = min(start_y + distance, h - 10) # Move down to pull UI up - else: - distance = int(h * random.uniform(0.6, 0.75)) - duration = random.uniform(100, 150) - end_y = start_y - distance + # Aggressive fast fling to skip quickly. NO CORRECTIONS. + distance = int(h * random.uniform(0.6, 0.75)) + duration = random.uniform(150, 250) # slightly longer to ensure smooth fling registration + end_y = start_y - distance + do_correction = False # Force false else: # Playful, organic human scrolling play_choice = random.random() @@ -97,7 +90,7 @@ def humanized_scroll(device, is_skip=False, resonance_score=None): end_y = start_y - distance # --- Behavioral Micro-Patterns (new human behaviors) --- - behavior = _select_scroll_behavior() + behavior = None if is_skip else _select_scroll_behavior() if behavior == "pre_touch_dwell": # Finger lands on glass before swiping (50-200ms dwell) diff --git a/tests/unit/test_physics_humanized.py b/tests/unit/test_physics_humanized.py index ac052d6..bb50f13 100644 --- a/tests/unit/test_physics_humanized.py +++ b/tests/unit/test_physics_humanized.py @@ -46,3 +46,40 @@ def test_humanized_scroll_speeds(MockInjector): # Timing intervals must all be positive for t in timing: assert t > 0, f"Timing interval must be positive, got {t}" + + +@patch("GramAddict.core.physics.humanized_input.SendEventInjector") +def test_humanized_scroll_skip_is_strictly_forward(MockInjector): + """ + Ensures that when is_skip=True (e.g. for aggressive ad skipping), + the generated gesture is purely forward (bottom to top swipe), + and does NOT contain backwards 'Doomscroll corrections' or + biomechanical 'reading pauses'. + """ + mock_injector = MagicMock() + MockInjector.get_instance.return_value = mock_injector + + device = MagicMock() + device.get_info.return_value = {"displayHeight": 2400, "displayWidth": 1080} + + from GramAddict.core.physics.humanized_input import humanized_scroll + + # Run multiple times to overcome randomness in play_choice / do_correction + for _ in range(50): + mock_injector.reset_mock() + humanized_scroll(device, is_skip=True) + + args = mock_injector.inject_gesture.call_args + points = args[0][0] + timing = args[0][1] + + # In a forward scroll (bottom to top), the Y coordinate must go from a higher number to a lower number. + start_y = points[0][1] + end_y = points[-1][1] + + # End Y MUST be less than Start Y (meaning we scrolled down the feed, swiping finger up) + assert end_y < start_y, f"Expected end_y ({end_y}) to be < start_y ({start_y}) for a skip." + + # Verify no long pauses (reading pause adds 0.5s to 2.0s to timing) + for t in timing: + assert t < 500, "Found a massive pause in a skip gesture, meaning a reading_pause or dwell was incorrectly inserted!"