3.4 KiB
3.4 KiB
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.
sequenceDiagram
autonumber
participant Entry as move.ts
participant Keeper as runKeeper
participant Sim as simulateActivity
participant Strat as Strategy<br/>(e.g. line)
participant Exec as executePath
participant Dev as Device<br/>(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)<br/>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]<br/>ctx = { start, width, height, rng }
Sim->>Exec: executePath(strategy, ctx, dev, log, config)
Exec->>Strat: path(ctx)
Strat-->>Exec: Iterable<Point>
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 (== start; re-sync)
Note over Keeper: lastActivity = now<br/>loop continues
Invariants this path relies on
createNutDevice()is the only nut.js touchpoint. It disables nut.js's 100ms auto-delay soexecutePathowns cadence viastepDelay.- 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-upgetPosition()inrunKeeperre-syncslastPosto 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).