@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
@@ -3,50 +3,16 @@
3
3
  import { Neat } from '../../../../src/neataptic';
4
4
  import Network from '../../../../src/architecture/network';
5
5
  import { createXorshift32 } from '../rng';
6
- import {
7
- commitObservationMemoryStep,
8
- resolveFlapDecision,
9
- resolveObservationVector,
10
- } from '../browser-entry/browser-entry.observation.utils';
11
- import {
12
- createBirdColor,
13
- resolveDifficultyProfile,
14
- sampleGapCenterY,
15
- } from '../browser-entry/browser-entry.spawn.utils';
16
6
  import type {
17
- WorkerHeuristicObservationFeatures,
18
7
  WorkerInitMessage,
19
- WorkerPlaybackFrameSnapshot,
20
- WorkerPopulationPipe,
21
8
  WorkerPlaybackState,
22
9
  WorkerRequestMessage,
23
10
  WorkerRequestPlaybackStepMessage,
24
11
  WorkerResponseMessage,
12
+ WorkerStartPlaybackMessage,
25
13
  } from './flappy-evolution-worker.types';
26
14
  import { createInitializedWorkerRuntime } from './flappy-evolution-worker.runtime.service';
27
- import { FLAPPY_BIRD_VIEWPORT_X_RATIO } from '../constants/constants';
28
- import {
29
- FLAPPY_BIRD_RADIUS_PX,
30
- FLAPPY_BIRD_X_PX,
31
- FLAPPY_PIPE_COLLISION_ENTRANCE_EXPAND_PX,
32
- FLAPPY_PIPE_COLLISION_SIDE_EXPAND_PX,
33
- FLAPPY_CONTROL_SUBSTEPS_PER_FRAME,
34
- FLAPPY_ENABLE_RUNTIME_INSTRUMENTATION,
35
- FLAPPY_FLAP_VELOCITY_PX_PER_FRAME,
36
- FLAPPY_GRAVITY_PX_PER_FRAME2,
37
- FLAPPY_MAX_FALL_SPEED_PX_PER_FRAME,
38
- FLAPPY_PIPE_WIDTH_PX,
39
- FLAPPY_WORLD_HEIGHT_PX,
40
- } from '../constants/constants';
41
- import {
42
- FLAPPY_WORKER_GEN0_PRETRAIN_BATCH_SIZE,
43
- FLAPPY_WORKER_GEN0_PRETRAIN_BIAS_NOISE_STDDEV,
44
- FLAPPY_WORKER_GEN0_PRETRAIN_ITERATIONS,
45
- FLAPPY_WORKER_GEN0_PRETRAIN_RATE,
46
- FLAPPY_WORKER_GEN0_PRETRAIN_SAMPLE_COUNT,
47
- FLAPPY_WORKER_GEN0_PRETRAIN_VISIBLE_WORLD_WIDTH_PX,
48
- FLAPPY_WORKER_GEN0_PRETRAIN_WEIGHT_NOISE_STDDEV,
49
- } from './flappy-evolution-worker.constants';
15
+ import { FLAPPY_ENABLE_RUNTIME_INSTRUMENTATION } from '../constants/constants';
50
16
  import {
51
17
  createWorkerErrorMessage,
52
18
  createWorkerErrorMessageFromUnknown,
@@ -59,21 +25,26 @@ import {
59
25
  processWorkerPlaybackStep,
60
26
  } from './flappy-evolution-worker.playback.service';
61
27
  import { createWorkerPlaybackSnapshot } from './flappy-evolution-worker.snapshot.utils';
62
- import { createSharedObservationMemoryState } from '../flappy.simulation.shared.utils';
63
- import {
64
- createWorkerPopulationRenderState,
65
- stepWorkerPopulationFrame,
66
- } from './flappy-evolution-worker.simulation.utils';
28
+ import { createWorkerPopulationRenderState } from './flappy-evolution-worker.simulation.utils';
29
+ import { stepWorkerPopulationFrame } from './flappy-evolution-worker.simulation.frame.service';
30
+ import { warmStartWorkerGenerationZeroIfNeeded } from './flappy-evolution-worker.warm-start.service';
31
+
32
+ const FLAPPY_WORKER_INITIAL_SEED = 0;
33
+ const FLAPPY_WORKER_INITIAL_WINNER_INDEX = -1;
34
+
35
+ type WorkerMutableRuntimeState = {
36
+ stopped: boolean;
37
+ neatRuntime: Neat | undefined;
38
+ currentPopulation: Network[];
39
+ currentPlaybackState: WorkerPlaybackState | undefined;
40
+ currentPlaybackRng: ReturnType<typeof createXorshift32> | undefined;
41
+ playbackWinnerIndex: number;
42
+ initializationPromise: Promise<void> | undefined;
43
+ workerInitSeed: number;
44
+ generationZeroWarmStartApplied: boolean;
45
+ };
67
46
 
68
- let stopped = false;
69
- let neatRuntime: Neat | undefined;
70
- let currentPopulation: Network[] = [];
71
- let currentPlaybackState: WorkerPlaybackState | undefined;
72
- let currentPlaybackRng: ReturnType<typeof createXorshift32> | undefined;
73
- let playbackWinnerIndex = -1;
74
- let initializationPromise: Promise<void> | undefined;
75
- let workerInitSeed = 0;
76
- let generationZeroWarmStartApplied = false;
47
+ const workerMutableRuntimeState = createWorkerMutableRuntimeState();
77
48
 
78
49
  /**
79
50
  * Main worker message router.
@@ -90,50 +61,84 @@ let generationZeroWarmStartApplied = false;
90
61
  * - `request-playback-step`: advance playback and stream telemetry snapshots.
91
62
  * - `stop`: mark worker as stopped (future generation requests fail fast).
92
63
  */
93
- self.onmessage = (event: MessageEvent<WorkerRequestMessage>) => {
94
- // Step 1: Decode the incoming discriminated-union message.
95
- const workerMessage = event.data;
64
+ self.onmessage = createWorkerMessageHandler(workerMutableRuntimeState);
65
+
66
+ /**
67
+ * Creates the mutable worker runtime state container.
68
+ *
69
+ * @returns Mutable worker runtime state.
70
+ */
71
+ function createWorkerMutableRuntimeState(): WorkerMutableRuntimeState {
72
+ // Step 1: Initialize the worker state with empty runtime and playback fields.
73
+ return {
74
+ stopped: false,
75
+ neatRuntime: undefined,
76
+ currentPopulation: [],
77
+ currentPlaybackState: undefined,
78
+ currentPlaybackRng: undefined,
79
+ playbackWinnerIndex: FLAPPY_WORKER_INITIAL_WINNER_INDEX,
80
+ initializationPromise: undefined,
81
+ workerInitSeed: FLAPPY_WORKER_INITIAL_SEED,
82
+ generationZeroWarmStartApplied: false,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Creates the top-level worker message handler.
88
+ *
89
+ * @param workerMutableRuntimeState - Mutable worker runtime state.
90
+ * @returns Worker message handler.
91
+ */
92
+ function createWorkerMessageHandler(
93
+ workerMutableRuntimeState: WorkerMutableRuntimeState,
94
+ ): (event: MessageEvent<WorkerRequestMessage>) => void {
95
+ // Step 1: Capture the protocol handlers so each message reuses the same stateful callbacks.
96
+ const workerProtocolHandlers = createWorkerProtocolHandlers(
97
+ workerMutableRuntimeState,
98
+ );
96
99
 
97
- // Step 2: Route inbound protocol message to worker lifecycle handlers.
98
- routeWorkerProtocolMessage(workerMessage, {
99
- markStopped: () => {
100
- stopped = true;
100
+ // Step 2: Return the thin orchestration handler used by `self.onmessage`.
101
+ return (event: MessageEvent<WorkerRequestMessage>): void => {
102
+ const workerMessage = event.data;
103
+ routeWorkerProtocolMessage(workerMessage, workerProtocolHandlers);
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Creates protocol handlers bound to the mutable worker runtime state.
109
+ *
110
+ * @param workerMutableRuntimeState - Mutable worker runtime state.
111
+ * @returns Protocol handler bundle.
112
+ */
113
+ function createWorkerProtocolHandlers(
114
+ workerMutableRuntimeState: WorkerMutableRuntimeState,
115
+ ) {
116
+ // Step 1: Bind each protocol operation to the shared mutable runtime state.
117
+ return {
118
+ markStopped: (): void => {
119
+ workerMutableRuntimeState.stopped = true;
120
+ },
121
+ beginInitialization: (payload: WorkerInitMessage['payload']): void => {
122
+ beginWorkerInitialization(workerMutableRuntimeState, payload);
101
123
  },
102
- beginInitialization: (payload) => {
103
- initializationPromise = initializeRuntime(payload).catch(
104
- (error: unknown) => {
105
- postWorkerMessage(createWorkerErrorMessageFromUnknown(error));
106
- throw error;
107
- },
108
- );
109
- void initializationPromise.catch(() => {
110
- postWorkerMessage(
111
- createWorkerErrorMessage(FLAPPY_WORKER_INIT_FAILED_ERROR_MESSAGE),
112
- );
113
- });
124
+ beginGenerationRequest: (): void => {
125
+ beginWorkerGenerationRequest(workerMutableRuntimeState);
114
126
  },
115
- beginGenerationRequest: () => {
116
- void evolveAndPublishGeneration().catch((error: unknown) => {
117
- postWorkerMessage(createWorkerErrorMessageFromUnknown(error));
118
- });
127
+ hasPopulation: (): boolean =>
128
+ workerMutableRuntimeState.currentPopulation.length > 0,
129
+ startPlayback: (payload: WorkerStartPlaybackMessage['payload']): void => {
130
+ beginWorkerPlayback(workerMutableRuntimeState, payload);
119
131
  },
120
- hasPopulation: () =>
121
- Array.isArray(currentPopulation) && currentPopulation.length > 0,
122
- startPlayback: (payload) => {
123
- const nextPlaybackSessionState = beginWorkerPlaybackSession({
124
- currentPopulation,
125
- payload,
126
- createPopulationRenderState: createWorkerPopulationRenderState,
127
- });
128
- currentPlaybackState = nextPlaybackSessionState.currentPlaybackState;
129
- currentPlaybackRng = nextPlaybackSessionState.currentPlaybackRng;
130
- playbackWinnerIndex = nextPlaybackSessionState.playbackWinnerIndex;
132
+ hasPlaybackState: (): boolean =>
133
+ workerMutableRuntimeState.currentPlaybackState != null,
134
+ processPlaybackStep: (
135
+ payload: WorkerRequestPlaybackStepMessage['payload'],
136
+ ): void => {
137
+ processWorkerPlaybackStepRequest(workerMutableRuntimeState, payload);
131
138
  },
132
- hasPlaybackState: () => currentPlaybackState != null,
133
- processPlaybackStep,
134
139
  postWorkerMessage,
135
- });
136
- };
140
+ };
141
+ }
137
142
 
138
143
  /**
139
144
  * Initializes the worker-local NEAT runtime used by browser evolution playback.
@@ -147,13 +152,15 @@ self.onmessage = (event: MessageEvent<WorkerRequestMessage>) => {
147
152
  * @returns Promise resolved when runtime setup is complete.
148
153
  */
149
154
  async function initializeRuntime(
155
+ workerMutableRuntimeState: WorkerMutableRuntimeState,
150
156
  initPayload: WorkerInitMessage['payload'],
151
157
  ): Promise<void> {
152
158
  // Step 1: Persist the worker seed so warm-start logic can reuse it deterministically.
153
- workerInitSeed = initPayload.rngSeed;
159
+ workerMutableRuntimeState.workerInitSeed = initPayload.rngSeed;
154
160
 
155
161
  // Step 2: Build and configure the NEAT runtime controller.
156
- neatRuntime = createInitializedWorkerRuntime(initPayload);
162
+ workerMutableRuntimeState.neatRuntime =
163
+ createInitializedWorkerRuntime(initPayload);
157
164
  }
158
165
 
159
166
  /**
@@ -166,14 +173,23 @@ async function initializeRuntime(
166
173
  *
167
174
  * @returns Promise resolved after generation payload is posted.
168
175
  */
169
- async function evolveAndPublishGeneration(): Promise<void> {
176
+ async function evolveAndPublishGeneration(
177
+ workerMutableRuntimeState: WorkerMutableRuntimeState,
178
+ ): Promise<void> {
170
179
  const generationPayload = await evolveAndBuildGenerationReadyMessage({
171
- initializationPromise,
172
- neatRuntime,
173
- isStopped: () => stopped,
174
- warmStartGenerationZeroIfNeeded,
180
+ initializationPromise: workerMutableRuntimeState.initializationPromise,
181
+ neatRuntime: workerMutableRuntimeState.neatRuntime,
182
+ isStopped: () => workerMutableRuntimeState.stopped,
183
+ warmStartGenerationZeroIfNeeded: (neatController) =>
184
+ warmStartWorkerGenerationZeroIfNeeded(neatController, {
185
+ workerInitSeed: workerMutableRuntimeState.workerInitSeed,
186
+ generationZeroWarmStartApplied:
187
+ workerMutableRuntimeState.generationZeroWarmStartApplied,
188
+ }).then(() => {
189
+ workerMutableRuntimeState.generationZeroWarmStartApplied = true;
190
+ }),
175
191
  setCurrentPopulation: (nextPopulation) => {
176
- currentPopulation = nextPopulation;
192
+ workerMutableRuntimeState.currentPopulation = nextPopulation;
177
193
  },
178
194
  });
179
195
 
@@ -181,263 +197,75 @@ async function evolveAndPublishGeneration(): Promise<void> {
181
197
  }
182
198
 
183
199
  /**
184
- * Applies a one-time generation-0 warm-start to improve initial demo quality.
185
- *
186
- * Educational note:
187
- * NEAT in this setup starts from cloned copies of one seed architecture.
188
- * A short supervised warm-up can reduce the "all-random" first-generation
189
- * behavior while preserving evolutionary search in later generations.
190
- *
191
- * Strategy:
192
- * 1) Build heuristic-labeled synthetic observations.
193
- * 2) Train one template network quickly.
194
- * 3) Copy template parameters to each genome with small Gaussian noise.
195
- *
196
- * @param neatController - Initialized NEAT runtime.
197
- * @returns Promise resolved when warm-start decision completes.
198
- */
199
- async function warmStartGenerationZeroIfNeeded(
200
- neatController: Neat,
201
- ): Promise<void> {
202
- // Step 1: Exit fast when warm-start is already processed.
203
- if (generationZeroWarmStartApplied) return;
204
-
205
- // Step 2: Warm-start only generation 0 to avoid skewing later evolution.
206
- if (neatController.generation !== 0) {
207
- generationZeroWarmStartApplied = true;
208
- return;
209
- }
210
-
211
- // Step 3: Validate population availability.
212
- const population = neatController.population;
213
- if (!Array.isArray(population) || population.length === 0) {
214
- generationZeroWarmStartApplied = true;
215
- return;
216
- }
217
-
218
- // Step 1: Build a small synthetic dataset labeled by a simple heuristic.
219
- // The goal is NOT to solve Flappy here, only to avoid a totally random first generation.
220
- const warmStartRng = createXorshift32(workerInitSeed ^ 0x9e37_79b9);
221
- const trainingSet = buildHeuristicPretrainSet(
222
- warmStartRng,
223
- FLAPPY_WORKER_GEN0_PRETRAIN_SAMPLE_COUNT,
224
- );
225
-
226
- // Step 2: Train a single template network (fast), then seed the whole population from it.
227
- const templateNetwork = population[0]?.clone();
228
- if (!templateNetwork) {
229
- generationZeroWarmStartApplied = true;
230
- return;
231
- }
232
-
233
- try {
234
- templateNetwork.train(trainingSet, {
235
- iterations: FLAPPY_WORKER_GEN0_PRETRAIN_ITERATIONS,
236
- rate: FLAPPY_WORKER_GEN0_PRETRAIN_RATE,
237
- batchSize: FLAPPY_WORKER_GEN0_PRETRAIN_BATCH_SIZE,
238
- optimizer: 'adam',
239
- // Keep the training loop simple and deterministic.
240
- mixedPrecision: false,
241
- });
242
- } catch {
243
- // If training fails for any reason (e.g., incompatible topology), fall back to pure noise seeding.
244
- }
245
-
246
- // Step 3: Copy trained weights/biases into each genome with small noise for diversity.
247
- for (const genome of population) {
248
- applyTemplateWeightsWithNoise(genome, templateNetwork, warmStartRng, {
249
- weightStdDev: FLAPPY_WORKER_GEN0_PRETRAIN_WEIGHT_NOISE_STDDEV,
250
- biasStdDev: FLAPPY_WORKER_GEN0_PRETRAIN_BIAS_NOISE_STDDEV,
251
- });
252
- (genome as unknown as { score?: number }).score = undefined;
253
- }
254
-
255
- generationZeroWarmStartApplied = true;
256
- }
257
-
258
- /**
259
- * Builds synthetic supervised samples for generation-0 behavior cloning.
260
- *
261
- * Educational note:
262
- * Samples are generated from plausible Flappy states and passed through the
263
- * exact observation encoder used during runtime. This keeps feature semantics
264
- * aligned between pretraining and live simulation.
200
+ * Begins worker initialization and captures asynchronous failures.
265
201
  *
266
- * @param rng - Deterministic random source.
267
- * @param sampleCount - Requested number of synthetic samples.
268
- * @returns Supervised dataset of { input, output } pairs.
202
+ * @param workerMutableRuntimeState - Mutable worker runtime state.
203
+ * @param initPayload - Initialization payload.
204
+ * @returns Nothing.
269
205
  */
270
- function buildHeuristicPretrainSet(
271
- rng: ReturnType<typeof createXorshift32>,
272
- sampleCount: number,
273
- ): Array<{ input: number[]; output: number[] }> {
274
- // Step 1: Resolve bounded sample count and a baseline difficulty profile.
275
- const clampedSampleCount = Math.max(8, Math.trunc(sampleCount));
276
- const difficultyProfile = resolveDifficultyProfile(0);
277
- const trainingSet: Array<{ input: number[]; output: number[] }> = [];
278
-
279
- // Step 2: Sample state tuples, encode observations, and attach heuristic labels.
280
- for (let sampleIndex = 0; sampleIndex < clampedSampleCount; sampleIndex++) {
281
- const pipeGapPx = difficultyProfile.pipeGapPx;
282
- const gapHalfPx = pipeGapPx * 0.5;
283
-
284
- const birdYPx = rng.nextFloat01() * FLAPPY_WORLD_HEIGHT_PX;
285
- const velocityYPxPerFrame =
286
- (rng.nextFloat01() * 2 - 1) * FLAPPY_MAX_FALL_SPEED_PX_PER_FRAME;
287
-
288
- const pipeXMinPx = FLAPPY_BIRD_X_PX + 40;
289
- const pipeXMaxPx =
290
- FLAPPY_BIRD_X_PX +
291
- FLAPPY_WORKER_GEN0_PRETRAIN_VISIBLE_WORLD_WIDTH_PX * 0.92;
292
- const pipeXSamplePx =
293
- pipeXMinPx + rng.nextFloat01() * Math.max(1, pipeXMaxPx - pipeXMinPx);
294
-
295
- const minGapCenterYPx = gapHalfPx;
296
- const maxGapCenterYPx = Math.max(
297
- minGapCenterYPx,
298
- FLAPPY_WORLD_HEIGHT_PX - gapHalfPx,
299
- );
300
- const gapCenterYPx =
301
- minGapCenterYPx + rng.nextFloat01() * (maxGapCenterYPx - minGapCenterYPx);
302
-
303
- const pipes: WorkerPopulationPipe[] = [
304
- {
305
- id: 1,
306
- xPx: pipeXSamplePx,
307
- gapCenterYPx,
308
- gapSizePx: pipeGapPx,
309
- },
310
- ];
311
-
312
- const observationMemoryState = createSharedObservationMemoryState();
313
- const observation = resolveObservationVector(
314
- birdYPx,
315
- velocityYPxPerFrame,
316
- pipes,
317
- FLAPPY_WORKER_GEN0_PRETRAIN_VISIBLE_WORLD_WIDTH_PX,
318
- FLAPPY_WORLD_HEIGHT_PX,
319
- difficultyProfile,
320
- difficultyProfile.pipeSpawnIntervalFrames,
321
- observationMemoryState,
322
- );
206
+ function beginWorkerInitialization(
207
+ workerMutableRuntimeState: WorkerMutableRuntimeState,
208
+ initPayload: WorkerInitMessage['payload'],
209
+ ): void {
210
+ // Step 1: Start initialization and retain the promise for later generation requests.
211
+ workerMutableRuntimeState.initializationPromise = initializeRuntime(
212
+ workerMutableRuntimeState,
213
+ initPayload,
214
+ ).catch((error: unknown) => {
215
+ postWorkerMessage(createWorkerErrorMessageFromUnknown(error));
216
+ throw error;
217
+ });
323
218
 
324
- const shouldFlap = resolveHeuristicTeacherFlapDecision(
325
- observation.observationFeatures,
219
+ // Step 2: Emit a stable init-failed error if the initialization promise rejects.
220
+ void workerMutableRuntimeState.initializationPromise.catch(() => {
221
+ postWorkerMessage(
222
+ createWorkerErrorMessage(FLAPPY_WORKER_INIT_FAILED_ERROR_MESSAGE),
326
223
  );
327
- trainingSet.push({
328
- input: observation.observationVector,
329
- output: shouldFlap ? [0, 1] : [1, 0],
330
- });
331
- }
332
-
333
- // Step 3: Return immutable training pairs consumed by `Network.train`.
334
- return trainingSet;
335
- }
336
-
337
- /**
338
- * Heuristic teacher policy used to label synthetic pretraining samples.
339
- *
340
- * Educational note:
341
- * The teacher is intentionally simple and interpretable, acting as a rough
342
- * "stay near next gap center" controller. The objective is biasing the initial
343
- * policy away from chaotic behavior, not producing expert trajectories.
344
- *
345
- * @param features - Structured observation features for one synthetic state.
346
- * @returns True when the teacher says "flap".
347
- */
348
- function resolveHeuristicTeacherFlapDecision(
349
- features: WorkerHeuristicObservationFeatures,
350
- ): boolean {
351
- // Heuristic intent:
352
- // - If the bird is below the upcoming gap center, flap to correct upward.
353
- // - Avoid spamming flap when already moving upward quickly.
354
- // - Prefer corrections closer to the pipe (or when urgency is high).
355
- const isBelowNextGapCenter = features.normalizedDeltaToNextGap > 0.035;
356
- const isNotAlreadyRisingFast = features.normalizedVelocity > -0.25;
357
- const isNearGapEntry = features.normalizedFramesToGapEntry < 0.8;
358
- const isUrgent = features.normalizedEntryUrgency > 0.18;
359
-
360
- return (
361
- isBelowNextGapCenter &&
362
- isNotAlreadyRisingFast &&
363
- (isNearGapEntry || isUrgent)
364
- );
224
+ });
365
225
  }
366
226
 
367
227
  /**
368
- * Copies template parameters into a genome and injects small Gaussian noise.
228
+ * Begins one asynchronous generation request and captures failures.
369
229
  *
370
- * Educational note:
371
- * Copying one trained template without noise would collapse diversity. Adding
372
- * mild deterministic jitter keeps the population near a useful baseline while
373
- * preserving variation for selection and mutation.
374
- *
375
- * @param genome - Target genome to mutate in-place.
376
- * @param template - Trained template source network.
377
- * @param rng - Deterministic random source for noise sampling.
378
- * @param noise - Standard deviations for weight and bias perturbations.
230
+ * @param workerMutableRuntimeState - Mutable worker runtime state.
379
231
  * @returns Nothing.
380
232
  */
381
- function applyTemplateWeightsWithNoise(
382
- genome: Network,
383
- template: Network,
384
- rng: ReturnType<typeof createXorshift32>,
385
- noise: { weightStdDev: number; biasStdDev: number },
233
+ function beginWorkerGenerationRequest(
234
+ workerMutableRuntimeState: WorkerMutableRuntimeState,
386
235
  ): void {
387
- // Step 1: Clamp noise scales to valid non-negative values.
388
- const weightStdDev = Math.max(0, noise.weightStdDev);
389
- const biasStdDev = Math.max(0, noise.biasStdDev);
390
-
391
- // Step 2: Copy node biases with additive Gaussian noise.
392
- const genomeNodes = genome.nodes;
393
- const templateNodes = template.nodes;
394
- const nodeCopyCount = Math.min(genomeNodes.length, templateNodes.length);
395
- for (let nodeIndex = 0; nodeIndex < nodeCopyCount; nodeIndex++) {
396
- const templateBias = templateNodes[nodeIndex]?.bias ?? 0;
397
- genomeNodes[nodeIndex].bias =
398
- templateBias + sampleGaussian(rng) * biasStdDev;
399
- }
400
-
401
- // Step 3: Copy connection weights with additive Gaussian noise.
402
- const genomeConnections = genome.connections;
403
- const templateConnections = template.connections;
404
- const connectionCopyCount = Math.min(
405
- genomeConnections.length,
406
- templateConnections.length,
236
+ // Step 1: Run the generation pipeline and publish worker errors on failure.
237
+ void evolveAndPublishGeneration(workerMutableRuntimeState).catch(
238
+ (error: unknown) => {
239
+ postWorkerMessage(createWorkerErrorMessageFromUnknown(error));
240
+ },
407
241
  );
408
- for (
409
- let connectionIndex = 0;
410
- connectionIndex < connectionCopyCount;
411
- connectionIndex++
412
- ) {
413
- const templateWeight = templateConnections[connectionIndex]?.weight ?? 0;
414
- genomeConnections[connectionIndex].weight =
415
- templateWeight + sampleGaussian(rng) * weightStdDev;
416
- }
417
242
  }
418
243
 
419
244
  /**
420
- * Samples one standard-normal value using the Box–Muller transform.
245
+ * Begins a new playback session from the current evolved population.
421
246
  *
422
- * Educational note:
423
- * Uniform RNG is easy to produce, but Gaussian noise is usually a better fit
424
- * for small parameter perturbations because it concentrates probability around
425
- * zero while still occasionally exploring larger offsets.
426
- *
427
- * @param rng - Deterministic random source.
428
- * @returns One approximately standard-normal random value.
247
+ * @param workerMutableRuntimeState - Mutable worker runtime state.
248
+ * @param payload - Playback start payload.
249
+ * @returns Nothing.
429
250
  */
430
- function sampleGaussian(rng: ReturnType<typeof createXorshift32>): number {
431
- // Box–Muller transform (deterministic given rng).
432
- // Step 1: Avoid `log(0)` by flooring uniforms away from zero.
433
- const epsilon = 1e-12;
434
- const uniformA = Math.max(epsilon, rng.nextFloat01());
435
- const uniformB = Math.max(epsilon, rng.nextFloat01());
251
+ function beginWorkerPlayback(
252
+ workerMutableRuntimeState: WorkerMutableRuntimeState,
253
+ payload: { visibleWorldWidthPx: number; visibleWorldHeightPx: number },
254
+ ): void {
255
+ // Step 1: Create the next playback session from the current population snapshot.
256
+ const nextPlaybackSessionState = beginWorkerPlaybackSession({
257
+ currentPopulation: workerMutableRuntimeState.currentPopulation,
258
+ payload,
259
+ createPopulationRenderState: createWorkerPopulationRenderState,
260
+ });
436
261
 
437
- // Step 2: Convert two uniforms into one normal sample.
438
- const magnitude = Math.sqrt(-2 * Math.log(uniformA));
439
- const angle = 2 * Math.PI * uniformB;
440
- return magnitude * Math.cos(angle);
262
+ // Step 2: Persist the next playback state, RNG, and winner index.
263
+ workerMutableRuntimeState.currentPlaybackState =
264
+ nextPlaybackSessionState.currentPlaybackState;
265
+ workerMutableRuntimeState.currentPlaybackRng =
266
+ nextPlaybackSessionState.currentPlaybackRng;
267
+ workerMutableRuntimeState.playbackWinnerIndex =
268
+ nextPlaybackSessionState.playbackWinnerIndex;
441
269
  }
442
270
 
443
271
  /**
@@ -451,30 +279,41 @@ function sampleGaussian(rng: ReturnType<typeof createXorshift32>): number {
451
279
  * @param playbackStepPayload - Host-selected simulation-step budget and viewport.
452
280
  * @returns Nothing.
453
281
  */
454
- function processPlaybackStep(
282
+ function processWorkerPlaybackStepRequest(
283
+ workerMutableRuntimeState: WorkerMutableRuntimeState,
455
284
  playbackStepPayload: WorkerRequestPlaybackStepMessage['payload'],
456
285
  ): void {
457
- if (!currentPlaybackState || !currentPlaybackRng) {
286
+ // Step 1: Guard against playback-step requests before playback state exists.
287
+ if (
288
+ !workerMutableRuntimeState.currentPlaybackState ||
289
+ !workerMutableRuntimeState.currentPlaybackRng
290
+ ) {
458
291
  throw new Error(
459
292
  'Playback random source is unavailable. Start playback first.',
460
293
  );
461
294
  }
462
295
 
296
+ // Step 2: Advance playback and build the next compact snapshot payload.
463
297
  const nextPlaybackStepState = processWorkerPlaybackStep({
464
298
  playbackStepPayload,
465
- currentPlaybackState,
466
- currentPlaybackRng,
467
- currentPopulation,
468
- neatRuntime,
299
+ currentPlaybackState: workerMutableRuntimeState.currentPlaybackState,
300
+ currentPlaybackRng: workerMutableRuntimeState.currentPlaybackRng,
301
+ currentPopulation: workerMutableRuntimeState.currentPopulation,
302
+ neatRuntime: workerMutableRuntimeState.neatRuntime,
469
303
  stepPopulationFrame: stepWorkerPopulationFrame,
470
304
  createPlaybackSnapshot: createWorkerPlaybackSnapshot,
471
305
  postWorkerMessage,
472
306
  });
473
307
 
474
- currentPlaybackState = nextPlaybackStepState.currentPlaybackState;
475
- currentPlaybackRng = nextPlaybackStepState.currentPlaybackRng;
476
- currentPopulation = nextPlaybackStepState.currentPopulation;
477
- playbackWinnerIndex = nextPlaybackStepState.playbackWinnerIndex;
308
+ // Step 3: Persist the advanced playback state for the next host request.
309
+ workerMutableRuntimeState.currentPlaybackState =
310
+ nextPlaybackStepState.currentPlaybackState;
311
+ workerMutableRuntimeState.currentPlaybackRng =
312
+ nextPlaybackStepState.currentPlaybackRng;
313
+ workerMutableRuntimeState.currentPopulation =
314
+ nextPlaybackStepState.currentPopulation;
315
+ workerMutableRuntimeState.playbackWinnerIndex =
316
+ nextPlaybackStepState.playbackWinnerIndex;
478
317
  }
479
318
 
480
319
  /**