@reicek/neataptic-ts 0.1.26 → 0.1.27

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.
Files changed (123) hide show
  1. package/.github/agents/boundary-mapper.agent.md +13 -8
  2. package/.github/agents/docs-scout.agent.md +14 -9
  3. package/.github/agents/plan-scout.agent.md +11 -6
  4. package/.github/agents/solid-split.agent.md +33 -17
  5. package/.github/copilot-instructions.md +100 -88
  6. package/.github/skills/educational-docs/SKILL.md +90 -40
  7. package/.github/skills/educational-docs/assets/mermaid-diagram-playbook.md +141 -141
  8. package/.github/skills/educational-docs/assets/visual-style-guide.md +1 -1
  9. package/.github/skills/plan-alignment/SKILL.md +1 -1
  10. package/.github/skills/solid-split/SKILL.md +54 -35
  11. package/.github/skills/solid-split/assets/docs-checklist.md +1 -1
  12. package/.github/skills/solid-split/assets/split-plan-template.md +41 -12
  13. package/.github/skills/solid-split/assets/split-workflow-checklist.md +4 -1
  14. package/.github/skills/test-fix-workflow/SKILL.md +12 -7
  15. package/.github/skills/trace-analyzer-extension/SKILL.md +1 -1
  16. package/.github/skills/trace-analyzer-extension/assets/extension-checklist.md +1 -1
  17. package/.github/skills/trace-analyzer-extension/references/analyzer-extension-workflow.md +1 -1
  18. package/.github/skills/trace-audit-reporting/SKILL.md +1 -1
  19. package/.github/skills/trace-audit-reporting/assets/performance-report-template.md +1 -1
  20. package/.github/skills/trace-audit-reporting/references/trace-analysis-workflow.md +1 -1
  21. package/.github/skills/tracker-handoff/SKILL.md +53 -5
  22. package/package.json +1 -1
  23. package/plans/Flappy_Bird_Folder_Documentation_Pass.logs.md +42 -0
  24. package/plans/Flappy_Bird_Folder_Documentation_Pass.md +22 -104
  25. package/plans/README.md +6 -2
  26. package/plans/Roadmap.md +35 -31
  27. package/plans/analyze-trace-solid-split.logs.md +35 -0
  28. package/plans/analyze-trace-solid-split.plans.md +21 -52
  29. package/plans/architecture-solid-split.logs.md +44 -0
  30. package/plans/architecture-solid-split.plans.md +20 -653
  31. package/plans/asciiMaze-typescript-repair.logs.md +35 -0
  32. package/plans/asciiMaze-typescript-repair.plans.md +30 -36
  33. package/plans/generate-docs-solid-split.logs.md +36 -0
  34. package/plans/generate-docs-solid-split.plans.md +21 -73
  35. package/plans/methods-docs.logs.md +37 -0
  36. package/plans/methods-docs.plans.md +22 -183
  37. package/plans/methods-solid-split.logs.md +41 -0
  38. package/plans/methods-solid-split.plans.md +23 -65
  39. package/plans/neat-docs.logs.md +38 -0
  40. package/plans/neat-docs.plans.md +21 -63
  41. package/plans/neat-test-surface-repair.logs.md +38 -0
  42. package/plans/neat-test-surface-repair.plans.md +24 -24
  43. package/plans/readme-first-section-pass.logs.md +63 -0
  44. package/plans/readme-first-section-pass.plans.md +42 -0
  45. package/plans/render-docs-html-solid-split.logs.md +36 -0
  46. package/plans/render-docs-html-solid-split.plans.md +20 -54
  47. package/plans/src-no-explicit-any-cleanup.logs.md +39 -0
  48. package/plans/src-no-explicit-any-cleanup.plans.md +23 -161
  49. package/plans/utils-docs.logs.md +37 -0
  50. package/plans/utils-docs.plans.md +20 -35
  51. package/src/README.md +88 -22
  52. package/src/architecture/README.md +94 -17
  53. package/src/architecture/activationArrayPool/README.md +53 -0
  54. package/src/architecture/activationArrayPool/activationArrayPool.ts +53 -0
  55. package/src/architecture/layer/README.md +94 -5
  56. package/src/architecture/layer/layer.ts +94 -5
  57. package/src/architecture/network/README.md +85 -14
  58. package/src/architecture/network/activate/README.md +301 -213
  59. package/src/architecture/network/activate/docs.order.json +9 -0
  60. package/src/architecture/network/activate/network.activate.utils.ts +90 -0
  61. package/src/architecture/network/connect/README.md +80 -3
  62. package/src/architecture/network/connect/docs.order.json +9 -0
  63. package/src/architecture/network/connect/network.connect.utils.ts +79 -0
  64. package/src/architecture/network/deterministic/README.md +96 -12
  65. package/src/architecture/network/deterministic/docs.order.json +10 -0
  66. package/src/architecture/network/deterministic/network.deterministic.utils.ts +90 -0
  67. package/src/architecture/network/evolve/README.md +286 -228
  68. package/src/architecture/network/evolve/docs.order.json +11 -0
  69. package/src/architecture/network/evolve/network.evolve.utils.ts +59 -0
  70. package/src/architecture/network/gating/README.md +58 -16
  71. package/src/architecture/network/gating/docs.order.json +9 -0
  72. package/src/architecture/network/gating/network.gating.utils.ts +43 -0
  73. package/src/architecture/network/genetic/README.md +75 -36
  74. package/src/architecture/network/genetic/docs.order.json +10 -0
  75. package/src/architecture/network/genetic/network.genetic.utils.ts +53 -17
  76. package/src/architecture/network/network.ts +91 -46
  77. package/src/architecture/network/standalone/README.md +309 -285
  78. package/src/architecture/network/standalone/docs.order.json +13 -0
  79. package/src/architecture/network/standalone/network.standalone.utils.ts +51 -28
  80. package/src/architecture/network.ts +94 -17
  81. package/src/architecture/nodePool/README.md +53 -0
  82. package/src/architecture/nodePool/nodePool.ts +53 -0
  83. package/src/methods/README.md +80 -34
  84. package/src/methods/methods.ts +80 -34
  85. package/src/methods/mutation/README.md +39 -93
  86. package/src/methods/mutation/mutation.ts +58 -0
  87. package/src/multithreading/README.md +114 -14
  88. package/src/multithreading/docs.order.json +5 -0
  89. package/src/multithreading/multi.ts +103 -7
  90. package/src/multithreading/types.ts +6 -1
  91. package/src/multithreading/workers/README.md +72 -2
  92. package/src/multithreading/workers/browser/README.md +39 -0
  93. package/src/multithreading/workers/browser/testworker.ts +40 -0
  94. package/src/multithreading/workers/node/README.md +50 -11
  95. package/src/multithreading/workers/node/docs.order.json +4 -0
  96. package/src/multithreading/workers/node/testworker.ts +43 -3
  97. package/src/multithreading/workers/workers.ts +36 -1
  98. package/src/neat/README.md +230 -152
  99. package/src/neat/adaptive/core/README.md +99 -42
  100. package/src/neat/adaptive/core/adaptive.core.ts +71 -14
  101. package/src/neat/adaptive/core/docs.order.json +8 -0
  102. package/src/neat/docs.order.json +9 -0
  103. package/src/neat/lineage/README.md +118 -16
  104. package/src/neat/lineage/lineage.ts +115 -13
  105. package/src/neat/neat.defaults.constants.ts +91 -13
  106. package/src/neat.ts +86 -20
  107. package/src/utils/README.md +71 -21
  108. package/src/utils/memory.ts +69 -19
  109. package/test/examples/asciiMaze/browser-entry/README.md +81 -8
  110. package/test/examples/asciiMaze/browser-entry/browser-entry.ts +81 -8
  111. package/test/examples/asciiMaze/dashboardManager/README.md +68 -4
  112. package/test/examples/asciiMaze/dashboardManager/dashboardManager.ts +68 -4
  113. package/test/examples/asciiMaze/evolutionEngine/README.md +1259 -1174
  114. package/test/examples/asciiMaze/evolutionEngine/docs.order.json +10 -0
  115. package/test/examples/asciiMaze/evolutionEngine/evolutionEngine.types.ts +87 -0
  116. package/test/examples/asciiMaze/mazeMovement/README.md +81 -4
  117. package/test/examples/asciiMaze/mazeMovement/mazeMovement.ts +81 -4
  118. package/test/examples/flappy_bird/browser-entry/README.md +64 -21
  119. package/test/examples/flappy_bird/browser-entry/browser-entry.ts +64 -21
  120. package/test/examples/flappy_bird/flappy-evolution-worker/README.md +98 -10
  121. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.ts +98 -10
  122. package/test/examples/flappy_bird/simulation-shared/README.md +88 -4
  123. package/test/examples/flappy_bird/simulation-shared/simulation-shared.types.ts +88 -4
@@ -1,62 +1,89 @@
1
1
  # evolutionEngine
2
2
 
3
- Shared contracts for the ASCII Maze evolution engine subsystem.
4
-
5
- The `evolutionEngine/` folder is where the public `EvolutionEngine` facade
6
- fans out into the lower-level machinery that keeps long maze runs practical:
7
- pooled scratch state, deterministic RNG caches, telemetry workspaces,
8
- sampling helpers, warm-start support, and population-level recovery logic.
9
-
10
- This file is the common footing for that subsystem. It defines the shared
11
- state and scratch-buffer contracts that let the rest of the engine stay
12
- allocation-light and orchestration-first instead of passing dozens of loose
13
- arrays and counters through every hot-path helper.
14
-
15
- ## evolutionEngine/engineState.types.ts
16
-
17
- ### EngineProfilingState
18
-
19
- Aggregated profiling configuration and accumulators shared across the evolution run.
20
-
21
- ### EngineScratchState
22
-
23
- Centralised shared state for the ASCII maze evolution façade.
24
-
25
- Responsibilities:
26
- 1. Define the scratch-buffer schema consumed by telemetry, population, and inspection helpers.
27
- 2. Expose runtime toggle state (`EngineToggleState`) that drives optional phases and telemetry density.
28
- 3. Provide factory and maintenance helpers (`createEngineState`, `initialiseTelemetryScratch`, `ensureVisitedHashCapacity`, `ensureRngCacheBatch`, `reseedRngState`) that size buffers and keep deterministic RNG state in sync.
29
- 4. Export the project-wide singleton `engineState` so extracted modules can share the façade’s pooled resources while still accepting injected state for testing.
30
-
31
- Callers mutate the returned scratch instances in place to avoid per-generation allocations; higher-level modules should treat the helpers as the sole entry point for sizing or resetting shared buffers.
32
-
33
- ### EngineState
34
-
35
- Shared engine state instance combining pooled scratch buffers with toggle flags.
36
-
37
- ### EngineToggleState
38
-
39
- Runtime switches that adjust telemetry verbosity and optional training phases.
40
-
41
- ### RngCacheHandles
42
-
43
- Handles returned after ensuring the RNG cache is ready for consumption.
44
-
45
- ### RngCacheParameters
46
-
47
- Parameters controlling the RNG cache refill process.
48
-
49
- ### TelemetryScratchHandles
50
-
51
- Collection of scratch buffers handed back after initialisation for convenience.
52
-
53
- ### TelemetryScratchRequest
3
+ Population-level runtime contracts for the ASCII Maze evolution engine.
4
+
5
+ This folder is where one maze, one fitness story, and one NEAT controller
6
+ turn into a repeatable training program. The public `EvolutionEngine` facade
7
+ uses these contracts to normalize options, coordinate host callbacks, keep
8
+ deterministic state reproducible, and decide when a curriculum phase has
9
+ produced a winner worth carrying forward.
10
+
11
+ The important distinction is scale. `mazeMovement/` explains one agent run.
12
+ `fitness.ts` explains how that run is scored. `dashboardManager/` explains
13
+ how progress is shown to a human. `evolutionEngine/` explains how many runs
14
+ across many generations become one population-level search loop with stop
15
+ reasons, telemetry, warm starts, and phase outcomes that the next maze can
16
+ reuse.
17
+
18
+ This file is the right chapter opening because it names the public nouns of
19
+ that loop before the reader hits pooled scratch buffers or hot-path helpers.
20
+ It answers four questions quickly: what a caller can configure, what a host
21
+ may observe or interrupt, what result the engine returns, and which shared
22
+ runtime contexts keep the hot path allocation-light.
23
+
24
+ A useful mental model is to treat the engine as a control tower rather than
25
+ the aircraft itself. The engine does not move the agent through one maze cell
26
+ at a time. It schedules phases, batches generations, preserves deterministic
27
+ state, and hands structured outcomes back to browser or terminal hosts.
28
+
29
+ Read the chapter in three passes. Start here for the public contracts and the
30
+ meaning of a run result. Continue to `engineState.types.ts` when you want the
31
+ shared scratch and toggle state that keeps the loop cheap. Finish with
32
+ `evolutionLoop.ts`, `evolutionEngine.services.ts`, and `sampling.ts` when you
33
+ want the actual orchestration and telemetry mechanics.
34
+
35
+ ```mermaid
36
+ flowchart LR
37
+ classDef base fill:#08131f,stroke:#1ea7ff,color:#dff6ff,stroke-width:1px;
38
+ classDef accent fill:#0f2233,stroke:#ffd166,color:#fff4cc,stroke-width:1.5px;
39
+
40
+ Caller["Caller or host"]:::base --> Options["IRunMazeEvolutionOptions\nrun inputs"]:::base
41
+ Options --> Engine["EvolutionEngine facade\npopulation-level control"]:::accent
42
+ Engine --> Loop["generation loop\nevaluate mutate telemetry"]:::base
43
+ Loop --> Result["MazeEvolutionRunResult\nbest network + exit reason"]:::base
44
+ Result --> Phase["MazeEvolutionCurriculumPhaseOutcome\ncarry winner forward"]:::base
45
+ Engine --> Host["EvolutionHostAdapter\npause and stop hooks"]:::base
46
+ ```
47
+
48
+ ```mermaid
49
+ flowchart TD
50
+ classDef base fill:#08131f,stroke:#1ea7ff,color:#dff6ff,stroke-width:1px;
51
+ classDef accent fill:#0f2233,stroke:#ffd166,color:#fff4cc,stroke-width:1.5px;
52
+
53
+ EngineFolder["evolutionEngine/"]:::accent --> Contracts["evolutionEngine.types.ts\npublic run contracts"]:::base
54
+ EngineFolder --> Scratch["engineState.types.ts\nshared scratch and toggles"]:::base
55
+ EngineFolder --> Loop["evolutionLoop.ts\nmain generation orchestration"]:::base
56
+ EngineFolder --> Services["evolutionEngine.services.ts\nand helpers"]:::base
57
+ EngineFolder --> Sampling["sampling.ts\nand telemetry support"]:::base
58
+ ```
59
+
60
+ For background reading on the staged difficulty idea behind the browser and
61
+ curriculum-style runs, see Wikipedia contributors,
62
+ [Curriculum learning](https://en.wikipedia.org/wiki/Curriculum_learning),
63
+ which captures the broader teaching idea of solving easier tasks before
64
+ harder ones.
65
+
66
+ Example: describe the host adapter and reporting hooks the engine may call.
54
67
 
55
- Configuration describing which telemetry scratch buffers require capacity guarantees.
68
+ ```ts
69
+ const hostAdapter: EvolutionHostAdapter = {
70
+ isPauseRequested: () => window.asciiMazePaused === true,
71
+ handleStop: ({ reason, completedGenerations }) => {
72
+ console.log(reason, completedGenerations);
73
+ },
74
+ };
75
+ ```
56
76
 
57
- ### VisitedHashScratchHandles
77
+ Example: sketch one engine run configuration before execution begins.
58
78
 
59
- Handles exposed after ensuring the visited-coordinate hash table capacity.
79
+ ```ts
80
+ const runOptions: IRunMazeEvolutionOptions = {
81
+ mazeConfig: { maze },
82
+ agentSimConfig: { maxSteps: 160 },
83
+ evolutionAlgorithmConfig: { popSize: 120, deterministic: true },
84
+ reportingConfig: { dashboardManager, logEvery: 5 },
85
+ };
86
+ ```
60
87
 
61
88
  ## evolutionEngine/evolutionEngine.types.ts
62
89
 
@@ -255,1507 +282,1638 @@ Network instance annotated with telemetry fields during a generation.
255
282
 
256
283
  Training constants used by Lamarckian warm-start and refinement helpers.
257
284
 
258
- ## evolutionEngine/sampling.ts
285
+ ## evolutionEngine/engineState.types.ts
259
286
 
260
- Sampling and history helpers extracted from the ASCII maze evolution façade.
287
+ Shared contracts for the ASCII Maze evolution engine subsystem.
261
288
 
262
- Responsibilities:
263
- 1. Provide allocation-light array sampling utilities that reuse the shared `EngineState` scratch pools.
264
- 2. Expose history helpers that mirror the façade behaviour while keeping pooled buffers centralised.
265
- 3. Centralise RNG parameter resolution for sampling paths to keep behaviour deterministic under shared state.
289
+ The `evolutionEngine/` folder is where the public `EvolutionEngine` facade
290
+ fans out into the lower-level machinery that keeps long maze runs practical:
291
+ pooled scratch state, deterministic RNG caches, telemetry workspaces,
292
+ sampling helpers, warm-start support, and population-level recovery logic.
266
293
 
267
- These helpers look small, but they sit under several hot paths. The main job
268
- is not "random choice" in the abstract; it is random choice without quietly
269
- reintroducing per-generation array churn into long-running experiments.
294
+ This file is the common footing for that subsystem. It defines the shared
295
+ state and scratch-buffer contracts that let the rest of the engine stay
296
+ allocation-light and orchestration-first instead of passing dozens of loose
297
+ arrays and counters through every hot-path helper.
270
298
 
271
- ### getTail
299
+ ### EngineProfilingState
272
300
 
273
- ```ts
274
- getTail(
275
- state: EngineState,
276
- source: T[] | undefined,
277
- count: number,
278
- ): T[]
279
- ```
301
+ Aggregated profiling configuration and accumulators shared across the evolution run.
280
302
 
281
- Extract the last `count` items from `source` into the shared tail buffer.
303
+ ### EngineScratchState
282
304
 
283
- Steps:
284
- 1. Validate the source array and clamp the requested tail length.
285
- 2. Grow the pooled tail buffer to the next power of two when necessary.
286
- 3. Copy the suffix into the pooled buffer and trim its logical length.
305
+ Centralised shared state for the ASCII maze evolution façade.
287
306
 
288
- Parameters:
289
- - `state` - Shared engine state providing the tail history buffer.
290
- - `source` - Source array reference.
291
- - `count` - Number of trailing items requested.
307
+ Responsibilities:
308
+ 1. Define the scratch-buffer schema consumed by telemetry, population, and inspection helpers.
309
+ 2. Expose runtime toggle state (`EngineToggleState`) that drives optional phases and telemetry density.
310
+ 3. Provide factory and maintenance helpers (`createEngineState`, `initialiseTelemetryScratch`, `ensureVisitedHashCapacity`, `ensureRngCacheBatch`, `reseedRngState`) that size buffers and keep deterministic RNG state in sync.
311
+ 4. Export the project-wide singleton `engineState` so extracted modules can share the façade’s pooled resources while still accepting injected state for testing.
292
312
 
293
- Returns: Pooled array containing the requested tail slice.
313
+ Callers mutate the returned scratch instances in place to avoid per-generation allocations; higher-level modules should treat the helpers as the sole entry point for sizing or resetting shared buffers.
294
314
 
295
- Example:
315
+ ### EngineState
296
316
 
297
- const recent = getTail(sharedState, telemetryLog, 40);
317
+ Shared engine state instance combining pooled scratch buffers with toggle flags.
298
318
 
299
- ### pushHistory
319
+ ### EngineToggleState
300
320
 
301
- ```ts
302
- pushHistory(
303
- buffer: T[] | undefined,
304
- value: T,
305
- maxLength: number,
306
- ): T[]
307
- ```
321
+ Runtime switches that adjust telemetry verbosity and optional training phases.
308
322
 
309
- Proxy to {@link MazeUtils.pushHistory} for consistency with the façade API.
323
+ ### RngCacheHandles
310
324
 
311
- Parameters:
312
- - `buffer` - Existing history buffer (may be undefined).
313
- - `value` - Value to append to the buffer.
314
- - `maxLength` - Maximum allowed buffer length.
325
+ Handles returned after ensuring the RNG cache is ready for consumption.
315
326
 
316
- Returns: Updated history buffer with `value` appended and trimmed to `maxLength`.
327
+ ### RngCacheParameters
317
328
 
318
- Example:
329
+ Parameters controlling the RNG cache refill process.
319
330
 
320
- const history = pushHistory(existingHistory, snapshot, 20);
331
+ ### TelemetryScratchHandles
321
332
 
322
- ### sampleArray
333
+ Collection of scratch buffers handed back after initialisation for convenience.
323
334
 
324
- ```ts
325
- sampleArray(
326
- state: EngineState,
327
- source: T[],
328
- sampleCount: number,
329
- ): T[]
330
- ```
335
+ ### TelemetryScratchRequest
331
336
 
332
- Sample `sampleCount` items (with replacement) from `source` into the pooled scratch buffer.
337
+ Configuration describing which telemetry scratch buffers require capacity guarantees.
333
338
 
334
- This helper is for callers that want an ephemeral array view immediately. The
335
- returned array reuses shared scratch storage, so callers should copy it first
336
- if they need the contents to survive another helper call.
339
+ ### VisitedHashScratchHandles
337
340
 
338
- Steps:
339
- 1. Validate the input array and normalise `sampleCount` to an integer.
340
- 2. Grow the shared `sampleResultBuffer` to the next power of two when capacity is insufficient.
341
- 3. Fill the pooled buffer using the shared fast RNG and truncate the logical length to `sampleCount`.
341
+ Handles exposed after ensuring the visited-coordinate hash table capacity.
342
342
 
343
- Parameters:
344
- - `state` - Shared engine state providing pooled scratch buffers.
345
- - `source` - Source array to sample from.
346
- - `sampleCount` - Number of samples to draw (with replacement).
343
+ ## evolutionEngine/evolutionLoop.ts
347
344
 
348
- Returns: Pooled ephemeral array containing the sampled elements.
345
+ Evolution Loop Module
349
346
 
350
- Example:
347
+ Purpose:
348
+ -------
349
+ Provides utilities for running the main NEAT evolution loop, including
350
+ generation orchestration, cancellation checking, and loop helper preparation.
351
351
 
352
- const picks = sampleArray(sharedState, population, 16);
353
- const safeCopy = [...picks];
352
+ This module encapsulates:
353
+ - Cancellation detection (AbortSignal and legacy cancellation API)
354
+ - Loop helper preparation (frame flushing, persistence, logging)
355
+ - Generation execution and orchestration
356
+ - Stop condition evaluation
354
357
 
355
- ### sampleIntoScratch
358
+ ES2023 Policy:
359
+ -------------
360
+ - Uses nullish coalescing `??` and optional chaining `?.`
361
+ - Descriptive variable names (no short identifiers)
362
+ - Async/await for generation loops
363
+ - Best-effort error handling (swallow non-fatal errors)
364
+
365
+ ### checkCancellation
356
366
 
357
367
  ```ts
358
- sampleIntoScratch(
359
- state: EngineState,
360
- source: T[],
361
- sampleCount: number,
362
- ): number
368
+ checkCancellation(
369
+ options: EvolutionOptions,
370
+ bestResult: IMazeRunResult | undefined,
371
+ ): string | undefined
363
372
  ```
364
373
 
365
- Sample up to `sampleCount` items (with replacement) into the shared `samplePool` buffer.
374
+ Inspect cooperative cancellation sources and annotate the provided result when cancelled.
366
375
 
367
- Compared with `sampleArray`, this form is for callers that only need a count
368
- plus access to the pooled buffer on `state.scratch.samplePool`. That avoids
369
- one more logical array object on the hottest paths.
376
+ This function checks for cancellation from two sources in priority order:
377
+ 1) Legacy cancellation object with `isCancelled()` method
378
+ 2) Standard AbortSignal with `aborted` property
370
379
 
