Metadata-Version: 2.5
Name: guided-motion
Version: 0.1.1
Summary: Constraint-safe timed motion planning guided by reference paths
Project-URL: Homepage, https://github.com/ma-pony/guided-motion
Project-URL: Documentation, https://github.com/ma-pony/guided-motion#readme
Project-URL: Chinese-Documentation, https://github.com/ma-pony/guided-motion/blob/main/README.zh-CN.md
Project-URL: Changelog, https://github.com/ma-pony/guided-motion/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/ma-pony/guided-motion/issues
Project-URL: Source, https://github.com/ma-pony/guided-motion
Author: ma-pony
License-Expression: MIT
License-File: LICENSE
Keywords: browser-automation,cursor,motion-planning,mouse-movement,path-planning,playwright,reference-path,trajectory
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Provides-Extra: playwright
Requires-Dist: playwright>=1.40; extra == 'playwright'
Description-Content-Type: text/markdown

# Guided Motion

<p align="center">
  <a href="https://github.com/ma-pony/guided-motion/blob/main/README.md">English</a> |
  <a href="https://github.com/ma-pony/guided-motion/blob/main/README.zh-CN.md">简体中文</a>
</p>

<p align="center">
  <a href="https://pypi.org/project/guided-motion/"><img src="https://img.shields.io/pypi/v/guided-motion" alt="PyPI version"></a>
  <a href="https://pypi.org/project/guided-motion/"><img src="https://img.shields.io/pypi/pyversions/guided-motion" alt="Python versions"></a>
  <a href="https://github.com/ma-pony/guided-motion/actions/workflows/test.yml"><img src="https://github.com/ma-pony/guided-motion/actions/workflows/test.yml/badge.svg" alt="Test status"></a>
  <a href="https://github.com/ma-pony/guided-motion/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/guided-motion" alt="License"></a>
</p>

Guided Motion turns a reference polyline into a bounded, timed sequence of pointer events.
Unlike point-to-point cursor generators, it preserves the shape of an arbitrary multi-anchor
path while varying its spatial sampling and timing inside explicit safety limits.

The core is pure Python with no runtime dependencies. A small optional adapter can execute a
plan through Playwright.

## Why

Many automation tools generate one curve between two points. That is useful for navigation but
not for drawing, annotation, route replay, complex dragging, or externally recognized gesture
paths. Guided Motion starts with the path you already have and makes controlled variations while
guaranteeing:

- every event remains inside rectangular bounds;
- the result stays within a maximum deviation from the reference path;
- the first and last events obey a tighter endpoint budget;
- event times are strictly increasing;
- the same seed produces the same complete plan.

## Installation

Install the dependency-free planner:

```bash
pip install guided-motion
```

Install it with Playwright available:

```bash
pip install "guided-motion[playwright]"
playwright install
```

## Plan a reference path

```python
from guided_motion import Bounds, Constraints, MotionPlanner

reference = [
    (30, 80),
    (90, 35),
    (145, 95),
    (220, 55),
]

plan = MotionPlanner(seed=42).plan(
    reference,
    bounds=Bounds(x=0, y=0, width=260, height=140),
    constraints=Constraints(
        max_deviation=6.0,
        endpoint_deviation=1.5,
    ),
)

print(plan.duration)
print(plan.max_deviation)
for event in plan.events:
    print(event.x, event.y, event.t)
```

`MotionPlan.events` is an immutable tuple of `MotionEvent(x, y, t)`. Time `t` is relative to the
start of execution in seconds.

## Execute with Playwright

Guided Motion sends every planned event once with `steps=1` and schedules it against the plan's
absolute relative deadline. It does not let Playwright add its own intermediate points.

```python
from guided_motion.playwright import execute_motion

first = plan.events[0]
await page.mouse.move(first.x, first.y)
await page.mouse.down()
try:
    await execute_motion(page.mouse, plan)
finally:
    await page.mouse.up()
```

Pressing, releasing, retries, and application state remain the caller's responsibility.

## VAPTCHA integration example

VAPTCHA gesture challenges are one possible source of a reference path. Guided Motion does not
take screenshots or recognize the challenge. Supply those points from your own authorized
recognition flow, convert them into page coordinates, and plan within the challenge image bounds:

```python
from guided_motion import Bounds, Constraints, MotionPlanner
from guided_motion.playwright import execute_motion

# Returned by the caller's screenshot/recognition code in image-local coordinates.
recognized_points = [(18, 92), (46, 65), (81, 104), (126, 48), (178, 83)]

box = await challenge_image.bounding_box()
if box is None:
    raise RuntimeError("challenge image is not visible")

reference = [(box["x"] + x, box["y"] + y) for x, y in recognized_points]
plan = MotionPlanner().plan(
    reference,
    bounds=Bounds(box["x"], box["y"], box["width"], box["height"]),
    constraints=Constraints(max_deviation=6.0, endpoint_deviation=1.5),
)

first = plan.events[0]
await page.mouse.move(first.x, first.y)
await page.mouse.down()
try:
    await execute_motion(page.mouse, plan)
finally:
    await page.mouse.up()
```

Recognition accuracy, coordinate transforms, verification status, challenge refresh, retries,
browser context lifecycle, and site authorization are deliberately outside this package.

## Mechanical uniformity and detection

Fixed Bezier shapes, fixed event counts, equal-distance samples, and fixed durations can make a
large set of automation traces mechanically uniform. Guided Motion introduces correlated spatial
and temporal variation while keeping every result inside measurable constraints.

This does **not** make automation undetectable and is not a general risk-control bypass. Detection
systems can also use network identity, browser state, surrounding interaction, account history,
and server-side signals. Guided Motion only plans pointer movement. Use it for systems and flows
you are authorized to automate.

## Constraints

```python
Constraints(
    max_deviation=6.0,
    endpoint_deviation=1.5,
    min_duration=0.55,
    max_duration=2.4,
    min_events=18,
    max_events=180,
)
```

The input reference must contain at least two distinct consecutive points and must already lie
inside `Bounds`. Guided Motion fails clearly when that contract is not satisfied instead of
silently changing coordinate systems.

## Reproducibility

Create a new planner with the same seed to reproduce a complete plan:

```python
first = MotionPlanner(seed=7).plan(reference, bounds=bounds)
second = MotionPlanner(seed=7).plan(reference, bounds=bounds)
assert first == second
```

A planner owns its random stream, so repeated calls on the same instance intentionally produce
new plans.

## Development

```bash
uv sync --all-extras --dev
uv run pytest -q
uv run ruff check src tests
uv run mypy src/guided_motion
uv build
```

Guided Motion supports Python 3.10 through 3.13 and is licensed under the MIT License.
