docs: add execution happy-path sequence diagram
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# 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<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 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).
|
||||
Reference in New Issue
Block a user