371
- Steps:
372
- 1. Validate inputs and ensure the pooled buffer exists.
373
- 2. Grow the buffer by powers of two when more capacity is required.
374
- 3. Fill the buffer with random selections and return the number of items written.
380
+ Design Rationale:
381
+ - Allocation-free (uses only short-lived local references)
382
+ - Best-effort error handling (swallow exceptions to avoid disrupting loop)
383
+ - Sets `exitReason` on result object for caller inspection
384
+ - Safe to call on hot paths (no scratch buffers or pooling needed)
375
385
 
376
- Parameters:
377
- - `state` - Shared engine state providing the pooled buffer.
378
- - `source` - Source array to sample from.
379
- - `sampleCount` - Number of elements requested (floored to an integer).
386
+ Cancellation Priority:
387
+ 1. Check `options.cancellation.isCancelled()` (legacy API)
388
+ 2. Check `options.signal.aborted` (standard AbortSignal)
389
+ 3. Return undefined if no cancellation detected
380
390
 
381
- Returns: Number of sampled elements written into the pooled buffer.
391
+ Parameters:
382
392
 
383
- Example:
393
+ Parameters:
394
+ - `options` - - Optional run configuration which may contain `cancellation` and/or `signal`
395
+ - `bestResult` - - Optional mutable result object that will be annotated with `exitReason`
384
396
 
385
- const sampled = sampleIntoScratch(sharedState, population, 24);
386
- for (let index = 0; index < sampled; index++) {
387
- processGenome(sharedState.scratch.samplePool[index]);
388
- }
397
+ Returns: A reason string ('cancelled' | 'aborted') when cancellation is detected, otherwise `undefined`
389
398
 
390
- ### sampleSegmentIntoScratch
399
+ Examples:
391
400
 
392
- ```ts
393
- sampleSegmentIntoScratch(
394
- state: EngineState,
395
- source: T[],
396
- segmentStart: number,
397
- sampleCount: number,
398
- ): number
399
- ```
401
+ // Check cancellation before starting next generation
402
+ const cancelReason = checkCancellation(opts, runResult);
403
+ if (cancelReason) return cancelReason;
400
404
 
401
- Sample from a suffix of `source` starting at `segmentStart` into the pooled buffer.
405
+ // Check cancellation without result annotation
406
+ if (checkCancellation(opts)) {
407
+ console.log('User requested cancellation');
408
+ break;
409
+ }
402
410
 
403
- The common use case is "sample from the non-elite tail" or some later slice
404
- of a population without first allocating `source.slice(segmentStart)`.
411
+ ### checkStopConditions
405
412
 
406
- Steps:
407
- 1. Clamp indices and ensure there is a non-empty segment.
408
- 2. Ensure the pooled buffer can store the requested sample count.
409
- 3. Fill the buffer via the shared RNG, returning the number of items written.
413
+ ```ts
414
+ checkStopConditions(
415
+ bestResult: IMazeRunResult | undefined,
416
+ bestNetwork: default | null,
417
+ maze: string[],
418
+ completedGenerations: number,
419
+ neat: default,
420
+ dashboardManager: IDashboardManager | undefined,
421
+ flushToFrame: () => Promise<void>,
422
+ hostAdapter: EvolutionHostAdapter | undefined,
423
+ minProgressToPass: number,
424
+ autoPauseOnSolve: boolean,
425
+ stopOnlyOnSolve: boolean,
426
+ stagnantGenerations: number,
427
+ maxStagnantGenerations: number,
428
+ maxGenerations: number,
429
+ ): Promise<string | undefined>
430
+ ```
410
431
 
411
- Parameters:
412
- - `state` - Shared engine state providing pooled sampling buffers.
413
- - `source` - Source array to sample from.
414
- - `segmentStart` - Inclusive start index of the segment.
415
- - `sampleCount` - Number of items to sample (with replacement).
432
+ Inspect common termination conditions and perform minimal, best-effort side-effects.
416
433
 
417
- Returns: Number of elements written into the pooled buffer.
434
+ This function checks three canonical stop reasons in priority order:
435
+ 1) **Solved**: Best result achieves minimum progress threshold
436
+ 2) **Stagnation**: No improvement for maxStagnantGenerations
437
+ 3) **MaxGenerations**: Absolute generation cap reached
418
438
 
419
- Example:
439
+ Design Rationale:
440
+ - Allocation-light (uses only local references)
441
+ - Best-effort error handling (all side effects swallowed)
442
+ - Safe to call frequently on hot paths
443
+ - Updates dashboard and yields to host when stopping
444
+ - Emits optional 'asciiMazeSolved' event on solve (browser only)
420
445
 
421
- const written = sampleSegmentIntoScratch(sharedState, population, elitismCount, 12);
422
- const genome = sharedState.scratch.samplePool[0];
446
+ Side Effects (Best-Effort):
447
+ - Updates dashboard manager when stopping
448
+ - Awaits flushToFrame to yield to host
449
+ - Reports stop events through the optional host adapter
450
+ - Annotates `bestResult.exitReason` with canonical reason string
423
451
 
424
- ## evolutionEngine/engineState.ts
452
+ Parameters:
425
453
 
426
- Centralised shared state for the ASCII maze evolution façade.
454
+ Parameters:
455
+ - `bestResult` - - Mutable run summary object (may be mutated with `exitReason`)
456
+ - `bestNetwork` - - Network object associated with the best result (for dashboard)
457
+ - `maze` - - Maze descriptor passed to dashboard updates/events
458
+ - `completedGenerations` - - Current generation index (integer)
459
+ - `neat` - - NEAT driver instance (passed to dashboard update)
460
+ - `dashboardManager` - - Optional manager exposing `update(maze, result, network, gen, neat)`
461
+ - `flushToFrame` - - Async function to yield to host renderer (e.g. requestAnimationFrame)
462
+ - `hostAdapter` - - Optional host adapter that owns host-side stop behavior
463
+ - `minProgressToPass` - - Numeric threshold to consider a run 'solved'
464
+ - `autoPauseOnSolve` - - When truthy request host-side pause handling on solve
465
+ - `stopOnlyOnSolve` - - When true ignore stagnation/maxGenerations as stop reasons
466
+ - `stagnantGenerations` - - Current count of stagnant generations observed
467
+ - `maxStagnantGenerations` - - Max allowed stagnant generations before stopping
468
+ - `maxGenerations` - - Absolute generation cap after which the run stops
427
469
 
428
- Responsibilities:
429
- 1. Define the scratch-buffer schema consumed by telemetry, population, and inspection helpers.
430
- 2. Expose runtime toggle state (`EngineToggleState`) that drives optional phases and telemetry density.
431
- 3. Provide factory and maintenance helpers (`createEngineState`, `initialiseTelemetryScratch`, `ensureVisitedHashCapacity`, `ensureRngCacheBatch`, `reseedRngState`) that size buffers and keep deterministic RNG state in sync.
432
- 4. Export the project-wide singleton `engineState` so extracted modules can share the façade’s pooled resources while still accepting injected state for testing.
470
+ Returns: A canonical reason string ('solved'|'stagnation'|'maxGenerations') when stopping, otherwise `undefined`
433
471
 
434
- Callers mutate the returned scratch instances in place to avoid per-generation allocations; higher-level modules should treat the helpers as the sole entry point for sizing or resetting shared buffers.
472
+ Example:
435
473
 
436
- ### createEngineState
474
+ // Check stop conditions after each generation
475
+ const reason = await checkStopConditions(
476
+ bestResult, bestNet, maze, gen, neat, dashboard, flush,
477
+ 95, true, false, stagnant, 500, 10000
478
+ );
479
+ if (reason) {
480
+ console.log('Stopping due to', reason);
481
+ break;
482
+ }
483
+
484
+ ### emitProfileSummary
437
485
 
438
486
  ```ts
439
- createEngineState(): EngineState
487
+ emitProfileSummary(
488
+ engineState: EngineState,
489
+ safeWrite: (msg: string) => void,
490
+ completedGenerations: number,
491
+ totalEvolveMs: number,
492
+ totalLamarckMs: number,
493
+ totalSimMs: number,
494
+ isProfilingDetailsEnabledFn: (state: EngineState) => boolean,
495
+ getProfilingAccumulatorsFn: (state: EngineState) => ProfilingAccumulators,
496
+ ): void
440
497
  ```
441
498
 
442
- Fabricates a new {@link EngineState} with pre-sized scratch buffers and default toggle values.
499
+ Emit a formatted profiling summary showing average per-generation timings.
443
500
 
444
- Returns: Initialized engine state used by the maze evolution façade.
501
+ This function prints a compact profiling summary with average millisecond timings
502
+ for the main evolution phases (evolve, Lamarckian training, simulation). If detailed
503
+ profiling is enabled, it also prints averages for telemetry, simplify, snapshot, and
504
+ prune operations.
445
505
 
446
- ### createProfilingState
506
+ Design Rationale:
507
+ - Allocation-free (reuses pooled Float64Array for intermediate calculations)
508
+ - Best-effort error handling (swallow all exceptions)
509
+ - Defensive numeric validation with divide-by-zero guards
510
+ - Conditional detailed profiling output
447
511
 
448
- ```ts
449
- createProfilingState(): EngineProfilingState
450
- ```
512
+ Calculation Steps:
513
+ 1. Validate and normalize generation count (guard divide-by-zero)
514
+ 2. Store totals in pooled scratch buffer (4-slot Float64Array)
515
+ 3. Compute per-generation averages by dividing totals by generation count
516
+ 4. Format numbers with 2 decimal places and print compact summary
517
+ 5. If detailed profiling enabled, print averaged detail line
451
518
 
452
- Fabricates the profiling state bundle consumed by timing helpers.
519
+ Parameters:
453
520
 
454
- Returns: Profiling configuration and accumulator state.
521
+ Parameters:
522
+ - `engineState` - - Shared engine state with scratch buffers and profiling accumulators
523
+ - `safeWrite` - - Safe logging function (best-effort, never throws)
524
+ - `completedGenerations` - - Number of completed generations (must be > 0)
525
+ - `totalEvolveMs` - - Total milliseconds spent in NEAT evolve() calls
526
+ - `totalLamarckMs` - - Total milliseconds spent in Lamarckian training
527
+ - `totalSimMs` - - Total milliseconds spent in simulation
528
+ - `isProfilingDetailsEnabledFn` - - Function to check if detailed profiling is enabled
529
+ - `getProfilingAccumulatorsFn` - - Function to get detailed profiling accumulators
455
530
 
456
531
  Example:
457
532
 
458
- const profiling = createProfilingState();
459
- console.log(profiling.detailsEnabled); // false unless env flag set
533
+ // Print averages after a run that completed 100 generations
534
+ emitProfileSummary(
535
+ state, console.log, 100, 12000, 3000, 4500,
536
+ isProfilingDetailsEnabled, getProfilingAccumulators
537
+ );
460
538
 
461
- ### createScratchState
539
+ ### EvolutionLoopResult
462
540
 
463
- ```ts
464
- createScratchState(): EngineScratchState
465
- ```
541
+ Evolution loop result
466
542
 
467
- Fabricates the default pooled scratch buffers shared by the evolution façade.
543
+ ### GenerationOutcome
468
544
 
469
- Returns: Scratch bundle sized for the baseline maze curriculum workload.
545
+ Generation outcome with profiling timings
470
546
 
471
- Example:
547
+ ### MutableMazeResult
472
548
 
473
- const scratch = createScratchState();
474
- console.log(scratch.exps.length); // 4
549
+ Mutable result object with exitReason field
475
550
 
476
- ### createToggleState
551
+ ### persistSnapshotIfNeeded
477
552
 
478
553
  ```ts
479
- createToggleState(): EngineToggleState
554
+ persistSnapshotIfNeeded(
555
+ engineState: EngineState,
556
+ fs: { writeFileSync?: ((path: string, data: string) => void) | undefined; } | null,
557
+ pathModule: { join?: ((...paths: string[]) => string) | undefined; } | null,
558
+ persistDir: string | undefined,
559
+ persistTopK: number,
560
+ completedGenerations: number,
561
+ persistEvery: number,
562
+ neat: default,
563
+ bestFitness: number,
564
+ simplifyMode: boolean,
565
+ plateauCounter: number,
566
+ scratchSnapshotObj: Record<string, unknown>,
567
+ scratchSnapshotTop: SnapshotEntry[],
568
+ collectTelemetryTailFn: (state: EngineState, neat: default, count: number) => unknown,
569
+ getSortedIndicesByScoreFn: (state: EngineState, population: default[]) => number[],
570
+ isProfilingDetailsEnabledFn: (state: EngineState) => boolean,
571
+ profilingStartTimestampFn: (state: EngineState) => number,
572
+ accumulateProfilingDurationFn: (state: EngineState, label: string, duration: number) => void,
573
+ ): void
480
574
  ```
481
575
 
482
- Fabricates the default runtime toggle bundle for the evolution façade.
576
+ Persist a population snapshot to disk at the configured interval.
483
577
 
484
- Returns: Toggle state with all optional phases enabled.
578
+ This function writes a JSON snapshot of the current generation state when the
579
+ generation cadence aligns with the configured persistence interval. It captures
580
+ top-K genomes, telemetry tail, and metadata for later analysis or resume.
485
581
 
