@turing-machine-js/machine 6.3.0 → 7.0.0-alpha.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,166 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [7.0.0-alpha.1] - 2026-05-21
8
+
9
+ First v7 pre-release. Consolidates the composition-representation overhaul landed across [#149](https://github.com/mellonis/turing-machine-js/issues/149), [#148](https://github.com/mellonis/turing-machine-js/issues/148), [#138](https://github.com/mellonis/turing-machine-js/issues/138), and [#139](https://github.com/mellonis/turing-machine-js/issues/139). Published to npm under the `next` dist-tag: `npm install @turing-machine-js/machine@next`.
10
+
11
+ **Pre-release — the API surface may still shift before stable v7.0.0.** Pin to a specific alpha for reproducibility: `@turing-machine-js/machine@7.0.0-alpha.1`. Migration walkthrough at the bottom.
12
+
13
+ ### Changed
14
+
15
+ - **`State.prototype.withOverrodeHaltState` renamed to `withOverriddenHaltState`** ([#149](https://github.com/mellonis/turing-machine-js/issues/149)). Grammar fix on a name introduced in 2019: `overridden` (past participle) fits the "with a halt-state that has been ___" naming idiom; `overrode` (simple past) didn't. Hard cutover — no deprecated alias. Renames in lockstep:
16
+ - public method `State.prototype.withOverrodeHaltState` → `withOverriddenHaltState`
17
+ - getter `state.overrodeHaltState` → `state.overriddenHaltState`
18
+ - private field `#overrodeHaltState` → `#overriddenHaltState`
19
+ - serialized `Graph` data field `node.overrodeHaltStateId` → `node.overriddenHaltStateId`
20
+
21
+ - **Wrapped-state composite name format flipped from `bare>override` to `bare(override)`** ([#148](https://github.com/mellonis/turing-machine-js/issues/148)). The old `>`-flat notation collided structurally-distinct wrap-trees into the same string: `A.with(B.with(A))` and `A.with(B).with(A)` both rendered as `A>B>A` despite being different runtime shapes. Paren-nested keeps them distinct: `A(B(A))` vs `A(B)(A)`. As a consequence, **user-provided state names must not contain `(` or `)`** — `State` constructor now throws on names with these characters. `>` is no longer reserved and is valid in user-provided names again.
22
+
23
+ - **`toMermaid` wrapped-state emit overhaul** ([#138](https://github.com/mellonis/turing-machine-js/issues/138), [#139](https://github.com/mellonis/turing-machine-js/issues/139)). Each `withOverriddenHaltState` wrapper collapses onto its bare's representation — `GraphNode.isWrapped: true`, no separate wrapper node in graph data. `toMermaid` wraps each `[[bare]]` (Mermaid subroutine shape) + a synthesized `(((halt)))` halt-marker (`GraphNode.isHaltMarker: true`) inside a `subgraph w_${bareId}["halt frame"] … end` block. Dotted `onHalt` from `[[bare]]` crosses the subgraph border to the override target. An always-emitted `idle([idle])` stadium sentinel + `idle -. enter .-> sN` arrow marks the initial state (replaces the v6 `((round))` shape convention).
24
+
25
+ - **Edge-label vocabulary rewritten** for readability — `[reads] → [writes]/[moves]` with each role wrapped in `[…]` (the tape-block indicator; always present, even single-tape). Read cells: `'X'` literal-quoted, `*` (ASCII; ifOtherSymbol catch-all — literal `*` in the alphabet renders as `'*'`), `B` (tape's blank shorthand). Write cells: literal-quoted, `K` (keep), `E` (erase = write blank). Move cells: `L` / `R` / `S`. Alternation per-pattern bracket: `['^']|['1']` for single-tape, `['0','a']|['1','b']` for multi-tape. Compact in-bracket `['^'|'1']` form is rejected by `fromMermaid` (would read as cross-product semantics in multi-tape).
26
+
27
+ - **Thick `==>` arrows for stack-pushing transitions.** When a transition's target is a wrapped state AND ≠ source (i.e. would push the wrapper's override onto the runtime stack per `TuringMachine.run`'s line ~220 transition-time push), the engine emits a thick `==>` arrow — visual signal for "this transition fires a stack push."
28
+
29
+ - **`GraphTransition.id`** — stable, deterministic per-edge identifier (`${fromNodeId}-${patternIx}`). Supports downstream tooling for edge-targeting in rendered Mermaid SVG (e.g. highlight-the-next-transition in interactive viewers).
30
+
31
+ - **`summarize().stateCount` filters `isHaltMarker` nodes** — halt markers are visualization sentinels (all map back to singleton `haltState` at runtime), not distinct runtime states. Matches the per-algorithm header count in `library-binary-numbers/states.md`.
32
+
33
+ ### Added
34
+
35
+ - **#139 regression test** — `toMermaid → fromMermaid → toGraph → toMermaid` is bytewise stable for simple wrappers. The wrapper's composite name (e.g. `scanToX(eraseHere)`) no longer appears as any graph-node label, so `fromGraph` reconstructs and recomputes the composite fresh on each pass — no round-trip name accumulation.
36
+
37
+ - **Multi-tape example** in the engine README illustrating the bracketed format with two tapes.
38
+
39
+ - **§Diagram conventions section** in the engine README — full reference: node shapes, edge styles, groupings, edge label format + cell vocabulary, alternation rule, multi-tape example.
40
+
41
+ - **Cross-package introspection consistency** — `library-binary-numbers/states.md` per-algorithm header line now uses `summarizeGraph(graph)` for its `N states; N transitions; N wrappers (max nesting depth N); has cycles` summary, dogfooding the same API any consumer would use.
42
+
43
+ ### Removed
44
+
45
+ - Hand-drawn pedagogical Mermaid blocks in engine + root READMEs. The v7 `toMermaid` output is now the primary illustration — no more vocabulary mismatch between hand-drawn and engine-rendered diagrams.
46
+
47
+ ### Migration
48
+
49
+ For consumers updating from v6.x:
50
+
51
+ **1. Identifier rename** (mechanical):
52
+
53
+ ```sh
54
+ git grep -l 'OverrodeHaltState\|overrodeHaltState' | xargs sed -i '' \
55
+ -e 's/OverrodeHaltState/OverriddenHaltState/g' \
56
+ -e 's/overrodeHaltState/overriddenHaltState/g'
57
+ ```
58
+
59
+ Persisted `State.toGraph` JSON dumps need the same `overrodeHaltStateId` → `overriddenHaltStateId` field rename.
60
+
61
+ **2. Wrapper composite name format** — code that pattern-matches `state.name` for wrapper composites needs to switch from `>`-split to paren-parse: `'A>B'` is now `'A(B)'`.
62
+
63
+ **3. State name validation** — any code that constructs `new State(null, 'foo(bar)')` now throws. Real-world identifier-style state names (`scanToX`, `goHome`, `incT1`) are unaffected.
64
+
65
+ **4. `toMermaid` output format** — if you render `toMermaid` output programmatically, the edge-label vocabulary changed completely (bracketed-tape-block format with `K`/`E`/`B`/`*`/`L`/`R`/`S`). See the engine README's §Diagram conventions for the full vocabulary.
66
+
67
+ **5. `Graph` data shape** — `GraphNode` gained `isWrapped: boolean` + `isHaltMarker: boolean`; `GraphTransition` gained `id: string`. Most consumers don't need to change. If you call `summarize().stateCount`, the value now excludes halt markers (typically matches what you actually wanted — runtime state count, not visualization-node count).
68
+
69
+ ### Out of v7-alpha.1 (still pending for stable v7.0.0)
70
+
71
+ - **[#102](https://github.com/mellonis/turing-machine-js/issues/102)** — debugger step-in / step-out / step-over primitives. Additive — won't change any existing API. Will land in `v7.0.0-alpha.2` or stable `v7.0.0`.
72
+
73
+ ### Compatibility
74
+
75
+ - Peer dep `@turing-machine-js/machine` widened `^6.0.0` → `^7.0.0-alpha.1` on `@turing-machine-js/builder`, `@turing-machine-js/library-binary-numbers`, `@turing-machine-js/library-binary-numbers-bare`.
76
+
77
+ ## [6.4.0] - 2026-05-19
78
+
79
+ Adds a new awaited hook on `TuringMachine.run`. Minor release, additive — no breaking changes. Closes [#163](https://github.com/mellonis/turing-machine-js/issues/163).
80
+
81
+ ### Added
82
+
83
+ - **`onIter` callback** on `run()`: `onIter?: (m: MachineState) => void | Promise<void>`. Fires once per iteration at end-of-iter — *after* both `onPause(before, K)` and `onPause(after, K)` dispatches on the same yield. Awaited inline, so consumers returning a Promise suspend the run loop between iters. Unaffected by the `debug` master switch — `onIter` fires on every iter regardless of whether any `state.debug` breakpoints are armed.
84
+
85
+ Use cases the hook serves:
86
+ - **Per-iter throttle** (interactive debugger / animation UIs) — `await new Promise(r => setTimeout(r, intervalMs))` inside the callback gives a "wait between iters" pattern with no `state.debug` mutation.
87
+ - **Iter-correct bookkeeping** — for downstream libraries that maintain per-iter prev state (e.g. PostMachine's `arrivalPath` tracking), `onIter` is the natural "iter K is done" boundary, after all `onPause` hooks have read their own snapshots. Avoids the prev-advance-during-onStep ordering hazard.
88
+ - **Yield-to-other-work in batched runs** — a sync `await new Promise(r => queueMicrotask(r))` per iter lets long runs interleave with other event-loop work without owning the run loop.
89
+
90
+ ### Three-hook contract recap
91
+
92
+ | Hook | Sync/Async | When | Use |
93
+ |---|---|---|---|
94
+ | `onStep` | Sync, not awaited | Mid-iter (between before/after) | Cheap tracer/logger, microtask-free hot loop |
95
+ | `onPause` | Awaited | Conditional on `state.debug[when]` match | User-authored breakpoints / debugger UI |
96
+ | `onIter` | Awaited | End of every iter | Per-iter coordination — throttle, prev-tracking, animation, yield-to-other-work |
97
+
98
+ ### Changed (docs)
99
+
100
+ - README's **Throttle pattern** section rewritten to recommend `onIter` as the canonical hook. The v6.3.0 `onPause`-rearm workaround is superseded — that pattern still works (it's just the engine-native primitives), but `onIter` is the cleaner shape.
101
+
102
+ ### Compatibility
103
+
104
+ - Default `onIter` is `undefined` → no behavior change for any existing consumer that doesn't opt in.
105
+ - Sync consumers using only `onStep` pay zero microtask overhead — `onIter` is purely opt-in.
106
+ - Peer-deps unchanged (`^6.0.0` includes 6.4.0 for the dependents).
107
+
108
+ ## [6.3.0] - 2026-05-19
109
+
110
+ Corrective minor release. **Reverts v6.2.0's `await onStep` change** and restores the original sync `onStep` contract that held since the hook was introduced. See [GitHub release](https://github.com/mellonis/turing-machine-js/releases/tag/v6.3.0) for the full post-mortem.
111
+
112
+ ### Changed
113
+
114
+ - **`TuringMachine.run`** — `await onStep(m)` reverted to `onStep(m)`; type narrowed back from `(m: MachineState) => void | Promise<void>` to `(m: MachineState) => void`. The original "Sync, ~free hook ... must not be async" contract is restored, with the docstring augmented to point readers at the new Throttle pattern section.
115
+
116
+ ### Removed
117
+
118
+ - The v6.2.0 test `async onStep is awaited between iters (#158)` (no longer the contract).
119
+
120
+ ### Added (docs)
121
+
122
+ - README: new **Throttle pattern** section under *Debugging breakpoints* showing the engine-native shape for per-iter throttle / "wait between iters" UIs — arm `state.debug.after = true` on the initial state, throttle inside `onPause`, rearm `m.nextState.debug.after = true` in the callback. Replaces the v6.2.0 throttle-in-`onStep` pattern.
123
+
124
+ ### Kept from v6.2.0 (independent improvements)
125
+
126
+ - CI `typecheck` step + dependent packages' `tsconfig` `paths`/`rootDir` fix.
127
+
128
+ ### Migration
129
+
130
+ - From v6.2.0: drop any `await` you wrote inside `onStep`. The engine ignores the returned Promise now. If you used the throttle-in-`onStep` pattern, migrate to the Throttle pattern section in the README.
131
+ - From v6.1.0 or earlier: no change for you.
132
+
133
+ ## [6.2.0] - 2026-05-19 [SUPERSEDED by 6.3.0]
134
+
135
+ > ⚠️ **This release was a mistake.** It overturned the sync `onStep` contract that had held since the hook was introduced, motivated by a downstream throttle use case that didn't actually need an engine API change. Restored to sync in [6.3.0](#630---2026-05-19). The `await onStep` semantic shipped briefly on npm; consumers should upgrade to 6.3.0 and use the README's Throttle pattern instead.
136
+
137
+ ### Changed
138
+
139
+ - **`TuringMachine.run`** — `onStep` type widened from `(m) => void` to `(m) => void | Promise<void>`; the run loop now awaits each `onStep(m)` call.
140
+
141
+ ### Internal
142
+
143
+ - CI `typecheck` step added (mirrors the spec-including tsconfig to catch TS errors in `*.spec.ts`).
144
+ - Dependent packages' `tsconfig` `paths` and `rootDir` corrected — they now resolve `@turing-machine-js/machine` from source in CI (previously required a pre-built `dist/`).
145
+
146
+ ## [6.1.0] - 2026-05-16
147
+
148
+ Minor release. One additive ergonomic improvement to `state.debug`, no breaking changes.
149
+
150
+ ### Added
151
+
152
+ - **Lazy-init `DebugConfig` + `Object.seal` on instance ([#151](https://github.com/mellonis/turing-machine-js/pull/151), closes [#150](https://github.com/mellonis/turing-machine-js/issues/150)).** `state.debug` is now always a non-null `DebugConfig` instance, lazy-initialized on first read. Chained writes like `state.debug.before = true` work on a fresh state without a prior `state.debug = {}` setup step. The `DebugConfig` instance is `Object.seal`-ed — typos like `state.debug.bofore = true` throw `TypeError` (in strict mode — default for TS-emitted modules) instead of silently creating a useless own property.
153
+
154
+ ### Changed
155
+
156
+ - **`state.debug` getter type narrowed** — `DebugConfig | null` → `DebugConfig`. Setter still accepts `null`; the semantic shifts to "reset filters" (next read returns a fresh empty config rather than literal `null`).
157
+ - **Wrapper sharing preserved** — `withOverrodeHaltState`'s `#debugRef` cell continues to share a single `DebugConfig` instance across wrapper and original; first read on either lazy-creates, both subsequent reads return the same instance.
158
+
159
+ ### Migration
160
+
161
+ No code changes required:
162
+ - `state.debug?.X` patterns keep working (the `?.` is now redundant but correct).
163
+ - `state.debug = { ... }` whole-object assignment works as before.
164
+ - `state.debug = null` works as before, with the new "reset filters" semantic.
165
+ - Consumer code checking `if (state.debug === null)` becomes dead code but doesn't crash; the narrowed getter type may surface TS warnings on those branches, safe to delete.
166
+
7
167
  ## [6.0.0] - 2026-05-09
8
168
 
9
169
  ### Changed (BREAKING)
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![build](https://github.com/mellonis/turing-machine-js/actions/workflows/main.yml/badge.svg)](https://github.com/mellonis/turing-machine-js/actions/workflows/main.yml)
4
4
  ![npm (tag)](https://img.shields.io/npm/v/@turing-machine-js/machine)
5
5
 
6
- A composable Turing-machine engine for JavaScript: multi-tape, subroutine composition via `withOverrodeHaltState`, Mermaid round-trip, and runtime breakpoints.
6
+ A composable Turing-machine engine for JavaScript: multi-tape, subroutine composition via `withOverriddenHaltState`, Mermaid round-trip, and runtime breakpoints.
7
7
 
8
8
  <details>
9
9
  <summary>Table of contents</summary>
@@ -12,10 +12,11 @@ A composable Turing-machine engine for JavaScript: multi-tape, subroutine compos
12
12
  - [Quick start](#quick-start)
13
13
  - [Building from a state table](#building-from-a-state-table)
14
14
  - [Classes](#classes) — [`Alphabet`](#alphabet) · [`Tape`](#tape) · [`TapeBlock`](#tapeblock) · [`TapeCommand`](#tapecommand) · [`Command`](#command) · [`State`](#state) · [`Reference`](#reference) · [`TuringMachine`](#turingmachine)
15
- - [Subroutine composition with `withOverrodeHaltState`](#subroutine-composition-with-withoverrodehaltstate)
15
+ - [Subroutine composition with `withOverriddenHaltState`](#subroutine-composition-with-withoverriddenhaltstate)
16
16
  - [Debugging breakpoints](#debugging-breakpoints)
17
17
  - [Special objects](#special-objects) — [`haltState`](#haltstate) · [`ifOtherSymbol`](#ifothersymbol) · [`movements`](#movements) · [`symbolCommands`](#symbolcommands)
18
18
  - [Introspection and testing](#introspection-and-testing)
19
+ - [Diagram conventions](#diagram-conventions)
19
20
  - [Versioning notes](#versioning-notes)
20
21
  - [Libraries](#libraries)
21
22
  - [Links](#links)
@@ -70,39 +71,25 @@ await machine.run({
70
71
  console.log(tape.symbols.join('').trim()); // a*c*a
71
72
  ```
72
73
 
73
- The state graph for the example above:
74
-
75
- ```mermaid
76
- flowchart LR
77
- S(("**replaceB**"))
78
- H((("**halt**")))
79
- S -- "b → *, R" --> S
80
- S -- "_ → keep, L" --> H
81
- S -- "any other → keep, R" --> S
82
- ```
83
-
84
- *Reading the labels: `read → write, move`. `_` is the blank symbol.*
85
-
86
- <details>
87
- <summary>📊 Same diagram, generated by <code>toMermaid()</code> (the engine's actual output)</summary>
74
+ The state graph for the example above (`toMermaid(toGraph(replaceB, tapeBlock))`):
88
75
 
89
76
  ```mermaid
90
77
  flowchart TD
91
78
  %% alphabets: [[" ","a","b","c","*"]]
92
79
  s0(((halt)))
93
- s1(("replaceB"))
94
- s1 -- "b → */R" --> s1
95
- s1 -- "- ·/L" --> s0
96
- s1 -- "*·/R" --> s1
80
+ s1["replaceB"]
81
+ idle([idle])
82
+ idle -. enter .-> s1
83
+ s1 -- "['b']['*']/[R]" --> s1
84
+ s1 -- "[B] → [K]/[L]" --> s0
85
+ s1 -- "[*] → [K]/[R]" --> s1
97
86
  ```
98
87
 
99
- Engine notation: `read write/move`; `·` = keep, `⌫` = erase, `*` = `ifOtherSymbol` catch-all, `-` = the blank symbol. `(((double-paren)))` = halt; `(("round"))` = the initial state passed to `toGraph`; `["square"]` = a state reachable from the initial state.
100
-
101
- </details>
88
+ Reading this specific diagram: `replaceB` (the rectangle) is the start state, marked by the dotted `enter` arrow from the `idle` sentinel. Three self-or-halt transitions: read `'b'` write `'*'` and step right; read anything else (`*`) keep, step right; read blank (`B`) keep, step left, halt. Full notation reference — shapes, edge styles, label vocabulary — in [§Diagram conventions](#diagram-conventions).
102
89
 
103
90
  A `State` is keyed by JS `Symbol`s returned from `tapeBlock.symbol(pattern)` — the pattern lists the expected symbol under each tape's head. Sentinels and constants used throughout: [`ifOtherSymbol`](#ifothersymbol) is the fallback key when nothing else matches; transitioning into [`haltState`](#haltstate) stops the run; [`movements`](#movements)`.{left,right,stay}` direct head moves; [`symbolCommands`](#symbolcommands)`.{keep,erase}` are write shortcuts. Full definitions in [§Special objects](#special-objects).
104
91
 
105
- For multi-tape machines, pass one element per tape: `tapeBlock.symbol(['0', 'a'])` matches only when tape 1 is at `'0'` and tape 2 is at `'a'`.
92
+ For multi-tape machines, pass one element per tape: `tapeBlock.symbol(['0', 'a'])` matches only when tape 1 is at `'0'` and tape 2 is at `'a'`. See the multi-tape example in [§Diagram conventions](#diagram-conventions) for what the rendered graph looks like.
106
93
 
107
94
  ## Building from a state table
108
95
 
@@ -251,7 +238,7 @@ const s = new State({
251
238
  Notable members and statics:
252
239
 
253
240
  - **`state.id`**, **`state.name`** — identity (`isHalt` is `id === 0`).
254
- - **`state.withOverrodeHaltState(other)`** — returns a copy whose would-be halt transitions fall through to `other`. The subroutine-call composition mechanism (see `library-binary-numbers/src/index.ts` for examples).
241
+ - **`state.withOverriddenHaltState(other)`** — returns a copy whose would-be halt transitions fall through to `other`. The subroutine-call composition mechanism (see `library-binary-numbers/src/index.ts` for examples).
255
242
  - **`State.toGraph(state, tapeBlock)`** — walks the reachable graph from `state` and returns a serializable `Graph` (states, transitions, alphabets).
256
243
  - **`State.fromGraph(graph)`** — inverse of `toGraph`: rebuilds `State` instances + a fresh `TapeBlock` from a `Graph`. Round-trips together with `toMermaid` / `fromMermaid`.
257
244
 
@@ -270,16 +257,18 @@ The string `toMermaid` produces is a real Mermaid flowchart that renders in-plac
270
257
  flowchart TD
271
258
  %% alphabets: [[" ","0","1","$"]]
272
259
  s0(((halt)))
273
- s1(("name"))
274
- s1 -- "1 → 0/R" --> s1
275
- s1 -- "$ ·/L" --> s0
260
+ s1["name"]
261
+ idle([idle])
262
+ idle -. enter .-> s1
263
+ s1 -- "['1'] → ['0']/[R]" --> s1
264
+ s1 -- "['$'] → [K]/[L]" --> s0
276
265
  ```
277
266
 
278
- *Edge labels are `read → write/move`. `·` is the keep-current-symbol marker (no write); `L` / `R` / `S` are head moves.*
267
+ *Edge labels are `read → write/move`. Write commands: `K` = keep (no write), `E` = erase (write the blank). Literal alphabet symbols are quoted (`'1'`, `'$'`). Movements: `L` (left), `R` (right), `S` (stay).*
279
268
 
280
269
  > 💡 **Mermaid renders at most one edge per source/target pair.** If a state has two distinct transitions back to itself (or two parallel transitions to the same target), only one shows in the diagram. The string output is correct — this is a viewer-side limitation. For graphs with multiple parallel edges, paste the `toMermaid` output into [mermaid.live](https://mermaid.live) and switch to the `stateDiagram-v2` renderer, or post-process the output to your preferred format.
281
270
 
282
- `fromMermaid` parses the same format back into a `Graph`. The round-trip is **behaviorally** lossless — the rebuilt graph runs to the same outputs on the same inputs (tested in `test/round-trip.spec.ts` for the binary-numbers libraries). It is *not* bytewise lossless: state IDs auto-reassign on each rebuild, and for `withOverrodeHaltState` wrappers the composite name gains a `>${override.name}` suffix on each pass (e.g., `scanToX>eraseHere` becomes `scanToX>eraseHere>eraseHere` on a second round-trip — tracked in [#138](https://github.com/mellonis/turing-machine-js/issues/138)).
271
+ `fromMermaid` parses the same format back into a `Graph`. The round-trip is **behaviorally** lossless — the rebuilt graph runs to the same outputs on the same inputs (tested in `test/round-trip.spec.ts` for the binary-numbers libraries). It is *not* bytewise lossless: state IDs auto-reassign on each rebuild, and for `withOverriddenHaltState` wrappers the composite name gains an extra `(${override.name})` wrapping on each pass (e.g., `scanToX(eraseHere)` becomes `scanToX(eraseHere)(eraseHere)` on a second round-trip — tracked in [#138](https://github.com/mellonis/turing-machine-js/issues/138)).
283
272
 
284
273
  ### Reference
285
274
 
@@ -292,31 +281,20 @@ const b = new State({ [symbol(['y'])]: { nextState: a } }, 'b');
292
281
  ref.bind(b); // a's transition now resolves to b at run time
293
282
  ```
294
283
 
295
- The resulting cycle:
296
-
297
- ```mermaid
298
- flowchart LR
299
- a(("**a**"))
300
- b(("**b**"))
301
- a -- "x" --> b
302
- b -- "y" --> a
303
- ```
304
-
305
- <details>
306
- <summary>📊 Same diagram, generated by <code>toMermaid()</code></summary>
284
+ The resulting cycle (`toMermaid(toGraph(a, tapeBlock))`):
307
285
 
308
286
  ```mermaid
309
287
  flowchart TD
310
288
  %% alphabets: [[" ","x","y"]]
311
- s1(("a"))
289
+ s1["a"]
312
290
  s2["b"]
313
- s1 -- "x → ·/S" --> s2
314
- s2 -- "y ·/S" --> s1
291
+ idle([idle])
292
+ idle -. enter .-> s1
293
+ s1 -- "['x'] → [K]/[S]" --> s2
294
+ s2 -- "['y'] → [K]/[S]" --> s1
315
295
  ```
316
296
 
317
- `a` is round (the initial state passed to `toGraph`); `b` is square (reachable from `a`).
318
-
319
- </details>
297
+ `idle -. enter .->` points at the initial state passed to `toGraph` (`a` here); `b` is reachable from `a` via the bound `Reference`.
320
298
 
321
299
  `reference.ref` returns the bound state and throws if the reference is still unbound when the machine runs. `bind()` is sticky — the first call wins; subsequent calls are silent no-ops that return the existing binding.
322
300
 
@@ -366,9 +344,9 @@ Both APIs are first-class — `run()` is built on top of `runStepByStep()` (see
366
344
 
367
345
  **Don't split one logical flow across both APIs.** A consumer that wants stepwise UI *and* hook-driven breakpoints should use `run({ onStep, onPause, debug })` exclusively. Routing some operations through `runStepByStep()` and others through `run()` means `state.debug` only flows through one of the two paths — a subtle footgun where breakpoints silently disappear on whichever code path uses the generator directly. For per-iter throttle / "wait between steps" UIs, see [Throttle pattern](#throttle-pattern).
368
346
 
369
- ## Subroutine composition with `withOverrodeHaltState`
347
+ ## Subroutine composition with `withOverriddenHaltState`
370
348
 
371
- `state.withOverrodeHaltState(other)` returns a copy of `state` whose would-be halt transitions fall through to `other` at run time. The original is left untouched. This is the engine's only composition primitive — bigger machines are built by stacking smaller halt-on-completion subroutines.
349
+ `state.withOverriddenHaltState(other)` returns a copy of `state` whose would-be halt transitions fall through to `other` at run time. The original is left untouched. This is the engine's only composition primitive — bigger machines are built by stacking smaller halt-on-completion subroutines.
372
350
 
373
351
  ```javascript
374
352
  import { Alphabet, State, TapeBlock, TuringMachine, Tape, haltState, ifOtherSymbol, movements, symbolCommands } from '@turing-machine-js/machine';
@@ -389,7 +367,7 @@ const eraseHere = new State({
389
367
  }, 'eraseHere');
390
368
 
391
369
  // Compose: scan to X, then ERASE it. scanToX is unmodified.
392
- const scanThenErase = scanToX.withOverrodeHaltState(eraseHere);
370
+ const scanThenErase = scanToX.withOverriddenHaltState(eraseHere);
393
371
 
394
372
  const tape = new Tape({ alphabet, symbols: ['a', 'b', 'X', 'b', 'a'] });
395
373
  tapeBlock.replaceTape(tape);
@@ -400,38 +378,17 @@ console.log(tape.symbols.join('')); // "ab ba" — the X at index 2 is gone, hea
400
378
 
401
379
  What changes between *running `scanToX` standalone* and *running the composed wrapper*:
402
380
 
403
- ```mermaid
404
- flowchart LR
405
- subgraph standalone["scanToX (standalone) — halts at X"]
406
- direction LR
407
- a1(("scanToX"))
408
- h1(((halt)))
409
- a1 -- "X → keep, S" --> h1
410
- a1 -- "any other → keep, R" --> a1
411
- end
412
- subgraph composed["scanToX.withOverrodeHaltState(eraseHere) — halt is intercepted"]
413
- direction LR
414
- a2(("scanToX"))
415
- b2(("eraseHere"))
416
- h2(((halt)))
417
- a2 -. "X → keep, S<br/>intercepted" .-> b2
418
- a2 -- "any other → keep, R" --> a2
419
- b2 -- "any → erase, S" --> h2
420
- end
421
- ```
422
-
423
- <details>
424
- <summary>📊 Both diagrams, generated by <code>toMermaid()</code></summary>
425
-
426
381
  `toMermaid(toGraph(scanToX, tapeBlock))` — the standalone subroutine:
427
382
 
428
383
  ```mermaid
429
384
  flowchart TD
430
385
  %% alphabets: [[" ","a","b","X"]]
431
386
  s0(((halt)))
432
- s1(("scanToX"))
433
- s1 -- "X → ·/S" --> s0
434
- s1 -- "* ·/R" --> s1
387
+ s1["scanToX"]
388
+ idle([idle])
389
+ idle -. enter .-> s1
390
+ s1 -- "['X'] → [K]/[S]" --> s0
391
+ s1 -- "[*] → [K]/[R]" --> s1
435
392
  ```
436
393
 
437
394
  `toMermaid(toGraph(scanThenErase, tapeBlock))` — the wrapped composition:
@@ -440,29 +397,32 @@ flowchart TD
440
397
  flowchart TD
441
398
  %% alphabets: [[" ","a","b","X"]]
442
399
  s0(((halt)))
443
- s1["scanToX"]
444
400
  s2["eraseHere"]
445
- s3(("scanToX>eraseHere"))
446
- s1 -- "X → ·/S" --> s0
447
- s1 -- "* → ·/R" --> s1
448
- s2 -- "* → ⌫/S" --> s0
449
- s3 -- "X → ·/S" --> s0
450
- s3 -- "* ·/R" --> s1
401
+ idle([idle])
402
+ subgraph w_3["halt frame"]
403
+ s3[["scanToX"]]
404
+ c3(((halt)))
405
+ end
406
+ idle -. enter .-> s3
407
+ s2 -- "[*] → [E]/[S]" --> s0
408
+ s3 -- "['X'] → [K]/[S]" --> c3
409
+ s3 -- "[*] → [K]/[R]" --> s3
451
410
  s3 -. onHalt .-> s2
452
411
  ```
453
412
 
454
- **Reading guide** — the wrapped diagram is denser than the simplified hand-drawn version above. To parse it:
413
+ **Reading guide** — the v7 emit (introduced in [#138](https://github.com/mellonis/turing-machine-js/issues/138)) makes the wrapper's runtime stack-frame semantics visible:
455
414
 
456
- 1. **Three non-halt nodes:** the wrapper `scanToX>eraseHere` (round, the initial state); `scanToX` (square, the original subroutine unmodified); `eraseHere` (square, the override target). The wrapper appears as a *separate* state from `scanToX`-the-original because `withOverrodeHaltState` returns a new `State` instance even though it shares the transition map.
457
- 2. **Solid edges from the wrapper duplicate `scanToX`'s edges.** That's because the wrapper inherits the same `symbolToDataMap`. Importantly, the wrapper's `* ·/R` edge points at *`scanToX`-the-original*, not at the wrapper itself so after the first iteration, control transfers to `scanToX` and stays there.
458
- 3. **The dotted `onHalt` edge is attached to the wrapper** but it doesn't fire on a single edge at runtime. Instead, the runtime pushes `eraseHere` onto an internal stack at startup, and *any* halt-bound transition reachable during the run (whether on the wrapper itself or on `scanToX`) gets redirected to `eraseHere` via stack-pop. The dotted edge is the engine's static fingerprint of "this graph was wrapped by `withOverrodeHaltState`."
459
- 4. **What actually fires at runtime, on tape `['a','b','X','b','a']`:** the wrapper runs once, transferring to `scanToX`; `scanToX` self-loops on `* ·/R` until it sees `X`; the `X ·/S` edge tries to go to halt; the runtime pops `eraseHere` off the stack and substitutes it; `eraseHere` erases the cell and halts. The wrapper's own `X ·/S halt` edge in the diagram is *never traversed* because control left the wrapper after iteration 1.
415
+ 1. **The subgraph rectangle labeled `"halt frame"`** is the wrapper's runtime scope while execution is "inside" this rectangle, the override target (`eraseHere`) sits on the runtime stack waiting to catch a halt. Visual-only; it does not mutate any edges.
416
+ 2. **`[[scanToX]]` (Mermaid subroutine / double-walled-rectangle shape)** is the wrapper node. It's both the runtime entry point (execution starts here when entering the wrapper) AND the source of the dotted `onHalt` redirect. The wrapper's composite name (`scanToX(eraseHere)`) is computed at runtime via `state.name` but does not appear as a graph node label only the bare's name is in the graph.
417
+ 3. **The halt-marker `(((halt)))` inside the subgraph** (`c3` here) is where the bare's halt-bound transitions land *inside* the wrapper's scope. `haltState` is a runtime singleton; the halt marker is a teaching aid showing "halt is caught here, not at the real terminus." Solid arrows from the bare to the halt marker all stay inside the rectangle.
418
+ 4. **The dotted `onHalt` arrow from `[[scanToX]]` to `eraseHere`** is the wrapper's catch-and-redirect. Originates from the wrapper-node since the wrapper *is* the catcher. Solid arrows from `[[scanToX]]` to other states can also cross the subgraph border those are just regular runtime transitions whose target happens to be drawn outside this rectangle (only the dotted `onHalt` carries wrapper-machinery meaning). In larger compositions (`library-binary-numbers`'s `minusOne`), solid transitions whose target is *itself* a wrapped state render as a **thick `==>` arrow** instead of `-->` — that's the visual signal for "this transition enters a halt frame, pushing the override onto the runtime stack." Stack-growth structure is then scannable from the diagram: count thick arrows along an execution path to see how deep the stack gets.
419
+ 5. **Real `(((halt)))` outside any subgraph** (`s0`) is the actual run terminus. Reached only by states that are *not* inside a wrapper's halt-frame — here, by `eraseHere` after it erases the cell.
460
420
 
461
- > 💡 **The engine's emit could be more user-friendly here.** Tracked in [#138](https://github.com/mellonis/turing-machine-js/issues/138) — the wrapper's duplicated edges and the misleading single-edge `onHalt` placement are candidates for a cleaner `toMermaid` output.
421
+ **Reading runtime sequence on tape `['a','b','X','b','a']`:** enter the `halt frame` at `[[scanToX]]` (with `eraseHere` on the stack); `[*] → [K]/[R]` self-loops until the head sees `X`; the `['X'] → [K]/[S]` solid edge would normally halt it lands on the halt marker `c3`, the wrapper's catch-and-redirect kicks in, pop the stack `eraseHere`; `eraseHere` runs `[*] [E]/[S]` and halts at real `s0`. Run terminates.
462
422
 
463
- </details>
423
+ > 💡 **Round-trip caveat.** `toMermaid → fromMermaid → toGraph → toMermaid` is bytewise stable for simple wrappers like this one ([#139](https://github.com/mellonis/turing-machine-js/issues/139) regression). For shared-bare cases (same `State` instance used as the bare in multiple wrappers — e.g., `library-binary-numbers`'s `minusOne`), per-context duplication produces wrapper-id-dependent ordering that doesn't byte-match across rebuilds — equivalent runtime behavior, different emit-line order.
464
424
 
465
- Wrappers nest: `inner.withOverrodeHaltState(middle).withOverrodeHaltState(outer)` chains halt-redirects through `middle → outer → halt`. `library-binary-numbers/src/index.ts`'s `minusOne` (the `~(~x + 1)` composition) uses a 4-deep nest of wrappers.
425
+ Wrappers nest: `inner.withOverriddenHaltState(middle).withOverriddenHaltState(outer)` chains halt-redirects through `middle → outer → halt`. `library-binary-numbers/src/index.ts`'s `minusOne` (the `~(~x + 1)` composition) uses a 4-deep nest of wrappers.
466
426
 
467
427
  ## Debugging breakpoints
468
428
 
@@ -492,7 +452,7 @@ myState.debug = null;
492
452
 
493
453
  > ⚠️ **`haltState.debug.after` throws.** Halt is terminal — there is no iteration-after-halt for an after-fire to anchor on. Assigning a truthy `.after` to `haltState.debug` (including `{ before: true, after: true }`) throws at write time. Symbol-list filters on `haltState.debug.before` are silent no-ops, since halt has no head symbol; only the wildcard `true` activates.
494
454
 
495
- The `debug` field is mutable — toggle breakpoints at runtime without rebuilding the graph. The internal cell is shared with `state.withOverrodeHaltState(...)` wrappers, so an assignment on the original is visible from every wrapper. `state.debug` is always a `DebugConfig` instance (lazy-initialized on first read); plain-object input (`state.debug = { before: true }`) is wrapped in a fresh `DebugConfig` automatically. The instance itself is `Object.seal`-ed — typos like `state.debug.bofore = true` throw `TypeError` instead of silently creating a useless property. Per-property setters validate and freeze the stored array, so `state.debug.before.push(...)` also throws `TypeError`.
455
+ The `debug` field is mutable — toggle breakpoints at runtime without rebuilding the graph. The internal cell is shared with `state.withOverriddenHaltState(...)` wrappers, so an assignment on the original is visible from every wrapper. `state.debug` is always a `DebugConfig` instance (lazy-initialized on first read); plain-object input (`state.debug = { before: true }`) is wrapped in a fresh `DebugConfig` automatically. The instance itself is `Object.seal`-ed — typos like `state.debug.bofore = true` throw `TypeError` instead of silently creating a useless property. Per-property setters validate and freeze the stored array, so `state.debug.before.push(...)` also throws `TypeError`.
496
456
 
497
457
  `run()` is async and accepts an `onPause` hook:
498
458
 
@@ -518,34 +478,27 @@ If `onPause` is not provided, breaks fire-and-resume invisibly — the trajector
518
478
 
519
479
  ### Throttle pattern
520
480
 
521
- For per-iter throttle / animation / "wait between steps" UIs, do the await inside `onPause`, not `onStep` `onPause` is the engine's awaited callback. To make it fire on every iter (no user-set breakpoints needed), arm `.after = true` on the initial state and rearm `m.nextState.debug.after = true` inside the callback. The chain is self-propagating:
481
+ For per-iter throttle / animation / "wait between steps" UIs, use the **`onIter`** hookan awaited callback that fires once at the end of every iter, after both `onPause` dispatches on the same yield. It's the engine-native shape for per-iter coordination:
522
482
 
523
483
  ```ts
524
- initialState.debug.after = true; // first pause-point
525
-
526
484
  await machine.run({
527
485
  initialState,
528
- debug: true,
529
- onPause: async (m) => {
530
- // m.debugBreak.after is true here on every iter.
531
- await new Promise((r) => setTimeout(r, intervalMs)); // throttle
532
-
533
- // Rearm for the next iter (engine processes m.nextState next).
534
- // `state.debug` is shared across `withOverrodeHaltState` wrappers — a
535
- // single arm reaches all of them.
536
- if (!m.nextState.isHalt) m.nextState.debug.after = true;
486
+ onIter: async (m) => {
487
+ // Fires after before(m.state) / step / after(m.state) on iter m.step.
488
+ await new Promise((r) => setTimeout(r, intervalMs));
537
489
  },
538
490
  });
539
491
  ```
540
492
 
493
+ `onIter` is unaffected by the `debug` master switch and unrelated to `state.debug` — it fires on every iter regardless of whether any breakpoints are armed. It coexists cleanly with user-authored `state.debug` breakpoints: on an iter with both `.before` and `.after` armed, the consumer sees `onPause(before)` → `onStep` → `onPause(after)` → `onIter`, in that order, on the same yield.
494
+
541
495
  A few details:
542
496
 
543
- - **Halting iter**: `m.nextState === haltState` on the last user iter. The example skips the rearm there. The halting iter's own `.after` already fired this callback (engine v5+ fixed [#108](https://github.com/mellonis/turing-machine-js/issues/108) part 1).
544
- - **`haltState.debug.after`**: throws on assignment (#108 part 2). Don't try to rearm onto haltState itself pause for halt by checking `m.nextState.isHalt` in the callback before the rearm step.
545
- - **Coexists with user-authored breaks**: if a user state also has `.before = true` set by the consumer's own code, `onPause` fires twice on that iter (before + after). Tell them apart with `m.debugBreak`.
546
- - **Click-pause** during throttling: keep a flag set from the outside; check it inside `onPause` before the `setTimeout` and surface a "real" pause (await a resolvable Promise the UI controls) instead of the throttle one. The engine doesn't need to know — it just sees a longer awaited `onPause`.
497
+ - **Halting iter**: `onIter` still fires on the iter whose `m.nextState === haltState`, after any halt-time `onPause` dispatches. Engine returns cleanly after that. Use this to land "halted" UI state in interactive consumers.
498
+ - **Click-pause / external interruption**: keep a flag set from the outside; check it inside `onIter` and `await` a resolvable Promise the UI controls (instead of the bare `setTimeout`). The engine just sees a longer awaited `onIter` no engine surface needed for the pause.
499
+ - **Sync consumers should keep using `onStep`**: it's microtask-free; `onIter` adds one awaited boundary per iter. Use the right hook for the right verb (logging/tracing `onStep`, throttle/coordination `onIter`, user breakpoints `onPause`).
547
500
 
548
- (v6.2.0 briefly widened `onStep` to async to enable a throttle-in-onStep pattern. That was a mistake — restored to sync in v6.3.0. The rearm-via-`onPause` pattern above is the engine-native shape.)
501
+ (History: v6.2.0 briefly widened `onStep` to `void | Promise<void>` and added an inline `await`, motivated by this same throttle use case. That was a mistake — restored to sync in v6.3.0. v6.3.0 documented a workaround using `onPause` self-rearm on `state.debug.after = true`; that workaround is superseded by `onIter` in v6.4.0+.)
549
502
 
550
503
  ## Special objects
551
504
 
@@ -613,6 +566,77 @@ Together: use `summarize` to ask "is this machine the right shape?" (size, compo
613
566
 
614
567
  For visualization and round-tripping, see `State.toGraph` / `State.fromGraph` and `toMermaid` / `fromMermaid`.
615
568
 
569
+ ## Diagram conventions
570
+
571
+ The full reference for reading `toMermaid` output — shapes, edge styles, and the bracketed edge-label vocabulary. All shapes and arrows are standard [Mermaid flowchart syntax](https://mermaid.js.org/syntax/flowchart.html); any Mermaid renderer (GitHub preview, IDE plugins, [mermaid-js](https://github.com/mermaid-js/mermaid) client-side) paints these diagrams the same way.
572
+
573
+ ### Node shapes
574
+
575
+ | Shape | Meaning |
576
+ |---|---|
577
+ | `s0(((halt)))` | the halt state |
578
+ | `sN["name"]` | a regular state |
579
+ | `sN[["name"]]` | a `withOverriddenHaltState` wrapper-bare (subroutine shape) — see [§Subroutine composition](#subroutine-composition-with-withoverriddenhaltstate) |
580
+ | `cN(((halt)))` inside a subgraph | halt marker (visualization aid; maps back to the singleton `haltState` at runtime) |
581
+ | `idle([idle])` | pre-execution sentinel (not a real state) |
582
+
583
+ ### Edge styles
584
+
585
+ | Style | Where | Meaning |
586
+ |---|---|---|
587
+ | `-->` regular solid | between states | plain transition |
588
+ | `==>` thick solid | between states | transition INTO a wrapped state — stack-push happens at runtime |
589
+ | `-. onHalt .->` dotted | from `[[bare]]` to override | wrapper's catch-and-redirect |
590
+ | `-. enter .->` dotted | from `idle` to initial state | execution-start marker |
591
+
592
+ ### Groupings
593
+
594
+ `subgraph w_N["halt frame"] … end` wraps a `[[bare]]` + its halt marker — visual grouping of the wrapper's runtime halt-handling scope.
595
+
596
+ ### Edge label format
597
+
598
+ `[reads] → [writes]/[moves]`. Each bracketed list is a tape-block reading — one entry per tape; brackets always present, even single-tape.
599
+
600
+ | Glyph | Where | Meaning |
601
+ |---|---|---|
602
+ | `'X'` | read, write | literal alphabet symbol (single-quoted) |
603
+ | `*` | read only | `ifOtherSymbol` catch-all (ASCII `*`; a literal `*` in the alphabet renders as the quoted `'*'`, so the marker stays unambiguous) |
604
+ | `B` | read only | the tape's blank symbol (a literal `B` in the alphabet appears as `'B'`, so the marker stays unambiguous) |
605
+ | `K` | write only | keep (no write) |
606
+ | `E` | write only | erase (write the tape's blank) |
607
+ | `L` / `R` / `S` | move only | left / right / stay |
608
+
609
+ ### Alternation rule
610
+
611
+ Alternative read patterns are always per-pattern-bracket:
612
+
613
+ - Single-tape: `['^']|['1']|['0']`
614
+ - Multi-tape: `['0','a']|['1','b']` — "(tape 1=`'0'` AND tape 2=`'a'`) OR (tape 1=`'1'` AND tape 2=`'b'`)"
615
+
616
+ The compact in-bracket form `['^'|'1']` is **rejected** by `fromMermaid` — and never emitted by `toMermaid`. The reason is pedagogical: each alternative is its own drawn transition, and the compact form would read as cross-product semantics in multi-tape (`['0'|'1','a'|'b']` could mean 4 combinations rather than 2 paired alternatives). One consistent rule across tape counts: each alternative is a full bracketed pattern.
617
+
618
+ ### Multi-tape example
619
+
620
+ A 2-tape "copier" machine — as long as tape 1 reads a non-blank, write the same symbol to tape 2 and step both right; halt when tape 1 reads blank:
621
+
622
+ ```mermaid
623
+ flowchart TD
624
+ %% alphabets: [[" ","0","1"],[" ","0","1"]]
625
+ s0(((halt)))
626
+ s1["copy"]
627
+ idle([idle])
628
+ idle -. enter .-> s1
629
+ s1 -- "['0',*] → [K,'0']/[R,R]" --> s1
630
+ s1 -- "['1',*] → [K,'1']/[R,R]" --> s1
631
+ s1 -- "[B,*] → [K]/[S]" --> s0
632
+ ```
633
+
634
+ Reading `['0',*] → [K,'0']/[R,R]`:
635
+
636
+ - **Read** `['0',*]` — tape 1 must be literal `'0'`; tape 2 is `ifOtherSymbol` (any).
637
+ - **Write** `[K,'0']` — tape 1: keep; tape 2: write literal `'0'`.
638
+ - **Move** `[R,R]` — both tapes step right.
639
+
616
640
  ## Versioning notes
617
641
 
618
642
  API surface changes since v3, in past tense so the timing of each piece is explicit:
@@ -623,6 +647,11 @@ API surface changes since v3, in past tense so the timing of each piece is expli
623
647
  - **v6.1** — `state.debug` ergonomics: the field is now always a non-null `DebugConfig` instance (lazy-initialized on first read), so chained field writes like `state.debug.before = true` work on a fresh state without a prior whole-object assignment. The `DebugConfig` instance is `Object.seal`-ed, so typos like `state.debug.bofore = true` throw `TypeError` at write time instead of silently creating a useless property. `state.debug = null` continues to work but semantically means "reset filters" — the next read returns a fresh empty `DebugConfig` (#150).
624
648
  - **v6.2** *(superseded by v6.3.0)* — widened `onStep`'s signature to `(m) => void | Promise<void>` and added an inline `await onStep(...)` in the run loop, enabling throttle-in-`onStep` patterns. This overturned the docstring-stated contract that `onStep` is sync (microtask-free); the right place for per-iter throttling is `onPause` with self-rearm (see [Throttle pattern](#throttle-pattern)). Restored in v6.3.0.
625
649
  - **v6.3** — `onStep` reverted to its v6.0–v6.1 sync contract — `(m) => void`, called synchronously inside the run loop. The Throttle pattern section documents the engine-native shape for per-iter throttle / "wait between iters" UIs. No other API changes.
650
+ - **v6.4** — New **`onIter`** hook on `run()`: awaited, fires once at the end of every iter (after both `onPause` dispatches on the same yield), unaffected by the `debug` master switch. Use for per-iter throttle / animation / coordination needing a suspend point; complements the existing sync `onStep` (tracing) and conditional `onPause` (user breakpoints). Three-hook contract is now `onStep` (sync, mid-iter) / `onPause` (awaited, on `state.debug` match) / `onIter` (awaited, end-of-iter). Additive — peer-deps unchanged. The v6.3.0 README's `onPause`-rearm throttle workaround is superseded.
651
+ - **v7** *(alpha 1, 2026-05-21)* — Composition-representation overhaul. **First pre-release on the `next` dist-tag:** `npm install @turing-machine-js/machine@next` (or pin `@7.0.0-alpha.1`). Stable v7.0.0 still pending [#102](https://github.com/mellonis/turing-machine-js/issues/102) (debugger step-in/over/out primitives). Landed in alpha.1:
652
+ - **`withOverrodeHaltState` → `withOverriddenHaltState`** ([#149](https://github.com/mellonis/turing-machine-js/issues/149)). Grammar fix on a name introduced in 2019: the past-participle `overridden` fits the "with a halt-state that has been ___" naming idiom; `overrode` (simple past) didn't. Hard cutover — no deprecated alias. The getter (`state.overrodeHaltState` → `state.overriddenHaltState`) and the serialized `Graph` data field (`node.overrodeHaltStateId` → `node.overriddenHaltStateId`) rename in lockstep. Consumer migration: global find/replace `OverrodeHaltState` → `OverriddenHaltState` and `overrodeHaltState` → `overriddenHaltState`. Persisted `State.toGraph` JSON dumps would need the same field-rename treatment, but persistence isn't a known consumer pattern.
653
+ - **Paren-based wrapped-state naming** ([#148](https://github.com/mellonis/turing-machine-js/issues/148)). `withOverriddenHaltState`'s composite name format changed from flat `bare>override` to nested `bare(override)`. Same nesting depth reads as `A(B(A))` (bare = `A`, override = `B(A)`) versus `A(B)(A)` (bare = `A(B)`, override = `A`) — two structurally-different wrap-trees that the old `>`-flat notation collided into the single string `A>B>A`. As a consequence, **user-provided state names must not contain `(` or `)`** — `State` now throws at construction time if a user passes such a name. The `>` character stays valid in user names (no longer reserved). The `inspect()` / `toGraph` / `toMermaid` outputs carry the new format. `states.md` files in `library-binary-numbers` regenerate accordingly.
654
+ - **`toMermaid` wrapped-state emit overhaul** ([#138](https://github.com/mellonis/turing-machine-js/issues/138) / [#139](https://github.com/mellonis/turing-machine-js/issues/139)). The wrapper-and-its-bare pair collapses into a single graph node (`isWrapped: true`); the wrapper's composite name no longer appears as a node label (only the bare's name does). Each wrapper gets a Mermaid `subgraph w_${bareId}["halt frame"] … end` block containing the `[[bare]]` (subroutine shape) plus a halt-marker `(((halt)))` (visualization aid showing where halt-bound transitions land inside the scope). The dotted `onHalt` edge originates from the `[[bare]]` and crosses the subgraph border to the override target — exactly one per wrapper. `Graph` data shape gains `isWrapped` and `isHaltMarker` flags on `GraphNode` and a stable `id` on `GraphTransition` (deterministic per-edge identifier — supports downstream tooling like the `machines-demo` interactive viewer at [machines-demo#10](https://github.com/mellonis/machines-demo/issues/10)). Halt-marker graph nodes use negative ids and round-trip back to the singleton `haltState` via `fromGraph`. Bytewise round-trip stability falls out for simple wrappers (no composite name in the graph means `fromGraph(toGraph(state))` recomputes names fresh — no accumulation). Shared-bare cases (e.g. `minusOne`'s repeated `invertNumber`) use per-context duplication in the graph emit.
626
655
 
627
656
  For the full release history, see the [GitHub releases page](https://github.com/mellonis/turing-machine-js/releases).
628
657
 
@@ -25,7 +25,7 @@ export default class State {
25
25
  get id(): number;
26
26
  get name(): string;
27
27
  get isHalt(): boolean;
28
- get overrodeHaltState(): State | null;
28
+ get overriddenHaltState(): State | null;
29
29
  get ref(): this;
30
30
  get debug(): DebugConfig;
31
31
  set debug(value: DebugConfig | {
@@ -37,12 +37,12 @@ export default class State {
37
37
  getSymbol(tapeBlock: TapeBlock): symbol;
38
38
  getCommand(symbol: symbol): Command;
39
39
  getNextState(symbol: symbol): State | Reference;
40
- withOverrodeHaltState(overrodeHaltState: State): State;
40
+ withOverriddenHaltState(overriddenHaltState: State): State;
41
41
  static inspect(state: State): {
42
42
  id: number;
43
43
  name: string;
44
44
  isHalt: boolean;
45
- overrodeHaltState: {
45
+ overriddenHaltState: {
46
46
  id: number;
47
47
  name: string;
48
48
  } | null;