Add pluggable movement strategies (v1.3.0)
Turn the hardcoded straight-line sweep into a strategy system behind three
seams so new patterns are easy to add and, for the first time, testable
without nut.js or a real screen:
- src/device.ts: injectable Device seam over nut.js (autoDelayMs lives
here now); the only module that touches the native lib.
- src/strategies.ts: pure per-pattern path generators + registry + lenient
name resolution. Ships line, diagonal, jitter, walk,
arc, figureEight.
- src/executor.ts: single executePath driver owning bounds policy
(abort/clamp/reflect), pacing, interrupt detection, and
restore-on-clean.
keeper.ts's simulateActivity now selects a strategy and delegates to the
executor; the default `line` pattern is byte-for-byte the previous behavior.
New config surface, layered CLI > file > default with strict validation:
- -p/--pattern <name> movement strategy (names matched case/-/_-insensitive)
- -s/--step-size <px> pixels per step; stepCount is now a step *count*
Robustness for the new edge-seeking patterns: interrupt detection compares
against the last commanded (rounded) point with a 2px tolerance, and
clamp/reflect stay a couple pixels off the screen edge, so sub-pixel cursor
placement on scaled/multi-monitor displays isn't misread as user activity.
jitter's radius scales with sweep length so it moves at the default stepSize.
Tests: new suites for strategies, the executor (all bounds policies,
rounding, interrupt, tolerance, pacing), and the keeper loop; config and
configFile suites extended for pattern/stepSize. editor.test.ts moved to
tests/ for consistency. 64 pass.
This commit is contained in:
+24
-1
@@ -10,7 +10,9 @@
|
||||
* moveInterval number seconds, positive
|
||||
* checkInterval number seconds, positive
|
||||
* stepDelay number milliseconds, positive
|
||||
* stepCount number pixels, positive
|
||||
* stepCount number count, positive
|
||||
* stepSize number pixels, positive
|
||||
* pattern string a registered strategy name
|
||||
* verbose boolean
|
||||
*
|
||||
* Unknown keys, wrong types, and non-positive numerics are rejected with a
|
||||
@@ -29,12 +31,15 @@ import { existsSync, readFileSync, statSync } from "node:fs";
|
||||
|
||||
import { defaultConfigPath, type ConfigOverrides } from "./config.ts";
|
||||
import { CliError } from "./errors.ts";
|
||||
import { PATTERN_NAMES, resolvePatternName } from "./strategies.ts";
|
||||
|
||||
const ALLOWED_KEYS: ReadonlySet<string> = new Set<string>([
|
||||
"moveInterval",
|
||||
"checkInterval",
|
||||
"stepDelay",
|
||||
"stepCount",
|
||||
"stepSize",
|
||||
"pattern",
|
||||
"verbose",
|
||||
]);
|
||||
|
||||
@@ -60,6 +65,16 @@ function requireBoolean(name: string, raw: unknown, path: string): boolean {
|
||||
return raw;
|
||||
}
|
||||
|
||||
function requirePatternName(name: string, raw: unknown, path: string): string {
|
||||
const canonical: string | null = typeof raw === "string" ? resolvePatternName(raw) : null;
|
||||
if (canonical === null) {
|
||||
throw new CliError(
|
||||
`invalid value for '${name}' in ${path}: ${JSON.stringify(raw)} (valid: ${PATTERN_NAMES.join(", ")})`,
|
||||
);
|
||||
}
|
||||
return canonical;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and validate the config file. See module docstring for return
|
||||
* semantics.
|
||||
@@ -130,6 +145,14 @@ export function loadConfigFile(explicitPath: string | undefined): ConfigOverride
|
||||
"stepCount" in parsed
|
||||
? requirePositiveNumber("stepCount", parsed.stepCount, path)
|
||||
: undefined,
|
||||
stepSize:
|
||||
"stepSize" in parsed
|
||||
? requirePositiveNumber("stepSize", parsed.stepSize, path)
|
||||
: undefined,
|
||||
pattern:
|
||||
"pattern" in parsed
|
||||
? requirePatternName("pattern", parsed.pattern, path)
|
||||
: undefined,
|
||||
verbose:
|
||||
"verbose" in parsed
|
||||
? requireBoolean("verbose", parsed.verbose, path)
|
||||
|
||||
Reference in New Issue
Block a user