486
- Example:
582
+ Design Rationale:
583
+ - Best-effort semantics: swallows all errors to avoid disrupting evolution loop
584
+ - Reuses pooled scratch objects (SCRATCH_SNAPSHOT_OBJ, SCRATCH_SNAPSHOT_TOP) to minimize allocations
585
+ - Optional profiling when enabled (measures snapshot serialization time)
586
+ - Defensive validation to ensure FS/path modules are available
487
587
 
488
- const toggles = createToggleState();
489
- console.log(toggles.reducedTelemetry); // false
588
+ Scheduling Logic:
589
+ - Only persists when `completedGenerations % persistEvery === 0`
590
+ - Requires valid fs.writeFileSync and pathModule.join functions
591
+ - Requires non-empty population
490
592
 
491
- ### DEFAULT_RNG_CACHE_BATCH_SIZE
593
+ Snapshot Structure:
594
+ - generation: completed generation index
595
+ - bestFitness: best fitness score this generation
596
+ - simplifyMode: whether simplification was active
597
+ - plateauCounter: current plateau detection counter
598
+ - timestamp: Date.now() when snapshot was created
599
+ - telemetryTail: last N telemetry entries
600
+ - top: top-K genomes with minimal metadata (idx, score, nodes, connections, json)
492
601
 
493
- Default RNG cache batch size mirroring the façade constant.
602
+ Parameters:
494
603
 
495
- ### DEFAULT_VISITED_HASH_LOAD_FACTOR
604
+ Parameters:
605
+ - `engineState` - - Shared engine state with scratch buffers and profiling
606
+ - `fs` - - Node.js fs module or compatible FS API
607
+ - `pathModule` - - Node.js path module or compatible path API
608
+ - `persistDir` - - Directory path where snapshots should be written
609
+ - `persistTopK` - - Number of top genomes to include in snapshot
610
+ - `completedGenerations` - - Current generation index
611
+ - `persistEvery` - - Generation interval for persistence (e.g., 25 = every 25th generation)
612
+ - `neat` - - NEAT instance with population
613
+ - `bestFitness` - - Best fitness score this generation
614
+ - `simplifyMode` - - Whether simplification mode is active
615
+ - `plateauCounter` - - Current plateau counter value
616
+ - `scratchSnapshotObj` - - Pooled snapshot object (reused across calls)
617
+ - `scratchSnapshotTop` - - Pooled top-K buffer (reused across calls)
618
+ - `collectTelemetryTailFn` - - Function to collect telemetry tail from state
619
+ - `getSortedIndicesByScoreFn` - - Function to get sorted genome indices
620
+ - `isProfilingDetailsEnabledFn` - - Function to check profiling state
621
+ - `profilingStartTimestampFn` - - Function to get profiling start time
622
+ - `accumulateProfilingDurationFn` - - Function to accumulate profiling duration
496
623
 
497
- Default load factor target for the visited coordinate hash table.
624
+ Example:
498
625
 
499
- ### EngineProfilingState
626
+ // Persist snapshot every 25 generations
627
+ persistSnapshotIfNeeded(
628
+ state, fs, path, './snapshots', 10, 50, 25, neat, 0.95, false, 3,
629
+ scratchObj, scratchTop, collectTail, getSorted, isProfilingEnabled, profileStart, profileAccum
630
+ );
500
631
 
501
- Aggregated profiling configuration and accumulators shared across the evolution run.
632
+ ### prepareLoopHelpers
502
633
 
503
- ### EngineScratchState
634
+ ```ts
635
+ prepareLoopHelpers(
636
+ opts: EvolutionOptions,
637
+ scratchBundle: ScratchBundle,
638
+ ): LoopHelpers
639
+ ```
504
640
 
505
- Centralised shared state for the ASCII maze evolution façade.
641
+ Build lightweight helpers used inside the evolution loop.
506
642
 
507
- Responsibilities:
508
- 1. Define the scratch-buffer schema consumed by telemetry, population, and inspection helpers.
509
- 2. Expose runtime toggle state (`EngineToggleState`) that drives optional phases and telemetry density.
510
- 3. Provide factory and maintenance helpers (`createEngineState`, `initialiseTelemetryScratch`, `ensureVisitedHashCapacity`, `ensureRngCacheBatch`, `reseedRngState`) that size buffers and keep deterministic RNG state in sync.
511
- 4. Export the project-wide singleton `engineState` so extracted modules can share the façade’s pooled resources while still accepting injected state for testing.
643
+ This function assembles the helper utilities needed by the main evolution loop:
644
+ - Frame flushing for cooperative yielding
645
+ - Persistence handles for snapshot saving (Node.js only)
646
+ - Safe logging writer with fallback chain
647
+ - Scratch buffer warm-up (best-effort)
512
648
 
513
- Callers mutate the returned scratch instances in place to avoid per-generation allocations; higher-level modules should treat the helpers as the sole entry point for sizing or resetting shared buffers.
649
+ Design Rationale:
650
+ - All initialization is best-effort (failures swallowed)
651
+ - Warms up common scratch buffers to reduce first-use allocation spikes
652
+ - Returns simple POJO with utilities (no class coupling)
653
+ - Side effects isolated to scratch bundle parameter
514
654
 
515
- ### EngineState
516
-
517
- Shared engine state instance combining pooled scratch buffers with toggle flags.
518
-
519
- ### EngineToggleState
520
-
521
- Runtime switches that adjust telemetry verbosity and optional training phases.
655
+ Scratch Buffer Warm-Up:
656
+ - samplePool: Array for population sampling
657
+ - profilingScratch: Float64Array(4) for timing accumulation
658
+ - exps: Float64Array(64) for exponential computations
522
659
 
523
- ### ensureRngCacheBatch
660
+ Parameters:
524
661
 
525
- ```ts
526
- ensureRngCacheBatch(
527
- parameters: RngCacheParameters,
528
- state: EngineState,
529
- ): RngCacheHandles
530
- ```
662
+ Parameters:
663
+ - `opts` - - Normalized run options (contains persistDir and dashboardManager)
664
+ - `scratchBundle` - - Engine scratch state for optional buffer warm-up
531
665
 
532
- Ensure the RNG cache contains fresh samples before consumption.
666
+ Returns: Object containing:
667
+ - flushToFrame: Async function for cooperative yielding
668
+ - fs: Node.js fs module (null in browsers)
669
+ - path: Node.js path module (null in browsers)
670
+ - safeWrite: Resilient logging function with fallback chain
533
671
 
534
- Parameters:
535
- - `parameters` - Congruential generator parameters and cache batch size.
536
- - `state` - Optional engine state container (defaults to the shared singleton).
672
+ Example:
537
673
 
538
- Returns: Handles exposing the cache and its batch size.
674
+ // Prepare loop helpers with scratch buffer warm-up
675
+ const { flushToFrame, fs, path, safeWrite } = prepareLoopHelpers(opts, engineState.scratch);
676
+ safeWrite('Starting evolution...\n');
677
+ await flushToFrame(); // Yield to host
539
678
 
540
- ### ensureVisitedHashCapacity
679
+ ### runEvolutionLoop
541
680
 
542
681
  ```ts
543
- ensureVisitedHashCapacity(
544
- targetEntryCount: number,
545
- state: EngineState,
546
- ): VisitedHashScratchHandles
682
+ runEvolutionLoop(
683
+ engineState: EngineState,
684
+ neat: default,
685
+ opts: EvolutionOptions,
686
+ lamarckianTrainingSet: { input: number[]; output: number[]; }[],
687
+ encodedMaze: number[][],
688
+ startPosition: readonly [number, number],
689
+ exitPosition: readonly [number, number],
690
+ distanceMap: number[][],
691
+ helpers: LoopHelpers,
692
+ doProfile: boolean,
693
+ runtimeContext: EvolutionLoopRuntimeContext,
694
+ initialRingState: LogitsRingState,
695
+ telemetryContext: EvolutionLoopTelemetryContext,
696
+ supportContext: EvolutionLoopSupportContext,
697
+ constants: TrainingConstants & { DEFAULT_TRAIN_BATCH_LARGE: number; FITTEST_TRAIN_ITERATIONS: number; TELEMETRY_MINIMAL: boolean; SATURATION_PRUNE_THRESHOLD: number; RECENT_WINDOW: number; REDUCED_TELEMETRY: boolean; DISABLE_BALDWIN: boolean; },
698
+ ): Promise<EvolutionLoopResult>
547
699
  ```
548
700
 
549
- Ensure the visited-coordinate hash table can store the requested entry count.
701
+ Internal evolution loop that executes generations until a stop condition or cancellation.
550
702
 
551
- Growth policy:
552
- 1. Tables expand geometrically (powers of two) while respecting the configured load factor.
553
- 2. When the existing table is large enough, it is cleared in place to preserve the allocation.
554
- 3. Load factors outside the (0,1) range fall back to {@link DEFAULT_VISITED_HASH_LOAD_FACTOR}.
703
+ Behaviour & contract:
704
+ - Runs generations in a resilient, best-effort manner; internal errors are swallowed
705
+ so a single failure cannot abort the whole run.
706
+ - When `doProfile` is truthy the loop accumulates timing into a pooled Float64Array
707
+ to avoid per-iteration allocations. The pooled buffer is reused across calls.
708
+ - The helper performs side-effects (dashboard updates, persistence) in a non-fatal
709
+ fashion and yields to the host when requested via `helpers.flushToFrame`.
555
710
 
556
711
  Parameters:
557
- - `targetEntryCount` - Expected number of unique coordinates to store.
558
- - `state` - Optional engine state container (defaults to the shared singleton).
712
+ - `engineState` - - Shared engine state with scratch buffers and configuration
713
+ - `neat` - - NEAT driver instance used to perform evolution and mutation operations
714
+ - `opts` - - Normalised run options (produced by normalizeRunOptions)
715
+ - `lamarckianTrainingSet` - - Optional supervised training cases used for Lamarckian warm-start
716
+ - `encodedMaze` - - Encoded maze representation consumed by simulators
717
+ - `startPosition` - - Start coordinates for the simulated agent
718
+ - `exitPosition` - - Exit coordinates for the simulated agent
719
+ - `distanceMap` - - Optional precomputed distance map to speed simulation
720
+ - `helpers` - - Helper utilities: { flushToFrame, fs, path, safeWrite }
721
+ - `doProfile` - - When truthy collect and return millisecond timings in the result
722
+ - `runtimeContext` - - Shared pooled ring buffers and limits for the hot path.
723
+ - `initialRingState` - - Current mutable ring state for this run.
724
+ - `telemetryContext` - - Telemetry thresholds and verbosity switches used during simulation.
725
+ - `supportContext` - - Shared scratch buffers and helper callbacks used by the loop.
726
+ - `constants` - - Object containing all engine constants (DEFAULT_TRAIN_ERROR, etc.)
559
727
 
560
- Returns: Cleared table reference plus the slot mask for probing loops.
728
+ Returns: Promise resolving to an object:
729
+ { bestNetwork, bestResult, neat, completedGenerations, totalEvolveMs, totalLamarckMs, totalSimMs, updatedRingState }
561
730
 
562
731
  Example:
563
732
 
564
- const { table, slotMask } = ensureVisitedHashCapacity(256);
733
+ const runSummary = await runEvolutionLoop(
734
+ state, neat, opts, trainingSet, maze, start, exit, distMap, helpers, true, ...
735
+ );
565
736
 
566
- ### initialiseTelemetryScratch
737
+ ### runGeneration
567
738
 
568
739
  ```ts
569
- initialiseTelemetryScratch(
570
- request: TelemetryScratchRequest,
571
- state: EngineState,
572
- ): TelemetryScratchHandles
740
+ runGeneration(
741
+ engineState: EngineState,
742
+ neat: default,
743
+ doProfile: boolean,
744
+ lamarckianIterations: number,
745
+ lamarckianTrainingSet: { input: number[]; output: number[]; }[],
746
+ lamarckianSampleSize: number | undefined,
747
+ safeWrite: (msg: string) => void,
748
+ completedGenerations: number,
749
+ dynamicPopEnabled: boolean,
750
+ dynamicPopMax: number,
751
+ plateauGenerations: number,
752
+ plateauCounter: number,
753
+ dynamicPopExpandInterval: number,
754
+ dynamicPopExpandFactor: number,
755
+ dynamicPopPlateauSlack: number,
756
+ speciesHistoryRef: number[],
757
+ emptyVec: default[],
758
+ scratchNodeIdx: Int32Array<ArrayBufferLike>,
759
+ getNodeIndicesByType: (nodes: NetworkNode[], type: string) => number,
760
+ constants: TrainingConstants,
761
+ ): Promise<GenerationOutcome>
573
762
  ```
574
763
 
575
- Ensure telemetry-related scratch buffers allocate enough capacity for upcoming work.
576
-
577
- Growth strategy:
578
- - Float64Array pools grow geometrically (powers of two) and preserve previously written prefixes.
579
- - String buffers are replaced with larger arrays (also geometric) to avoid repeated reallocations.
580
- - Optional higher-moment buffers (M3/M4/kurtosis) are only created when {@link TelemetryScratchRequest.includeHigherMoments}
581
- is truthy to keep reduced telemetry lightweight.
582
-
583
- Parameters:
584
- - `request` - Capacity hints for upcoming telemetry computations.
585
- - `state` - Optional engine state target (defaults to the singleton {@link engineState} ).
586
-
587
- Returns: Handles referencing the ensured scratch buffers for immediate use.
588
-
589
- ### reseedRngState
590
-
591
- ```ts
592
- reseedRngState(
593
- seed: number,
594
- state: EngineState,
595
- ): number
596
- ```
764
+ Run one generation: evolve, ensure output identity, update species history, maybe expand population,
765
+ and run Lamarckian training if configured.
597
766
 
598
- Persist a new RNG seed and schedule a cache refill on the next draw.
767
+ Behaviour & contract:
768
+ - Performs a single NEAT generation step in a best-effort, non-throwing manner.
769
+ - Measures profiling durations when `doProfile` is truthy. Profiling is optional and
770
+ kept allocation-free (uses local numeric temporaries only).
771
+ - Invokes the following steps in order (each step is wrapped in a try/catch so
772
+ the evolution loop remains resilient to per-stage failures):
773
+ 1) `neat.evolve()` to produce the fittest network for this generation.
774
+ 2) `ensureOutputIdentity` to normalise output activations for consumers.
775
+ 3) `handleSpeciesHistory` to update species statistics and history.
776
+ 4) `maybeExpandPopulation` to grow the population when configured and warranted.
777
+ 5) Optional Lamarckian warm-start training via `applyLamarckianTraining`.
778
+ - The method is allocation-light and reuses engine helpers / pooled buffers where
779
+ appropriate. It never throws; internal errors are swallowed and optionally logged
780
+ via the provided `safeWrite` function.
599
781
 
600
782
  Parameters:
601
- - `seed` - Raw numeric seed requested by deterministic mode.
602
- - `state` - Optional engine state container (defaults to the shared singleton).
783
+ - `engineState` - - Shared engine state with scratch buffers and RNG
784
+ - `neat` - - NEAT driver instance used for evolving the generation
785
+ - `doProfile` - - When truthy measure timing for the evolve step (ms) using engine clock
786
+ - `lamarckianIterations` - - Number of supervised training iterations to run per genome (0 to skip)
787
+ - `lamarckianTrainingSet` - - Array of supervised training cases used for warm-start (may be empty)
788
+ - `lamarckianSampleSize` - - Optional per-network sample size used by the warm-start routine
789
+ - `safeWrite` - - Safe logging function; used only for best-effort diagnostic messages
790
+ - `completedGenerations` - - Current generation index (used by expansion heuristics)
791
+ - `dynamicPopEnabled` - - Whether dynamic population expansion is enabled
792
+ - `dynamicPopMax` - - Upper bound on population size for expansion
793
+ - `plateauGenerations` - - Window size used by plateau detection
794
+ - `plateauCounter` - - Current plateau counter used by expansion heuristics
795
+ - `dynamicPopExpandInterval` - - Generation interval to attempt expansion
796
+ - `dynamicPopExpandFactor` - - Fractional growth factor used to compute new members
797
+ - `dynamicPopPlateauSlack` - - Minimum plateau ratio required to trigger expansion
798
+ - `speciesHistoryRef` - - Mutable array holding species history (maintained externally)
799
+ - `emptyVec` - - Empty array fallback to avoid ephemeral allocations
800
+ - `scratchNodeIdx` - - Pooled node index buffer (passed through to helpers)
801
+ - `getNodeIndicesByType` - - Helper function to collect node indices by type
802
+ - `constants` - - Object containing DEFAULT_TRAIN_ERROR, DEFAULT_TRAIN_RATE, DEFAULT_TRAIN_MOMENTUM, DEFAULT_TRAIN_BATCH_SMALL, DEFAULT_STD_SMALL, DEFAULT_STD_ADJUST_MULT
603
803
 
604
- Returns: Unsigned seed stored in scratch state for diagnostics.
804
+ Returns: An object shaped { fittest, tEvolve, tLamarck } where:
805
+ - `fittest` is the network returned by `neat.evolve()` (may be null on error),
806
+ - `tEvolve` is the measured evolve duration in milliseconds when `doProfile` is true (0 otherwise),
807
+ - `tLamarck` is the total time spent in Lamarckian training (0 when skipped)
605
808
 
606
- ### RngCacheHandles
809
+ Example:
607
810
 
608
- Handles returned after ensuring the RNG cache is ready for consumption.
811
+ // Run a single generation with profiling and optional Lamarckian warm-start
812
+ const { fittest, tEvolve, tLamarck } = await runGeneration(
813
+ engineState,
814
+ neatInstance,
815
+ true, // doProfile
816
+ 5, // lamarckianIterations
817
+ trainingSet,
818
+ 16, // lamarckianSampleSize
819
+ console.log,
820
+ genIndex,
821
+ true,
822
+ 500,
823
+ 10,
824
+ plateauCounter,
825
+ 5,
826
+ 0.1,
827
+ 0.75,
828
+ speciesHistory,
829
+ [],
830
+ nodeIndexBuffer,
831
+ getNodeIndicesByTypeFn,
832
+ { DEFAULT_TRAIN_ERROR: 0.01, ... }
833
+ );
609
834
 
610
- ### RngCacheParameters
835
+ ### SimResultWithOutputs
611
836
 
612
- Parameters controlling the RNG cache refill process.
837
+ Simulation result with step outputs
613
838
 
614
- ### setBaldwinPhaseDisabledFlag
839
+ ### simulateAndPostprocess
615
840
 
