Add loop mode (--loop): repeat movement until user activity

Introduce a continuous "loop" setting so a triggered sweep keeps the
cursor moving until the user moves the mouse (or Ctrl+C), instead of
firing a single sweep.

- strategies.ts: add optional `loopPath` to MovementStrategy; give `line`
  and `diagonal` infinite loop generators that pick a direction once and
  ramp forever (4px/step). Their finite `path` and declared `bounds` are
  unchanged, so single-sweep behavior is identical.
- executor.ts: add ExecuteOptions { restore?, bounds?, loop? }. Omitting
  options reproduces the original single-sweep contract exactly.
- keeper.ts: in loop mode, run an infinite loopPath once (stopped only by
  interruption) or chain a finite path cycle after cycle; force `reflect`
  bounds for every pattern and suppress the between-cycle restore, so
  line/diagonal bounce edge-to-edge instead of stopping at the first edge.
- config plumbing: new boolean `loop` through config.default.json,
  config.ts, configFile.ts, cli.ts (-l/--loop), and move.ts, mirroring
  the existing `verbose` precedence.
- docs: README loop-mode section + usage/validation updates; CHANGELOG
  Unreleased entry.
- tests: loopPath generators, executor options (bounds override, loop
  selection, restore suppression), config/configFile loop plumbing, and
  keeper-level loop behavior (ramps far vs. bounded single-sweep, chained
  cycles). 79 pass.
This commit is contained in:
2026-08-17 14:36:18 -05:00
parent 1ad724cd33
commit 7e632b3e9d
15 changed files with 397 additions and 31 deletions
+38 -7
View File
@@ -123,6 +123,9 @@ Options:
size and speed.
-V, --verbose Log every sweep, interrupt, and bounds event
(default prints only the startup banner).
-l, --loop Loop mode: once a sweep is triggered,
keep moving until you move the mouse (or
Ctrl+C), instead of firing a single sweep.
Precedence (highest wins): CLI flags > config file > built-in defaults.
```
@@ -180,14 +183,15 @@ doesn't set.
"checkInterval": 10,
"stepDelay": 50,
"pattern": "line",
"verbose": false
"verbose": false,
"loop": false
}
```
All keys are optional; supply only the ones you want to override. Keys
and units mirror the CLI flags exactly: `moveInterval` and
`checkInterval` are seconds, `stepDelay` is milliseconds, `pattern` is a
movement strategy name, `verbose` is a boolean.
movement strategy name, `verbose` and `loop` are booleans.
> The obsolete `stepCount` / `stepSize` keys (removed in 1.3.0) are
> tolerated for backward compatibility: they're ignored with a one-line
@@ -223,16 +227,36 @@ The loader is strict:
case and separators (`-`, `_`, spaces), so `figure-eight` and `figureEight`
are equivalent.
- `verbose` must be a boolean.
- `loop` must be a boolean.
Any validation failure prints a message naming the file and the offending
key to `stderr` and exits `2`.
### Known limitation: `verbose` can be turned on but not off from the CLI
### Loop mode (`--loop`)
`--verbose` is a presence-only flag (there is no `--no-verbose`). If the
config file sets `"verbose": true`, the CLI cannot force quiet mode in
that invocation. Workarounds: edit the file, or point at a different
file with `--config`.
By default a triggered sweep runs once and stops. With `-l` / `--loop` (or
`"loop": true` in the config file) the movement instead repeats until you
move the mouse (or press `Ctrl+C`) — a "keep moving until I'm back" mode.
It pairs naturally with the roaming patterns:
```sh
move --pattern diagonal --loop # roaming-DVD bounce around the screen
move --pattern figureEight --loop # traces the eight over and over
```
In loop mode the cursor is never restored between iterations, and every
pattern's bounds policy is forced to `reflect`, so `line` and `diagonal`
bounce edge-to-edge across the whole screen instead of ending at the first
edge. Interruption is detected via mouse movement only — there is no
keyboard hook — so if you resume by typing without touching the mouse, the
cursor keeps cycling until you nudge it or stop the process.
### Known limitation: `verbose` and `loop` can be turned on but not off from the CLI
`--verbose` and `--loop` are presence-only flags (there is no
`--no-verbose` / `--no-loop`). If the config file sets `"verbose": true` or
`"loop": true`, the CLI cannot force it back off in that invocation.
Workarounds: edit the file, or point at a different file with `--config`.
## How it works
@@ -307,6 +331,13 @@ to milliseconds before handing the resolved `Config` to `runKeeper`.
the next idle-check sees "no movement" and doesn't misread the synthetic
activity as real user input.
In loop mode (`--loop`) step 2 repeats until the user interrupts: a
pattern with an infinite `loopPath` (`line`, `diagonal`) runs that single
never-ending path, while the others chain their finite path cycle after
cycle. The restore in step 3 is skipped so successive cycles flow from where
the last left off, and the bounds policy is forced to `reflect` for every
pattern so edge-seeking motion bounces instead of stopping.
Comparing against the last commanded (rounded) point — not the strategy's
ideal, possibly fractional target — is what lets curved and stochastic
patterns run without every rounded step looking like user activity. The