# Execution: the happy path This traces one full idle-triggered sweep that completes cleanly — the "happy path" where the machine is idle long enough to fire, the configured pattern runs to exhaustion, and no real user activity interrupts it. For the module breakdown and the three seams (`device` / `strategies` / `executor`), see the "How it works" section of the [README](../README.md). ```mermaid sequenceDiagram autonumber participant Entry as move.ts participant Keeper as runKeeper participant Sim as simulateActivity participant Strat as Strategy
(e.g. line) participant Exec as executePath participant Dev as Device
(nut.js) Note over Entry: startup (args → config) Entry->>Entry: parseCliArgs() Entry->>Entry: loadConfigFile() Entry->>Entry: resolveConfig(file, cli) Entry->>Keeper: runKeeper(config) Keeper->>Dev: createNutDevice() Note right of Dev: sets mouse.config.autoDelayMs = 0 Keeper->>Keeper: log.info(banner) Keeper->>Dev: getPosition() Dev-->>Keeper: lastPos Note over Keeper: lastActivity = now loop every checkInterval (until idle long enough) Keeper->>Dev: sleep(checkInterval) Keeper->>Dev: getPosition() Dev-->>Keeper: pos Note over Keeper: pos == lastPos (no user movement)
now - lastActivity ≥ moveInterval → fire end Keeper->>Sim: simulateActivity(config, log, dev) Sim->>Dev: width() Dev-->>Sim: width Sim->>Dev: height() Dev-->>Sim: height Sim->>Dev: getPosition() Dev-->>Sim: start Note over Sim: strategy = STRATEGIES[config.pattern]
ctx = { start, width, height, rng } Sim->>Exec: executePath(strategy, ctx, dev, log, config) Exec->>Strat: path(ctx) Strat-->>Exec: iterable of Points loop for each target point (clean run) Exec->>Exec: resolveTarget(target) → point (reflected on-screen) Exec->>Dev: setPosition(point) Exec->>Dev: sleep(stepDelay) Exec->>Dev: getPosition() Dev-->>Exec: current Note over Exec: |current - point| ≤ 2px → not the user, continue end Note over Exec: path exhausted, no interruption Exec->>Dev: setPosition(round(start)) Note right of Exec: restore cursor to origin Exec-->>Sim: "completed" Sim-->>Keeper: (done) Keeper->>Dev: getPosition() Dev-->>Keeper: lastPos (equals start, re-synced) Note over Keeper: lastActivity = now
loop continues ``` ## Invariants this path relies on - **`createNutDevice()` is the only nut.js touchpoint.** It disables nut.js's 100ms auto-delay so `executePath` owns cadence via `stepDelay`. - **The strategy is pure.** `path(ctx)` yields ideal points from geometry alone (`start` / `width` / `height` / `rng`); it never touches the device, which is what makes every pattern unit-testable without a screen. - **Every step re-reads the cursor** and compares it against the *commanded* point (not the strategy's ideal, possibly fractional target) within a 2px tolerance. On the happy path each check passes, so the loop runs to exhaustion. A mismatch beyond tolerance is real user activity and returns `"interrupted"` without restoring — the branch this diagram omits. - **Clean completion restores the cursor to `round(start)`.** That is why the follow-up `getPosition()` in `runKeeper` re-syncs `lastPos` to the origin as a no-op, and the next idle check sees no net movement (so the synthetic sweep is never mistaken for the user returning). - **On-screen confinement is uniform.** `resolveTarget` reflects any coordinate past a screen edge back inside the travel range — the sole, per-pattern-independent policy. A strategy emits ideal geometry and never bounds its own output. ## Loop mode (`--loop`) This diagram is the single-sweep path (`config.loop === false`). Under `--loop`, `simulateActivity` instead repeats the step loop until the user interrupts: a pattern with an infinite `loopPath` (`line`, `diagonal`) runs that one never-ending path, while the others chain their finite `path` cycle after cycle, re-reading the cursor as the next `start` each time. The restore in the final step is skipped (`restore: false`), so successive cycles flow from where the last left off. Everything else — reflection, pacing, and the per-step interrupt check — is identical to the sweep traced above.