616
841
  ```ts
617
- setBaldwinPhaseDisabledFlag(
618
- isDisabled: boolean,
619
- ): void
842
+ simulateAndPostprocess(
843
+ engineState: EngineState,
844
+ fittest: default,
845
+ encodedMaze: number[][],
846
+ startPosition: readonly [number, number],
847
+ exitPosition: readonly [number, number],
848
+ distanceMap: number[][],
849
+ maxSteps: number | undefined,
850
+ doProfile: boolean,
851
+ safeWrite: (msg: string) => void,
852
+ logEvery: number,
853
+ completedGenerations: number,
854
+ neat: default,
855
+ runtimeContext: EvolutionLoopRuntimeContext,
856
+ ringState: LogitsRingState,
857
+ telemetryContext: EvolutionLoopTelemetryContext,
858
+ loopSupportContext: Pick<EvolutionLoopSupportContext, "loopHelpers">,
859
+ ): SimulationResult
620
860
  ```
621
861
 
622
- Enable or disable the Baldwin-phase warm-start pipeline.
862
+ Simulate the supplied `fittest` genome/network and perform allocation-light postprocessing.
623
863
 
624
- Parameters:
625
- - `isDisabled` - When true, skips the Lamarckian training stage.
864
+ Behaviour & contract:
865
+ - Runs the simulation via `MazeMovement.simulateAgent` and attaches compact telemetry
866
+ (saturation fraction, action entropy) directly onto the `fittest` object (in-place).
867
+ - When per-step logits are returned the helper attempts to copy them into the engine's pooled
868
+ ring buffers to avoid per-run allocations. Two copy modes are supported:
869
+ 1) Shared SAB-backed flat Float32Array with an atomic Int32 write index (cross-worker safe).
870
+ 2) Local in-process per-row Float32Array ring (`scratchLogitsRing`).
871
+ - Best-effort: all mutation and buffer-copy steps are guarded; failures are swallowed so the
872
+ evolution loop is not interrupted. Use `safeWrite` for optional diagnostic messages.
626
873
 
627
- ### setReducedTelemetryFlag
628
-
629
- ```ts
630
- setReducedTelemetryFlag(
631
- isEnabled: boolean,
632
- ): void
633
- ```
634
-
635
- Toggle the reduced telemetry mode.
636
-
637
- Parameters:
638
- - `isEnabled` - When true, trims telemetry to the essential metrics only.
639
-
640
- ### setTelemetryMinimalFlag
641
-
642
- ```ts
643
- setTelemetryMinimalFlag(
644
- isMinimal: boolean,
645
- ): void
646
- ```
874
+ Steps (high level):
875
+ 1) Run the simulator and capture wall-time when `doProfile` is truthy.
876
+ 2) Attach compact telemetry fields to `fittest` and ensure legacy `_lastStepOutputs` exists.
877
+ 3) If per-step logits are available, ensure ring capacity and copy them into the selected ring.
878
+ 4) Optionally prune saturated hidden->output connections and emit telemetry via logGenerationTelemetry.
879
+ 5) Return the raw simulation result and elapsed simulation time (ms when profiling enabled).
647
880
 
648
- Toggle the minimal telemetry mode for JSON output.
881
+ Notes on pooling / reentrancy:
882
+ - The local ring is not re-entrant; callers must avoid concurrent writes.
883
+ - When shared mode is true we prefer the SAB-backed path which uses Atomics and is safe
884
+ for cross-thread producers.
649
885
 
650
886
  Parameters:
651
- - `isMinimal` - When true, disables verbose telemetry capture.
887
+ - `engineState` - - Shared engine state with scratch buffers and ring configuration
888
+ - `fittest` - - Genome/network considered the generation's best; may be mutated with metadata
889
+ - `encodedMaze` - - Maze descriptor used by the simulator
890
+ - `startPosition` - - Start co-ordinates passed as-is to the simulator
891
+ - `exitPosition` - - Exit co-ordinates passed as-is to the simulator
892
+ - `distanceMap` - - Optional precomputed distance map consumed by the simulator
893
+ - `maxSteps` - - Optional maximum simulation steps; may be undefined to allow default
894
+ - `doProfile` - - When truthy measure and return the simulation time in milliseconds
895
+ - `safeWrite` - - Optional logger used for non-fatal diagnostic messages
896
+ - `logEvery` - - Emit telemetry every `logEvery` generations (0 disables periodic telemetry)
897
+ - `completedGenerations` - - Current generation index used for conditional telemetry
898
+ - `neat` - - NEAT driver instance passed to telemetry hooks
899
+ - `runtimeContext` - - Shared pooled ring buffers and limits for logits telemetry.
900
+ - `ringState` - - Current mutable ring state (capacity, shared-mode flag, write cursor).
901
+ - `telemetryContext` - - Telemetry thresholds and verbosity switches used after simulation.
902
+ - `loopSupportContext` - - Shared scratch buffers and helper callbacks used by the loop.
652
903
 
653
- ### TelemetryScratchHandles
904
+ Returns: An object { generationResult, simTime, updatedRingState } where simTime is ms when profiling is enabled
654
905
 
655
- Collection of scratch buffers handed back after initialisation for convenience.
906
+ Example:
656
907
 
657
- ### TelemetryScratchRequest
908
+ const { generationResult, simTime, updatedRingState } = simulateAndPostprocess(
909
+ state, bestGenome, maze, start, exit, distMap, 1000, true, console.log, 10, genIdx, neat, ...
910
+ );
658
911
 
659
- Configuration describing which telemetry scratch buffers require capacity guarantees.
912
+ ### SimulationOutcome
660
913
 
661
- ### VisitedHashScratchHandles
914
+ Simulation result with profiling and ring state
662
915
 
663
- Handles exposed after ensuring the visited-coordinate hash table capacity.
916
+ ### updateDashboardAndMaybeFlush
664
917
 
665
- ## evolutionEngine/rngAndTiming.ts
918
+ ```ts
919
+ updateDashboardAndMaybeFlush(
920
+ maze: string[],
921
+ result: IMazeRunResult | undefined,
922
+ network: default | null,
923
+ completedGenerations: number,
924
+ neat: default,
925
+ dashboardManager: IDashboardManager | undefined,
926
+ flushToFrame: (() => Promise<void>) | undefined,
927
+ ): Promise<void>
928
+ ```
666
929
 
667
- RNG and timing helpers extracted from the evolution façade.
930
+ Safely update a UI dashboard with the latest run state and optionally yield to the
931
+ host/frame via an awaited flush function.
668
932
 
669
- ### accumulateProfilingDuration
933
+ Behaviour (best-effort):
934
+ 1) If `dashboardManager.update` exists and is callable, call it with the stable
935
+ argument order (maze, result, network, completedGenerations, neat). Any exception
936
+ raised by the dashboard is swallowed to avoid interrupting the evolution loop.
937
+ 2) If `flushToFrame` is supplied as an async function, await it to yield control to
938
+ the event loop or renderer (for example `() => new Promise(r => requestAnimationFrame(r))`).
939
+ 3) The helper avoids heap allocations and relies on existing pooled scratch buffers in
940
+ the engine for heavy telemetry elsewhere; this method intentionally performs only
941
+ short-lived control flow and minimal work.
670
942
 
671
- ```ts
672
- accumulateProfilingDuration(
673
- state: EngineState,
674
- category: string,
675
- deltaMs: number,
676
- ): void
677
- ```
943
+ Design Rationale:
944
+ - Allocation-free (no ephemeral objects or arrays created)
945
+ - Best-effort error handling (swallow all dashboard/flush errors)
946
+ - Stable argument order for dashboard implementations
947
+ - Async support for cooperative yielding to host scheduler
678
948
 
679
- Accumulate a profiling duration under the supplied key when detail profiling is enabled.
949
+ Parameters:
680
950
 
681
951
  Parameters:
682
- - `state` - Shared engine state containing profiling configuration.
683
- - `category` - Profiling segment key (for example `telemetry`).
684
- - `deltaMs` - Millisecond duration to add to the accumulator.
952
+ - `maze` - - Maze instance or descriptor used by dashboard rendering.
953
+ - `result` - - Per-run result object (path, progress, telemetry, etc.).
954
+ - `network` - - Network or genome object that should be visualised.
955
+ - `completedGenerations` - - Integer index of the completed generation.
956
+ - `neat` - - NEAT manager instance (context passed to the dashboard update).
957
+ - `dashboardManager` - - Optional manager exposing `update(maze, result, network, gen, neat)`.
958
+ - `flushToFrame` - - Optional async function used to yield to the host/frame scheduler; may be omitted.
685
959
 
686
- Returns: void.
960
+ Example:
687
961
 
688
- ### clearDeterministicMode
962
+ // Yield to the browser's next repaint after dashboard update:
963
+ await updateDashboardAndMaybeFlush(
964
+ maze, genResult, fittestNetwork, gen, neatInstance, dashboard, () => new Promise(r => requestAnimationFrame(r))
965
+ );
966
+
967
+ ### updateDashboardPeriodic
689
968
 
690
969
  ```ts
691
- clearDeterministicMode(
692
- state: EngineState,
693
- ): void
970
+ updateDashboardPeriodic(
971
+ maze: string[],
972
+ bestResult: IMazeRunResult | undefined,
973
+ bestNetwork: default | null,
974
+ completedGenerations: number,
975
+ neat: default,
976
+ dashboardManager: IDashboardManager | undefined,
977
+ flushToFrame: (() => Promise<void>) | undefined,
978
+ ): Promise<void>
694
979
  ```
695
980
 
696
- Disable deterministic RNG mode.
697
-
698
- Parameters:
699
- - `state` - Shared engine state where the deterministic flag is stored.
700
-
701
- Returns: void.
981
+ Periodic dashboard update used when the engine wants to refresh a non-primary
982
+ dashboard view (for example background or periodic reporting). This helper is
983
+ intentionally small, allocation-light and best-effort: dashboard errors are
984
+ swallowed so the evolution loop cannot be interrupted by UI issues.
702
985
 
703
- ### drawFastRandom
986
+ Behavioural contract:
987
+ 1) If `dashboardManager.update` is present and callable the method invokes it with
988
+ the stable argument order: (maze, bestResult, bestNetwork, completedGenerations, neat).
989
+ 2) If `flushToFrame` is supplied the helper awaits it after the update to yield to
990
+ the host renderer (eg. requestAnimationFrame). Any exceptions raised by the
991
+ flush are swallowed.
992
+ 3) The helper avoids creating ephemeral arrays/objects and therefore does not use
993
+ typed-array scratch buffers here — there is no hot numerical work to pool. Other
994
+ engine helpers already reuse class-level scratch buffers where appropriate.
704
995
 
705
- ```ts
706
- drawFastRandom(
707
- state: EngineState,
708
- parameters: RngCacheParameters,
709
- ): number
710
- ```
996
+ Design Rationale:
997
+ - Fast-guard early when update cannot be performed
998
+ - Best-effort error handling (swallow all exceptions)
999
+ - Preserves dashboard `this` binding with `.call()`
1000
+ - Minimal allocations (no scratch buffers needed)
711
1001
 
712
- Generate a fast uniform random sample using the shared RNG cache.
1002
+ Steps / inline intent:
1003
+ 1. Fast-guard when an update cannot be performed (missing manager, update method,
1004
+ or missing content to visualise).
1005
+ 2. Call the dashboard update in a try/catch to preserve best-effort semantics.
1006
+ 3. Optionally await the provided `flushToFrame` function to yield to the host.
713
1007
 
714
1008
  Parameters:
715
- - `state` - Shared engine state providing the RNG cache and seed.
716
- - `parameters` - Congruential parameters forwarded to {@link ensureRngCacheBatch} .
717
1009
 
718
- Returns: Uniform sample in the range [0, 1).
1010
+ Parameters:
1011
+ - `maze` - - Maze descriptor passed to the dashboard renderer.
1012
+ - `bestResult` - - Best-run result object used for display (may be falsy when not present).
1013
+ - `bestNetwork` - - Network or genome object to visualise (may be falsy when not present).
1014
+ - `completedGenerations` - - Completed generation index (number).
1015
+ - `neat` - - NEAT manager instance (passed through to dashboard update).
1016
+ - `dashboardManager` - - Optional manager exposing `update(maze, result, network, gen, neat)`.
1017
+ - `flushToFrame` - - Optional async function used to yield to the host/frame scheduler
1018
+ (for example: `() => new Promise(r => requestAnimationFrame(r))`).
719
1019
 
720
1020
  Example:
721
1021
 
722
- const sample = drawFastRandom(sharedState, rngParameters);
1022
+ // Safe periodic update and yield to next frame
1023
+ await updateDashboardPeriodic(
1024
+ maze, result, network, gen, neatInstance, dashboard, () => new Promise(r => requestAnimationFrame(r))
1025
+ );
723
1026
 
724
- ### getProfilingAccumulators
1027
+ ## evolutionEngine/evolutionEngine.services.ts
1028
+
1029
+ ### applyEvolutionEngineRingState
725
1030
 
726
1031
  ```ts
727
- getProfilingAccumulators(
728
- state: EngineState,
729
- ): Record<string, number>
1032
+ applyEvolutionEngineRingState(
1033
+ updatedRingState: LogitsRingState,
1034
+ ): void
730
1035
  ```
731
1036
 
732
- Provide direct access to the profiling accumulator map.
1037
+ Apply the latest logits-ring runtime values returned by the evolution loop.
733
1038
 
734
1039
  Parameters:
735
- - `state` - Shared engine state containing the profiling accumulators.
736
-
737
- Returns: Mutable record of profiling accumulators keyed by category name.
1040
+ - `updatedRingState` - - New ring-capacity, shared-mode, and write-cursor values.
738
1041
 
739
- ### isDeterministicModeEnabled
1042
+ ### configureEvolutionEngineToggles
740
1043
 
741
1044
  ```ts
742
- isDeterministicModeEnabled(
743
- state: EngineState,
744
- ): boolean
1045
+ configureEvolutionEngineToggles(
1046
+ reducedTelemetry: boolean,
1047
+ telemetryMinimal: boolean,
1048
+ disableBaldwinPhase: boolean,
1049
+ ): void
745
1050
  ```
746
1051
 
747
- Retrieve the deterministic RNG flag stored on the shared engine state.
1052
+ Apply telemetry and Baldwin-phase toggles derived from one normalized run request.
748
1053
 
749
1054
  Parameters:
750
- - `state` - Shared engine state where the deterministic flag is stored.
751
-
752
- Returns: True when deterministic mode is active.
1055
+ - `reducedTelemetry` - - When true, keep only the essential telemetry metrics.
1056
+ - `telemetryMinimal` - - When true, disable verbose telemetry capture.
1057
+ - `disableBaldwinPhase` - - When true, skip the Baldwin refinement stage.
753
1058
 
754
- ### isProfilingDetailsEnabled
1059
+ ### getEvolutionEngineFacadeRuntimeState
755
1060
 
756
1061
  ```ts
757
- isProfilingDetailsEnabled(
758
- state: EngineState,
759
- ): boolean
1062
+ getEvolutionEngineFacadeRuntimeState(): LogitsRingState
760
1063
  ```
761
1064
 
762
- Determine whether detailed profiling accumulation is active.
763
-
764
- Parameters:
765
- - `state` - Shared engine state providing profiling configuration.
1065
+ Read the mutable facade-owned logits-ring runtime state.
766
1066
 
767
- Returns: True when detail profiling is enabled.
1067
+ Returns: Current ring-capacity, shared-mode, and write-cursor state.
768
1068
 
769
- ### profilingStartTimestamp
1069
+ ### getEvolutionEngineMaxLogitsRingCapacity
770
1070
 
771
1071
  ```ts
772
- profilingStartTimestamp(): number
1072
+ getEvolutionEngineMaxLogitsRingCapacity(): number
773
1073
  ```
774
1074
 
775
- Return a profiling start timestamp that mirrors the historic `#PROFILE_T0` helper.
1075
+ Read the hard maximum ring capacity used by the public facade.
776
1076
 
777
- Returns: Millisecond timestamp representing the profiling start time.
1077
+ Returns: Maximum ring capacity allowed for logits telemetry.
778
1078
 
779
- ### readHighResolutionTime
1079
+ ### getEvolutionEngineSharedState
780
1080
 
781
1081
  ```ts
782
- readHighResolutionTime(): number
1082
+ getEvolutionEngineSharedState(): EngineState
783
1083
  ```
784
1084
 
785
- Obtain a monotonic-ish timestamp suitable for profiling.
786
-
787
- Returns: Timestamp in milliseconds, preferring `performance.now` when available.
1085
+ Return the shared engine singleton used by extracted engine modules.
788
1086
 
789
- Example:
1087
+ The public facade now depends on the same owner as the rest of the engine
1088
+ boundary instead of creating a private duplicate singleton.
790
1089
 
791
- const timestamp = readHighResolutionTime();
1090
+ Returns: Shared engine state singleton.
792
1091
 
793
- ### resolveRngParameters
1092
+ ### resetEvolutionEngineRingState
794
1093
 
795
1094
  ```ts
796
- resolveRngParameters(): RngCacheParameters
1095
+ resetEvolutionEngineRingState(): void
797
1096
  ```
798
1097
 
799
- Provide cached congruential parameters used by the shared fast RNG helper.
1098
+ Reset the facade-owned logits-ring runtime state to its baseline defaults.
800
1099
 
801
- Returns: Immutable {@link RngCacheParameters} reference reused across draws.
1100
+ ## evolutionEngine/sampling.ts
802
1101
 
803
- Example:
1102
+ Sampling and history helpers extracted from the ASCII maze evolution façade.
804
1103
 
805
- const parameters = resolveRngParameters();
1104
+ Responsibilities:
1105
+ 1. Provide allocation-light array sampling utilities that reuse the shared `EngineState` scratch pools.
1106
+ 2. Expose history helpers that mirror the façade behaviour while keeping pooled buffers centralised.
1107
+ 3. Centralise RNG parameter resolution for sampling paths to keep behaviour deterministic under shared state.
806
1108
 
807
- ### setDeterministicMode
1109
+ These helpers look small, but they sit under several hot paths. The main job
1110
+ is not "random choice" in the abstract; it is random choice without quietly
1111
+ reintroducing per-generation array churn into long-running experiments.
1112
+
1113
+ ### getTail
808
1114
 
809
1115
  ```ts
810
- setDeterministicMode(
1116
+ getTail(
811
1117
  state: EngineState,
812
- seed: number | undefined,
813
- ): void
1118
+ source: T[] | undefined,
1119
+ count: number,
1120
+ ): T[]
814
1121
  ```
815
1122
 
816
- Enable deterministic RNG mode and optionally reseed the shared RNG state.
1123
+ Extract the last `count` items from `source` into the shared tail buffer.
817
1124
 
818
- Parameters:
819
- - `state` - Shared engine state where the deterministic flag is stored.
820
- - `seed` - Optional deterministic seed. Finite numeric inputs are normalised to u32.
1125
+ Steps:
1126
+ 1. Validate the source array and clamp the requested tail length.
1127
+ 2. Grow the pooled tail buffer to the next power of two when necessary.
1128
+ 3. Copy the suffix into the pooled buffer and trim its logical length.
821
1129
 
