@turing-machine-js/visuals 7.0.0-alpha.6 → 7.0.0-alpha.7

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.
@@ -1,94 +0,0 @@
1
- /**
2
- * Contract between the pure highlight logic (`applyHighlight`,
3
- * `applyIndicator`) and any consumer that actually renders the graph
4
- * (Svelte component, vanilla embed, server-side snapshot, etc.).
5
- *
6
- * The pure functions decide *what* should happen (which node gets a class,
7
- * which edge lights up, where to pulse); the consumer's `HighlightOps`
8
- * implementation decides *how* (DOM mutation, recording for tests, etc.).
9
- *
10
- * See `docs/graph-highlight-and-breakpoints.md` for the rules each
11
- * implementation must respect.
12
- */
13
-
14
- export type NodeKey = number | 'idle';
15
-
16
- /** Classes the apply-highlight pass may add to a `g.node` element. */
17
- export type HighlightClass =
18
- | 'mg-highlight-from'
19
- | 'mg-highlight-to'
20
- | 'mg-highlight-strong';
21
-
22
- /**
23
- * Operations the highlight logic invokes on the rendered graph. Purely
24
- * additive — the consumer is expected to clear previous highlight state
25
- * (classes, marker swaps) BEFORE invoking `applyHighlight`. The pure
26
- * function never reads back from the consumer; it just emits ops.
27
- *
28
- * Edge keys follow mermaid's data-id token form: `'idle'` for the
29
- * synthetic entry sentinel, `'s${id}'` for regular/wrapper/bare states,
30
- * `'c${id}'` for halt markers (id = `-frameId`), `'w_${id}'` for callable-
31
- * subtree subgraph clusters. Mermaid emits `L_${from}_${to}_${ix}` per
32
- * edge; ix-resolution is the consumer's concern (multiple edges between
33
- * the same pair are rare; the consumer typically picks the first match).
34
- */
35
- export interface HighlightOps {
36
- /** Add a highlight class to the node identified by `id`. */
37
- addNodeClass(id: NodeKey, cls: HighlightClass): void;
38
-
39
- /** Highlight the edge whose data-id matches `L_${fromKey}_${toKey}_*`. */
40
- highlightEdge(fromKey: string, toKey: string): void;
41
-
42
- /** Mark the callable-subtree cluster for `frameId` as active. */
43
- markFrameActive(frameId: number): void;
44
-
45
- /** Fire a one-shot pulse animation on the given node. */
46
- pulse(id: NodeKey): void;
47
-
48
- /** Scroll the given node into the visible area of its container. */
49
- scrollIntoView(id: NodeKey): void;
50
- }
51
-
52
- /** Operations the indicator (breakpoint dot) pass invokes. */
53
- export interface IndicatorOps {
54
- /** Set or clear the breakpoint indicator on the given node. */
55
- setBreakpoint(id: NodeKey, on: boolean): void;
56
- }
57
-
58
- /** A single recorded op — serializable, suitable for snapshot tests. */
59
- export type RecordedOp =
60
- | { op: 'addNodeClass'; id: NodeKey; cls: HighlightClass }
61
- | { op: 'highlightEdge'; fromKey: string; toKey: string }
62
- | { op: 'markFrameActive'; frameId: number }
63
- | { op: 'pulse'; id: NodeKey }
64
- | { op: 'scrollIntoView'; id: NodeKey }
65
- | { op: 'setBreakpoint'; id: NodeKey; on: boolean };
66
-
67
- /**
68
- * Build a recording `HighlightOps` + `IndicatorOps` pair plus the shared
69
- * `record` array of calls in invocation order. Used by tests to assert
70
- * what the pure logic would have done without running a real DOM.
71
- *
72
- * Snapshot-friendly: the record contains only plain JSON-serializable
73
- * values (no DOM nodes, no function refs).
74
- */
75
- export function recordingOps(): {
76
- highlight: HighlightOps;
77
- indicator: IndicatorOps;
78
- record: RecordedOp[];
79
- } {
80
- const record: RecordedOp[] = [];
81
- return {
82
- record,
83
- highlight: {
84
- addNodeClass(id, cls) { record.push({ op: 'addNodeClass', id, cls }); },
85
- highlightEdge(fromKey, toKey) { record.push({ op: 'highlightEdge', fromKey, toKey }); },
86
- markFrameActive(frameId) { record.push({ op: 'markFrameActive', frameId }); },
87
- pulse(id) { record.push({ op: 'pulse', id }); },
88
- scrollIntoView(id) { record.push({ op: 'scrollIntoView', id }); },
89
- },
90
- indicator: {
91
- setBreakpoint(id, on) { record.push({ op: 'setBreakpoint', id, on }); },
92
- },
93
- };
94
- }
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- // Public API — extracted modules + Snippet recording surface.
2
- export type { NodeKey, HighlightClass, HighlightOps, IndicatorOps, RecordedOp } from './highlightOps';
3
- export { recordingOps } from './highlightOps';
4
- export { bareIdOf, highlightExpand, equivalentIds } from './graphUtils';
5
- export type { GraphIndexes } from './graphIndexes';
6
- export { indexGraph } from './graphIndexes';
7
- export type { GraphHighlight, TapeSnapshot, Frame, Snippet } from './types';
8
- export { applyHighlight, applyIndicator } from './applyHighlight';
9
- export { formatCommand, formatStep } from './format';
10
- export { recordSnippet, type RecordSnippetOptions } from './recordSnippet';
@@ -1,275 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import {
3
- Alphabet,
4
- State,
5
- Tape,
6
- TapeBlock,
7
- TuringMachine,
8
- haltState,
9
- ifOtherSymbol,
10
- movements,
11
- symbolCommands,
12
- } from '@turing-machine-js/machine';
13
- import { recordSnippet } from './recordSnippet';
14
-
15
- /** Build a machine that converts each 'a' → 'b' (move right) until blank (halt). */
16
- function buildTwoAMachine() {
17
- const alphabet = new Alphabet([' ', 'a', 'b']);
18
- const tape = new Tape({ alphabet, symbols: ['a', 'a'] });
19
- const tapeBlock = TapeBlock.fromTapes([tape]);
20
- const machine = new TuringMachine({ tapeBlock });
21
-
22
- const initialState = new State({
23
- [tapeBlock.symbol(['a'])]: {
24
- command: [{ symbol: 'b', movement: movements.right }],
25
- },
26
- [ifOtherSymbol]: {
27
- nextState: haltState,
28
- },
29
- });
30
-
31
- const graph = State.toGraph(initialState, tapeBlock);
32
- const alphabets = [alphabet.symbols.filter((s) => s !== alphabet.blankSymbol).concat(alphabet.blankSymbol)];
33
-
34
- return { machine, initialState, graph, alphabets, alphabet };
35
- }
36
-
37
- describe('recordSnippet', () => {
38
- describe('frame 0 (initial state)', () => {
39
- it('step is 0', () => {
40
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
41
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
42
- expect(snippet.frames[0].step).toBe(0);
43
- });
44
-
45
- it('commands is undefined', () => {
46
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
47
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
48
- expect(snippet.frames[0].commands).toBeUndefined();
49
- });
50
-
51
- it('highlight is null', () => {
52
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
53
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
54
- expect(snippet.frames[0].highlight).toBeNull();
55
- });
56
-
57
- it('tape reflects initial state (position 0, contains "a")', () => {
58
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
59
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
60
- const frame0Tape = snippet.frames[0].tape[0];
61
- expect(frame0Tape.position).toBe(0);
62
- expect(frame0Tape.symbols).toContain('a');
63
- });
64
- });
65
-
66
- describe('frame count', () => {
67
- it('2 "a"s + halt iter = 3 iters → 4 frames (frame 0 + 3)', () => {
68
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
69
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
70
- // iter 1: read 'a', write 'b', move R
71
- // iter 2: read 'a', write 'b', move R
72
- // iter 3: read blank, nextState = haltState
73
- expect(snippet.frames).toHaveLength(4);
74
- });
75
- });
76
-
77
- describe('per-iter frame commands', () => {
78
- it('frame 1: read "a", write "b", move R', () => {
79
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
80
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
81
- expect(snippet.frames[1].commands).toEqual([{ movement: 'R', read: 'a', write: 'b' }]);
82
- });
83
-
84
- it('frame 2: read "a", write "b", move R', () => {
85
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
86
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
87
- expect(snippet.frames[2].commands).toEqual([{ movement: 'R', read: 'a', write: 'b' }]);
88
- });
89
-
90
- it('frame 3 (halt iter): read===write (keep), movement S (stay)', () => {
91
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
92
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
93
- // halt-bound transition has default command: keep + stay; blank cell read+written
94
- const blank = snippet.frames[3].commands![0].read;
95
- expect(snippet.frames[3].commands).toEqual([{ movement: 'S', read: blank, write: blank }]);
96
- });
97
- });
98
-
99
- describe('per-iter frame tape (post-command snapshots)', () => {
100
- it('frame 1 tape: "b" written at position 0, head at position 1', () => {
101
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
102
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
103
- const tape = snippet.frames[1].tape[0];
104
- expect(tape.symbols[0]).toBe('b');
105
- expect(tape.position).toBe(1);
106
- });
107
-
108
- it('frame 2 tape: both cells "b", head at position 2', () => {
109
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
110
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
111
- const tape = snippet.frames[2].tape[0];
112
- expect(tape.symbols[0]).toBe('b');
113
- expect(tape.symbols[1]).toBe('b');
114
- expect(tape.position).toBe(2);
115
- });
116
- });
117
-
118
- describe('snippet metadata', () => {
119
- it('version is 1', () => {
120
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
121
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
122
- expect(snippet.version).toBe(1);
123
- });
124
-
125
- it('graph is the passed-in graph (referential equality)', () => {
126
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
127
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
128
- expect(snippet.graph).toBe(graph);
129
- });
130
-
131
- it('alphabets is the passed-in alphabets (referential equality)', () => {
132
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
133
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
134
- expect(snippet.alphabets).toBe(alphabets);
135
- });
136
-
137
- it('name is set when provided', () => {
138
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
139
- const snippet = recordSnippet({ machine, initialState, graph, alphabets, name: 'test snippet' });
140
- expect(snippet.name).toBe('test snippet');
141
- });
142
-
143
- it('name is absent when not provided', () => {
144
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
145
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
146
- expect('name' in snippet).toBe(false);
147
- });
148
- });
149
-
150
- describe('log option', () => {
151
- it('attaches log to non-frame-0 frames when log returns a string', () => {
152
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
153
- const snippet = recordSnippet({
154
- machine,
155
- initialState,
156
- graph,
157
- alphabets,
158
- log: () => 'step line',
159
- });
160
- // frame 0 has no log
161
- expect(snippet.frames[0].log).toBeUndefined();
162
- // frames 1-3 all have log
163
- for (let i = 1; i < snippet.frames.length; i++) {
164
- expect(snippet.frames[i].log).toBe('step line');
165
- }
166
- });
167
-
168
- it('omits log when log returns undefined', () => {
169
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
170
- const snippet = recordSnippet({
171
- machine,
172
- initialState,
173
- graph,
174
- alphabets,
175
- log: () => undefined,
176
- });
177
- for (const frame of snippet.frames) {
178
- expect('log' in frame).toBe(false);
179
- }
180
- });
181
-
182
- it('passes current and previous MachineState to log', () => {
183
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
184
- const calls: [unknown, unknown][] = [];
185
- recordSnippet({
186
- machine,
187
- initialState,
188
- graph,
189
- alphabets,
190
- log: (m, prev) => { calls.push([m.step, prev ? prev.step : null]); return undefined; },
191
- });
192
- // 3 iters → 3 log calls
193
- expect(calls).toHaveLength(3);
194
- expect(calls[0]).toEqual([1, null]);
195
- expect(calls[1]).toEqual([2, 1]);
196
- expect(calls[2]).toEqual([3, 2]);
197
- });
198
- });
199
-
200
- describe('maxSteps truncation', () => {
201
- it('with maxSteps: 1 → 2 frames (frame 0 + frame 1)', () => {
202
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
203
- const snippet = recordSnippet({ machine, initialState, graph, alphabets, maxSteps: 1 });
204
- expect(snippet.frames).toHaveLength(2);
205
- });
206
-
207
- it('with maxSteps: 1, frame 1 tape reflects the first command applied', () => {
208
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
209
- const snippet = recordSnippet({ machine, initialState, graph, alphabets, maxSteps: 1 });
210
- const tape = snippet.frames[1].tape[0];
211
- // 'a' was written to 'b', head moved right
212
- expect(tape.symbols[0]).toBe('b');
213
- expect(tape.position).toBe(1);
214
- });
215
- });
216
-
217
- describe('highlight', () => {
218
- it('non-frame-0 frames have a non-null highlight with strong: "from" and paused: false', () => {
219
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
220
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
221
- for (let i = 1; i < snippet.frames.length; i++) {
222
- const h = snippet.frames[i].highlight;
223
- expect(h).not.toBeNull();
224
- expect(h!.strong).toBe('from');
225
- expect(h!.paused).toBe(false);
226
- }
227
- });
228
-
229
- it('halt-iter frame highlight has toId: 0 (haltState)', () => {
230
- const { machine, initialState, graph, alphabets } = buildTwoAMachine();
231
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
232
- const lastFrame = snippet.frames[snippet.frames.length - 1];
233
- expect(lastFrame.highlight!.toId).toBe(0);
234
- });
235
- });
236
-
237
- describe('single-step machine (halts immediately)', () => {
238
- it('produces 2 frames when machine halts on step 1', () => {
239
- const alphabet = new Alphabet([' ', 'x']);
240
- const tape = new Tape({ alphabet, symbols: ['x'] });
241
- const tapeBlock = TapeBlock.fromTapes([tape]);
242
- const machine = new TuringMachine({ tapeBlock });
243
- const initialState = new State({
244
- [ifOtherSymbol]: { nextState: haltState },
245
- });
246
- const graph = State.toGraph(initialState, tapeBlock);
247
- const alphabets = [['x', ' ']];
248
-
249
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
250
- expect(snippet.frames).toHaveLength(2);
251
- expect(snippet.frames[0].commands).toBeUndefined();
252
- expect(snippet.frames[1].commands).toBeDefined();
253
- });
254
- });
255
-
256
- describe('keep command → read === write', () => {
257
- it('a keep+stay command yields read === write in commands', () => {
258
- const alphabet = new Alphabet([' ', 'a']);
259
- const tape = new Tape({ alphabet, symbols: ['a'] });
260
- const tapeBlock = TapeBlock.fromTapes([tape]);
261
- const machine = new TuringMachine({ tapeBlock });
262
- const initialState = new State({
263
- [tapeBlock.symbol(['a'])]: {
264
- command: [{ symbol: symbolCommands.keep, movement: movements.stay }],
265
- nextState: haltState,
266
- },
267
- });
268
- const graph = State.toGraph(initialState, tapeBlock);
269
- const alphabets = [['a', ' ']];
270
-
271
- const snippet = recordSnippet({ machine, initialState, graph, alphabets });
272
- expect(snippet.frames[1].commands![0]).toEqual({ movement: 'S', read: 'a', write: 'a' });
273
- });
274
- });
275
- });
@@ -1,141 +0,0 @@
1
- import {
2
- haltState,
3
- type Graph,
4
- type MachineState,
5
- type State,
6
- type TuringMachine,
7
- } from '@turing-machine-js/machine';
8
- import { MOVEMENT_LETTER } from './format';
9
- import { bareIdOf } from './graphUtils';
10
- import type { Frame, GraphHighlight, Snippet, TapeSnapshot } from './types';
11
-
12
- export type RecordSnippetOptions = {
13
- machine: TuringMachine;
14
- initialState: State;
15
- graph: Graph;
16
- alphabets: string[][];
17
- name?: string;
18
- /**
19
- * Maximum number of iteration steps to record. Defaults to 1000.
20
- * If the machine hasn't halted after `maxSteps` iters, recording stops
21
- * and the snippet contains `maxSteps + 1` frames (frame 0 plus one per iter).
22
- */
23
- maxSteps?: number;
24
- /**
25
- * Optional per-frame log formatter. Called with the current and previous
26
- * `MachineState`; return a string to attach as `frame.log`, or `undefined`
27
- * to omit. Not called for frame 0 (initial state — no transition has fired).
28
- */
29
- log?: (m: MachineState, prev: MachineState | null) => string | undefined;
30
- };
31
-
32
- const DEFAULT_MAX_STEPS = 1000;
33
-
34
- function snapshotTapes(machine: TuringMachine): TapeSnapshot[] {
35
- return machine.tapeBlock.tapes.map((t) => ({
36
- symbols: [...t.symbols],
37
- position: t.position,
38
- }));
39
- }
40
-
41
- function deriveCommands(
42
- m: MachineState,
43
- ): NonNullable<Frame['commands']> {
44
- return m.movements.map((mv, i) => ({
45
- movement: MOVEMENT_LETTER.get(mv) ?? 'S',
46
- read: m.currentSymbols[i],
47
- // nextSymbols is already resolved (keep → current symbol, erase → blank);
48
- // when write === read the command was a keep (UI suppresses the flash).
49
- write: m.nextSymbols[i],
50
- }));
51
- }
52
-
53
- function deriveHighlight(m: MachineState, graph: Graph): GraphHighlight {
54
- return {
55
- fromId: bareIdOf(m.state.id, graph),
56
- toId: m.nextState === haltState ? 0 : m.nextState.id,
57
- strong: 'from',
58
- paused: false,
59
- };
60
- }
61
-
62
- /**
63
- * Record a full machine run into a `Snippet` — a self-contained playback
64
- * artifact suitable for embeds, articles, or landing-page panels.
65
- *
66
- * The returned snippet contains one frame per iteration plus a frame-0
67
- * initial-state snapshot. Recording stops when the machine halts or when
68
- * `maxSteps` iterations have been consumed (default 1000).
69
- *
70
- * Tape-timing note: `runStepByStep` yields BEFORE applying its command
71
- * (the command is applied after the yield resumes). The recorder uses a
72
- * one-step-delayed snapshot so each frame's `tape` reflects the
73
- * post-command state for that frame's iter.
74
- */
75
- export function recordSnippet(opts: RecordSnippetOptions): Snippet {
76
- const {
77
- machine,
78
- initialState,
79
- graph,
80
- alphabets,
81
- name,
82
- maxSteps = DEFAULT_MAX_STEPS,
83
- log,
84
- } = opts;
85
-
86
- const frames: Frame[] = [
87
- { step: 0, tape: snapshotTapes(machine), highlight: null },
88
- ];
89
-
90
- // pending holds everything for the frame whose tape snapshot is not yet
91
- // available (because applyCommand hasn't fired yet). It is flushed at the
92
- // start of the NEXT iter (when the tape reflects the previous command) and
93
- // after the loop (when the final command has been applied).
94
- let pending: Omit<Frame, 'tape'> | null = null;
95
- let prev: MachineState | null = null;
96
-
97
- try {
98
- for (const m of machine.runStepByStep({ initialState, stepsLimit: maxSteps })) {
99
- // At this point applyCommand for the PREVIOUS iter has already run
100
- // (the generator called applyCommand before looping back to yield).
101
- // So the current tape state = post-command of the previous iter.
102
- if (pending !== null) {
103
- frames.push({ ...pending, tape: snapshotTapes(machine) });
104
- }
105
-
106
- const commands = deriveCommands(m);
107
- const highlight = deriveHighlight(m, graph);
108
- const logLine = log ? log(m, prev) : undefined;
109
-
110
- pending = {
111
- step: m.step,
112
- commands,
113
- highlight,
114
- ...(logLine !== undefined ? { log: logLine } : {}),
115
- };
116
-
117
- prev = m;
118
- }
119
- } catch (e) {
120
- // runStepByStep throws 'Long execution' when stepsLimit is hit.
121
- // At that point applyCommand for the last yielded iter has run, so the
122
- // tape is in the post-command state we want for the pending frame.
123
- if (!(e instanceof Error) || e.message !== 'Long execution') {
124
- throw e;
125
- }
126
- }
127
-
128
- // Flush the last pending frame. After the loop (or after the catch), the
129
- // tape reflects the post-command state of the final yielded iter.
130
- if (pending !== null) {
131
- frames.push({ ...pending, tape: snapshotTapes(machine) });
132
- }
133
-
134
- return {
135
- version: 1,
136
- ...(name !== undefined ? { name } : {}),
137
- graph,
138
- alphabets,
139
- frames,
140
- };
141
- }
package/src/types.ts DELETED
@@ -1,96 +0,0 @@
1
- import type { Graph } from '@turing-machine-js/machine';
2
-
3
- /**
4
- * State-graph highlight descriptor (machines-demo#10). MachineView derives it
5
- * from `executionMode` + the latest pause-response data; MachineGraph reads
6
- * it to light up the `from → edge → to` triple in the rendered SVG.
7
- *
8
- * - `fromId: 'idle'` represents the synthetic `idle([idle])` sentinel that
9
- * `toMermaid` emits at the entry point. Used in IDLE mode to mark "where
10
- * execution would start".
11
- * - `fromId: number` is an engine `GraphNode.id` — the source state.
12
- * - `toId: number | null` is the destination state's id (or `null` at halt).
13
- * - `strong` selects which end of the triple gets the bolder/stronger
14
- * accent. Per the (B) rule: `from` strong at `before` pause; `to` strong
15
- * at `after` / iter-end pause / IDLE (destination feels current).
16
- */
17
- export type GraphHighlight = {
18
- fromId: number | 'idle';
19
- toId: number | null;
20
- strong: 'from' | 'to';
21
- /**
22
- * True when this highlight reflects a paused-event apply (RUNNING_PAUSED),
23
- * false for per-iter idle applies (RUNNING_AUTO). MachineGraph uses this
24
- * to detect cross-pause same-state revisits — pulsing the strong node when
25
- * the current paused apply lands on the same state as the previous paused
26
- * apply, even when intermediate idle applies pointed elsewhere
27
- * (e.g., stateA breakpoint → continue → run through stateB/C → stateA
28
- * breakpoint fires again). The simpler "previous apply's strong matches"
29
- * check already covers AUTO self-loops; this flag drives the second pulse
30
- * trigger.
31
- */
32
- paused: boolean;
33
- };
34
-
35
- /**
36
- * Per-tape snapshot: the cells visible/usable plus the head's index into them.
37
- * Same shape as machines-demo's TapeSnapshot. Pure data — no library handles.
38
- */
39
- export type TapeSnapshot = {
40
- symbols: string[];
41
- position: number;
42
- };
43
-
44
- /**
45
- * One frame of a recorded snippet — the state of the machine at iter `step`.
46
- * Frame 0 = initial state (before any transition); frame N = state after iter N's transition.
47
- *
48
- * `tape` is per-tape (single-tape machines: length 1). `highlight` describes
49
- * what to render on the state graph at this moment (null when no highlight).
50
- * `log` is optional pre-formatted text — a caption / status line consumers can render.
51
- */
52
- export type Frame = {
53
- step: number;
54
- tape: TapeSnapshot[];
55
- /**
56
- * Per-tape engine command for the iter that produced this frame. Carries
57
- * both sides of the cell so players can step bi-directionally without
58
- * recomputing from neighbouring frames:
59
- *
60
- * - `movement` — `'L' | 'R' | 'S'`. Forward step slides the tape this way;
61
- * backward step slides the opposite.
62
- * - `read` — symbol on the head's cell BEFORE this iter (what the engine
63
- * matched). Backward step writes this back.
64
- * - `write` — symbol on the cell AFTER this iter (=== `read` if no write
65
- * happened). Forward step writes this; UI triggers the per-cell flash
66
- * iff `write !== read`.
67
- *
68
- * Undefined on frame 0 (initial state — no transition has fired yet).
69
- */
70
- commands?: {
71
- movement: 'L' | 'R' | 'S';
72
- read: string;
73
- write: string;
74
- }[];
75
- highlight: GraphHighlight | null;
76
- log?: string;
77
- };
78
-
79
- /**
80
- * Recorded run of a machine — playback artifact for embeds, articles,
81
- * landing-page panels. Engine-agnostic (no `engine` field; identity lives
82
- * at the caller bucket level).
83
- *
84
- * - `version: 1` — schema integer. Additive fields don't bump it;
85
- * shape-breaking changes do.
86
- * - `graph` — engine `State.toGraph` output captured at recording time.
87
- * - `alphabets` — per-tape alphabet list (single-tape: length 1).
88
- * - `frames` — length === `stepsApplied + 1`; frame 0 is the initial state.
89
- */
90
- export type Snippet = {
91
- version: 1;
92
- name?: string;
93
- graph: Graph;
94
- alphabets: string[][];
95
- frames: Frame[];
96
- };
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "src",
5
- "composite": true
6
- },
7
- "references": [
8
- { "path": "../machine/tsconfig.build.json" }
9
- ],
10
- "exclude": ["**/*.spec.ts", "dist"]
11
- }