@reicek/neataptic-ts 0.1.21 → 0.1.22

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 (223) hide show
  1. package/.github/agents/boundary-mapper.agent.md +29 -0
  2. package/.github/agents/docs-scout.agent.md +29 -0
  3. package/.github/agents/plan-scout.agent.md +29 -0
  4. package/.github/agents/solid-split.agent.md +138 -0
  5. package/.github/copilot-instructions.md +103 -0
  6. package/package.json +6 -3
  7. package/plans/ES2023 migration +13 -8
  8. package/plans/Evolution_Training_Interoperability_Contracts.md +1 -1
  9. package/plans/Interactive_Examples_and_Learning_Path.md +10 -2
  10. package/plans/Memory_Optimization.md +3 -3
  11. package/plans/README.md +63 -0
  12. package/plans/Roadmap.md +15 -3
  13. package/plans/asciiMaze_SOLID_split.done.md +130 -0
  14. package/plans/flappy_bird_SOLID_split.done.md +67 -0
  15. package/scripts/assets/theme.css +221 -34
  16. package/scripts/copy-examples.mjs +9 -5
  17. package/scripts/export-onnx.mjs +3 -3
  18. package/scripts/generate-bench-tables.mjs +10 -10
  19. package/scripts/generate-bench-tables.ts +10 -10
  20. package/scripts/generate-docs.ts +1415 -449
  21. package/scripts/render-docs-html.ts +15 -8
  22. package/src/README.md +101 -223
  23. package/src/architecture/README.md +57 -185
  24. package/src/architecture/layer/README.md +38 -38
  25. package/src/architecture/network/README.md +33 -31
  26. package/src/architecture/network/activate/README.md +77 -77
  27. package/src/architecture/network/connect/README.md +15 -13
  28. package/src/architecture/network/deterministic/README.md +7 -7
  29. package/src/architecture/network/evolve/README.md +44 -44
  30. package/src/architecture/network/gating/README.md +20 -20
  31. package/src/architecture/network/genetic/README.md +51 -51
  32. package/src/architecture/network/mutate/README.md +97 -97
  33. package/src/architecture/network/onnx/README.md +264 -264
  34. package/src/architecture/network/prune/README.md +39 -39
  35. package/src/architecture/network/remove/README.md +26 -26
  36. package/src/architecture/network/serialize/README.md +56 -56
  37. package/src/architecture/network/slab/README.md +61 -61
  38. package/src/architecture/network/standalone/README.md +24 -24
  39. package/src/architecture/network/stats/README.md +9 -9
  40. package/src/architecture/network/topology/README.md +46 -46
  41. package/src/architecture/network/training/README.md +21 -21
  42. package/src/methods/README.md +9 -87
  43. package/src/multithreading/README.md +8 -77
  44. package/src/multithreading/workers/README.md +2 -2
  45. package/src/multithreading/workers/browser/README.md +0 -6
  46. package/src/multithreading/workers/node/README.md +0 -3
  47. package/src/neat/README.md +562 -568
  48. package/src/utils/README.md +18 -18
  49. package/test/examples/asciiMaze/README.md +59 -59
  50. package/test/examples/asciiMaze/asciiMaze.e2e.test.ts +14 -9
  51. package/test/examples/asciiMaze/browser-entry/README.md +196 -0
  52. package/test/examples/asciiMaze/browser-entry/browser-entry.abort.services.ts +95 -0
  53. package/test/examples/asciiMaze/browser-entry/browser-entry.constants.ts +23 -0
  54. package/test/examples/asciiMaze/browser-entry/browser-entry.curriculum.services.ts +115 -0
  55. package/test/examples/asciiMaze/browser-entry/browser-entry.globals.services.ts +106 -0
  56. package/test/examples/asciiMaze/browser-entry/browser-entry.host.services.ts +157 -0
  57. package/test/examples/asciiMaze/browser-entry/browser-entry.services.ts +14 -0
  58. package/test/examples/asciiMaze/browser-entry/browser-entry.ts +129 -0
  59. package/test/examples/asciiMaze/browser-entry/browser-entry.types.ts +120 -0
  60. package/test/examples/asciiMaze/browser-entry/browser-entry.utils.ts +98 -0
  61. package/test/examples/asciiMaze/browser-entry.ts +10 -576
  62. package/test/examples/asciiMaze/dashboardManager/README.md +276 -0
  63. package/test/examples/asciiMaze/dashboardManager/archive/README.md +16 -0
  64. package/test/examples/asciiMaze/dashboardManager/archive/dashboardManager.archive.services.ts +267 -0
  65. package/test/examples/asciiMaze/dashboardManager/dashboardManager.constants.ts +35 -0
  66. package/test/examples/asciiMaze/dashboardManager/dashboardManager.services.ts +103 -0
  67. package/test/examples/asciiMaze/dashboardManager/dashboardManager.ts +181 -0
  68. package/test/examples/asciiMaze/dashboardManager/dashboardManager.types.ts +267 -0
  69. package/test/examples/asciiMaze/dashboardManager/dashboardManager.utils.ts +254 -0
  70. package/test/examples/asciiMaze/dashboardManager/live/README.md +14 -0
  71. package/test/examples/asciiMaze/dashboardManager/live/dashboardManager.live.services.ts +264 -0
  72. package/test/examples/asciiMaze/dashboardManager/telemetry/README.md +47 -0
  73. package/test/examples/asciiMaze/dashboardManager/telemetry/dashboardManager.telemetry.services.ts +513 -0
  74. package/test/examples/asciiMaze/dashboardManager.ts +13 -2335
  75. package/test/examples/asciiMaze/evolutionEngine/README.md +1058 -0
  76. package/test/examples/asciiMaze/evolutionEngine/curriculumPhase.ts +90 -0
  77. package/test/examples/asciiMaze/evolutionEngine/engineState.constants.ts +36 -0
  78. package/test/examples/asciiMaze/evolutionEngine/engineState.ts +58 -513
  79. package/test/examples/asciiMaze/evolutionEngine/engineState.types.ts +212 -0
  80. package/test/examples/asciiMaze/evolutionEngine/engineState.utils.ts +301 -0
  81. package/test/examples/asciiMaze/evolutionEngine/evolutionEngine.types.ts +445 -0
  82. package/test/examples/asciiMaze/evolutionEngine/evolutionLoop.ts +81 -50
  83. package/test/examples/asciiMaze/evolutionEngine/optionsAndSetup.ts +2 -4
  84. package/test/examples/asciiMaze/evolutionEngine/populationDynamics.ts +17 -33
  85. package/test/examples/asciiMaze/evolutionEngine/populationPruning.ts +1 -1
  86. package/test/examples/asciiMaze/evolutionEngine/rngAndTiming.ts +1 -2
  87. package/test/examples/asciiMaze/evolutionEngine/sampling.ts +1 -1
  88. package/test/examples/asciiMaze/evolutionEngine/scratchPools.ts +2 -5
  89. package/test/examples/asciiMaze/evolutionEngine/setupHelpers.ts +30 -37
  90. package/test/examples/asciiMaze/evolutionEngine/telemetryMetrics.ts +16 -58
  91. package/test/examples/asciiMaze/evolutionEngine/trainingWarmStart.ts +2 -2
  92. package/test/examples/asciiMaze/evolutionEngine.ts +55 -55
  93. package/test/examples/asciiMaze/fitness.ts +2 -2
  94. package/test/examples/asciiMaze/fitness.types.ts +65 -0
  95. package/test/examples/asciiMaze/interfaces.ts +64 -1352
  96. package/test/examples/asciiMaze/mazeMovement/README.md +356 -0
  97. package/test/examples/asciiMaze/mazeMovement/finalization/README.md +49 -0
  98. package/test/examples/asciiMaze/mazeMovement/finalization/mazeMovement.finalization.ts +138 -0
  99. package/test/examples/asciiMaze/mazeMovement/mazeMovement.constants.ts +101 -0
  100. package/test/examples/asciiMaze/mazeMovement/mazeMovement.services.ts +230 -0
  101. package/test/examples/asciiMaze/mazeMovement/mazeMovement.ts +299 -0
  102. package/test/examples/asciiMaze/mazeMovement/mazeMovement.types.ts +185 -0
  103. package/test/examples/asciiMaze/mazeMovement/mazeMovement.utils.ts +153 -0
  104. package/test/examples/asciiMaze/mazeMovement/policy/README.md +91 -0
  105. package/test/examples/asciiMaze/mazeMovement/policy/mazeMovement.policy.ts +467 -0
  106. package/test/examples/asciiMaze/mazeMovement/runtime/README.md +95 -0
  107. package/test/examples/asciiMaze/mazeMovement/runtime/mazeMovement.runtime.ts +354 -0
  108. package/test/examples/asciiMaze/mazeMovement/shaping/README.md +124 -0
  109. package/test/examples/asciiMaze/mazeMovement/shaping/mazeMovement.shaping.ts +459 -0
  110. package/test/examples/asciiMaze/mazeMovement.ts +12 -2978
  111. package/test/examples/flappy_bird/Trace-20260309T191949.json +24124 -0
  112. package/test/examples/flappy_bird/browser-entry/README.md +1129 -0
  113. package/test/examples/flappy_bird/browser-entry/browser-entry.host.utils.ts +4 -324
  114. package/test/examples/flappy_bird/browser-entry/browser-entry.network-view.utils.ts +6 -399
  115. package/test/examples/flappy_bird/browser-entry/browser-entry.playback.utils.ts +1 -717
  116. package/test/examples/flappy_bird/browser-entry/browser-entry.spawn.utils.ts +11 -31
  117. package/test/examples/flappy_bird/browser-entry/browser-entry.visualization.utils.ts +15 -893
  118. package/test/examples/flappy_bird/browser-entry/host/README.md +307 -0
  119. package/test/examples/flappy_bird/browser-entry/host/host.resize.service.ts +1 -295
  120. package/test/examples/flappy_bird/browser-entry/host/host.ts +562 -6
  121. package/test/examples/flappy_bird/browser-entry/host/resize/README.md +274 -0
  122. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.constants.ts +31 -0
  123. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.services.ts +360 -0
  124. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.ts +117 -0
  125. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.types.ts +63 -0
  126. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.utils.ts +250 -0
  127. package/test/examples/flappy_bird/browser-entry/network-view/README.md +399 -0
  128. package/test/examples/flappy_bird/browser-entry/network-view/network-view.topology.utils.ts +255 -0
  129. package/test/examples/flappy_bird/browser-entry/network-view/network-view.ts +802 -7
  130. package/test/examples/flappy_bird/browser-entry/playback/README.md +684 -0
  131. package/test/examples/flappy_bird/browser-entry/playback/background/README.md +277 -0
  132. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/README.md +770 -0
  133. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.cache.services.ts +178 -0
  134. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.constants.ts +107 -0
  135. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.geometry.utils.ts +518 -0
  136. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.math.utils.ts +117 -0
  137. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.pulse.utils.ts +233 -0
  138. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.services.ts +211 -0
  139. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.ts +48 -0
  140. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.types.ts +212 -0
  141. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.utils.ts +81 -0
  142. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.cache.services.ts +96 -0
  143. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.constants.ts +62 -0
  144. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.services.ts +244 -0
  145. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.ts +53 -0
  146. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.types.ts +68 -0
  147. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.utils.ts +100 -0
  148. package/test/examples/flappy_bird/browser-entry/playback/frame-render/README.md +310 -0
  149. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.service.ts +92 -0
  150. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.services.ts +272 -0
  151. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.types.ts +39 -0
  152. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.utils.ts +493 -0
  153. package/test/examples/flappy_bird/browser-entry/playback/playback.constants.ts +1 -1
  154. package/test/examples/flappy_bird/browser-entry/playback/playback.frame-render.service.ts +4 -0
  155. package/test/examples/flappy_bird/browser-entry/playback/playback.snapshot.utils.ts +44 -0
  156. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.service.ts +39 -122
  157. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.services.ts +272 -0
  158. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.types.ts +62 -0
  159. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.utils.ts +11 -4
  160. package/test/examples/flappy_bird/browser-entry/playback/playback.ts +409 -8
  161. package/test/examples/flappy_bird/browser-entry/playback/playback.types.ts +4 -12
  162. package/test/examples/flappy_bird/browser-entry/runtime/README.md +235 -0
  163. package/test/examples/flappy_bird/browser-entry/runtime/runtime.evolution-launch.service.ts +45 -0
  164. package/test/examples/flappy_bird/browser-entry/runtime/runtime.lifecycle.service.ts +81 -0
  165. package/test/examples/flappy_bird/browser-entry/runtime/runtime.startup.service.ts +74 -0
  166. package/test/examples/flappy_bird/browser-entry/runtime/runtime.ts +31 -121
  167. package/test/examples/flappy_bird/browser-entry/runtime/runtime.types.ts +36 -0
  168. package/test/examples/flappy_bird/browser-entry/visualization/README.md +557 -0
  169. package/test/examples/flappy_bird/browser-entry/visualization/visualization.constants.ts +110 -0
  170. package/test/examples/flappy_bird/browser-entry/visualization/visualization.draw.service.ts +957 -19
  171. package/test/examples/flappy_bird/browser-entry/visualization/visualization.legend.utils.ts +138 -3
  172. package/test/examples/flappy_bird/browser-entry/visualization/visualization.topology.utils.ts +3 -27
  173. package/test/examples/flappy_bird/browser-entry/visualization/visualization.ts +1 -23
  174. package/test/examples/flappy_bird/browser-entry/worker-channel/README.md +156 -0
  175. package/test/examples/flappy_bird/constants/README.md +1179 -0
  176. package/test/examples/flappy_bird/constants/constants.network-view.ts +24 -0
  177. package/test/examples/flappy_bird/constants/constants.palette.ts +7 -0
  178. package/test/examples/flappy_bird/constants/constants.starfield.ts +78 -3
  179. package/test/examples/flappy_bird/environment/README.md +143 -0
  180. package/test/examples/flappy_bird/environment/environment.observation.utils.ts +1 -19
  181. package/test/examples/flappy_bird/environment/environment.step.service.ts +3 -66
  182. package/test/examples/flappy_bird/evaluation/README.md +130 -0
  183. package/test/examples/flappy_bird/evaluation/evaluation.fitness.utils.ts +1 -1
  184. package/test/examples/flappy_bird/evaluation/evaluation.rollout.service.ts +5 -375
  185. package/test/examples/flappy_bird/evaluation/rollout/README.md +291 -0
  186. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.constants.ts +30 -0
  187. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.service.ts +58 -0
  188. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.services.ts +310 -0
  189. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.types.ts +56 -0
  190. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.utils.ts +368 -0
  191. package/test/examples/flappy_bird/flappy-evolution-worker/README.md +618 -0
  192. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.playback.service.ts +7 -7
  193. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.simulation.frame.service.ts +364 -0
  194. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.simulation.types.ts +14 -0
  195. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.simulation.utils.ts +4 -201
  196. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.ts +184 -345
  197. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.warm-start.service.ts +291 -0
  198. package/test/examples/flappy_bird/flappy.simulation.shared.utils.ts +5 -0
  199. package/test/examples/flappy_bird/simulation-shared/README.md +417 -0
  200. package/test/examples/flappy_bird/simulation-shared/observation/README.md +183 -0
  201. package/test/examples/flappy_bird/simulation-shared/observation/observation.features.utils.ts +301 -0
  202. package/test/examples/flappy_bird/simulation-shared/observation/observation.ts +9 -0
  203. package/test/examples/flappy_bird/simulation-shared/observation/observation.vector.utils.ts +59 -0
  204. package/test/examples/flappy_bird/simulation-shared/simulation-shared.observation.utils.ts +5 -403
  205. package/test/examples/flappy_bird/simulation-shared/simulation-shared.spawn.utils.ts +20 -6
  206. package/test/examples/flappy_bird/{evaluation/evaluation.statistics.utils.ts → simulation-shared/simulation-shared.statistics.utils.ts} +23 -8
  207. package/test/examples/flappy_bird/trainer/README.md +563 -0
  208. package/test/examples/flappy_bird/trainer/evaluation/README.md +199 -0
  209. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.constants.ts +9 -0
  210. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.services.ts +73 -0
  211. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.ts +165 -0
  212. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.types.ts +25 -0
  213. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.utils.ts +161 -0
  214. package/test/examples/flappy_bird/trainer/trainer.evaluation.service.ts +13 -0
  215. package/test/examples/flappy_bird/trainer/trainer.report.service.services.ts +181 -0
  216. package/test/examples/flappy_bird/trainer/trainer.report.service.ts +126 -0
  217. package/test/examples/flappy_bird/trainer/trainer.selection.utils.ts +89 -0
  218. package/test/examples/flappy_bird/trainer/trainer.ts +11 -553
  219. package/test/examples/flappy_bird/browser-entry/browser-entry.utils.ts +0 -12
  220. package/test/examples/flappy_bird/environment/environment.ts +0 -7
  221. package/test/examples/flappy_bird/evaluation/evaluation.ts +0 -7
  222. package/test/examples/flappy_bird/simulation-shared/simulation-shared.ts +0 -15
  223. package/test/examples/flappy_bird/trainer/trainer.statistics.utils.ts +0 -78