822
- Returns: void.
1130
+ Parameters:
1131
+ - `state` - Shared engine state providing the tail history buffer.
1132
+ - `source` - Source array reference.
1133
+ - `count` - Number of trailing items requested.
823
1134
 
824
- ## evolutionEngine/scratchPools.ts
1135
+ Returns: Pooled array containing the requested tail slice.
825
1136
 
826
- Scratch pool management helpers extracted from the maze evolution façade.
1137
+ Example:
827
1138
 
828
- These utilities centralise the logic that grows and shrinks pooled buffers attached to the
829
- shared {@link EngineState}. They keep the façade lean by encapsulating heuristics for logits
830
- ring sizing, telemetry scratch sizing, and connection flag pooling.
1139
+ const recent = getTail(sharedState, telemetryLog, 40);
831
1140
 
832
- ### allocateLogitsRing
1141
+ ### pushHistory
833
1142
 
834
1143
  ```ts
835
- allocateLogitsRing(
836
- capacity: number,
837
- actionDimension: number,
838
- ): Float32Array<ArrayBufferLike>[]
1144
+ pushHistory(
1145
+ buffer: T[] | undefined,
1146
+ value: T,
1147
+ maxLength: number,
1148
+ ): T[]
839
1149
  ```
840
1150
 
841
- Build a non-shared logits ring sized to the requested capacity.
1151
+ Proxy to {@link MazeUtils.pushHistory} for consistency with the façade API.
842
1152
 
843
1153
  Parameters:
844
- - `capacity` - Number of rows to create.
845
- - `actionDimension` - Number of logits stored per row.
1154
+ - `buffer` - Existing history buffer (may be undefined).
1155
+ - `value` - Value to append to the buffer.
1156
+ - `maxLength` - Maximum allowed buffer length.
846
1157
 
847
- Returns: Array of typed rows representing the ring buffer.
1158
+ Returns: Updated history buffer with `value` appended and trimmed to `maxLength`.
848
1159
 
849
- ### ensureConnFlagsCapacity
1160
+ Example:
1161
+
1162
+ const history = pushHistory(existingHistory, snapshot, 20);
1163
+
1164
+ ### sampleArray
850
1165
 
851
1166
  ```ts
852
- ensureConnFlagsCapacity(
1167
+ sampleArray(
853
1168
  state: EngineState,
854
- minimumCapacity: number,
855
- ): Int8Array<ArrayBufferLike> | null
1169
+ source: T[],
1170
+ sampleCount: number,
1171
+ ): T[]
856
1172
  ```
857
1173
 
858
- Ensure the recurrent/gated detection bitmap has sufficient capacity.
859
-
860
- Parameters:
861
- - `state` - Shared engine state containing the pooled bitmap.
862
- - `minimumCapacity` - Minimum number of entries required by the caller.
1174
+ Sample `sampleCount` items (with replacement) from `source` into the pooled scratch buffer.
863
1175
 
864
- Returns: Int8Array bitmap or null when allocation failed.
1176
+ This helper is for callers that want an ephemeral array view immediately. The
1177
+ returned array reuses shared scratch storage, so callers should copy it first
1178
+ if they need the contents to survive another helper call.
865
1179
 
866
- ### ensureLogitsRingCapacity
1180
+ Steps:
1181
+ 1. Validate the input array and normalise `sampleCount` to an integer.
1182
+ 2. Grow the shared `sampleResultBuffer` to the next power of two when capacity is insufficient.
1183
+ 3. Fill the pooled buffer using the shared fast RNG and truncate the logical length to `sampleCount`.
867
1184
 
868
- ```ts
869
- ensureLogitsRingCapacity(
870
- capacityRequest: LogitsRingCapacityOptions,
871
- ): LogitsRingCapacityResult
872
- ```
1185
+ Parameters:
1186
+ - `state` - Shared engine state providing pooled scratch buffers.
1187
+ - `source` - Source array to sample from.
1188
+ - `sampleCount` - Number of samples to draw (with replacement).
873
1189
 
874
- Grow or shrink the logits ring to accomodate the requested recent-step budget.
1190
+ Returns: Pooled ephemeral array containing the sampled elements.
875
1191
 
876
- Parameters:
877
- - `capacityRequest` - Parameters describing the requested resize.
1192
+ Example:
878
1193
 
879
- Returns: The resulting capacity and whether shared-array mode stayed enabled.
1194
+ const picks = sampleArray(sharedState, population, 16);
1195
+ const safeCopy = [...picks];
880
1196
 
881
- ### ensureScratchCapacity
1197
+ ### sampleIntoScratch
882
1198
 
883
1199
  ```ts
884
- ensureScratchCapacity(
1200
+ sampleIntoScratch(
885
1201
  state: EngineState,
886
- request: ScratchCapacityRequest,
887
- ): void
1202
+ source: T[],
1203
+ sampleCount: number,
1204
+ ): number
888
1205
  ```
889
1206
 
890
- Grow pooled scratch buffers to conservative sizes for the upcoming run.
1207
+ Sample up to `sampleCount` items (with replacement) into the shared `samplePool` buffer.
1208
+
1209
+ Compared with `sampleArray`, this form is for callers that only need a count
1210
+ plus access to the pooled buffer on `state.scratch.samplePool`. That avoids
1211
+ one more logical array object on the hottest paths.
1212
+
1213
+ Steps:
1214
+ 1. Validate inputs and ensure the pooled buffer exists.
1215
+ 2. Grow the buffer by powers of two when more capacity is required.
1216
+ 3. Fill the buffer with random selections and return the number of items written.
891
1217
 
892
1218
  Parameters:
893
- - `state` - Shared engine state exposing scratch buffers.
894
- - `request` - Sizing request describing the evolution workload.
1219
+ - `state` - Shared engine state providing the pooled buffer.
1220
+ - `source` - Source array to sample from.
1221
+ - `sampleCount` - Number of elements requested (floored to an integer).
895
1222
 
896
- ### initialiseSharedLogitsRing
1223
+ Returns: Number of sampled elements written into the pooled buffer.
1224
+
1225
+ Example:
1226
+
1227
+ const sampled = sampleIntoScratch(sharedState, population, 24);
1228
+ for (let index = 0; index < sampled; index++) {
1229
+ processGenome(sharedState.scratch.samplePool[index]);
1230
+ }
1231
+
1232
+ ### sampleSegmentIntoScratch
897
1233
 
898
1234
  ```ts
899
- initialiseSharedLogitsRing(
1235
+ sampleSegmentIntoScratch(
900
1236
  state: EngineState,
901
- config: SharedLogitsConfig,
902
- ): boolean
1237
+ source: T[],
1238
+ segmentStart: number,
1239
+ sampleCount: number,
1240
+ ): number
903
1241
  ```
904
1242
 
905
- Attempt to allocate SharedArrayBuffer-backed logits ring storage.
1243
+ Sample from a suffix of `source` starting at `segmentStart` into the pooled buffer.
1244
+
1245
+ The common use case is "sample from the non-elite tail" or some later slice
1246
+ of a population without first allocating `source.slice(segmentStart)`.
1247
+
1248
+ Steps:
1249
+ 1. Clamp indices and ensure there is a non-empty segment.
1250
+ 2. Ensure the pooled buffer can store the requested sample count.
1251
+ 3. Fill the buffer via the shared RNG, returning the number of items written.
906
1252
 
907
1253
  Parameters:
908
- - `state` - Shared engine state containing the logits buffers.
909
- - `config` - Shared ring configuration (capacity and action dimension).
1254
+ - `state` - Shared engine state providing pooled sampling buffers.
1255
+ - `source` - Source array to sample from.
1256
+ - `segmentStart` - Inclusive start index of the segment.
1257
+ - `sampleCount` - Number of items to sample (with replacement).
910
1258
 
911
- Returns: true when shared mode was activated successfully.
1259
+ Returns: Number of elements written into the pooled buffer.
912
1260
 
913
- ### LogitsRingCapacityOptions
1261
+ Example:
914
1262
 
915
- Shape describing the parameters used when ensuring the logits ring capacity.
1263
+ const written = sampleSegmentIntoScratch(sharedState, population, elitismCount, 12);
1264
+ const genome = sharedState.scratch.samplePool[0];
916
1265
 
917
- ### LogitsRingCapacityResult
1266
+ ## evolutionEngine/engineState.ts
918
1267
 
919
- Result returned after resizing the logits ring.
1268
+ Centralised shared state for the ASCII maze evolution façade.
920
1269
 
921
- ### maybeShrinkScratch
1270
+ Responsibilities:
1271
+ 1. Define the scratch-buffer schema consumed by telemetry, population, and inspection helpers.
1272
+ 2. Expose runtime toggle state (`EngineToggleState`) that drives optional phases and telemetry density.
1273
+ 3. Provide factory and maintenance helpers (`createEngineState`, `initialiseTelemetryScratch`, `ensureVisitedHashCapacity`, `ensureRngCacheBatch`, `reseedRngState`) that size buffers and keep deterministic RNG state in sync.
1274
+ 4. Export the project-wide singleton `engineState` so extracted modules can share the façade’s pooled resources while still accepting injected state for testing.
1275
+
1276
+ Callers mutate the returned scratch instances in place to avoid per-generation allocations; higher-level modules should treat the helpers as the sole entry point for sizing or resetting shared buffers.
1277
+
1278
+ ### createEngineState
922
1279
 
923
1280
  ```ts
924
- maybeShrinkScratch(
925
- state: EngineState,
926
- populationSize: number,
927
- ): void
1281
+ createEngineState(): EngineState
928
1282
  ```
929
1283
 
930
- Shrink oversized scratch buffers once the population size drops significantly.
1284
+ Fabricates a new {@link EngineState} with pre-sized scratch buffers and default toggle values.
931
1285
 
932
- Parameters:
933
- - `state` - Shared engine state exposing scratch buffers.
934
- - `populationSize` - Current population size used to derive shrink heuristics.
1286
+ Returns: Initialized engine state used by the maze evolution façade.
935
1287
 
936
- ### ScratchCapacityRequest
1288
+ ### createProfilingState
937
1289
 
938
- Parameters describing the scratch sizing requirements for ensureScratchCapacity.
1290
+ ```ts
1291
+ createProfilingState(): EngineProfilingState
1292
+ ```
939
1293
 
940
- ### SharedLogitsConfig
1294
+ Fabricates the profiling state bundle consumed by timing helpers.
941
1295
 
942
- Parameters passed when attempting to initialise the shared logits ring buffers.
1296
+ Returns: Profiling configuration and accumulator state.
943
1297
 
944
- ## evolutionEngine/setupHelpers.ts
1298
+ Example:
945
1299
 
946
- Setup helpers for the ASCII maze evolution engine.
1300
+ const profiling = createProfilingState();
1301
+ console.log(profiling.detailsEnabled); // false unless env flag set
947
1302
 
948
- Responsibilities:
949
- - Create cooperative frame-yielding helpers for async evolution loops.
950
- - Initialize Node.js persistence helpers when available.
951
- - Build resilient logging writers with dashboard and console fallbacks.
1303
+ ### createScratchState
952
1304
 
953
- ### DashboardManagerLike
1305
+ ```ts
1306
+ createScratchState(): EngineScratchState
1307
+ ```
954
1308
 
955
- Dashboard manager shape for logging (optional log function).
1309
+ Fabricates the default pooled scratch buffers shared by the evolution façade.
956
1310
 
957
- ### FilesystemModule
1311
+ Returns: Scratch bundle sized for the baseline maze curriculum workload.
958
1312
 
959
- Minimal filesystem module shape for type safety (Node.js fs module subset).
1313
+ Example:
960
1314
 
961
- ### initPersistence
1315
+ const scratch = createScratchState();
1316
+ console.log(scratch.exps.length); // 4
1317
+
1318
+ ### createToggleState
962
1319
 
963
1320
  ```ts
964
- initPersistence(
965
- persistDir: string | undefined,
966
- ): { fs: FilesystemModule | null; path: PathModule | null; }
1321
+ createToggleState(): EngineToggleState
967
1322
  ```
968
1323
 
969
- Initialize persistence helpers (Node `fs` & `path`) when available and ensure the target
970
- directory exists. This helper intentionally does nothing in browser-like hosts.
1324
+ Fabricates the default runtime toggle bundle for the evolution façade.
971
1325
 
972
- Steps:
973
- 1. Detect whether a Node-like `require` is available and attempt to load `fs` and `path`
974
- 2. If both modules are available and `persistDir` is provided, ensure the directory exists
975
- by creating it recursively when necessary
976
- 3. Return an object containing the (possibly null) `{ fs, path }` references for callers to use
1326
+ Returns: Toggle state with all optional phases enabled.
977
1327
 
978
- Notes:
979
- - This helper deliberately performs defensive checks and swallows synchronous errors because
980
- persistence is optional in many host environments (tests, browser demos)
981
- - No large allocations are performed here; the function returns lightweight references
1328
+ Example:
982
1329
 
983
- Parameters:
984
- - `persistDir` - - Optional directory to ensure exists. If falsy, no filesystem mutations are attempted.
1330
+ const toggles = createToggleState();
1331
+ console.log(toggles.reducedTelemetry); // false
985
1332
 
986
- Returns: Object with `{ fs, path }` where each value may be `null` when unavailable.
1333
+ ### DEFAULT_RNG_CACHE_BATCH_SIZE
987
1334
 
988
- Example:
1335
+ Default RNG cache batch size mirroring the façade constant.
989
1336
 
990
- const { fs, path } = initPersistence('./snapshots');
991
- if (fs && path) {
992
- fs.writeFileSync(path.join(dir, 'snapshot.json'), data);
993
- }
1337
+ ### DEFAULT_VISITED_HASH_LOAD_FACTOR
994
1338
 
995
- ### isPauseRequested
1339
+ Default load factor target for the visited coordinate hash table.
1340
+
1341
+ ### EngineProfilingState
1342
+
1343
+ Aggregated profiling configuration and accumulators shared across the evolution run.
1344
+
1345
+ ### EngineScratchState
1346
+
1347
+ Centralised shared state for the ASCII maze evolution façade.
1348
+
1349
+ Responsibilities:
1350
+ 1. Define the scratch-buffer schema consumed by telemetry, population, and inspection helpers.
1351
+ 2. Expose runtime toggle state (`EngineToggleState`) that drives optional phases and telemetry density.
1352
+ 3. Provide factory and maintenance helpers (`createEngineState`, `initialiseTelemetryScratch`, `ensureVisitedHashCapacity`, `ensureRngCacheBatch`, `reseedRngState`) that size buffers and keep deterministic RNG state in sync.
1353
+ 4. Export the project-wide singleton `engineState` so extracted modules can share the façade’s pooled resources while still accepting injected state for testing.
1354
+
1355
+ Callers mutate the returned scratch instances in place to avoid per-generation allocations; higher-level modules should treat the helpers as the sole entry point for sizing or resetting shared buffers.
1356
+
1357
+ ### EngineState
1358
+
1359
+ Shared engine state instance combining pooled scratch buffers with toggle flags.
1360
+
1361
+ ### EngineToggleState
1362
+
1363
+ Runtime switches that adjust telemetry verbosity and optional training phases.
1364
+
1365
+ ### ensureRngCacheBatch
996
1366
 
997
1367
  ```ts
998
- isPauseRequested(
999
- hostAdapter: EvolutionHostAdapter | undefined,
1000
- ): boolean
1368
+ ensureRngCacheBatch(
1369
+ parameters: RngCacheParameters,
1370
+ state: EngineState,
1371
+ ): RngCacheHandles
1001
1372
  ```
1002
1373
 
1003
- Read host-controlled pause state without letting host errors break the engine.
1374
+ Ensure the RNG cache contains fresh samples before consumption.
1004
1375
 
1005
1376
  Parameters:
1006
- - `hostAdapter` - - Optional host adapter implementing pause polling.
1377
+ - `parameters` - Congruential generator parameters and cache batch size.
1378
+ - `state` - Optional engine state container (defaults to the shared singleton).
1007
1379
 
1008
- Returns: True when the host asks the engine to remain paused.
1380
+ Returns: Handles exposing the cache and its batch size.
1009
1381
 
1010
- ### makeFlushToFrame
1382
+ ### ensureVisitedHashCapacity
1011
1383
 
1012
1384
  ```ts
1013
- makeFlushToFrame(
1014
- hostAdapter: EvolutionHostAdapter | undefined,
1015
- ): () => Promise<void>
1385
+ ensureVisitedHashCapacity(
1386
+ targetEntryCount: number,
1387
+ state: EngineState,
1388
+ ): VisitedHashScratchHandles
1016
1389
  ```
1017
1390
 
1018
- Create a cooperative frame-yielding function used by the evolution loop.
1019
-
1020
- Behaviour:
1021
- - Prefers `requestAnimationFrame` when available (browser hosts)
1022
- - Falls back to `setImmediate` when available (Node) or `setTimeout(...,0)` otherwise
1023
- - Respects an optional host adapter pause callback by polling between ticks without busy-waiting
1024
- - Resolves once a single new frame or tick is available and the host is not paused
1391
+ Ensure the visited-coordinate hash table can store the requested entry count.
1025
1392
 
1026
- Steps:
1027
- 1. Choose the preferred tick function based on the host runtime
1028
- 2. When called, await the preferred tick; if the host adapter reports pause, poll again after the tick
1029
- 3. Resolve once a tick passed while not paused
1393
+ Growth policy:
1394
+ 1. Tables expand geometrically (powers of two) while respecting the configured load factor.
1395
+ 2. When the existing table is large enough, it is cleared in place to preserve the allocation.
1396
+ 3. Load factors outside the (0,1) range fall back to {@link DEFAULT_VISITED_HASH_LOAD_FACTOR}.
1030
1397
 
1031
1398
  Parameters:
1032
- - `hostAdapter` - - Optional host adapter that owns cooperative pause state.
1399
+ - `targetEntryCount` - Expected number of unique coordinates to store.
1400
+ - `state` - Optional engine state container (defaults to the shared singleton).
1033
1401
 
1034
- Returns: A function that yields cooperatively to the next animation frame / tick.
1402
+ Returns: Cleared table reference plus the slot mask for probing loops.
1035
1403
 
1036
1404
  Example:
1037
1405
 
1038
- const flushToFrame = makeFlushToFrame();
1039
- await flushToFrame(); // yields to next frame/tick
1406
+ const { table, slotMask } = ensureVisitedHashCapacity(256);
1040
1407
 
1041
- ### makeSafeWriter
1408
+ ### initialiseTelemetryScratch
1042
1409
 
