# 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: getPosition() Dev-->>Sim: start Sim->>Dev: width() Dev-->>Sim: width Sim->>Dev: height() Dev-->>Sim: height 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(bounds, target) → point 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).