scan-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Scan Engine
2
+
3
+ Headless scan engine for switch-scanning interfaces. This package provides scanning strategies (row/column, linear, snake, elimination, etc.) without any UI. You provide a `ScanSurface` adapter for your UI and wire `ScanCallbacks` for selection events.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install scan-engine
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { LinearScanner, type ScanSurface, type ScanConfigProvider } from 'scan-engine';
15
+
16
+ const surface: ScanSurface = {
17
+ getItemsCount: () => items.length,
18
+ getColumns: () => 8,
19
+ setFocus: (indices) => highlight(indices),
20
+ setSelected: (index) => flash(index),
21
+ getItemData: (index) => ({ label: items[index].label })
22
+ };
23
+
24
+ const config: ScanConfigProvider = {
25
+ get: () => ({
26
+ scanRate: 800,
27
+ scanInputMode: 'auto',
28
+ scanDirection: 'circular',
29
+ scanPattern: 'linear',
30
+ scanTechnique: 'point',
31
+ scanMode: null,
32
+ continuousTechnique: 'crosshair',
33
+ compassMode: 'continuous',
34
+ eliminationSwitchCount: 4,
35
+ allowEmptyItems: false,
36
+ initialItemPause: 0,
37
+ scanLoops: 0,
38
+ criticalOverscan: { enabled: false, fastRate: 100, slowRate: 1000 },
39
+ colorCode: { errorRate: 0.1, selectThreshold: 0.95 }
40
+ })
41
+ };
42
+
43
+ const scanner = new LinearScanner(surface, config, {
44
+ onSelect: (index) => console.log('Selected', index)
45
+ });
46
+
47
+ scanner.start();
48
+ ```
49
+
50
+ ## Continuous Scanning UI Hooks
51
+
52
+ Continuous scanning is headless. Provide optional hooks for UI:
53
+
54
+ ```ts
55
+ import type { ContinuousUpdate, ScanSurface, ScanCallbacks } from 'scan-engine';
56
+
57
+ const surface: ScanSurface = {
58
+ getItemsCount: () => items.length,
59
+ getColumns: () => 8,
60
+ setFocus: (indices) => highlight(indices),
61
+ setSelected: (index) => flash(index),
62
+ resolveIndexAtPoint: (xPercent, yPercent) => resolveIndex(xPercent, yPercent)
63
+ };
64
+
65
+ const callbacks: ScanCallbacks = {
66
+ onContinuousUpdate: (state: ContinuousUpdate) => {
67
+ renderContinuousOverlay(state);
68
+ }
69
+ };
70
+ ```
71
+
72
+ If you are using a DOM grid, the companion package `scan-engine-dom` provides an overlay renderer and hit-testing helper.
73
+
74
+ ## Strategies
75
+
76
+ - Row/Column
77
+ - Linear
78
+ - Snake
79
+ - Quadrant
80
+ - Group-Row-Column
81
+ - Elimination
82
+ - Continuous (crosshair, gliding, eight-direction)
83
+ - Probability
84
+ - Cause/Effect
85
+ - ColorCode
86
+
87
+ ## License
88
+
89
+ MIT
@@ -0,0 +1,317 @@
1
+ type SwitchAction = 'select' | 'step' | 'reset' | 'cancel' | 'menu' | 'switch-1' | 'switch-2' | 'switch-3' | 'switch-4' | 'switch-5' | 'switch-6' | 'switch-7' | 'switch-8';
2
+ interface ScanItemData {
3
+ label?: string;
4
+ isEmpty?: boolean;
5
+ }
6
+ interface ScanItemStyle {
7
+ backgroundColor?: string;
8
+ textColor?: string;
9
+ borderColor?: string;
10
+ borderWidth?: number;
11
+ boxShadow?: string;
12
+ opacity?: number;
13
+ }
14
+ interface FocusMeta {
15
+ phase?: 'major' | 'minor' | 'item';
16
+ scanRate?: number;
17
+ scanPattern?: string;
18
+ scanTechnique?: string;
19
+ scanDirection?: string;
20
+ }
21
+ type ContinuousTechnique = 'gliding' | 'crosshair' | 'eight-direction';
22
+ type ContinuousState = 'x-scan' | 'y-scan' | 'x-scanning' | 'x-capturing' | 'y-scanning' | 'y-capturing' | 'direction-scan' | 'moving' | 'processing';
23
+ interface ContinuousUpdate {
24
+ technique: ContinuousTechnique;
25
+ state: ContinuousState;
26
+ xPos: number;
27
+ yPos: number;
28
+ bufferLeft: number;
29
+ bufferRight: number;
30
+ bufferTop: number;
31
+ bufferBottom: number;
32
+ fineXPos: number;
33
+ fineYPos: number;
34
+ lockedXPosition: number;
35
+ compassAngle: number;
36
+ currentDirection: number;
37
+ directionName: string;
38
+ directionDx: number;
39
+ directionDy: number;
40
+ }
41
+ interface ScanSurface {
42
+ getItemsCount(): number;
43
+ getColumns(): number;
44
+ setFocus(indices: number[], meta?: FocusMeta): void;
45
+ setSelected(index: number): void;
46
+ getItemData?(index: number): ScanItemData | null;
47
+ setItemStyle?(index: number, style: ScanItemStyle): void;
48
+ clearItemStyles?(): void;
49
+ getContainerElement?(): HTMLElement | null;
50
+ resolveIndexAtPoint?(xPercent: number, yPercent: number): number | null;
51
+ }
52
+ interface ScanCallbacks {
53
+ onScanStep?: () => void;
54
+ onSelect?: (index: number) => void;
55
+ onRedraw?: () => void;
56
+ onContinuousUpdate?: (state: ContinuousUpdate) => void;
57
+ }
58
+ interface CriticalOverscanConfig {
59
+ enabled: boolean;
60
+ fastRate: number;
61
+ slowRate: number;
62
+ }
63
+ interface ColorCodeConfig {
64
+ errorRate: number;
65
+ selectThreshold: number;
66
+ }
67
+ interface ScanConfig {
68
+ scanRate: number;
69
+ scanInputMode: 'auto' | 'manual';
70
+ scanDirection: 'circular' | 'reverse' | 'oscillating';
71
+ scanPattern: 'row-column' | 'column-row' | 'linear' | 'snake' | 'quadrant' | 'elimination';
72
+ scanTechnique: 'block' | 'point';
73
+ scanMode: 'group-row-column' | 'continuous' | 'probability' | 'cause-effect' | 'color-code' | null;
74
+ continuousTechnique: ContinuousTechnique;
75
+ compassMode: 'continuous' | 'fixed-8';
76
+ eliminationSwitchCount: 2 | 3 | 4 | 5 | 6 | 7 | 8;
77
+ allowEmptyItems: boolean;
78
+ initialItemPause: number;
79
+ scanLoops: number;
80
+ criticalOverscan: CriticalOverscanConfig;
81
+ colorCode: ColorCodeConfig;
82
+ }
83
+ interface ScanConfigProvider {
84
+ get(): ScanConfig;
85
+ }
86
+
87
+ declare enum OverscanState {
88
+ FAST = "fast",
89
+ SLOW_BACKWARD = "slow_backward"
90
+ }
91
+ declare abstract class Scanner {
92
+ protected surface: ScanSurface;
93
+ protected config: ScanConfigProvider;
94
+ protected callbacks: ScanCallbacks;
95
+ protected isRunning: boolean;
96
+ protected timer: number | null;
97
+ protected stepCount: number;
98
+ protected overscanState: OverscanState;
99
+ protected loopCount: number;
100
+ constructor(surface: ScanSurface, config: ScanConfigProvider, callbacks?: ScanCallbacks);
101
+ start(): void;
102
+ stop(): void;
103
+ handleAction(action: SwitchAction): void;
104
+ protected handleSelectAction(): void;
105
+ protected abstract step(): void;
106
+ protected abstract reset(): void;
107
+ protected reportCycleCompleted(): void;
108
+ protected scheduleNextStep(): void;
109
+ protected triggerSelection(index: number): void;
110
+ protected triggerRedraw(): void;
111
+ abstract getCost(itemIndex: number): number;
112
+ protected abstract doSelection(): void;
113
+ mapContentToGrid<T>(content: T[], _rows: number, _cols: number): T[];
114
+ }
115
+
116
+ declare class RowColumnScanner extends Scanner {
117
+ private level;
118
+ private currentRow;
119
+ private currentCol;
120
+ private totalRows;
121
+ private isColumnRow;
122
+ private useBlockScanning;
123
+ start(): void;
124
+ private recalcDimensions;
125
+ protected reset(): void;
126
+ protected step(): void;
127
+ private stepMajor;
128
+ private stepMinor;
129
+ private highlightMajor;
130
+ handleAction(action: SwitchAction): void;
131
+ protected doSelection(): void;
132
+ getCost(itemIndex: number): number;
133
+ mapContentToGrid<T>(content: T[], rows: number, cols: number): T[];
134
+ }
135
+
136
+ declare class LinearScanner extends Scanner {
137
+ private currentIndex;
138
+ private totalItems;
139
+ private direction;
140
+ start(): void;
141
+ private countItems;
142
+ protected reset(): void;
143
+ protected step(): void;
144
+ handleAction(action: SwitchAction): void;
145
+ protected doSelection(): void;
146
+ getCost(itemIndex: number): number;
147
+ }
148
+
149
+ declare class SnakeScanner extends Scanner {
150
+ private currentRow;
151
+ private currentCol;
152
+ private direction;
153
+ private maxRow;
154
+ private maxCol;
155
+ start(): void;
156
+ private updateDimensions;
157
+ protected reset(): void;
158
+ protected step(): void;
159
+ handleAction(action: SwitchAction): void;
160
+ protected doSelection(): void;
161
+ getCost(itemIndex: number): number;
162
+ mapContentToGrid<T>(content: T[], rows: number, cols: number): T[];
163
+ }
164
+
165
+ declare class QuadrantScanner extends Scanner {
166
+ private level;
167
+ private currentQuad;
168
+ private currentRow;
169
+ private currentCol;
170
+ private quads;
171
+ start(): void;
172
+ private calcQuadrants;
173
+ protected reset(): void;
174
+ protected step(): void;
175
+ private stepQuadrant;
176
+ private stepRow;
177
+ private stepCell;
178
+ private highlightQuad;
179
+ private highlightRowSegment;
180
+ handleAction(action: SwitchAction): void;
181
+ protected doSelection(): void;
182
+ private restartTimer;
183
+ getCost(itemIndex: number): number;
184
+ mapContentToGrid<T>(content: T[], rows: number, cols: number): T[];
185
+ }
186
+
187
+ declare class GroupScanner extends Scanner {
188
+ private level;
189
+ private currentGroup;
190
+ private currentRow;
191
+ private currentCol;
192
+ private groups;
193
+ start(): void;
194
+ private calcGroups;
195
+ protected reset(): void;
196
+ protected step(): void;
197
+ private stepGroup;
198
+ private stepRow;
199
+ private stepCell;
200
+ private highlightGroup;
201
+ private highlightRow;
202
+ handleAction(action: SwitchAction): void;
203
+ protected doSelection(): void;
204
+ private restartTimer;
205
+ getCost(itemIndex: number): number;
206
+ }
207
+
208
+ declare class EliminationScanner extends Scanner {
209
+ private rangeStart;
210
+ private rangeEnd;
211
+ private currentBlock;
212
+ private numSwitches;
213
+ private partitionHistory;
214
+ start(): void;
215
+ protected reset(): void;
216
+ private clearHighlights;
217
+ protected step(): void;
218
+ private highlightCurrentBlock;
219
+ private calculatePartitions;
220
+ private getSwitchAction;
221
+ handleAction(action: SwitchAction): void;
222
+ protected doSelection(): void;
223
+ private restartTimer;
224
+ getCost(itemIndex: number): number;
225
+ protected scheduleNextStep(): void;
226
+ }
227
+
228
+ declare class ContinuousScanner extends Scanner {
229
+ private state;
230
+ private xPos;
231
+ private yPos;
232
+ private technique;
233
+ private numCols;
234
+ private numRows;
235
+ private bufferWidth;
236
+ private direction;
237
+ private pauseTimer;
238
+ private bufferLeft;
239
+ private bufferRight;
240
+ private bufferTop;
241
+ private bufferBottom;
242
+ private fineXPos;
243
+ private fineYPos;
244
+ private lockedXPosition;
245
+ private currentDirection;
246
+ private compassAngle;
247
+ private compassMode;
248
+ private directionStepCounter;
249
+ private directionStepsPerChange;
250
+ private directions;
251
+ start(): void;
252
+ stop(): void;
253
+ protected reset(): void;
254
+ protected step(): void;
255
+ private getDirectionFromAngle;
256
+ protected scheduleNextStep(): void;
257
+ handleAction(action: SwitchAction): void;
258
+ protected doSelection(): void;
259
+ private selectFocusedItem;
260
+ private selectAtPoint;
261
+ private selectAtPercent;
262
+ private estimateIndexAtPercent;
263
+ private emitUpdate;
264
+ getCost(itemIndex: number): number;
265
+ }
266
+
267
+ declare class ProbabilityScanner extends Scanner {
268
+ private predictor;
269
+ private scanOrder;
270
+ private currentIndex;
271
+ start(): void;
272
+ protected reset(): void;
273
+ protected step(): void;
274
+ handleAction(action: SwitchAction): void;
275
+ protected doSelection(): void;
276
+ private updateProbabilities;
277
+ getCost(itemIndex: number): number;
278
+ }
279
+
280
+ declare class CauseEffectScanner extends Scanner {
281
+ start(): void;
282
+ handleAction(action: SwitchAction): void;
283
+ protected doSelection(): void;
284
+ protected step(): void;
285
+ protected reset(): void;
286
+ getCost(_itemIndex: number): number;
287
+ }
288
+
289
+ declare class ColorCodeScanner extends Scanner {
290
+ private probabilities;
291
+ private colors;
292
+ start(): void;
293
+ stop(): void;
294
+ handleAction(action: SwitchAction): void;
295
+ protected step(): void;
296
+ protected reset(): void;
297
+ getCost(_itemIndex: number): number;
298
+ protected doSelection(): void;
299
+ private initializeBelief;
300
+ private assignColors;
301
+ private applyColors;
302
+ private updateBelief;
303
+ }
304
+
305
+ interface Prediction {
306
+ text: string;
307
+ probability: number;
308
+ }
309
+ declare class PredictorManager {
310
+ private predictor;
311
+ constructor();
312
+ addToContext(char: string): void;
313
+ resetContext(): void;
314
+ predictNext(): Prediction[];
315
+ }
316
+
317
+ export { CauseEffectScanner, ColorCodeScanner, ContinuousScanner, type ContinuousState, type ContinuousTechnique, type ContinuousUpdate, EliminationScanner, type FocusMeta, GroupScanner, LinearScanner, OverscanState, PredictorManager, ProbabilityScanner, QuadrantScanner, RowColumnScanner, type ScanCallbacks, type ScanConfig, type ScanConfigProvider, type ScanItemData, type ScanItemStyle, type ScanSurface, Scanner, SnakeScanner, type SwitchAction };