1043
1410
  ```ts
1044
- makeSafeWriter(
1045
- dashboardManager: DashboardManagerLike | undefined,
1046
- ): (msg: string) => void
1411
+ initialiseTelemetryScratch(
1412
+ request: TelemetryScratchRequest,
1413
+ state: EngineState,
1414
+ ): TelemetryScratchHandles
1047
1415
  ```
1048
1416
 
1049
- Build a resilient writer that attempts to write to Node stdout, then a provided
1050
- dashboard logger, and finally `console.log` as a last resort.
1051
-
1052
- Steps:
1053
- 1. If Node `process.stdout.write` is available, use it (no trailing newline forced)
1054
- 2. Else if `dashboardManager.logFunction` exists, call it
1055
- 3. Else fall back to `console.log` and trim the message
1417
+ Ensure telemetry-related scratch buffers allocate enough capacity for upcoming work.
1056
1418
 
1057
- Notes:
1058
- - Errors are swallowed; logging must never throw and disrupt the evolution loop
1059
- - This factory is allocation-light; the returned function only creates a trimmed string
1060
- when falling back to `console.log`
1419
+ Growth strategy:
1420
+ - Float64Array pools grow geometrically (powers of two) and preserve previously written prefixes.
1421
+ - String buffers are replaced with larger arrays (also geometric) to avoid repeated reallocations.
1422
+ - Optional higher-moment buffers (M3/M4/kurtosis) are only created when {@link TelemetryScratchRequest.includeHigherMoments}
1423
+ is truthy to keep reduced telemetry lightweight.
1061
1424
 
1062
1425
  Parameters:
1063
- - `dashboardManager` - - Optional manager exposing `logFunction(msg:string)` used in some UIs.
1426
+ - `request` - Capacity hints for upcoming telemetry computations.
1427
+ - `state` - Optional engine state target (defaults to the singleton {@link engineState} ).
1064
1428
 
1065
- Returns: A function accepting a single string message to write.
1429
+ Returns: Handles referencing the ensured scratch buffers for immediate use.
1066
1430
 
1067
- Example:
1431
+ ### reseedRngState
1068
1432
 
1069
- const safeWrite = makeSafeWriter(dashboardManager);
1070
- safeWrite('[INFO] Generation 42 complete\n');
1433
+ ```ts
1434
+ reseedRngState(
1435
+ seed: number,
1436
+ state: EngineState,
1437
+ ): number
1438
+ ```
1071
1439
 
1072
- ### PathModule
1440
+ Persist a new RNG seed and schedule a cache refill on the next draw.
1073
1441
 
1074
- Minimal path module shape for type safety (Node.js path module subset).
1442
+ Parameters:
1443
+ - `seed` - Raw numeric seed requested by deterministic mode.
1444
+ - `state` - Optional engine state container (defaults to the shared singleton).
1075
1445
 
1076
- ## evolutionEngine/evolutionLoop.ts
1446
+ Returns: Unsigned seed stored in scratch state for diagnostics.
1077
1447
 
1078
- Evolution Loop Module
1448
+ ### RngCacheHandles
1079
1449
 
1080
- Purpose:
1081
- -------
1082
- Provides utilities for running the main NEAT evolution loop, including
1083
- generation orchestration, cancellation checking, and loop helper preparation.
1450
+ Handles returned after ensuring the RNG cache is ready for consumption.
1084
1451
 
1085
- This module encapsulates:
1086
- - Cancellation detection (AbortSignal and legacy cancellation API)
1087
- - Loop helper preparation (frame flushing, persistence, logging)
1088
- - Generation execution and orchestration
1089
- - Stop condition evaluation
1452
+ ### RngCacheParameters
1090
1453
 
1091
- ES2023 Policy:
1092
- -------------
1093
- - Uses nullish coalescing `??` and optional chaining `?.`
1094
- - Descriptive variable names (no short identifiers)
1095
- - Async/await for generation loops
1096
- - Best-effort error handling (swallow non-fatal errors)
1454
+ Parameters controlling the RNG cache refill process.
1097
1455
 
1098
- ### checkCancellation
1456
+ ### setBaldwinPhaseDisabledFlag
1099
1457
 
1100
1458
  ```ts
1101
- checkCancellation(
1102
- options: EvolutionOptions,
1103
- bestResult: IMazeRunResult | undefined,
1104
- ): string | undefined
1459
+ setBaldwinPhaseDisabledFlag(
1460
+ isDisabled: boolean,
1461
+ ): void
1105
1462
  ```
1106
1463
 
1107
- Inspect cooperative cancellation sources and annotate the provided result when cancelled.
1464
+ Enable or disable the Baldwin-phase warm-start pipeline.
1108
1465
 
1109
- This function checks for cancellation from two sources in priority order:
1110
- 1) Legacy cancellation object with `isCancelled()` method
1111
- 2) Standard AbortSignal with `aborted` property
1466
+ Parameters:
1467
+ - `isDisabled` - When true, skips the Lamarckian training stage.
1112
1468
 
1113
- Design Rationale:
1114
- - Allocation-free (uses only short-lived local references)
1115
- - Best-effort error handling (swallow exceptions to avoid disrupting loop)
1116
- - Sets `exitReason` on result object for caller inspection
1117
- - Safe to call on hot paths (no scratch buffers or pooling needed)
1469
+ ### setReducedTelemetryFlag
1118
1470
 
1119
- Cancellation Priority:
1120
- 1. Check `options.cancellation.isCancelled()` (legacy API)
1121
- 2. Check `options.signal.aborted` (standard AbortSignal)
1122
- 3. Return undefined if no cancellation detected
1471
+ ```ts
1472
+ setReducedTelemetryFlag(
1473
+ isEnabled: boolean,
1474
+ ): void
1475
+ ```
1123
1476
 
1124
- Parameters:
1477
+ Toggle the reduced telemetry mode.
1125
1478
 
1126
1479
  Parameters:
1127
- - `options` - - Optional run configuration which may contain `cancellation` and/or `signal`
1128
- - `bestResult` - - Optional mutable result object that will be annotated with `exitReason`
1480
+ - `isEnabled` - When true, trims telemetry to the essential metrics only.
1129
1481
 
1130
- Returns: A reason string ('cancelled' | 'aborted') when cancellation is detected, otherwise `undefined`
1482
+ ### setTelemetryMinimalFlag
1131
1483
 
1132
- Examples:
1484
+ ```ts
1485
+ setTelemetryMinimalFlag(
1486
+ isMinimal: boolean,
1487
+ ): void
1488
+ ```
1133
1489
 
1134
- // Check cancellation before starting next generation
1135
- const cancelReason = checkCancellation(opts, runResult);
1136
- if (cancelReason) return cancelReason;
1490
+ Toggle the minimal telemetry mode for JSON output.
1137
1491
 
1138
- // Check cancellation without result annotation
1139
- if (checkCancellation(opts)) {
1140
- console.log('User requested cancellation');
1141
- break;
1142
- }
1492
+ Parameters:
1493
+ - `isMinimal` - When true, disables verbose telemetry capture.
1143
1494
 
1144
- ### checkStopConditions
1495
+ ### TelemetryScratchHandles
1145
1496
 
1146
- ```ts
1147
- checkStopConditions(
1148
- bestResult: IMazeRunResult | undefined,
1149
- bestNetwork: default | null,
1150
- maze: string[],
1151
- completedGenerations: number,
1152
- neat: default,
1153
- dashboardManager: IDashboardManager | undefined,
1154
- flushToFrame: () => Promise<void>,
1155
- hostAdapter: EvolutionHostAdapter | undefined,
1156
- minProgressToPass: number,
1157
- autoPauseOnSolve: boolean,
1158
- stopOnlyOnSolve: boolean,
1159
- stagnantGenerations: number,
1160
- maxStagnantGenerations: number,
1161
- maxGenerations: number,
1162
- ): Promise<string | undefined>
1163
- ```
1497
+ Collection of scratch buffers handed back after initialisation for convenience.
1164
1498
 
1165
- Inspect common termination conditions and perform minimal, best-effort side-effects.
1499
+ ### TelemetryScratchRequest
1166
1500
 
1167
- This function checks three canonical stop reasons in priority order:
1168
- 1) **Solved**: Best result achieves minimum progress threshold
1169
- 2) **Stagnation**: No improvement for maxStagnantGenerations
1170
- 3) **MaxGenerations**: Absolute generation cap reached
1501
+ Configuration describing which telemetry scratch buffers require capacity guarantees.
1171
1502
 
1172
- Design Rationale:
1173
- - Allocation-light (uses only local references)
1174
- - Best-effort error handling (all side effects swallowed)
1175
- - Safe to call frequently on hot paths
1176
- - Updates dashboard and yields to host when stopping
1177
- - Emits optional 'asciiMazeSolved' event on solve (browser only)
1503
+ ### VisitedHashScratchHandles
1178
1504
 
1179
- Side Effects (Best-Effort):
1180
- - Updates dashboard manager when stopping
1181
- - Awaits flushToFrame to yield to host
1182
- - Reports stop events through the optional host adapter
1183
- - Annotates `bestResult.exitReason` with canonical reason string
1505
+ Handles exposed after ensuring the visited-coordinate hash table capacity.
1184
1506
 
1185
- Parameters:
1507
+ ## evolutionEngine/rngAndTiming.ts
1186
1508
 
1187
- Parameters:
1188
- - `bestResult` - - Mutable run summary object (may be mutated with `exitReason`)
1189
- - `bestNetwork` - - Network object associated with the best result (for dashboard)
1190
- - `maze` - - Maze descriptor passed to dashboard updates/events
1191
- - `completedGenerations` - - Current generation index (integer)
1192
- - `neat` - - NEAT driver instance (passed to dashboard update)
1193
- - `dashboardManager` - - Optional manager exposing `update(maze, result, network, gen, neat)`
1194
- - `flushToFrame` - - Async function to yield to host renderer (e.g. requestAnimationFrame)
1195
- - `hostAdapter` - - Optional host adapter that owns host-side stop behavior
1196
- - `minProgressToPass` - - Numeric threshold to consider a run 'solved'
1197
- - `autoPauseOnSolve` - - When truthy request host-side pause handling on solve
1198
- - `stopOnlyOnSolve` - - When true ignore stagnation/maxGenerations as stop reasons
1199
- - `stagnantGenerations` - - Current count of stagnant generations observed
1200
- - `maxStagnantGenerations` - - Max allowed stagnant generations before stopping
1201
- - `maxGenerations` - - Absolute generation cap after which the run stops
1509
+ RNG and timing helpers extracted from the evolution façade.
1202
1510
 
1203
- Returns: A canonical reason string ('solved'|'stagnation'|'maxGenerations') when stopping, otherwise `undefined`
1511
+ ### accumulateProfilingDuration
1204
1512
 
1205
- Example:
1513
+ ```ts
1514
+ accumulateProfilingDuration(
1515
+ state: EngineState,
1516
+ category: string,
1517
+ deltaMs: number,
1518
+ ): void
1519
+ ```
1206
1520
 
1207
- // Check stop conditions after each generation
1208
- const reason = await checkStopConditions(
1209
- bestResult, bestNet, maze, gen, neat, dashboard, flush,
1210
- 95, true, false, stagnant, 500, 10000
1211
- );
1212
- if (reason) {
1213
- console.log('Stopping due to', reason);
1214
- break;
1215
- }
1521
+ Accumulate a profiling duration under the supplied key when detail profiling is enabled.
1216
1522
 
1217
- ### emitProfileSummary
1523
+ Parameters:
1524
+ - `state` - Shared engine state containing profiling configuration.
1525
+ - `category` - Profiling segment key (for example `telemetry`).
1526
+ - `deltaMs` - Millisecond duration to add to the accumulator.
1527
+
1528
+ Returns: void.
1529
+
1530
+ ### clearDeterministicMode
1218
1531
 
1219
1532
  ```ts
1220
- emitProfileSummary(
1221
- engineState: EngineState,
1222
- safeWrite: (msg: string) => void,
1223
- completedGenerations: number,
1224
- totalEvolveMs: number,
1225
- totalLamarckMs: number,
1226
- totalSimMs: number,
1227
- isProfilingDetailsEnabledFn: (state: EngineState) => boolean,
1228
- getProfilingAccumulatorsFn: (state: EngineState) => ProfilingAccumulators,
1533
+ clearDeterministicMode(
1534
+ state: EngineState,
1229
1535
  ): void
1230
1536
  ```
1231
1537
 
1232
- Emit a formatted profiling summary showing average per-generation timings.
1538
+ Disable deterministic RNG mode.
1233
1539
 
1234
- This function prints a compact profiling summary with average millisecond timings
1235
- for the main evolution phases (evolve, Lamarckian training, simulation). If detailed
1236
- profiling is enabled, it also prints averages for telemetry, simplify, snapshot, and
1237
- prune operations.
1540
+ Parameters:
1541
+ - `state` - Shared engine state where the deterministic flag is stored.
1238
1542
 
1239
- Design Rationale:
1240
- - Allocation-free (reuses pooled Float64Array for intermediate calculations)
1241
- - Best-effort error handling (swallow all exceptions)
1242
- - Defensive numeric validation with divide-by-zero guards
1243
- - Conditional detailed profiling output
1543
+ Returns: void.
1244
1544
 
1245
- Calculation Steps:
1246
- 1. Validate and normalize generation count (guard divide-by-zero)
1247
- 2. Store totals in pooled scratch buffer (4-slot Float64Array)
1248
- 3. Compute per-generation averages by dividing totals by generation count
1249
- 4. Format numbers with 2 decimal places and print compact summary
1250
- 5. If detailed profiling enabled, print averaged detail line
1545
+ ### drawFastRandom
1251
1546
 
1252
- Parameters:
1547
+ ```ts
1548
+ drawFastRandom(
1549
+ state: EngineState,
1550
+ parameters: RngCacheParameters,
1551
+ ): number
1552
+ ```
1553
+
1554
+ Generate a fast uniform random sample using the shared RNG cache.
1253
1555
 
1254
1556
  Parameters:
1255
- - `engineState` - - Shared engine state with scratch buffers and profiling accumulators
1256
- - `safeWrite` - - Safe logging function (best-effort, never throws)
1257
- - `completedGenerations` - - Number of completed generations (must be > 0)
1258
- - `totalEvolveMs` - - Total milliseconds spent in NEAT evolve() calls
1259
- - `totalLamarckMs` - - Total milliseconds spent in Lamarckian training
1260
- - `totalSimMs` - - Total milliseconds spent in simulation
1261
- - `isProfilingDetailsEnabledFn` - - Function to check if detailed profiling is enabled
1262
- - `getProfilingAccumulatorsFn` - - Function to get detailed profiling accumulators
1557
+ - `state` - Shared engine state providing the RNG cache and seed.
1558
+ - `parameters` - Congruential parameters forwarded to {@link ensureRngCacheBatch} .
1263
1559
 
1264
- Example:
1560
+ Returns: Uniform sample in the range [0, 1).
1265
1561
 
1266
- // Print averages after a run that completed 100 generations
1267
- emitProfileSummary(
1268
- state, console.log, 100, 12000, 3000, 4500,
1269
- isProfilingDetailsEnabled, getProfilingAccumulators
1270
- );
1562
+ Example:
1271
1563
 
1272
- ### EvolutionLoopResult
1564
+ const sample = drawFastRandom(sharedState, rngParameters);
1273
1565
 
1274
- Evolution loop result
1566
+ ### getProfilingAccumulators
1275
1567
 
1276
- ### GenerationOutcome
1568
+ ```ts
1569
+ getProfilingAccumulators(
1570
+ state: EngineState,
1571
+ ): Record<string, number>
1572
+ ```
1277
1573
 
1278
- Generation outcome with profiling timings
1574
+ Provide direct access to the profiling accumulator map.
1279
1575
 
1280
- ### MutableMazeResult
1576
+ Parameters:
1577
+ - `state` - Shared engine state containing the profiling accumulators.
1281
1578
 
1282
- Mutable result object with exitReason field
1579
+ Returns: Mutable record of profiling accumulators keyed by category name.
1283
1580
 
1284
- ### persistSnapshotIfNeeded
1581
+ ### isDeterministicModeEnabled
1285
1582
 
1286
1583
  ```ts
1287
- persistSnapshotIfNeeded(
1288
- engineState: EngineState,
1289
- fs: { writeFileSync?: ((path: string, data: string) => void) | undefined; } | null,
1290
- pathModule: { join?: ((...paths: string[]) => string) | undefined; } | null,
1291
- persistDir: string | undefined,
1292
- persistTopK: number,
1293
- completedGenerations: number,
1294
- persistEvery: number,
1295
- neat: default,
1296
- bestFitness: number,
1297
- simplifyMode: boolean,
1298
- plateauCounter: number,
1299
- scratchSnapshotObj: Record<string, unknown>,
1300
- scratchSnapshotTop: SnapshotEntry[],
1301
- collectTelemetryTailFn: (state: EngineState, neat: default, count: number) => unknown,
1302
- getSortedIndicesByScoreFn: (state: EngineState, population: default[]) => number[],
1303
- isProfilingDetailsEnabledFn: (state: EngineState) => boolean,
1304
- profilingStartTimestampFn: (state: EngineState) => number,
1305
- accumulateProfilingDurationFn: (state: EngineState, label: string, duration: number) => void,
1306
- ): void
1584
+ isDeterministicModeEnabled(
1585
+ state: EngineState,
1586
+ ): boolean
1307
1587
  ```
1308
1588
 
1309
- Persist a population snapshot to disk at the configured interval.
1589
+ Retrieve the deterministic RNG flag stored on the shared engine state.
1310
1590
 
1311
- This function writes a JSON snapshot of the current generation state when the
1312
- generation cadence aligns with the configured persistence interval. It captures
1313
- top-K genomes, telemetry tail, and metadata for later analysis or resume.
1591
+ Parameters:
1592
+ - `state` - Shared engine state where the deterministic flag is stored.
1314
1593
 
1315
- Design Rationale:
1316
- - Best-effort semantics: swallows all errors to avoid disrupting evolution loop
1317
- - Reuses pooled scratch objects (SCRATCH_SNAPSHOT_OBJ, SCRATCH_SNAPSHOT_TOP) to minimize allocations
1318
- - Optional profiling when enabled (measures snapshot serialization time)
1319
- - Defensive validation to ensure FS/path modules are available
1594
+ Returns: True when deterministic mode is active.
1320
1595
 
1321
- Scheduling Logic:
1322
- - Only persists when `completedGenerations % persistEvery === 0`
1323
- - Requires valid fs.writeFileSync and pathModule.join functions
1324
- - Requires non-empty population
1596
+ ### isProfilingDetailsEnabled
1325
1597
 