@@ -0,0 +1,513 @@
1
+ import { MazeUtils } from '../../mazeUtils';
2
+ import { DASHBOARD_MANAGER_CONSTANTS as C } from '../dashboardManager.constants';
3
+ import type {
4
+ AsciiMazeDetailedStats,
5
+ AsciiMazeTelemetrySnapshot,
6
+ DashboardManagerState,
7
+ DashboardTelemetry,
8
+ DashboardTelemetryPayload,
9
+ MutationStatsMap,
10
+ NeatInstance,
11
+ OperatorStatsEntry,
12
+ } from '../dashboardManager.types';
13
+ import {
14
+ buildDashboardSparkline,
15
+ sliceDashboardHistoryForExport,
16
+ } from '../dashboardManager.utils';
17
+
18
+ /**
19
+ * Produce the latest public telemetry snapshot from current dashboard state.
20
+ *
21
+ * @param state - Mutable dashboard state.
22
+ * @returns Public telemetry snapshot used by browser hosts.
23
+ */
24
+ export function getDashboardLastTelemetry(
25
+ state: DashboardManagerState,
26
+ ): AsciiMazeTelemetrySnapshot {
27
+ const elapsedMs =
28
+ state.perfStart != null && typeof performance !== 'undefined'
29
+ ? performance.now() - state.perfStart
30
+ : state.runStartTs
31
+ ? Date.now() - state.runStartTs
32
+ : 0;
33
+ const generation = state.lastGeneration ?? 0;
34
+ const gensPerSec = elapsedMs > 0 ? generation / (elapsedMs / 1000) : 0;
35
+
36
+ return {
37
+ generation,
38
+ bestFitness: state.lastBestFitness,
39
+ progress: state.currentBest?.result?.progress ?? null,
40
+ speciesCount: MazeUtils.safeLast(state.histories.speciesCount) ?? null,
41
+ gensPerSec: +gensPerSec.toFixed(3),
42
+ timestamp: resolveLastUpdateWallMs(state),
43
+ details: state.lastDetailedStats,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Pull the latest NEAT telemetry snapshot and update bounded dashboard histories.
49
+ *
50
+ * @param state - Mutable dashboard state that owns bounded histories.
51
+ * @param neatInstance - Optional NEAT-like runtime exposing `getTelemetry()`.
52
+ */
53
+ export function updateTelemetryHistory(
54
+ state: DashboardManagerState,
55
+ neatInstance?: { getTelemetry?: () => unknown[] } | undefined,
56
+ ): void {
57
+ const telemetrySeriesCandidate = neatInstance?.getTelemetry?.();
58
+ if (
59
+ !Array.isArray(telemetrySeriesCandidate) ||
60
+ !telemetrySeriesCandidate.length
61
+ ) {
62
+ return;
63
+ }
64
+
65
+ const telemetrySeries = telemetrySeriesCandidate as DashboardTelemetry[];
66
+ state.lastTelemetry =
67
+ MazeUtils.safeLast<DashboardTelemetry>(telemetrySeries) ?? null;
68
+
69
+ const latestFitness = state.currentBest?.result?.fitness;
70
+ if (typeof latestFitness === 'number') {
71
+ state.lastBestFitness = latestFitness;
72
+ state.histories.bestFitness = MazeUtils.pushHistory(
73
+ state.histories.bestFitness,
74
+ latestFitness,
75
+ C.HISTORY_MAX,
76
+ );
77
+ }
78
+
79
+ const complexitySnapshot = state.lastTelemetry?.complexity;
80
+ if (typeof complexitySnapshot?.meanNodes === 'number') {
81
+ state.histories.complexityNodes = MazeUtils.pushHistory(
82
+ state.histories.complexityNodes,
83
+ complexitySnapshot.meanNodes,
84
+ C.HISTORY_MAX,
85
+ );
86
+ }
87
+ if (typeof complexitySnapshot?.meanConns === 'number') {
88
+ state.histories.complexityConns = MazeUtils.pushHistory(
89
+ state.histories.complexityConns,
90
+ complexitySnapshot.meanConns,
91
+ C.HISTORY_MAX,
92
+ );
93
+ }
94
+
95
+ if (typeof state.lastTelemetry?.hyper === 'number') {
96
+ state.histories.hypervolume = MazeUtils.pushHistory(
97
+ state.histories.hypervolume,
98
+ state.lastTelemetry.hyper,
99
+ C.HISTORY_MAX,
100
+ );
101
+ }
102
+
103
+ const currentProgress = state.currentBest?.result?.progress;
104
+ if (typeof currentProgress === 'number') {
105
+ state.histories.progress = MazeUtils.pushHistory(
106
+ state.histories.progress,
107
+ currentProgress,
108
+ C.HISTORY_MAX,
109
+ );
110
+ }
111
+
112
+ if (typeof state.lastTelemetry?.species === 'number') {
113
+ state.histories.speciesCount = MazeUtils.pushHistory(
114
+ state.histories.speciesCount,
115
+ state.lastTelemetry.species,
116
+ C.HISTORY_MAX,
117
+ );
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Build the rich telemetry detail snapshot shown in browser hooks and exported snapshots.
123
+ *
124
+ * @param state - Mutable dashboard state with histories and current best candidate.
125
+ * @param neat - Optional NEAT instance used for population-level telemetry.
126
+ * @returns Detailed telemetry snapshot or `null` when no data is available.
127
+ */
128
+ export function createDetailedStatsSnapshot(
129
+ state: DashboardManagerState,
130
+ neat?: unknown,
131
+ ): AsciiMazeDetailedStats | null {
132
+ const telemetry = state.lastTelemetry;
133
+ if (!telemetry && !state.currentBest) return null;
134
+
135
+ try {
136
+ const complexitySnapshot = telemetry?.complexity;
137
+ const populationStats = computePopulationStats(state, neat);
138
+ const bestFitnessValue = state.currentBest?.result?.fitness;
139
+ if (populationStats.mean == null && typeof bestFitnessValue === 'number') {
140
+ populationStats.mean = +bestFitnessValue.toFixed(2);
141
+ }
142
+ if (
143
+ populationStats.median == null &&
144
+ typeof bestFitnessValue === 'number'
145
+ ) {
146
+ populationStats.median = +bestFitnessValue.toFixed(2);
147
+ }
148
+ if (
149
+ populationStats.speciesCount == null &&
150
+ typeof telemetry?.species === 'number'
151
+ ) {
152
+ populationStats.speciesCount = telemetry.species;
153
+ }
154
+
155
+ const sparklines = {
156
+ fitness:
157
+ buildDashboardSparkline(
158
+ state.histories.bestFitness,
159
+ C.GENERAL_SPARK_WIDTH,
160
+ ) || null,
161
+ nodes:
162
+ buildDashboardSparkline(
163
+ state.histories.complexityNodes,
164
+ C.GENERAL_SPARK_WIDTH,
165
+ ) || null,
166
+ conns:
167
+ buildDashboardSparkline(
168
+ state.histories.complexityConns,
169
+ C.GENERAL_SPARK_WIDTH,
170
+ ) || null,
171
+ hyper:
172
+ buildDashboardSparkline(
173
+ state.histories.hypervolume,
174
+ C.GENERAL_SPARK_WIDTH,
175
+ ) || null,
176
+ progress:
177
+ buildDashboardSparkline(
178
+ state.histories.progress,
179
+ C.GENERAL_SPARK_WIDTH,
180
+ ) || null,
181
+ species:
182
+ buildDashboardSparkline(
183
+ state.histories.speciesCount,
184
+ C.GENERAL_SPARK_WIDTH,
185
+ ) || null,
186
+ } as const;
187
+
188
+ const rawFrontsArray = Array.isArray(telemetry?.fronts)
189
+ ? telemetry.fronts
190
+ : null;
191
+ const neatRuntime = neat as NeatInstance;
192
+ const mutationStatsObj: MutationStatsMap | null =
193
+ telemetry?.mutationStats ?? telemetry?.mutation?.stats ?? null;
194
+
195
+ return {
196
+ generation: state.currentBest?.generation ?? 0,
197
+ bestFitness:
198
+ typeof bestFitnessValue === 'number' ? bestFitnessValue : null,
199
+ bestFitnessDelta: computeBestFitnessDelta(state.histories.bestFitness),
200
+ saturationFraction:
201
+ typeof state.currentBest?.result?.saturationFraction === 'number'
202
+ ? state.currentBest.result.saturationFraction
203
+ : null,
204
+ actionEntropy:
205
+ typeof state.currentBest?.result?.actionEntropy === 'number'
206
+ ? state.currentBest.result.actionEntropy
207
+ : null,
208
+ populationMean: populationStats.mean,
209
+ populationMedian: populationStats.median,
210
+ enabledConnRatio: populationStats.enabledRatio,
211
+ complexity: complexitySnapshot || null,
212
+ simplifyPhaseActive: Boolean(
213
+ (typeof complexitySnapshot?.growthNodes === 'number' &&
214
+ complexitySnapshot.growthNodes < 0) ||
215
+ (typeof complexitySnapshot?.growthConns === 'number' &&
216
+ complexitySnapshot.growthConns < 0),
217
+ ),
218
+ perf: telemetry?.perf || null,
219
+ lineage: telemetry?.lineage || null,
220
+ diversity: telemetry?.diversity || null,
221
+ speciesCount: populationStats.speciesCount,
222
+ topSpeciesSizes: computeTopSpeciesSizes(state, neat),
223
+ objectives: telemetry?.objectives || null,
224
+ paretoFrontSizes: rawFrontsArray
225
+ ? rawFrontsArray.map((frontValue) => frontValue?.length ?? 0)
226
+ : null,
227
+ firstFrontSize: rawFrontsArray?.[0]?.length || 0,
228
+ hypervolume:
229
+ typeof telemetry?.hyper === 'number' ? telemetry.hyper : null,
230
+ noveltyArchiveSize: safeInvoke(
231
+ () =>
232
+ neatRuntime?.getNoveltyArchive
233
+ ? (neatRuntime.getNoveltyArchive()?.length ?? null)
234
+ : null,
235
+ null,
236
+ ),
237
+ operatorAcceptance: computeOperatorAcceptance(state, neat),
238
+ topMutations: computeTopMutations(state, mutationStatsObj),
239
+ mutationStats: mutationStatsObj || null,
240
+ trends: sparklines,
241
+ histories: {
242
+ bestFitness: sliceDashboardHistoryForExport(
243
+ state.histories.bestFitness,
244
+ ),
245
+ nodes: sliceDashboardHistoryForExport(state.histories.complexityNodes),
246
+ conns: sliceDashboardHistoryForExport(state.histories.complexityConns),
247
+ hyper: sliceDashboardHistoryForExport(state.histories.hypervolume),
248
+ progress: sliceDashboardHistoryForExport(state.histories.progress),
249
+ species: sliceDashboardHistoryForExport(state.histories.speciesCount),
250
+ },
251
+ timestamp: Date.now(),
252
+ };
253
+ } catch {
254
+ return null;
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Emit the structured telemetry payload used by browser hosts and runtime hooks.
260
+ *
261
+ * @param state - Mutable dashboard state used to assemble the payload.
262
+ * @param generation - Current generation number.
263
+ * @param telemetryHook - Optional runtime hook installed by the browser host.
264
+ */
265
+ export function emitTelemetryPayload(
266
+ state: DashboardManagerState,
267
+ generation: number,
268
+ telemetryHook?: (payload: DashboardTelemetryPayload) => void,
269
+ ): void {
270
+ try {
271
+ const elapsedMs =
272
+ state.perfStart != null && globalThis.performance?.now
273
+ ? globalThis.performance.now() - state.perfStart
274
+ : state.runStartTs
275
+ ? Date.now() - state.runStartTs
276
+ : 0;
277
+ const generationsPerSecond =
278
+ elapsedMs > 0 ? generation / (elapsedMs / 1000) : 0;
279
+ const payload: DashboardTelemetryPayload = {
280
+ type: 'asciiMaze:telemetry',
281
+ generation,
282
+ bestFitness: state.lastBestFitness,
283
+ progress: state.currentBest?.result?.progress ?? null,
284
+ speciesCount: state.histories.speciesCount.at(-1) ?? null,
285
+ gensPerSec: +generationsPerSecond.toFixed(3),
286
+ timestamp: Date.now(),
287
+ details: state.lastDetailedStats,
288
+ };
289
+
290
+ if (typeof window !== 'undefined') {
291
+ try {
292
+ window.dispatchEvent(
293
+ new CustomEvent('asciiMazeTelemetry', { detail: payload }),
294
+ );
295
+ } catch {
296
+ // Ignore browser event dispatch failures.
297
+ }
298
+ try {
299
+ if (window.parent && window.parent !== window) {
300
+ window.parent.postMessage(payload, '*');
301
+ }
302
+ } catch {
303
+ // Ignore parent-frame postMessage failures.
304
+ }
305
+ (
306
+ window as Window & {
307
+ asciiMazeLastTelemetry?: DashboardTelemetryPayload;
308
+ }
309
+ ).asciiMazeLastTelemetry = payload;
310
+ }
311
+
312
+ try {
313
+ telemetryHook?.(payload);
314
+ } catch {
315
+ // Ignore runtime telemetry hook failures.
316
+ }
317
+ } catch {
318
+ // Ignore telemetry emission failures.
319
+ }
320
+ }
321
+
322
+ function resolveLastUpdateWallMs(state: DashboardManagerState): number {
323
+ if (state.lastUpdateTs == null) return Date.now();
324
+ if (
325
+ state.perfStart != null &&
326
+ typeof globalThis.performance?.now === 'function' &&
327
+ state.runStartTs != null
328
+ ) {
329
+ return state.runStartTs + (state.lastUpdateTs - state.perfStart);
330
+ }
331
+ return state.lastUpdateTs;
332
+ }
333
+
334
+ function computePopulationStats(
335
+ state: DashboardManagerState,
336
+ neat?: unknown,
337
+ ): {
338
+ mean: number | null;
339
+ median: number | null;
340
+ speciesCount: number | null;
341
+ enabledRatio: number | null;
342
+ } {
343
+ const neatRuntime = neat as NeatInstance;
344
+ if (
345
+ !neatRuntime ||
346
+ !Array.isArray(neatRuntime.population) ||
347
+ neatRuntime.population.length === 0
348
+ ) {
349
+ return {
350
+ mean: null,
351
+ median: null,
352
+ speciesCount: null,
353
+ enabledRatio: null,
354
+ };
355
+ }
356
+
357
+ const { scores } = state.scratch;
358
+ scores.length = 0;
359
+ let enabledConnectionsCount = 0;
360
+ let totalConnectionsCount = 0;
361
+ for (const genome of neatRuntime.population) {
362
+ if (typeof genome?.score === 'number') {
363
+ scores.push(genome.score);
364
+ }
365
+
366
+ if (!Array.isArray(genome?.connections)) continue;
367
+ for (const connectionValue of genome.connections) {
368
+ totalConnectionsCount++;
369
+ if (connectionValue?.enabled !== false) {
370
+ enabledConnectionsCount++;
371
+ }
372
+ }
373
+ }
374
+
375
+ let mean: number | null = null;
376
+ let median: number | null = null;
377
+ if (scores.length) {
378
+ const sum = scores.reduce(
379
+ (runningTotal, scoreValue) => runningTotal + scoreValue,
380
+ 0,
381
+ );
382
+ mean = +(sum / scores.length).toFixed(2);
383
+ const sortedScores = scores.toSorted(
384
+ (leftScore, rightScore) => leftScore - rightScore,
385
+ );
386
+ const middleIndex = Math.floor(sortedScores.length / 2);
387
+ const medianRaw =
388
+ sortedScores.length % 2 === 0
389
+ ? (sortedScores[middleIndex - 1] + sortedScores[middleIndex]) / 2
390
+ : sortedScores[middleIndex];
391
+ median = +medianRaw.toFixed(2);
392
+ }
393
+
394
+ const speciesCount = Array.isArray(neatRuntime.species)
395
+ ? neatRuntime.species.length
396
+ : null;
397
+ const enabledRatio = totalConnectionsCount
398
+ ? +(enabledConnectionsCount / totalConnectionsCount).toFixed(2)
399
+ : null;
400
+
401
+ return { mean, median, speciesCount, enabledRatio };
402
+ }
403
+
404
+ function computeOperatorAcceptance(
405
+ state: DashboardManagerState,
406
+ neat?: unknown,
407
+ ): Array<{ name: string; acceptancePct: number }> | null {
408
+ const neatRuntime = neat as NeatInstance;
409
+ if (typeof neatRuntime?.getOperatorStats !== 'function') return null;
410
+
411
+ let rawOperatorStats: unknown;
412
+ try {
413
+ rawOperatorStats = neatRuntime.getOperatorStats();
414
+ } catch {
415
+ return null;
416
+ }
417
+ if (!Array.isArray(rawOperatorStats) || !rawOperatorStats.length) return null;
418
+
419
+ const scratchBuffer = state.scratch.operatorStats;
420
+ scratchBuffer.length = 0;
421
+ for (const operatorValue of rawOperatorStats) {
422
+ const operatorRecord = operatorValue as Record<string, unknown>;
423
+ if (
424
+ typeof operatorRecord.name === 'string' &&
425
+ typeof operatorRecord.success === 'number' &&
426
+ typeof operatorRecord.attempts === 'number'
427
+ ) {
428
+ scratchBuffer.push(operatorRecord as unknown as OperatorStatsEntry);
429
+ }
430
+ }
431
+ if (!scratchBuffer.length) return null;
432
+
433
+ return scratchBuffer
434
+ .toSorted((leftStat, rightStat) => {
435
+ const leftAcceptance = leftStat.success / Math.max(1, leftStat.attempts);
436
+ const rightAcceptance =
437
+ rightStat.success / Math.max(1, rightStat.attempts);
438
+ return rightAcceptance - leftAcceptance;
439
+ })
440
+ .slice(0, C.TOP_OPERATOR_LIMIT)
441
+ .map((rankedStat) => ({
442
+ name: rankedStat.name,
443
+ acceptancePct: +(
444
+ (100 * rankedStat.success) /
445
+ Math.max(1, rankedStat.attempts)
446
+ ).toFixed(2),
447
+ }));
448
+ }
449
+
450
+ function computeTopMutations(
451
+ state: DashboardManagerState,
452
+ mutationStats: MutationStatsMap | null,
453
+ ): Array<{ name: string; count: number }> | null {
454
+ if (!mutationStats) return null;
455
+
456
+ const mutationEntries = state.scratch.mutationEntries;
457
+ mutationEntries.length = 0;
458
+ for (const [mutationName, mutationCount] of Object.entries(mutationStats)) {
459
+ if (typeof mutationCount === 'number' && Number.isFinite(mutationCount)) {
460
+ mutationEntries.push([mutationName, mutationCount]);
461
+ }
462
+ }
463
+ if (!mutationEntries.length) return null;
464
+
465
+ return mutationEntries
466
+ .toSorted((leftEntry, rightEntry) => rightEntry[1] - leftEntry[1])
467
+ .slice(0, C.TOP_MUTATION_LIMIT)
468
+ .map(([mutationName, mutationCount]) => ({
469
+ name: mutationName,
470
+ count: mutationCount,
471
+ }));
472
+ }
473
+
474
+ function computeTopSpeciesSizes(
475
+ state: DashboardManagerState,
476
+ neat?: unknown,
477
+ ): number[] | null {
478
+ const neatRuntime = neat as NeatInstance;
479
+ if (!Array.isArray(neatRuntime?.species) || !neatRuntime.species.length) {
480
+ return null;
481
+ }
482
+
483
+ const speciesSizes = state.scratch.speciesSizes;
484
+ speciesSizes.length = 0;
485
+ for (const speciesEntry of neatRuntime.species) {
486
+ const memberCount = Array.isArray(speciesEntry?.members)
487
+ ? speciesEntry.members.length
488
+ : 0;
489
+ speciesSizes.push(memberCount);
490
+ }
491
+ if (!speciesSizes.length) return null;
492
+
493
+ return speciesSizes
494
+ .toSorted((leftSize, rightSize) => rightSize - leftSize)
495
+ .slice(0, C.TOP_SPECIES_LIMIT);
496
+ }
497
+
498
+ function computeBestFitnessDelta(history: number[]): number | null {
499
+ const previousSample = history.at(-2);
500
+ const latestSample = history.at(-1);
501
+ if (typeof previousSample !== 'number' || typeof latestSample !== 'number') {
502
+ return null;
503
+ }
504
+ return +(latestSample - previousSample).toFixed(3);
505
+ }
506
+
507
+ function safeInvoke<T>(operation: () => T, fallback: T): T {
508
+ try {
509
+ return operation();
510
+ } catch {
511
+ return fallback;
512
+ }
513
+ }