Table of Contents
MouseReplayer
MouseReplayer captures how a specific human moves a mouse — not just where, but the velocity profile, micro-tremor, overshoot pattern, and approach angle. It replays those trajectories through the Linux kernel input stack at the same temporal precision they were recorded.
The Pipeline
Human → [Capture] → Parquet → [Replay] → OS Input Stack → [Validate] → Fidelity Score
The framework is built around four stages, each with its own architectural decisions documented in this wiki:
- Capture — A threaded sampler decoupled from the render loop records cursor position at 120 Hz alongside 18 kinematic features, persisted as self-describing Apache Parquet files. See Capture.
- Persist — Sessions are serialised with full target-layout metadata, making every Parquet file a self-contained record reproducible without external state. See Data Model and Seeded Layouts.
- Replay — A two-phase injection strategy (absolute warp, then relative deltas) drives a plugin-based backend architecture. The primary backend uses Linux uinput for kernel-level injection, producing
isTrusted: truebrowser events. See Playback and Why evdev. - Validate — Eleven kinematic features are compared between human and replay sessions; each metric must stay within 20% relative error for the replay to pass. See Fidelity Scoring.
Design Principles
1. Kernel-Level Injection for Trusted Events
Browsers expose event.isTrusted to JavaScript — events injected through Playwright or Selenium always carry isTrusted: false, detectable by a single line of client-side code. To produce events the browser treats as genuine hardware, injection must happen below the browser. See Why evdev and Not xdotool/ydotool.
2. Sampling Decoupled from Rendering
Vsync-based frame capping limits capture rate to the display refresh rate (e.g., 60 Hz on a 60 Hz panel), aliasing the kinematic signal. A dedicated SamplerThread fires at perf_counter-based deadlines independently of the render loop, maintaining 120 Hz capture even on a 60 Hz display. See Why Sampling Is Decoupled.
3. Deterministic, Self-Contained Sessions
If a recorded session's target layout depends on external state — an unseeded RNG, a config file that may not exist at replay time — the environment cannot be faithfully reconstructed. Every recording starts from a hex seed that deterministically drives target layout generation via numpy.random.default_rng(). The seed, the target positions, and the arena dimensions are stored in the Parquet file's metadata. Replaying a session reconstructs the exact layout from metadata alone — no RNG, no external config. See Seeded Layouts.
4. Minimum-Jerk as a Comparison Baseline
To measure what makes a human trajectory distinct from the shortest-path ideal, you need a reference baseline. The ideal trajectory between two points is modelled as a 5th-order polynomial minimising integrated jerk (rate of change of acceleration), producing a symmetric bell-shaped velocity profile with zero velocity at start and end. This is not a generative human model — it is that baseline. Deviation from the ideal is the signal. See The Minimum-Jerk Trajectory.
5. Two-Phase, Not Single-Curve
Real human pointing has a ballistic (open-loop) phase followed by a corrective (closed-loop) phase, producing multi-peak velocity profiles. The current phase detection labels samples as grab, transit, approach, dwell, or click based on spatial proximity to the target, capturing the transition from gross transport to fine correction. See Movement Phases and Two-Phase Pointing.
Guide to This Wiki
The wiki is organised into five sections. If you're new to the project, read in this order:
- Concepts — The motivation and theory. Start with Why Mouse Movement Fidelity Matters, then Fitts' Law, Minimum-Jerk Trajectory, Two-Phase Pointing, and Jitter.
- Data Model — The schema, phase labels, and layout reproducibility. Reference pages you will return to while working with recorded data.
- Capture — Two architectural decisions exclusive to the capture path: the threaded sampler and the vsync-off display config.
- Playback — How a Parquet file becomes injected hardware events. Read in sequence: backend architecture, evdev rationale, absolute+relative injection, and why stored
dx/dyare ignored. - Analysis — What you do with recorded data: fidelity scoring, corpus construction, and cross-session biometric consistency.
Project Layout
src/mousereplayer/
├── arena/ # Rendering, target primitives, deterministic canvas generation
├── telemetry/ # Sampling, kinematic recording, feature extraction
├── storage/ # Parquet I/O, date-based session organisation
├── analytics/ # Kinetic baseline model, validation, segment comparison
├── playback/ # PlaybackEngine, uinput injection driver, backend registry
├── observability/ # Coloured console and file logging
├── config.py # Frozen dataclass configuration
├── controller.py # AppController — recording lifecycle coordinator
├── main.py # CLI entry point
└── __main__.py # `python -m mousereplayer` entry point
Quick Start Reference
pip install -r requirements.txt
# Interactive capture session
python -m mousereplayer record
# Replay via hardware injection (dry-run for non-Linux)
python -m mousereplayer playback sessions/*.parquet --dry-run
# Validate playback fidelity
python -m mousereplayer validate sessions/human.parquet sessions/replay.parquet
# List and inspect recorded sessions
python manage.py list
python manage.py inspect sessions/*.parquet
Mousereplayer Wiki
Concepts
- Why Mouse Movement Fidelity Matters
- Fitts' Law and Why We Use It (For Now)
- The Minimum-Jerk Trajectory: Math and Rationale
- Two-Phase Pointing: Ballistic and Corrective
- Jitter: What We Measure and Why Not Jerk
Data Model
Capture
Playback
- Why
dx/dyAre Ignored During Replay - The Playback Backend Plugin Architecture
- Why evdev and Not xdotool/ydotool
- Absolute + Relative Injection Strategy