1326
- Snapshot Structure:
1327
- - generation: completed generation index
1328
- - bestFitness: best fitness score this generation
1329
- - simplifyMode: whether simplification was active
1330
- - plateauCounter: current plateau detection counter
1331
- - timestamp: Date.now() when snapshot was created
1332
- - telemetryTail: last N telemetry entries
1333
- - top: top-K genomes with minimal metadata (idx, score, nodes, connections, json)
1598
+ ```ts
1599
+ isProfilingDetailsEnabled(
1600
+ state: EngineState,
1601
+ ): boolean
1602
+ ```
1334
1603
 
1335
- Parameters:
1604
+ Determine whether detailed profiling accumulation is active.
1336
1605
 
1337
1606
  Parameters:
1338
- - `engineState` - - Shared engine state with scratch buffers and profiling
1339
- - `fs` - - Node.js fs module or compatible FS API
1340
- - `pathModule` - - Node.js path module or compatible path API
1341
- - `persistDir` - - Directory path where snapshots should be written
1342
- - `persistTopK` - - Number of top genomes to include in snapshot
1343
- - `completedGenerations` - - Current generation index
1344
- - `persistEvery` - - Generation interval for persistence (e.g., 25 = every 25th generation)
1345
- - `neat` - - NEAT instance with population
1346
- - `bestFitness` - - Best fitness score this generation
1347
- - `simplifyMode` - - Whether simplification mode is active
1348
- - `plateauCounter` - - Current plateau counter value
1349
- - `scratchSnapshotObj` - - Pooled snapshot object (reused across calls)
1350
- - `scratchSnapshotTop` - - Pooled top-K buffer (reused across calls)
1351
- - `collectTelemetryTailFn` - - Function to collect telemetry tail from state
1352
- - `getSortedIndicesByScoreFn` - - Function to get sorted genome indices
1353
- - `isProfilingDetailsEnabledFn` - - Function to check profiling state
1354
- - `profilingStartTimestampFn` - - Function to get profiling start time
1355
- - `accumulateProfilingDurationFn` - - Function to accumulate profiling duration
1356
-
1357
- Example:
1607
+ - `state` - Shared engine state providing profiling configuration.
1358
1608
 
1359
- // Persist snapshot every 25 generations
1360
- persistSnapshotIfNeeded(
1361
- state, fs, path, './snapshots', 10, 50, 25, neat, 0.95, false, 3,
1362
- scratchObj, scratchTop, collectTail, getSorted, isProfilingEnabled, profileStart, profileAccum
1363
- );
1609
+ Returns: True when detail profiling is enabled.
1364
1610
 
1365
- ### prepareLoopHelpers
1611
+ ### profilingStartTimestamp
1366
1612
 
1367
1613
  ```ts
1368
- prepareLoopHelpers(
1369
- opts: EvolutionOptions,
1370
- scratchBundle: ScratchBundle,
1371
- ): LoopHelpers
1614
+ profilingStartTimestamp(): number
1372
1615
  ```
1373
1616
 
1374
- Build lightweight helpers used inside the evolution loop.
1375
-
1376
- This function assembles the helper utilities needed by the main evolution loop:
1377
- - Frame flushing for cooperative yielding
1378
- - Persistence handles for snapshot saving (Node.js only)
1379
- - Safe logging writer with fallback chain
1380
- - Scratch buffer warm-up (best-effort)
1617
+ Return a profiling start timestamp that mirrors the historic `#PROFILE_T0` helper.
1381
1618
 
1382
- Design Rationale:
1383
- - All initialization is best-effort (failures swallowed)
1384
- - Warms up common scratch buffers to reduce first-use allocation spikes
1385
- - Returns simple POJO with utilities (no class coupling)
1386
- - Side effects isolated to scratch bundle parameter
1619
+ Returns: Millisecond timestamp representing the profiling start time.
1387
1620
 
1388
- Scratch Buffer Warm-Up:
1389
- - samplePool: Array for population sampling
1390
- - profilingScratch: Float64Array(4) for timing accumulation
1391
- - exps: Float64Array(64) for exponential computations
1621
+ ### readHighResolutionTime
1392
1622
 
1393
- Parameters:
1623
+ ```ts
1624
+ readHighResolutionTime(): number
1625
+ ```
1394
1626
 
1395
- Parameters:
1396
- - `opts` - - Normalized run options (contains persistDir and dashboardManager)
1397
- - `scratchBundle` - - Engine scratch state for optional buffer warm-up
1627
+ Obtain a monotonic-ish timestamp suitable for profiling.
1398
1628
 
1399
- Returns: Object containing:
1400
- - flushToFrame: Async function for cooperative yielding
1401
- - fs: Node.js fs module (null in browsers)
1402
- - path: Node.js path module (null in browsers)
1403
- - safeWrite: Resilient logging function with fallback chain
1629
+ Returns: Timestamp in milliseconds, preferring `performance.now` when available.
1404
1630
 
1405
1631
  Example:
1406
1632
 
1407
- // Prepare loop helpers with scratch buffer warm-up
1408
- const { flushToFrame, fs, path, safeWrite } = prepareLoopHelpers(opts, engineState.scratch);
1409
- safeWrite('Starting evolution...\n');
1410
- await flushToFrame(); // Yield to host
1633
+ const timestamp = readHighResolutionTime();
1411
1634
 
1412
- ### runEvolutionLoop
1635
+ ### resolveRngParameters
1413
1636
 
1414
1637
  ```ts
1415
- runEvolutionLoop(
1416
- engineState: EngineState,
1417
- neat: default,
1418
- opts: EvolutionOptions,
1419
- lamarckianTrainingSet: { input: number[]; output: number[]; }[],
1420
- encodedMaze: number[][],
1421
- startPosition: readonly [number, number],
1422
- exitPosition: readonly [number, number],
1423
- distanceMap: number[][],
1424
- helpers: LoopHelpers,
1425
- doProfile: boolean,
1426
- runtimeContext: EvolutionLoopRuntimeContext,
1427
- initialRingState: LogitsRingState,
1428
- telemetryContext: EvolutionLoopTelemetryContext,
1429
- supportContext: EvolutionLoopSupportContext,
1430
- constants: TrainingConstants & { DEFAULT_TRAIN_BATCH_LARGE: number; FITTEST_TRAIN_ITERATIONS: number; TELEMETRY_MINIMAL: boolean; SATURATION_PRUNE_THRESHOLD: number; RECENT_WINDOW: number; REDUCED_TELEMETRY: boolean; DISABLE_BALDWIN: boolean; },
1431
- ): Promise<EvolutionLoopResult>
1638
+ resolveRngParameters(): RngCacheParameters
1432
1639
  ```
1433
1640
 
1434
- Internal evolution loop that executes generations until a stop condition or cancellation.
1641
+ Provide cached congruential parameters used by the shared fast RNG helper.
1435
1642
 
1436
- Behaviour & contract:
1437
- - Runs generations in a resilient, best-effort manner; internal errors are swallowed
1438
- so a single failure cannot abort the whole run.
1439
- - When `doProfile` is truthy the loop accumulates timing into a pooled Float64Array
1440
- to avoid per-iteration allocations. The pooled buffer is reused across calls.
1441
- - The helper performs side-effects (dashboard updates, persistence) in a non-fatal
1442
- fashion and yields to the host when requested via `helpers.flushToFrame`.
1643
+ Returns: Immutable {@link RngCacheParameters} reference reused across draws.
1644
+
1645
+ Example:
1646
+
1647
+ const parameters = resolveRngParameters();
1648
+
1649
+ ### setDeterministicMode
1650
+
1651
+ ```ts
1652
+ setDeterministicMode(
1653
+ state: EngineState,
1654
+ seed: number | undefined,
1655
+ ): void
1656
+ ```
1657
+
1658
+ Enable deterministic RNG mode and optionally reseed the shared RNG state.
1443
1659
 
1444
1660
  Parameters:
1445
- - `engineState` - - Shared engine state with scratch buffers and configuration
1446
- - `neat` - - NEAT driver instance used to perform evolution and mutation operations
1447
- - `opts` - - Normalised run options (produced by normalizeRunOptions)
1448
- - `lamarckianTrainingSet` - - Optional supervised training cases used for Lamarckian warm-start
1449
- - `encodedMaze` - - Encoded maze representation consumed by simulators
1450
- - `startPosition` - - Start coordinates for the simulated agent
1451
- - `exitPosition` - - Exit coordinates for the simulated agent
1452
- - `distanceMap` - - Optional precomputed distance map to speed simulation
1453
- - `helpers` - - Helper utilities: { flushToFrame, fs, path, safeWrite }
1454
- - `doProfile` - - When truthy collect and return millisecond timings in the result
1455
- - `runtimeContext` - - Shared pooled ring buffers and limits for the hot path.
1456
- - `initialRingState` - - Current mutable ring state for this run.
1457
- - `telemetryContext` - - Telemetry thresholds and verbosity switches used during simulation.
1458
- - `supportContext` - - Shared scratch buffers and helper callbacks used by the loop.
1459
- - `constants` - - Object containing all engine constants (DEFAULT_TRAIN_ERROR, etc.)
1661
+ - `state` - Shared engine state where the deterministic flag is stored.
1662
+ - `seed` - Optional deterministic seed. Finite numeric inputs are normalised to u32.
1460
1663
 
1461
- Returns: Promise resolving to an object:
1462
- { bestNetwork, bestResult, neat, completedGenerations, totalEvolveMs, totalLamarckMs, totalSimMs, updatedRingState }
1664
+ Returns: void.
1463
1665
 
1464
- Example:
1666
+ ## evolutionEngine/scratchPools.ts
1465
1667
 
1466
- const runSummary = await runEvolutionLoop(
1467
- state, neat, opts, trainingSet, maze, start, exit, distMap, helpers, true, ...
1468
- );
1668
+ Scratch pool management helpers extracted from the maze evolution façade.
1469
1669
 
1470
- ### runGeneration
1670
+ These utilities centralise the logic that grows and shrinks pooled buffers attached to the
1671
+ shared {@link EngineState}. They keep the façade lean by encapsulating heuristics for logits
1672
+ ring sizing, telemetry scratch sizing, and connection flag pooling.
1673
+
1674
+ ### allocateLogitsRing
1471
1675
 
1472
1676
  ```ts
1473
- runGeneration(
1474
- engineState: EngineState,
1475
- neat: default,
1476
- doProfile: boolean,
1477
- lamarckianIterations: number,
1478
- lamarckianTrainingSet: { input: number[]; output: number[]; }[],
1479
- lamarckianSampleSize: number | undefined,
1480
- safeWrite: (msg: string) => void,
1481
- completedGenerations: number,
1482
- dynamicPopEnabled: boolean,
1483
- dynamicPopMax: number,
1484
- plateauGenerations: number,
1485
- plateauCounter: number,
1486
- dynamicPopExpandInterval: number,
1487
- dynamicPopExpandFactor: number,
1488
- dynamicPopPlateauSlack: number,
1489
- speciesHistoryRef: number[],
1490
- emptyVec: default[],
1491
- scratchNodeIdx: Int32Array<ArrayBufferLike>,
1492
- getNodeIndicesByType: (nodes: NetworkNode[], type: string) => number,
1493
- constants: TrainingConstants,
1494
- ): Promise<GenerationOutcome>
1677
+ allocateLogitsRing(
1678
+ capacity: number,
1679
+ actionDimension: number,
1680
+ ): Float32Array<ArrayBufferLike>[]
1495
1681
  ```
1496
1682
 
1497
- Run one generation: evolve, ensure output identity, update species history, maybe expand population,
1498
- and run Lamarckian training if configured.
1683
+ Build a non-shared logits ring sized to the requested capacity.
1499
1684
 
1500
- Behaviour & contract:
1501
- - Performs a single NEAT generation step in a best-effort, non-throwing manner.
1502
- - Measures profiling durations when `doProfile` is truthy. Profiling is optional and
1503
- kept allocation-free (uses local numeric temporaries only).
1504
- - Invokes the following steps in order (each step is wrapped in a try/catch so
1505
- the evolution loop remains resilient to per-stage failures):
1506
- 1) `neat.evolve()` to produce the fittest network for this generation.
1507
- 2) `ensureOutputIdentity` to normalise output activations for consumers.
1508
- 3) `handleSpeciesHistory` to update species statistics and history.
1509
- 4) `maybeExpandPopulation` to grow the population when configured and warranted.
1510
- 5) Optional Lamarckian warm-start training via `applyLamarckianTraining`.
1511
- - The method is allocation-light and reuses engine helpers / pooled buffers where
1512
- appropriate. It never throws; internal errors are swallowed and optionally logged
1513
- via the provided `safeWrite` function.
1685
+ Parameters:
1686
+ - `capacity` - Number of rows to create.
1687
+ - `actionDimension` - Number of logits stored per row.
1688
+
1689
+ Returns: Array of typed rows representing the ring buffer.
1690
+
1691
+ ### ensureConnFlagsCapacity
1692
+
1693
+ ```ts
1694
+ ensureConnFlagsCapacity(
1695
+ state: EngineState,
1696
+ minimumCapacity: number,
1697
+ ): Int8Array<ArrayBufferLike> | null
1698
+ ```
1699
+
1700
+ Ensure the recurrent/gated detection bitmap has sufficient capacity.
1514
1701
 
1515
1702
  Parameters:
1516
- - `engineState` - - Shared engine state with scratch buffers and RNG
1517
- - `neat` - - NEAT driver instance used for evolving the generation
1518
- - `doProfile` - - When truthy measure timing for the evolve step (ms) using engine clock
1519
- - `lamarckianIterations` - - Number of supervised training iterations to run per genome (0 to skip)
1520
- - `lamarckianTrainingSet` - - Array of supervised training cases used for warm-start (may be empty)
1521
- - `lamarckianSampleSize` - - Optional per-network sample size used by the warm-start routine
1522
- - `safeWrite` - - Safe logging function; used only for best-effort diagnostic messages
1523
- - `completedGenerations` - - Current generation index (used by expansion heuristics)
1524
- - `dynamicPopEnabled` - - Whether dynamic population expansion is enabled
1525
- - `dynamicPopMax` - - Upper bound on population size for expansion
1526
- - `plateauGenerations` - - Window size used by plateau detection
1527
- - `plateauCounter` - - Current plateau counter used by expansion heuristics
1528
- - `dynamicPopExpandInterval` - - Generation interval to attempt expansion
1529
- - `dynamicPopExpandFactor` - - Fractional growth factor used to compute new members
1530
- - `dynamicPopPlateauSlack` - - Minimum plateau ratio required to trigger expansion
1531
- - `speciesHistoryRef` - - Mutable array holding species history (maintained externally)
1532
- - `emptyVec` - - Empty array fallback to avoid ephemeral allocations
1533
- - `scratchNodeIdx` - - Pooled node index buffer (passed through to helpers)
1534
- - `getNodeIndicesByType` - - Helper function to collect node indices by type
1535
- - `constants` - - Object containing DEFAULT_TRAIN_ERROR, DEFAULT_TRAIN_RATE, DEFAULT_TRAIN_MOMENTUM, DEFAULT_TRAIN_BATCH_SMALL, DEFAULT_STD_SMALL, DEFAULT_STD_ADJUST_MULT
1703
+ - `state` - Shared engine state containing the pooled bitmap.
1704
+ - `minimumCapacity` - Minimum number of entries required by the caller.
1536
1705
 
1537
- Returns: An object shaped { fittest, tEvolve, tLamarck } where:
1538
- - `fittest` is the network returned by `neat.evolve()` (may be null on error),
1539
- - `tEvolve` is the measured evolve duration in milliseconds when `doProfile` is true (0 otherwise),
1540
- - `tLamarck` is the total time spent in Lamarckian training (0 when skipped)
1706
+ Returns: Int8Array bitmap or null when allocation failed.
1541
1707
 
1542
- Example:
1708
+ ### ensureLogitsRingCapacity
1543
1709
 
1544
- // Run a single generation with profiling and optional Lamarckian warm-start
1545
- const { fittest, tEvolve, tLamarck } = await runGeneration(
1546
- engineState,
1547
- neatInstance,
1548
- true, // doProfile
1549
- 5, // lamarckianIterations
1550
- trainingSet,
1551
- 16, // lamarckianSampleSize
1552
- console.log,
1553
- genIndex,
1554
- true,
1555
- 500,
1556
- 10,
1557
- plateauCounter,
1558
- 5,
1559
- 0.1,
1560
- 0.75,
1561
- speciesHistory,
1562
- [],
1563
- nodeIndexBuffer,
1564
- getNodeIndicesByTypeFn,
1565
- { DEFAULT_TRAIN_ERROR: 0.01, ... }
1566
- );
1710
+ ```ts
1711
+ ensureLogitsRingCapacity(
1712
+ capacityRequest: LogitsRingCapacityOptions,
1713
+ ): LogitsRingCapacityResult
1714
+ ```
1567
1715
 
1568
- ### SimResultWithOutputs
1716
+ Grow or shrink the logits ring to accomodate the requested recent-step budget.
1569
1717
 
1570
- Simulation result with step outputs
1718
+ Parameters:
1719
+ - `capacityRequest` - Parameters describing the requested resize.
1571
1720
 
1572
- ### simulateAndPostprocess
1721
+ Returns: The resulting capacity and whether shared-array mode stayed enabled.
1722
+
1723
+ ### ensureScratchCapacity
1573
1724
 
1574
1725
  ```ts
1575
- simulateAndPostprocess(
1576
- engineState: EngineState,
1577
- fittest: default,
1578
- encodedMaze: number[][],
1579
- startPosition: readonly [number, number],
1580
- exitPosition: readonly [number, number],
1581
- distanceMap: number[][],
1582
- maxSteps: number | undefined,
1583
- doProfile: boolean,
1584
- safeWrite: (msg: string) => void,
1585
- logEvery: number,
1586
- completedGenerations: number,
1587
- neat: default,
1588
- runtimeContext: EvolutionLoopRuntimeContext,
1589
- ringState: LogitsRingState,
1590
- telemetryContext: EvolutionLoopTelemetryContext,
1591
- loopSupportContext: Pick<EvolutionLoopSupportContext, "loopHelpers">,
1592
- ): SimulationResult
1726
+ ensureScratchCapacity(
1727
+ state: EngineState,
1728
+ request: ScratchCapacityRequest,
1729
+ ): void
1593
1730
  ```
1594
1731
 
1595
- Simulate the supplied `fittest` genome/network and perform allocation-light postprocessing.
1732
+ Grow pooled scratch buffers to conservative sizes for the upcoming run.
1596
1733
 
