Remove stepCount/stepSize; patterns own their geometry
The stepCount and stepSize knobs were two controls for one quantity users actually care about (reach), and the number of steps is an implementation detail nobody meaningfully tunes. Each pattern has a natural size and resolution — a jitter is inherently small, an arc a broad curve — so those now live as constants in each strategy rather than as global config. - strategies.ts: each pattern defines its own step count and size; MoveContext drops `config` down to pure geometry (start/width/height/rng), and the module no longer imports Config at all (dissolving the type-only-import cycle workaround). line stays byte-for-byte: 250 one-pixel steps. - executor.ts: executePath takes `config` for pacing (stepDelay); the path itself needs nothing from it. - config.ts / cli.ts / move.ts / config.default.json: drop stepCount and stepSize from the type, seed, validation, resolver, CLI flags (-n, -s), and help. stepDelay stays as the one pacing lever. - configFile.ts: tolerate the removed keys instead of rejecting them — every pre-1.3.0 install seeded stepCount, so a hard "unknown key" failure on upgrade is avoided. They're ignored with a one-line stderr notice; genuine unknown keys still error. The -n/--step-count CLI flag (shipped since 1.0.0) is now an unknown option; config files degrade gracefully, command lines don't. Stays in the unpushed 1.3.0 release. 64 tests pass.
This commit is contained in:
+34
-18
@@ -10,14 +10,14 @@
|
||||
* moveInterval number seconds, positive
|
||||
* checkInterval number seconds, positive
|
||||
* stepDelay number milliseconds, 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
|
||||
* `CliError` so the entry point can exit 2 (user error) with a clear
|
||||
* message pointing at the offending file.
|
||||
* message pointing at the offending file. The removed `stepCount` /
|
||||
* `stepSize` keys are the exception: they're tolerated (ignored with a
|
||||
* one-line notice) so an older seeded config keeps working after upgrade.
|
||||
*
|
||||
* Return semantics:
|
||||
* - `null` when no `explicitPath` was passed and the default path does
|
||||
@@ -37,12 +37,23 @@ const ALLOWED_KEYS: ReadonlySet<string> = new Set<string>([
|
||||
"moveInterval",
|
||||
"checkInterval",
|
||||
"stepDelay",
|
||||
"stepCount",
|
||||
"stepSize",
|
||||
"pattern",
|
||||
"verbose",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Keys that used to be valid but have since been removed. They're tolerated
|
||||
* (not rejected like a genuine unknown key) so upgrading doesn't hard-fail a
|
||||
* config that was seeded with them — every pre-1.3.0 install has `stepCount`
|
||||
* in its file. They no longer do anything: sweep size and step count are now
|
||||
* properties of each movement pattern. A one-line notice points the user at
|
||||
* the file so they can remove them at leisure.
|
||||
*/
|
||||
const DEPRECATED_KEYS: ReadonlySet<string> = new Set<string>([
|
||||
"stepCount",
|
||||
"stepSize",
|
||||
]);
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
@@ -119,13 +130,26 @@ export function loadConfigFile(explicitPath: string | undefined): ConfigOverride
|
||||
throw new CliError(`config file ${path} must contain a JSON object at the root`);
|
||||
}
|
||||
|
||||
// Strict mode: reject any key we don't know about. Catches typos like
|
||||
// 'movInterval' that would otherwise sail through silently.
|
||||
// Strict mode: reject any key we don't know about (catches typos like
|
||||
// 'movInterval'), but tolerate keys we've since removed — collect those
|
||||
// and warn once, rather than hard-failing a config seeded by an older
|
||||
// install.
|
||||
const deprecatedFound: string[] = [];
|
||||
for (const key of Object.keys(parsed)) {
|
||||
if (!ALLOWED_KEYS.has(key)) {
|
||||
const allowed: string = [...ALLOWED_KEYS].join(", ");
|
||||
throw new CliError(`unknown key '${key}' in ${path} (allowed: ${allowed})`);
|
||||
if (ALLOWED_KEYS.has(key)) continue;
|
||||
if (DEPRECATED_KEYS.has(key)) {
|
||||
deprecatedFound.push(key);
|
||||
continue;
|
||||
}
|
||||
const allowed: string = [...ALLOWED_KEYS].join(", ");
|
||||
throw new CliError(`unknown key '${key}' in ${path} (allowed: ${allowed})`);
|
||||
}
|
||||
if (deprecatedFound.length > 0) {
|
||||
const names: string = deprecatedFound.map((k) => `'${k}'`).join(", ");
|
||||
process.stderr.write(
|
||||
`move: ignoring obsolete key(s) ${names} in ${path}\n` +
|
||||
` (sweep size is now defined by each movement pattern)\n`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -141,14 +165,6 @@ export function loadConfigFile(explicitPath: string | undefined): ConfigOverride
|
||||
"stepDelay" in parsed
|
||||
? requirePositiveNumber("stepDelay", parsed.stepDelay, path)
|
||||
: undefined,
|
||||
stepCount:
|
||||
"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)
|
||||
|
||||
Reference in New Issue
Block a user