1597
- Behaviour & contract:
1598
- - Runs the simulation via `MazeMovement.simulateAgent` and attaches compact telemetry
1599
- (saturation fraction, action entropy) directly onto the `fittest` object (in-place).
1600
- - When per-step logits are returned the helper attempts to copy them into the engine's pooled
1601
- ring buffers to avoid per-run allocations. Two copy modes are supported:
1602
- 1) Shared SAB-backed flat Float32Array with an atomic Int32 write index (cross-worker safe).
1603
- 2) Local in-process per-row Float32Array ring (`scratchLogitsRing`).
1604
- - Best-effort: all mutation and buffer-copy steps are guarded; failures are swallowed so the
1605
- evolution loop is not interrupted. Use `safeWrite` for optional diagnostic messages.
1734
+ Parameters:
1735
+ - `state` - Shared engine state exposing scratch buffers.
1736
+ - `request` - Sizing request describing the evolution workload.
1606
1737
 
1607
- Steps (high level):
1608
- 1) Run the simulator and capture wall-time when `doProfile` is truthy.
1609
- 2) Attach compact telemetry fields to `fittest` and ensure legacy `_lastStepOutputs` exists.
1610
- 3) If per-step logits are available, ensure ring capacity and copy them into the selected ring.
1611
- 4) Optionally prune saturated hidden->output connections and emit telemetry via logGenerationTelemetry.
1612
- 5) Return the raw simulation result and elapsed simulation time (ms when profiling enabled).
1738
+ ### initialiseSharedLogitsRing
1739
+
1740
+ ```ts
1741
+ initialiseSharedLogitsRing(
1742
+ state: EngineState,
1743
+ config: SharedLogitsConfig,
1744
+ ): boolean
1745
+ ```
1746
+
1747
+ Attempt to allocate SharedArrayBuffer-backed logits ring storage.
1748
+
1749
+ Parameters:
1750
+ - `state` - Shared engine state containing the logits buffers.
1751
+ - `config` - Shared ring configuration (capacity and action dimension).
1752
+
1753
+ Returns: true when shared mode was activated successfully.
1754
+
1755
+ ### LogitsRingCapacityOptions
1756
+
1757
+ Shape describing the parameters used when ensuring the logits ring capacity.
1758
+
1759
+ ### LogitsRingCapacityResult
1760
+
1761
+ Result returned after resizing the logits ring.
1762
+
1763
+ ### maybeShrinkScratch
1764
+
1765
+ ```ts
1766
+ maybeShrinkScratch(
1767
+ state: EngineState,
1768
+ populationSize: number,
1769
+ ): void
1770
+ ```
1771
+
1772
+ Shrink oversized scratch buffers once the population size drops significantly.
1773
+
1774
+ Parameters:
1775
+ - `state` - Shared engine state exposing scratch buffers.
1776
+ - `populationSize` - Current population size used to derive shrink heuristics.
1777
+
1778
+ ### ScratchCapacityRequest
1779
+
1780
+ Parameters describing the scratch sizing requirements for ensureScratchCapacity.
1781
+
1782
+ ### SharedLogitsConfig
1783
+
1784
+ Parameters passed when attempting to initialise the shared logits ring buffers.
1785
+
1786
+ ## evolutionEngine/setupHelpers.ts
1787
+
1788
+ Setup helpers for the ASCII maze evolution engine.
1789
+
1790
+ Responsibilities:
1791
+ - Create cooperative frame-yielding helpers for async evolution loops.
1792
+ - Initialize Node.js persistence helpers when available.
1793
+ - Build resilient logging writers with dashboard and console fallbacks.
1794
+
1795
+ ### DashboardManagerLike
1796
+
1797
+ Dashboard manager shape for logging (optional log function).
1798
+
1799
+ ### FilesystemModule
1800
+
1801
+ Minimal filesystem module shape for type safety (Node.js fs module subset).
1802
+
1803
+ ### initPersistence
1804
+
1805
+ ```ts
1806
+ initPersistence(
1807
+ persistDir: string | undefined,
1808
+ ): { fs: FilesystemModule | null; path: PathModule | null; }
1809
+ ```
1613
1810
 
1614
- Notes on pooling / reentrancy:
1615
- - The local ring is not re-entrant; callers must avoid concurrent writes.
1616
- - When shared mode is true we prefer the SAB-backed path which uses Atomics and is safe
1617
- for cross-thread producers.
1811
+ Initialize persistence helpers (Node `fs` & `path`) when available and ensure the target
1812
+ directory exists. This helper intentionally does nothing in browser-like hosts.
1813
+
1814
+ Steps:
1815
+ 1. Detect whether a Node-like `require` is available and attempt to load `fs` and `path`
1816
+ 2. If both modules are available and `persistDir` is provided, ensure the directory exists
1817
+ by creating it recursively when necessary
1818
+ 3. Return an object containing the (possibly null) `{ fs, path }` references for callers to use
1819
+
1820
+ Notes:
1821
+ - This helper deliberately performs defensive checks and swallows synchronous errors because
1822
+ persistence is optional in many host environments (tests, browser demos)
1823
+ - No large allocations are performed here; the function returns lightweight references
1618
1824
 
1619
1825
  Parameters:
1620
- - `engineState` - - Shared engine state with scratch buffers and ring configuration
1621
- - `fittest` - - Genome/network considered the generation's best; may be mutated with metadata
1622
- - `encodedMaze` - - Maze descriptor used by the simulator
1623
- - `startPosition` - - Start co-ordinates passed as-is to the simulator
1624
- - `exitPosition` - - Exit co-ordinates passed as-is to the simulator
1625
- - `distanceMap` - - Optional precomputed distance map consumed by the simulator
1626
- - `maxSteps` - - Optional maximum simulation steps; may be undefined to allow default
1627
- - `doProfile` - - When truthy measure and return the simulation time in milliseconds
1628
- - `safeWrite` - - Optional logger used for non-fatal diagnostic messages
1629
- - `logEvery` - - Emit telemetry every `logEvery` generations (0 disables periodic telemetry)
1630
- - `completedGenerations` - - Current generation index used for conditional telemetry
1631
- - `neat` - - NEAT driver instance passed to telemetry hooks
1632
- - `runtimeContext` - - Shared pooled ring buffers and limits for logits telemetry.
1633
- - `ringState` - - Current mutable ring state (capacity, shared-mode flag, write cursor).
1634
- - `telemetryContext` - - Telemetry thresholds and verbosity switches used after simulation.
1635
- - `loopSupportContext` - - Shared scratch buffers and helper callbacks used by the loop.
1826
+ - `persistDir` - - Optional directory to ensure exists. If falsy, no filesystem mutations are attempted.
1636
1827
 
1637
- Returns: An object { generationResult, simTime, updatedRingState } where simTime is ms when profiling is enabled
1828
+ Returns: Object with `{ fs, path }` where each value may be `null` when unavailable.
1638
1829
 
1639
1830
  Example:
1640
1831
 
1641
- const { generationResult, simTime, updatedRingState } = simulateAndPostprocess(
1642
- state, bestGenome, maze, start, exit, distMap, 1000, true, console.log, 10, genIdx, neat, ...
1643
- );
1832
+ const { fs, path } = initPersistence('./snapshots');
1833
+ if (fs && path) {
1834
+ fs.writeFileSync(path.join(dir, 'snapshot.json'), data);
1835
+ }
1644
1836
 
1645
- ### SimulationOutcome
1837
+ ### isPauseRequested
1646
1838
 
1647
- Simulation result with profiling and ring state
1839
+ ```ts
1840
+ isPauseRequested(
1841
+ hostAdapter: EvolutionHostAdapter | undefined,
1842
+ ): boolean
1843
+ ```
1648
1844
 
1649
- ### updateDashboardAndMaybeFlush
1845
+ Read host-controlled pause state without letting host errors break the engine.
1846
+
1847
+ Parameters:
1848
+ - `hostAdapter` - - Optional host adapter implementing pause polling.
1849
+
1850
+ Returns: True when the host asks the engine to remain paused.
1851
+
1852
+ ### makeFlushToFrame
1650
1853
 
1651
1854
  ```ts
1652
- updateDashboardAndMaybeFlush(
1653
- maze: string[],
1654
- result: IMazeRunResult | undefined,
1655
- network: default | null,
1656
- completedGenerations: number,
1657
- neat: default,
1658
- dashboardManager: IDashboardManager | undefined,
1659
- flushToFrame: (() => Promise<void>) | undefined,
1660
- ): Promise<void>
1855
+ makeFlushToFrame(
1856
+ hostAdapter: EvolutionHostAdapter | undefined,
1857
+ ): () => Promise<void>
1661
1858
  ```
1662
1859
 
1663
- Safely update a UI dashboard with the latest run state and optionally yield to the
1664
- host/frame via an awaited flush function.
1860
+ Create a cooperative frame-yielding function used by the evolution loop.
1665
1861
 
1666
- Behaviour (best-effort):
1667
- 1) If `dashboardManager.update` exists and is callable, call it with the stable
1668
- argument order (maze, result, network, completedGenerations, neat). Any exception
1669
- raised by the dashboard is swallowed to avoid interrupting the evolution loop.
1670
- 2) If `flushToFrame` is supplied as an async function, await it to yield control to
1671
- the event loop or renderer (for example `() => new Promise(r => requestAnimationFrame(r))`).
1672
- 3) The helper avoids heap allocations and relies on existing pooled scratch buffers in
1673
- the engine for heavy telemetry elsewhere; this method intentionally performs only
1674
- short-lived control flow and minimal work.
1862
+ Behaviour:
1863
+ - Prefers `requestAnimationFrame` when available (browser hosts)
1864
+ - Falls back to `setImmediate` when available (Node) or `setTimeout(...,0)` otherwise
1865
+ - Respects an optional host adapter pause callback by polling between ticks without busy-waiting
1866
+ - Resolves once a single new frame or tick is available and the host is not paused
1675
1867
 
1676
- Design Rationale:
1677
- - Allocation-free (no ephemeral objects or arrays created)
1678
- - Best-effort error handling (swallow all dashboard/flush errors)
1679
- - Stable argument order for dashboard implementations
1680
- - Async support for cooperative yielding to host scheduler
1868
+ Steps:
1869
+ 1. Choose the preferred tick function based on the host runtime
1870
+ 2. When called, await the preferred tick; if the host adapter reports pause, poll again after the tick
1871
+ 3. Resolve once a tick passed while not paused
1681
1872
 
1682
1873
  Parameters:
1874
+ - `hostAdapter` - - Optional host adapter that owns cooperative pause state.
1683
1875
 
1684
- Parameters:
1685
- - `maze` - - Maze instance or descriptor used by dashboard rendering.
1686
- - `result` - - Per-run result object (path, progress, telemetry, etc.).
1687
- - `network` - - Network or genome object that should be visualised.
1688
- - `completedGenerations` - - Integer index of the completed generation.
1689
- - `neat` - - NEAT manager instance (context passed to the dashboard update).
1690
- - `dashboardManager` - - Optional manager exposing `update(maze, result, network, gen, neat)`.
1691
- - `flushToFrame` - - Optional async function used to yield to the host/frame scheduler; may be omitted.
1876
+ Returns: A function that yields cooperatively to the next animation frame / tick.
1692
1877
 
1693
1878
  Example:
1694
1879
 
1695
- // Yield to the browser's next repaint after dashboard update:
1696
- await updateDashboardAndMaybeFlush(
1697
- maze, genResult, fittestNetwork, gen, neatInstance, dashboard, () => new Promise(r => requestAnimationFrame(r))
1698
- );
1880
+ const flushToFrame = makeFlushToFrame();
1881
+ await flushToFrame(); // yields to next frame/tick
1699
1882
 
1700
- ### updateDashboardPeriodic
1883
+ ### makeSafeWriter
1701
1884
 
1702
1885
  ```ts
1703
- updateDashboardPeriodic(
1704
- maze: string[],
1705
- bestResult: IMazeRunResult | undefined,
1706
- bestNetwork: default | null,
1707
- completedGenerations: number,
1708
- neat: default,
1709
- dashboardManager: IDashboardManager | undefined,
1710
- flushToFrame: (() => Promise<void>) | undefined,
1711
- ): Promise<void>
1886
+ makeSafeWriter(
1887
+ dashboardManager: DashboardManagerLike | undefined,
1888
+ ): (msg: string) => void
1712
1889
  ```
1713
1890
 
1714
- Periodic dashboard update used when the engine wants to refresh a non-primary
1715
- dashboard view (for example background or periodic reporting). This helper is
1716
- intentionally small, allocation-light and best-effort: dashboard errors are
1717
- swallowed so the evolution loop cannot be interrupted by UI issues.
1718
-
1719
- Behavioural contract:
1720
- 1) If `dashboardManager.update` is present and callable the method invokes it with
1721
- the stable argument order: (maze, bestResult, bestNetwork, completedGenerations, neat).
1722
- 2) If `flushToFrame` is supplied the helper awaits it after the update to yield to
1723
- the host renderer (eg. requestAnimationFrame). Any exceptions raised by the
1724
- flush are swallowed.
1725
- 3) The helper avoids creating ephemeral arrays/objects and therefore does not use
1726
- typed-array scratch buffers here — there is no hot numerical work to pool. Other
1727
- engine helpers already reuse class-level scratch buffers where appropriate.
1891
+ Build a resilient writer that attempts to write to Node stdout, then a provided
1892
+ dashboard logger, and finally `console.log` as a last resort.
1728
1893
 
1729
- Design Rationale:
1730
- - Fast-guard early when update cannot be performed
1731
- - Best-effort error handling (swallow all exceptions)
1732
- - Preserves dashboard `this` binding with `.call()`
1733
- - Minimal allocations (no scratch buffers needed)
1894
+ Steps:
1895
+ 1. If Node `process.stdout.write` is available, use it (no trailing newline forced)
1896
+ 2. Else if `dashboardManager.logFunction` exists, call it
1897
+ 3. Else fall back to `console.log` and trim the message
1734
1898
 
1735
- Steps / inline intent:
1736
- 1. Fast-guard when an update cannot be performed (missing manager, update method,
1737
- or missing content to visualise).
1738
- 2. Call the dashboard update in a try/catch to preserve best-effort semantics.
1739
- 3. Optionally await the provided `flushToFrame` function to yield to the host.
1899
+ Notes:
1900
+ - Errors are swallowed; logging must never throw and disrupt the evolution loop
1901
+ - This factory is allocation-light; the returned function only creates a trimmed string
1902
+ when falling back to `console.log`
1740
1903
 
1741
1904
  Parameters:
1905
+ - `dashboardManager` - - Optional manager exposing `logFunction(msg:string)` used in some UIs.
1742
1906
 
1743
- Parameters:
1744
- - `maze` - - Maze descriptor passed to the dashboard renderer.
1745
- - `bestResult` - - Best-run result object used for display (may be falsy when not present).
1746
- - `bestNetwork` - - Network or genome object to visualise (may be falsy when not present).
1747
- - `completedGenerations` - - Completed generation index (number).
1748
- - `neat` - - NEAT manager instance (passed through to dashboard update).
1749
- - `dashboardManager` - - Optional manager exposing `update(maze, result, network, gen, neat)`.
1750
- - `flushToFrame` - - Optional async function used to yield to the host/frame scheduler
1751
- (for example: `() => new Promise(r => requestAnimationFrame(r))`).
1907
+ Returns: A function accepting a single string message to write.
1752
1908
 
1753
1909
  Example:
1754
1910
 
1755
- // Safe periodic update and yield to next frame
1756
- await updateDashboardPeriodic(
1757
- maze, result, network, gen, neatInstance, dashboard, () => new Promise(r => requestAnimationFrame(r))
1758
- );
1911
+ const safeWrite = makeSafeWriter(dashboardManager);
1912
+ safeWrite('[INFO] Generation 42 complete\n');
1913
+
1914
+ ### PathModule
1915
+
1916
+ Minimal path module shape for type safety (Node.js path module subset).
1759
1917
 
1760
1918
  ## evolutionEngine/curriculumPhase.ts
1761
1919
 
@@ -3586,79 +3744,6 @@ Minimum safe load factor applied when normalising visited-hash configuration.
3586
3744
 
3587
3745
  Knuth-derived 32-bit constant used when seeding the RNG state.
3588
3746
 
3589
- ## evolutionEngine/evolutionEngine.services.ts
3590
-
3591
- ### applyEvolutionEngineRingState
3592
-
3593
- ```ts
3594
- applyEvolutionEngineRingState(
3595
- updatedRingState: LogitsRingState,
3596
- ): void
3597
- ```
3598
-
3599
- Apply the latest logits-ring runtime values returned by the evolution loop.
3600
-
3601
- Parameters:
3602
- - `updatedRingState` - - New ring-capacity, shared-mode, and write-cursor values.
3603
-
3604
- ### configureEvolutionEngineToggles
3605
-
3606
- ```ts
3607
- configureEvolutionEngineToggles(
3608
- reducedTelemetry: boolean,
3609
- telemetryMinimal: boolean,
3610
- disableBaldwinPhase: boolean,
3611
- ): void
3612
- ```
3613
-
3614
- Apply telemetry and Baldwin-phase toggles derived from one normalized run request.
3615
-
3616
- Parameters:
3617
- - `reducedTelemetry` - - When true, keep only the essential telemetry metrics.
3618
- - `telemetryMinimal` - - When true, disable verbose telemetry capture.
3619
- - `disableBaldwinPhase` - - When true, skip the Baldwin refinement stage.
3620
-
3621
- ### getEvolutionEngineFacadeRuntimeState
3622
-
3623
- ```ts
3624
- getEvolutionEngineFacadeRuntimeState(): LogitsRingState
3625
- ```
3626
-
3627
- Read the mutable facade-owned logits-ring runtime state.
3628
-
3629
- Returns: Current ring-capacity, shared-mode, and write-cursor state.
3630
-
3631
- ### getEvolutionEngineMaxLogitsRingCapacity
3632
-
3633
- ```ts
3634
- getEvolutionEngineMaxLogitsRingCapacity(): number
3635
- ```
3636
-
3637
- Read the hard maximum ring capacity used by the public facade.
3638
-
3639
- Returns: Maximum ring capacity allowed for logits telemetry.
3640
-
3641
- ### getEvolutionEngineSharedState
3642
-
3643
- ```ts
3644
- getEvolutionEngineSharedState(): EngineState
3645
- ```
3646
-
3647
- Return the shared engine singleton used by extracted engine modules.
3648
-
3649
- The public facade now depends on the same owner as the rest of the engine
3650
- boundary instead of creating a private duplicate singleton.
3651
-
3652
- Returns: Shared engine state singleton.
3653
-
3654
- ### resetEvolutionEngineRingState
3655
-
3656
- ```ts
3657
- resetEvolutionEngineRingState(): void
3658
- ```
3659
-
3660
- Reset the facade-owned logits-ring runtime state to its baseline defaults.
3661
-
3662
3747
  ## evolutionEngine/evolutionEngine.constants.ts
3663
3748
 
3664
3749
  Stable tuning values consumed by the ASCII maze evolution facade.