@reicek/neataptic-ts 0.1.21 → 0.1.23

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 (361) hide show
  1. package/.github/agents/boundary-mapper.agent.md +31 -0
  2. package/.github/agents/docs-scout.agent.md +29 -0
  3. package/.github/agents/plan-scout.agent.md +31 -0
  4. package/.github/agents/solid-split.agent.md +143 -0
  5. package/.github/copilot-instructions.md +119 -0
  6. package/.github/skills/solid-split-playbook/SKILL.md +220 -0
  7. package/.github/skills/solid-split-playbook/assets/docs-checklist.md +34 -0
  8. package/.github/skills/solid-split-playbook/assets/split-plan-template.md +48 -0
  9. package/.github/skills/solid-split-playbook/assets/split-workflow-checklist.md +51 -0
  10. package/.github/skills/trace-analyzer-extension/SKILL.md +63 -0
  11. package/.github/skills/trace-analyzer-extension/assets/extension-checklist.md +24 -0
  12. package/.github/skills/trace-analyzer-extension/references/analyzer-extension-workflow.md +101 -0
  13. package/.github/skills/trace-audit-reporting/SKILL.md +96 -0
  14. package/.github/skills/trace-audit-reporting/assets/performance-report-template.md +123 -0
  15. package/.github/skills/trace-audit-reporting/references/trace-analysis-workflow.md +132 -0
  16. package/package.json +7 -3
  17. package/plans/ES2023 migration +13 -8
  18. package/plans/Evolution_Training_Interoperability_Contracts.md +1 -1
  19. package/plans/Flappy_Bird_Folder_Documentation_Pass.md +53 -0
  20. package/plans/Flappy_Evolution_Worker_Documentation_Pass.md +58 -0
  21. package/plans/Interactive_Examples_and_Learning_Path.md +10 -2
  22. package/plans/Memory_Optimization.md +3 -3
  23. package/plans/README.md +63 -0
  24. package/plans/Roadmap.md +15 -3
  25. package/plans/asciiMaze_SOLID_split.done.md +130 -0
  26. package/plans/flappy_bird_SOLID_split.done.md +67 -0
  27. package/scripts/analyze-trace.ts +590 -0
  28. package/scripts/assets/theme.css +221 -34
  29. package/scripts/copy-examples.mjs +9 -5
  30. package/scripts/export-onnx.mjs +3 -3
  31. package/scripts/generate-bench-tables.mjs +10 -10
  32. package/scripts/generate-bench-tables.ts +10 -10
  33. package/scripts/generate-docs.ts +1415 -449
  34. package/scripts/render-docs-html.ts +15 -8
  35. package/src/README.md +127 -222
  36. package/src/architecture/README.md +117 -184
  37. package/src/architecture/architect.ts +6 -0
  38. package/src/architecture/layer/README.md +38 -38
  39. package/src/architecture/network/README.md +49 -31
  40. package/src/architecture/network/activate/README.md +77 -77
  41. package/src/architecture/network/connect/README.md +15 -13
  42. package/src/architecture/network/deterministic/README.md +7 -7
  43. package/src/architecture/network/evolve/README.md +44 -44
  44. package/src/architecture/network/gating/README.md +20 -20
  45. package/src/architecture/network/genetic/README.md +51 -51
  46. package/src/architecture/network/mutate/README.md +97 -97
  47. package/src/architecture/network/network.types.ts +39 -0
  48. package/src/architecture/network/onnx/README.md +264 -264
  49. package/src/architecture/network/prune/README.md +39 -39
  50. package/src/architecture/network/remove/README.md +26 -26
  51. package/src/architecture/network/serialize/README.md +56 -56
  52. package/src/architecture/network/serialize/network.serialize.json.utils.ts +1 -0
  53. package/src/architecture/network/serialize/network.serialize.utils.ts +6 -1
  54. package/src/architecture/network/serialize/network.serialize.utils.types.ts +1 -1
  55. package/src/architecture/network/slab/README.md +61 -61
  56. package/src/architecture/network/standalone/README.md +24 -24
  57. package/src/architecture/network/stats/README.md +9 -9
  58. package/src/architecture/network/topology/README.md +46 -46
  59. package/src/architecture/network/training/README.md +21 -21
  60. package/src/architecture/network.ts +114 -10
  61. package/src/methods/README.md +9 -87
  62. package/src/multithreading/README.md +8 -77
  63. package/src/multithreading/workers/README.md +2 -2
  64. package/src/multithreading/workers/browser/README.md +0 -6
  65. package/src/multithreading/workers/node/README.md +0 -3
  66. package/src/neat/README.md +623 -568
  67. package/src/neat/neat.evolve.population.utils.ts +29 -5
  68. package/src/neat/neat.helpers.ts +16 -0
  69. package/src/neat/neat.topology-intent.utils.ts +160 -0
  70. package/src/utils/README.md +18 -18
  71. package/test/examples/asciiMaze/README.md +59 -59
  72. package/test/examples/asciiMaze/asciiMaze.e2e.test.ts +14 -9
  73. package/test/examples/asciiMaze/browser-entry/README.md +196 -0
  74. package/test/examples/asciiMaze/browser-entry/browser-entry.abort.services.ts +95 -0
  75. package/test/examples/asciiMaze/browser-entry/browser-entry.constants.ts +23 -0
  76. package/test/examples/asciiMaze/browser-entry/browser-entry.curriculum.services.ts +115 -0
  77. package/test/examples/asciiMaze/browser-entry/browser-entry.globals.services.ts +106 -0
  78. package/test/examples/asciiMaze/browser-entry/browser-entry.host.services.ts +157 -0
  79. package/test/examples/asciiMaze/browser-entry/browser-entry.services.ts +14 -0
  80. package/test/examples/asciiMaze/browser-entry/browser-entry.ts +129 -0
  81. package/test/examples/asciiMaze/browser-entry/browser-entry.types.ts +120 -0
  82. package/test/examples/asciiMaze/browser-entry/browser-entry.utils.ts +98 -0
  83. package/test/examples/asciiMaze/browser-entry.ts +10 -576
  84. package/test/examples/asciiMaze/dashboardManager/README.md +276 -0
  85. package/test/examples/asciiMaze/dashboardManager/archive/README.md +16 -0
  86. package/test/examples/asciiMaze/dashboardManager/archive/dashboardManager.archive.services.ts +267 -0
  87. package/test/examples/asciiMaze/dashboardManager/dashboardManager.constants.ts +35 -0
  88. package/test/examples/asciiMaze/dashboardManager/dashboardManager.services.ts +103 -0
  89. package/test/examples/asciiMaze/dashboardManager/dashboardManager.ts +181 -0
  90. package/test/examples/asciiMaze/dashboardManager/dashboardManager.types.ts +267 -0
  91. package/test/examples/asciiMaze/dashboardManager/dashboardManager.utils.ts +254 -0
  92. package/test/examples/asciiMaze/dashboardManager/live/README.md +14 -0
  93. package/test/examples/asciiMaze/dashboardManager/live/dashboardManager.live.services.ts +264 -0
  94. package/test/examples/asciiMaze/dashboardManager/telemetry/README.md +47 -0
  95. package/test/examples/asciiMaze/dashboardManager/telemetry/dashboardManager.telemetry.services.ts +513 -0
  96. package/test/examples/asciiMaze/dashboardManager.ts +13 -2335
  97. package/test/examples/asciiMaze/evolutionEngine/README.md +1058 -0
  98. package/test/examples/asciiMaze/evolutionEngine/curriculumPhase.ts +90 -0
  99. package/test/examples/asciiMaze/evolutionEngine/engineState.constants.ts +36 -0
  100. package/test/examples/asciiMaze/evolutionEngine/engineState.ts +58 -513
  101. package/test/examples/asciiMaze/evolutionEngine/engineState.types.ts +212 -0
  102. package/test/examples/asciiMaze/evolutionEngine/engineState.utils.ts +301 -0
  103. package/test/examples/asciiMaze/evolutionEngine/evolutionEngine.types.ts +445 -0
  104. package/test/examples/asciiMaze/evolutionEngine/evolutionLoop.ts +81 -50
  105. package/test/examples/asciiMaze/evolutionEngine/optionsAndSetup.ts +2 -4
  106. package/test/examples/asciiMaze/evolutionEngine/populationDynamics.ts +17 -33
  107. package/test/examples/asciiMaze/evolutionEngine/populationPruning.ts +1 -1
  108. package/test/examples/asciiMaze/evolutionEngine/rngAndTiming.ts +1 -2
  109. package/test/examples/asciiMaze/evolutionEngine/sampling.ts +1 -1
  110. package/test/examples/asciiMaze/evolutionEngine/scratchPools.ts +2 -5
  111. package/test/examples/asciiMaze/evolutionEngine/setupHelpers.ts +30 -37
  112. package/test/examples/asciiMaze/evolutionEngine/telemetryMetrics.ts +16 -58
  113. package/test/examples/asciiMaze/evolutionEngine/trainingWarmStart.ts +2 -2
  114. package/test/examples/asciiMaze/evolutionEngine.ts +55 -55
  115. package/test/examples/asciiMaze/fitness.ts +2 -2
  116. package/test/examples/asciiMaze/fitness.types.ts +65 -0
  117. package/test/examples/asciiMaze/interfaces.ts +64 -1352
  118. package/test/examples/asciiMaze/mazeMovement/README.md +356 -0
  119. package/test/examples/asciiMaze/mazeMovement/finalization/README.md +49 -0
  120. package/test/examples/asciiMaze/mazeMovement/finalization/mazeMovement.finalization.ts +138 -0
  121. package/test/examples/asciiMaze/mazeMovement/mazeMovement.constants.ts +101 -0
  122. package/test/examples/asciiMaze/mazeMovement/mazeMovement.services.ts +230 -0
  123. package/test/examples/asciiMaze/mazeMovement/mazeMovement.ts +299 -0
  124. package/test/examples/asciiMaze/mazeMovement/mazeMovement.types.ts +185 -0
  125. package/test/examples/asciiMaze/mazeMovement/mazeMovement.utils.ts +153 -0
  126. package/test/examples/asciiMaze/mazeMovement/policy/README.md +91 -0
  127. package/test/examples/asciiMaze/mazeMovement/policy/mazeMovement.policy.ts +467 -0
  128. package/test/examples/asciiMaze/mazeMovement/runtime/README.md +95 -0
  129. package/test/examples/asciiMaze/mazeMovement/runtime/mazeMovement.runtime.ts +354 -0
  130. package/test/examples/asciiMaze/mazeMovement/shaping/README.md +124 -0
  131. package/test/examples/asciiMaze/mazeMovement/shaping/mazeMovement.shaping.ts +459 -0
  132. package/test/examples/asciiMaze/mazeMovement.ts +12 -2978
  133. package/test/examples/flappy_bird/README.md +193 -88
  134. package/test/examples/flappy_bird/browser-entry/README.md +1441 -0
  135. package/test/examples/flappy_bird/browser-entry/browser-entry.host.utils.ts +4 -324
  136. package/test/examples/flappy_bird/browser-entry/browser-entry.network-view.utils.ts +9 -396
  137. package/test/examples/flappy_bird/browser-entry/browser-entry.playback.utils.ts +6 -714
  138. package/test/examples/flappy_bird/browser-entry/browser-entry.render.types.ts +26 -3
  139. package/test/examples/flappy_bird/browser-entry/browser-entry.runtime.types.ts +16 -1
  140. package/test/examples/flappy_bird/browser-entry/browser-entry.simulation.types.ts +39 -5
  141. package/test/examples/flappy_bird/browser-entry/browser-entry.spawn.utils.ts +11 -31
  142. package/test/examples/flappy_bird/browser-entry/browser-entry.stats.types.ts +32 -4
  143. package/test/examples/flappy_bird/browser-entry/browser-entry.ts +11 -0
  144. package/test/examples/flappy_bird/browser-entry/browser-entry.types.ts +8 -0
  145. package/test/examples/flappy_bird/browser-entry/browser-entry.visualization.types.ts +50 -7
  146. package/test/examples/flappy_bird/browser-entry/browser-entry.visualization.utils.ts +21 -893
  147. package/test/examples/flappy_bird/browser-entry/browser-entry.worker.types.ts +91 -10
  148. package/test/examples/flappy_bird/browser-entry/host/README.md +318 -0
  149. package/test/examples/flappy_bird/browser-entry/host/host.canvas.service.ts +16 -0
  150. package/test/examples/flappy_bird/browser-entry/host/host.constants.ts +20 -0
  151. package/test/examples/flappy_bird/browser-entry/host/host.dom.service.ts +10 -0
  152. package/test/examples/flappy_bird/browser-entry/host/host.resize.service.ts +1 -295
  153. package/test/examples/flappy_bird/browser-entry/host/host.stats.service.ts +14 -0
  154. package/test/examples/flappy_bird/browser-entry/host/host.ts +592 -6
  155. package/test/examples/flappy_bird/browser-entry/host/host.types.ts +13 -0
  156. package/test/examples/flappy_bird/browser-entry/host/resize/README.md +309 -0
  157. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.constants.ts +47 -0
  158. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.services.ts +392 -0
  159. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.ts +132 -0
  160. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.types.ts +92 -0
  161. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.utils.ts +272 -0
  162. package/test/examples/flappy_bird/browser-entry/network-view/README.md +389 -0
  163. package/test/examples/flappy_bird/browser-entry/network-view/network-view.draw.service.ts +13 -0
  164. package/test/examples/flappy_bird/browser-entry/network-view/network-view.labels.utils.ts +12 -0
  165. package/test/examples/flappy_bird/browser-entry/network-view/network-view.layout.utils.ts +14 -0
  166. package/test/examples/flappy_bird/browser-entry/network-view/network-view.topology.utils.ts +267 -0
  167. package/test/examples/flappy_bird/browser-entry/network-view/network-view.ts +823 -7
  168. package/test/examples/flappy_bird/browser-entry/network-view/network-view.types.ts +11 -0
  169. package/test/examples/flappy_bird/browser-entry/playback/README.md +845 -0
  170. package/test/examples/flappy_bird/browser-entry/playback/background/README.md +355 -0
  171. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/README.md +1068 -0
  172. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.batch.services.ts +64 -0
  173. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.cache.services.ts +207 -0
  174. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.constants.ts +197 -0
  175. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.geometry.batch.utils.ts +114 -0
  176. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.geometry.layout.utils.test.ts +96 -0
  177. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.geometry.layout.utils.ts +204 -0
  178. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.geometry.services.ts +49 -0
  179. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.geometry.utils.ts +313 -0
  180. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.layer.services.ts +81 -0
  181. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.math.utils.test.ts +33 -0
  182. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.math.utils.ts +201 -0
  183. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.pulse.selection.utils.ts +171 -0
  184. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.pulse.timing.utils.ts +124 -0
  185. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.pulse.utils.test.ts +279 -0
  186. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.pulse.utils.ts +132 -0
  187. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.scene.services.ts +26 -0
  188. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.services.ts +65 -0
  189. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.ts +48 -0
  190. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.types.ts +342 -0
  191. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.utils.ts +10 -0
  192. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.cache.services.ts +96 -0
  193. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.constants.ts +127 -0
  194. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.draw.services.ts +184 -0
  195. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.scene.services.ts +64 -0
  196. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.services.ts +6 -0
  197. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.ts +53 -0
  198. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.types.ts +105 -0
  199. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.utils.ts +100 -0
  200. package/test/examples/flappy_bird/browser-entry/playback/frame-render/README.md +541 -0
  201. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.bird.utils.ts +180 -0
  202. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.canvas.services.ts +77 -0
  203. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.entity.services.ts +167 -0
  204. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.scene.services.ts +57 -0
  205. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.service.test.ts +176 -0
  206. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.service.ts +113 -0
  207. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.services.ts +35 -0
  208. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.trail.utils.ts +248 -0
  209. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.types.ts +103 -0
  210. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.utils.ts +11 -0
  211. package/test/examples/flappy_bird/browser-entry/playback/playback.constants.ts +1 -1
  212. package/test/examples/flappy_bird/browser-entry/playback/playback.frame-render.service.ts +10 -0
  213. package/test/examples/flappy_bird/browser-entry/playback/playback.iteration.services.ts +192 -0
  214. package/test/examples/flappy_bird/browser-entry/playback/playback.loop.service.ts +12 -0
  215. package/test/examples/flappy_bird/browser-entry/playback/playback.orchestration.types.ts +78 -0
  216. package/test/examples/flappy_bird/browser-entry/playback/playback.render.pipe-outline.service.ts +128 -0
  217. package/test/examples/flappy_bird/browser-entry/playback/playback.render.service.ts +1 -116
  218. package/test/examples/flappy_bird/browser-entry/playback/playback.session.services.ts +184 -0
  219. package/test/examples/flappy_bird/browser-entry/playback/playback.snapshot.utils.test.ts +121 -0
  220. package/test/examples/flappy_bird/browser-entry/playback/playback.snapshot.utils.ts +8 -0
  221. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.layer.services.ts +36 -0
  222. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.service.ts +11 -128
  223. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.services.ts +268 -0
  224. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.types.ts +91 -0
  225. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.utils.ts +11 -4
  226. package/test/examples/flappy_bird/browser-entry/playback/playback.trail.utils.ts +9 -86
  227. package/test/examples/flappy_bird/browser-entry/playback/playback.ts +75 -7
  228. package/test/examples/flappy_bird/browser-entry/playback/playback.types.ts +12 -9
  229. package/test/examples/flappy_bird/browser-entry/playback/playback.worker-channel.utils.ts +11 -123
  230. package/test/examples/flappy_bird/browser-entry/playback/snapshot/README.md +55 -0
  231. package/test/examples/flappy_bird/browser-entry/playback/snapshot/playback.snapshot.services.ts +103 -0
  232. package/test/examples/flappy_bird/browser-entry/playback/snapshot/playback.snapshot.summary.utils.test.ts +45 -0
  233. package/test/examples/flappy_bird/browser-entry/playback/snapshot/playback.snapshot.summary.utils.ts +28 -0
  234. package/test/examples/flappy_bird/browser-entry/playback/trail/README.md +95 -0
  235. package/test/examples/flappy_bird/browser-entry/playback/trail/playback.trail.history.services.test.ts +35 -0
  236. package/test/examples/flappy_bird/browser-entry/playback/trail/playback.trail.history.services.ts +64 -0
  237. package/test/examples/flappy_bird/browser-entry/playback/trail/playback.trail.opacity.utils.test.ts +37 -0
  238. package/test/examples/flappy_bird/browser-entry/playback/trail/playback.trail.opacity.utils.ts +74 -0
  239. package/test/examples/flappy_bird/browser-entry/playback/worker-channel/README.md +71 -0
  240. package/test/examples/flappy_bird/browser-entry/playback/worker-channel/playback.worker-channel.request.services.ts +45 -0
  241. package/test/examples/flappy_bird/browser-entry/playback/worker-channel/playback.worker-channel.summary.services.ts +74 -0
  242. package/test/examples/flappy_bird/browser-entry/playback/worker-channel/playback.worker-channel.types.ts +53 -0
  243. package/test/examples/flappy_bird/browser-entry/runtime/README.md +304 -0
  244. package/test/examples/flappy_bird/browser-entry/runtime/runtime.browser-globals.service.ts +15 -0
  245. package/test/examples/flappy_bird/browser-entry/runtime/runtime.errors.ts +17 -0
  246. package/test/examples/flappy_bird/browser-entry/runtime/runtime.evolution-launch.service.ts +56 -0
  247. package/test/examples/flappy_bird/browser-entry/runtime/runtime.evolution-loop.service.ts +19 -0
  248. package/test/examples/flappy_bird/browser-entry/runtime/runtime.lifecycle.service.ts +96 -0
  249. package/test/examples/flappy_bird/browser-entry/runtime/runtime.startup.service.ts +92 -0
  250. package/test/examples/flappy_bird/browser-entry/runtime/runtime.telemetry.service.ts +24 -0
  251. package/test/examples/flappy_bird/browser-entry/runtime/runtime.ts +31 -121
  252. package/test/examples/flappy_bird/browser-entry/runtime/runtime.types.ts +65 -0
  253. package/test/examples/flappy_bird/browser-entry/visualization/README.md +568 -0
  254. package/test/examples/flappy_bird/browser-entry/visualization/visualization.colors.utils.ts +26 -0
  255. package/test/examples/flappy_bird/browser-entry/visualization/visualization.constants.ts +110 -0
  256. package/test/examples/flappy_bird/browser-entry/visualization/visualization.draw.service.ts +979 -19
  257. package/test/examples/flappy_bird/browser-entry/visualization/visualization.legend.utils.ts +157 -3
  258. package/test/examples/flappy_bird/browser-entry/visualization/visualization.topology.utils.ts +13 -27
  259. package/test/examples/flappy_bird/browser-entry/visualization/visualization.ts +7 -20
  260. package/test/examples/flappy_bird/browser-entry/visualization/visualization.types.ts +14 -0
  261. package/test/examples/flappy_bird/browser-entry/worker-channel/README.md +238 -0
  262. package/test/examples/flappy_bird/browser-entry/worker-channel/worker-channel.errors.ts +11 -0
  263. package/test/examples/flappy_bird/browser-entry/worker-channel/worker-channel.generation.service.ts +12 -0
  264. package/test/examples/flappy_bird/browser-entry/worker-channel/worker-channel.playback.service.test.ts +143 -0
  265. package/test/examples/flappy_bird/browser-entry/worker-channel/worker-channel.playback.service.ts +140 -14
  266. package/test/examples/flappy_bird/browser-entry/worker-channel/worker-channel.request.service.ts +27 -0
  267. package/test/examples/flappy_bird/browser-entry/worker-channel/worker-channel.ts +8 -0
  268. package/test/examples/flappy_bird/browser-entry/worker-channel/worker-channel.types.ts +23 -0
  269. package/test/examples/flappy_bird/browser-entry/worker-channel/worker-channel.url.service.ts +5 -0
  270. package/test/examples/flappy_bird/constants/README.md +1163 -0
  271. package/test/examples/flappy_bird/constants/constants.birds.ts +16 -38
  272. package/test/examples/flappy_bird/constants/constants.difficulty.ts +21 -0
  273. package/test/examples/flappy_bird/constants/constants.network-view.ts +24 -0
  274. package/test/examples/flappy_bird/constants/constants.network.ts +1 -1
  275. package/test/examples/flappy_bird/constants/constants.observation.ts +7 -0
  276. package/test/examples/flappy_bird/constants/constants.palette.ts +9 -2
  277. package/test/examples/flappy_bird/constants/constants.physics.ts +9 -0
  278. package/test/examples/flappy_bird/constants/constants.pipe-render.ts +3 -0
  279. package/test/examples/flappy_bird/constants/constants.pipes.ts +22 -3
  280. package/test/examples/flappy_bird/constants/constants.runtime.ts +28 -4
  281. package/test/examples/flappy_bird/constants/constants.starfield.ts +78 -3
  282. package/test/examples/flappy_bird/constants/constants.ts +6 -0
  283. package/test/examples/flappy_bird/environment/README.md +182 -0
  284. package/test/examples/flappy_bird/environment/environment.collision.utils.ts +7 -0
  285. package/test/examples/flappy_bird/environment/environment.constants.ts +16 -3
  286. package/test/examples/flappy_bird/environment/environment.observation.utils.ts +12 -19
  287. package/test/examples/flappy_bird/environment/environment.state.service.ts +10 -0
  288. package/test/examples/flappy_bird/environment/environment.step.service.ts +15 -66
  289. package/test/examples/flappy_bird/environment/environment.types.ts +14 -0
  290. package/test/examples/flappy_bird/evaluation/README.md +155 -0
  291. package/test/examples/flappy_bird/evaluation/evaluation.constants.ts +23 -4
  292. package/test/examples/flappy_bird/evaluation/evaluation.fitness.utils.ts +16 -1
  293. package/test/examples/flappy_bird/evaluation/evaluation.rollout.service.ts +7 -374
  294. package/test/examples/flappy_bird/evaluation/evaluation.seed.utils.ts +4 -0
  295. package/test/examples/flappy_bird/evaluation/evaluation.types.ts +18 -2
  296. package/test/examples/flappy_bird/evaluation/rollout/README.md +355 -0
  297. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.constants.ts +38 -0
  298. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.service.ts +71 -0
  299. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.services.ts +338 -0
  300. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.types.ts +69 -0
  301. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.utils.ts +399 -0
  302. package/test/examples/flappy_bird/flappy-evolution-worker/README.md +845 -0
  303. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.constants.ts +49 -7
  304. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.errors.ts +34 -3
  305. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.evolution.service.ts +22 -0
  306. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.playback.service.ts +62 -26
  307. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.protocol.service.ts +27 -1
  308. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.runtime.service.ts +23 -0
  309. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.simulation.frame.service.ts +378 -0
  310. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.simulation.types.ts +22 -0
  311. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.simulation.utils.ts +20 -203
  312. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.snapshot.utils.test.ts +94 -0
  313. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.snapshot.utils.ts +78 -13
  314. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.ts +235 -344
  315. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.types.ts +170 -22
  316. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.warm-start.service.ts +314 -0
  317. package/test/examples/flappy_bird/flappy.simulation.shared.utils.ts +17 -0
  318. package/test/examples/flappy_bird/flappyEnvironment.ts +21 -0
  319. package/test/examples/flappy_bird/flappyEvaluation.ts +12 -0
  320. package/test/examples/flappy_bird/flappyEvolution.worker.ts +7 -0
  321. package/test/examples/flappy_bird/index.ts +8 -2
  322. package/test/examples/flappy_bird/rng.ts +10 -0
  323. package/test/examples/flappy_bird/simulation-shared/README.md +518 -0
  324. package/test/examples/flappy_bird/simulation-shared/observation/README.md +255 -0
  325. package/test/examples/flappy_bird/simulation-shared/observation/observation.features.utils.ts +339 -0
  326. package/test/examples/flappy_bird/simulation-shared/observation/observation.ts +19 -0
  327. package/test/examples/flappy_bird/simulation-shared/observation/observation.vector.utils.ts +81 -0
  328. package/test/examples/flappy_bird/simulation-shared/simulation-shared.constants.ts +3 -0
  329. package/test/examples/flappy_bird/simulation-shared/simulation-shared.control.utils.ts +6 -0
  330. package/test/examples/flappy_bird/simulation-shared/simulation-shared.difficulty.utils.ts +9 -0
  331. package/test/examples/flappy_bird/simulation-shared/simulation-shared.errors.ts +10 -1
  332. package/test/examples/flappy_bird/simulation-shared/simulation-shared.memory.utils.ts +18 -0
  333. package/test/examples/flappy_bird/simulation-shared/simulation-shared.observation.utils.ts +7 -402
  334. package/test/examples/flappy_bird/simulation-shared/simulation-shared.spawn.utils.ts +36 -6
  335. package/test/examples/flappy_bird/{evaluation/evaluation.statistics.utils.ts → simulation-shared/simulation-shared.statistics.utils.ts} +30 -9
  336. package/test/examples/flappy_bird/simulation-shared/simulation-shared.types.ts +38 -5
  337. package/test/examples/flappy_bird/trainFlappyBird.ts +13 -0
  338. package/test/examples/flappy_bird/trainer/README.md +676 -0
  339. package/test/examples/flappy_bird/trainer/evaluation/README.md +253 -0
  340. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.constants.ts +15 -0
  341. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.services.ts +86 -0
  342. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.ts +187 -0
  343. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.types.ts +32 -0
  344. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.utils.ts +182 -0
  345. package/test/examples/flappy_bird/trainer/trainer.evaluation.service.ts +13 -0
  346. package/test/examples/flappy_bird/trainer/trainer.fitness.service.ts +23 -1
  347. package/test/examples/flappy_bird/trainer/trainer.loop.service.ts +17 -1
  348. package/test/examples/flappy_bird/trainer/trainer.report.service.services.ts +181 -0
  349. package/test/examples/flappy_bird/trainer/trainer.report.service.ts +136 -0
  350. package/test/examples/flappy_bird/trainer/trainer.selection.utils.ts +89 -0
  351. package/test/examples/flappy_bird/trainer/trainer.setup.service.ts +22 -0
  352. package/test/examples/flappy_bird/trainer/trainer.signals.service.ts +8 -0
  353. package/test/examples/flappy_bird/trainer/trainer.ts +38 -553
  354. package/test/examples/flappy_bird/trainer/trainer.types.ts +44 -7
  355. package/test/neat/neat.topology-intent.test.ts +129 -0
  356. package/test/network/network.topology-intent.test.ts +44 -0
  357. package/test/examples/flappy_bird/browser-entry/browser-entry.utils.ts +0 -12
  358. package/test/examples/flappy_bird/environment/environment.ts +0 -7
  359. package/test/examples/flappy_bird/evaluation/evaluation.ts +0 -7
  360. package/test/examples/flappy_bird/simulation-shared/simulation-shared.ts +0 -15
  361. package/test/examples/flappy_bird/trainer/trainer.statistics.utils.ts +0 -78
@@ -1,21 +1,146 @@
1
+ import {
2
+ FLAPPY_MONOSPACE_FONT_FAMILY,
3
+ FLAPPY_NEON_PALETTE,
4
+ FLAPPY_NETWORK_HEADER_FONT_SIZE_PX,
5
+ FLAPPY_NETWORK_HEADER_TEXT_COLOR,
6
+ FLAPPY_NETWORK_HIDDEN_NODE_STROKE_COLOR,
7
+ FLAPPY_NETWORK_LEGEND_BACKGROUND,
8
+ FLAPPY_NETWORK_LEGEND_BIAS_TITLE_COLOR,
9
+ FLAPPY_NETWORK_LEGEND_COMPACT_FONT_SIZE_PX,
10
+ FLAPPY_NETWORK_LEGEND_CONNECTION_LINE_WIDTH_PX,
11
+ FLAPPY_NETWORK_LEGEND_CONNECTION_TITLE_COLOR,
12
+ FLAPPY_NETWORK_LEGEND_HEADER_COLOR,
13
+ FLAPPY_NETWORK_LEGEND_REGULAR_FONT_SIZE_PX,
14
+ FLAPPY_NETWORK_LEGEND_ROW_TEXT_COLOR,
15
+ FLAPPY_NETWORK_MIN_LABEL_HEIGHT_PX,
16
+ FLAPPY_NETWORK_NODE_LABEL_FILL_COLOR,
17
+ FLAPPY_NETWORK_NODE_LABEL_FONT_WEIGHT,
18
+ FLAPPY_NETWORK_NODE_LABEL_SIZE_RATIO,
19
+ FLAPPY_NETWORK_OUTPUT_NODE_GLOW_COLOR,
20
+ FLAPPY_NETWORK_OUTPUT_NODE_STROKE_COLOR,
21
+ FLAPPY_VIEWPORT_NETWORK_OVERLAY_HIDDEN_BREAKPOINT_PX,
22
+ } from '../../constants/constants';
23
+ import { applyAlphaToHexColor, clamp } from '../browser-entry.math.utils';
1
24
  import type {
25
+ ColorLegendRow,
26
+ NetworkNodeDimensionsLike,
27
+ NetworkLegendLayout,
2
28
  PositionedNetworkNodeLike,
3
29
  VisualNetworkConnectionLike,
4
- NetworkNodeDimensionsLike,
5
30
  } from '../browser-entry.types';
31
+ import {
32
+ FLAPPY_NETWORK_DOTTED_CONNECTION_ALIGNMENT_EPSILON,
33
+ FLAPPY_NETWORK_DISABLED_CONNECTION_ALPHA,
34
+ FLAPPY_NETWORK_DISABLED_CONNECTION_DASH_PATTERN,
35
+ FLAPPY_NETWORK_DOTTED_CONNECTION_SQUARE_SIDE_PX,
36
+ FLAPPY_NETWORK_DOTTED_CONNECTION_STEP_COMPACT_RATIO,
37
+ FLAPPY_NETWORK_DOTTED_CONNECTION_WIDTH_SPACING_RATIO,
38
+ FLAPPY_NETWORK_ENABLED_CONNECTION_ALPHA,
39
+ FLAPPY_NETWORK_HEADER_LINE_HEIGHT_PX,
40
+ FLAPPY_NETWORK_HEADER_PADDING_PX,
41
+ FLAPPY_NETWORK_HIDDEN_NODE_STROKE_WIDTH_PX,
42
+ FLAPPY_NETWORK_HIDDEN_NODE_VERTICAL_PADDING_PX,
43
+ FLAPPY_NETWORK_LEGEND_ARCHITECTURE_GAP_PX,
44
+ FLAPPY_NETWORK_LEGEND_BIAS_LABEL_X_PX,
45
+ FLAPPY_NETWORK_LEGEND_BIAS_SWATCH_SIZE_PX,
46
+ FLAPPY_NETWORK_LEGEND_BIAS_SWATCH_X_PX,
47
+ FLAPPY_NETWORK_LEGEND_BIAS_SWATCH_Y_PX,
48
+ FLAPPY_NETWORK_LEGEND_BOX_PADDING_PX,
49
+ FLAPPY_NETWORK_LEGEND_CONNECTION_LABEL_X_PX,
50
+ FLAPPY_NETWORK_LEGEND_CONNECTION_SAMPLE_END_X_PX,
51
+ FLAPPY_NETWORK_LEGEND_CONNECTION_SAMPLE_Y_OFFSET_PX,
52
+ FLAPPY_NETWORK_LEGEND_HEADER_TOP_PADDING_PX,
53
+ FLAPPY_NETWORK_LEGEND_MIN_ARCHITECTURE_TOP_PX,
54
+ FLAPPY_NETWORK_MIN_RENDER_NODE_HEIGHT_PX,
55
+ FLAPPY_NETWORK_OUTPUT_NODE_HEIGHT_REDUCTION_PX,
56
+ FLAPPY_NETWORK_OUTPUT_NODE_SHADOW_BLUR_PX,
57
+ FLAPPY_NETWORK_OUTPUT_NODE_STROKE_WIDTH_PX,
58
+ } from './visualization.constants';
59
+ import {
60
+ createColorLegendRows,
61
+ resolveNetworkLegendLayout,
62
+ } from './visualization.legend.utils';
63
+ import { formatNodeBiasLabel } from './visualization.topology.utils';
6
64
  import type {
7
65
  DynamicColorScale,
8
66
  NetworkVisualizationColorScales,
9
67
  } from './visualization.types';
10
- import {
11
- drawBiasNodesLayerInternal,
12
- drawNetworkColorLegendInternal,
13
- drawNetworkVisualizationHeaderInternal,
14
- drawWeightedConnectionsLayerInternal,
15
- } from '../browser-entry.visualization.utils';
68
+ import { resolveTierColor } from './visualization.colors.utils';
69
+
70
+ /**
71
+ * Canvas drawing helpers for the network visualization panel.
72
+ *
73
+ * This module turns prepared topology, color scales, and legend layout into the
74
+ * actual rendered network view. The emphasis is educational readability: clear
75
+ * topology, readable bias labels, and a legend that explains the neon encoding.
76
+ */
77
+
78
+ const FLAPPY_MULTILINE_LABEL_SEPARATOR = '\n';
79
+ const FLAPPY_CANVAS_TEXT_ALIGN_LEFT: CanvasTextAlign = 'left';
80
+ const FLAPPY_CANVAS_TEXT_ALIGN_CENTER: CanvasTextAlign = 'center';
81
+ const FLAPPY_CANVAS_TEXT_BASELINE_TOP: CanvasTextBaseline = 'top';
82
+ const FLAPPY_CANVAS_TEXT_BASELINE_ALPHABETIC: CanvasTextBaseline = 'alphabetic';
83
+ const FLAPPY_TRANSPARENT_CANVAS_COLOR = 'transparent';
84
+ const FLAPPY_NETWORK_OUTPUT_NODE_TYPE = 'output';
85
+ const FLAPPY_NETWORK_LEGEND_TITLE = 'Legend';
86
+ const FLAPPY_NETWORK_CONNECTION_SECTION_TITLE = 'Connection weight';
87
+ const FLAPPY_NETWORK_BIAS_SECTION_TITLE = 'Node bias';
88
+ const FLAPPY_CONNECTION_LEGEND_SYMBOL = 'w';
89
+ const FLAPPY_BIAS_LEGEND_SYMBOL = 'b';
90
+
91
+ type WeightedConnectionScene = {
92
+ fromPosition: PositionedNetworkNodeLike;
93
+ toPosition: PositionedNetworkNodeLike;
94
+ connectionColor: string;
95
+ dashPattern: number[];
96
+ isNegativeConnection: boolean;
97
+ };
98
+
99
+ type BiasNodeLabelMetrics = {
100
+ labelAscentPx: number;
101
+ labelDescentPx: number;
102
+ measuredLabelHeightPx: number;
103
+ labelFont: string;
104
+ };
105
+
106
+ type BiasNodePaintStyle = {
107
+ isOutputNode: boolean;
108
+ nodeFillColor: string;
109
+ nodeStrokeColor: string;
110
+ nodeStrokeWidthPx: number;
111
+ nodeShadowBlurPx: number;
112
+ nodeShadowColor: string;
113
+ };
114
+
115
+ type BiasNodeScene = BiasNodePaintStyle & {
116
+ positionedNode: PositionedNetworkNodeLike;
117
+ nodeLabel: string;
118
+ nodeRectLeftPx: number;
119
+ nodeRectTopPx: number;
120
+ resolvedNodeHeightPx: number;
121
+ labelBaselineYPx: number;
122
+ labelFont: string;
123
+ };
124
+
125
+ type LegendSceneContext = NetworkLegendLayout & {
126
+ architectureLines: string[];
127
+ architectureTextTopPx: number;
128
+ connectionLegendRows: ColorLegendRow[];
129
+ biasLegendRows: ColorLegendRow[];
130
+ };
16
131
 
17
132
  /**
18
133
  * Draws weighted connection lines.
134
+ *
135
+ * Connection styling carries semantic meaning: color encodes magnitude and sign,
136
+ * while dash patterns and auxiliary marks help distinguish disabled or negative
137
+ * edges in a way that still reads quickly on a dense graph.
138
+ *
139
+ * @param context - Render context.
140
+ * @param runtimeConnections - Runtime connection list.
141
+ * @param positionByNodeIndex - Node layout map.
142
+ * @param connectionScale - Dynamic connection color scale.
143
+ * @returns Nothing.
19
144
  */
20
145
  export function drawWeightedConnectionsLayer(
21
146
  context: CanvasRenderingContext2D,
@@ -23,16 +148,38 @@ export function drawWeightedConnectionsLayer(
23
148
  positionByNodeIndex: Map<number, PositionedNetworkNodeLike>,
24
149
  connectionScale: DynamicColorScale,
25
150
  ): void {
26
- drawWeightedConnectionsLayerInternal(
27
- context,
28
- runtimeConnections,
29
- positionByNodeIndex,
30
- connectionScale,
151
+ // Step 1: Resolve the subset of connections that can be drawn from the layout map.
152
+ const drawableConnections = runtimeConnections.flatMap(
153
+ (runtimeConnection) => {
154
+ const weightedConnectionScene = resolveWeightedConnectionScene(
155
+ runtimeConnection,
156
+ positionByNodeIndex,
157
+ connectionScale,
158
+ );
159
+ return weightedConnectionScene ? [weightedConnectionScene] : [];
160
+ },
31
161
  );
162
+
163
+ // Step 2: Paint each connection using the resolved scene attributes.
164
+ drawableConnections.forEach((weightedConnectionScene) => {
165
+ drawWeightedConnectionScene(context, weightedConnectionScene);
166
+ });
167
+
168
+ // Step 3: Reset the dash pattern so later layers inherit a clean canvas state.
169
+ context.setLineDash([]);
32
170
  }
33
171
 
34
172
  /**
35
173
  * Draws all network nodes with bias labels.
174
+ *
175
+ * The node layer pairs each rectangle with a compact bias label so the panel can
176
+ * show both topology and a lightweight hint of parameter state.
177
+ *
178
+ * @param context - Render context.
179
+ * @param positionedNodes - Positioned nodes.
180
+ * @param nodeDimensions - Node dimensions.
181
+ * @param biasScale - Dynamic bias color scale.
182
+ * @returns Nothing.
36
183
  */
37
184
  export function drawBiasNodesLayer(
38
185
  context: CanvasRenderingContext2D,
@@ -40,31 +187,844 @@ export function drawBiasNodesLayer(
40
187
  nodeDimensions: NetworkNodeDimensionsLike,
41
188
  biasScale: DynamicColorScale,
42
189
  ): void {
43
- drawBiasNodesLayerInternal(
44
- context,
45
- positionedNodes,
46
- nodeDimensions,
47
- biasScale,
190
+ const halfNodeWidthPx = nodeDimensions.widthPx * 0.5;
191
+
192
+ // Step 1: Resolve each node into a paint-ready scene with stable label metrics.
193
+ const biasNodeScenes = positionedNodes.map((positionedNode) =>
194
+ resolveBiasNodeScene(
195
+ context,
196
+ positionedNode,
197
+ nodeDimensions,
198
+ halfNodeWidthPx,
199
+ biasScale,
200
+ ),
48
201
  );
202
+
203
+ // Step 2: Draw each node rectangle and optional bias label.
204
+ biasNodeScenes.forEach((biasNodeScene) => {
205
+ drawBiasNodeScene(context, biasNodeScene, nodeDimensions.widthPx);
206
+ });
49
207
  }
50
208
 
51
209
  /**
52
210
  * Draws network architecture header text.
211
+ *
212
+ * The header gives viewers a compact architecture summary before they inspect
213
+ * individual nodes and edges.
214
+ *
215
+ * @param context - Render context.
216
+ * @param architectureLabel - Header label.
217
+ * @returns Nothing.
53
218
  */
54
219
  export function drawNetworkVisualizationHeader(
55
220
  context: CanvasRenderingContext2D,
56
221
  architectureLabel: string,
57
222
  ): void {
58
- drawNetworkVisualizationHeaderInternal(context, architectureLabel);
223
+ // Step 1: Normalize the potentially multiline architecture label into rows.
224
+ const headerLines = architectureLabel.split(FLAPPY_MULTILINE_LABEL_SEPARATOR);
225
+
226
+ // Step 2: Paint the header block using the shared left-aligned text helper.
227
+ drawLeftAlignedTextRows(context, {
228
+ lines: headerLines,
229
+ leftPx: FLAPPY_NETWORK_HEADER_PADDING_PX,
230
+ topPx: FLAPPY_NETWORK_HEADER_PADDING_PX,
231
+ lineHeightPx: FLAPPY_NETWORK_HEADER_LINE_HEIGHT_PX,
232
+ font: `${FLAPPY_NETWORK_HEADER_FONT_SIZE_PX}px ${FLAPPY_MONOSPACE_FONT_FAMILY}`,
233
+ fillStyle: FLAPPY_NETWORK_HEADER_TEXT_COLOR,
234
+ });
59
235
  }
60
236
 
61
237
  /**
62
238
  * Draws the color legend for connections and node bias values.
239
+ *
240
+ * This legend is what turns the panel from "colorful art" into an interpretable
241
+ * instrument: it tells the viewer what each weight and bias color actually
242
+ * means numerically.
243
+ *
244
+ * @param context - Render context.
245
+ * @param architectureLabel - Compact architecture description.
246
+ * @param colorScales - Connection and bias color scales.
247
+ * @returns Nothing.
63
248
  */
64
249
  export function drawNetworkColorLegend(
65
250
  context: CanvasRenderingContext2D,
66
251
  architectureLabel: string,
67
252
  colorScales: NetworkVisualizationColorScales,
68
253
  ): void {
69
- drawNetworkColorLegendInternal(context, architectureLabel, colorScales);
254
+ // Step 1: Skip legend work entirely when the viewport intentionally hides overlays.
255
+ if (shouldHideNetworkColorLegend(context)) {
256
+ return;
257
+ }
258
+
259
+ // Step 2: Resolve legend rows, layout, and derived text bounds once up front.
260
+ const legendSceneContext = resolveLegendSceneContext(
261
+ context,
262
+ architectureLabel,
263
+ colorScales,
264
+ );
265
+
266
+ // Step 3: Paint the architecture label that sits above the legend box.
267
+ drawLegendArchitectureLabel(context, legendSceneContext);
268
+
269
+ // Step 4: Paint the legend container and section header.
270
+ drawLegendFrame(context, legendSceneContext);
271
+ drawLegendHeader(context, legendSceneContext);
272
+
273
+ // Step 5: Paint the connection and bias sections using the resolved rows.
274
+ drawLegendConnectionSection(context, legendSceneContext);
275
+ drawLegendBiasSection(context, legendSceneContext);
276
+ }
277
+
278
+ /**
279
+ * Resolves a renderable connection scene from runtime data and node positions.
280
+ *
281
+ * @param runtimeConnection - Candidate runtime connection.
282
+ * @param positionByNodeIndex - Node layout map.
283
+ * @param connectionScale - Connection color scale.
284
+ * @returns Renderable connection scene, when both endpoint nodes exist.
285
+ */
286
+ function resolveWeightedConnectionScene(
287
+ runtimeConnection: VisualNetworkConnectionLike,
288
+ positionByNodeIndex: Map<number, PositionedNetworkNodeLike>,
289
+ connectionScale: DynamicColorScale,
290
+ ): WeightedConnectionScene | undefined {
291
+ // Step 1: Resolve both endpoint indices and exit early when either side is missing.
292
+ const fromNodeIndex = runtimeConnection.from?.index;
293
+ const toNodeIndex = runtimeConnection.to?.index;
294
+ if (typeof fromNodeIndex !== 'number' || typeof toNodeIndex !== 'number') {
295
+ return undefined;
296
+ }
297
+
298
+ // Step 2: Resolve endpoint coordinates from the current node layout map.
299
+ const fromPosition = positionByNodeIndex.get(fromNodeIndex);
300
+ const toPosition = positionByNodeIndex.get(toNodeIndex);
301
+ if (!fromPosition || !toPosition) {
302
+ return undefined;
303
+ }
304
+
305
+ // Step 3: Convert weight semantics into color, alpha, and dash styling.
306
+ const connectionWeight = runtimeConnection.weight ?? 0;
307
+ const connectionEnabled = runtimeConnection.enabled !== false;
308
+ const baseColor = resolveTierColor(
309
+ connectionWeight,
310
+ connectionScale.tiers,
311
+ connectionScale.aboveTierColor,
312
+ );
313
+ const connectionAlpha = connectionEnabled
314
+ ? FLAPPY_NETWORK_ENABLED_CONNECTION_ALPHA
315
+ : FLAPPY_NETWORK_DISABLED_CONNECTION_ALPHA;
316
+
317
+ return {
318
+ fromPosition,
319
+ toPosition,
320
+ connectionColor: applyAlphaToHexColor(baseColor, connectionAlpha),
321
+ dashPattern: connectionEnabled
322
+ ? []
323
+ : [...FLAPPY_NETWORK_DISABLED_CONNECTION_DASH_PATTERN],
324
+ isNegativeConnection: connectionWeight < 0,
325
+ };
326
+ }
327
+
328
+ /**
329
+ * Draws a previously resolved weighted connection scene.
330
+ *
331
+ * @param context - Render context.
332
+ * @param weightedConnectionScene - Render-ready connection scene.
333
+ * @returns Nothing.
334
+ */
335
+ function drawWeightedConnectionScene(
336
+ context: CanvasRenderingContext2D,
337
+ weightedConnectionScene: WeightedConnectionScene,
338
+ ): void {
339
+ // Step 1: Use square-dot rendering for negative weights so polarity stays visually distinct.
340
+ if (weightedConnectionScene.isNegativeConnection) {
341
+ drawSquareDottedConnection(context, {
342
+ fromXPx: weightedConnectionScene.fromPosition.xPx,
343
+ fromYPx: weightedConnectionScene.fromPosition.yPx,
344
+ toXPx: weightedConnectionScene.toPosition.xPx,
345
+ toYPx: weightedConnectionScene.toPosition.yPx,
346
+ color: weightedConnectionScene.connectionColor,
347
+ lineWidthPx: FLAPPY_NETWORK_LEGEND_CONNECTION_LINE_WIDTH_PX,
348
+ });
349
+ return;
350
+ }
351
+
352
+ // Step 2: Paint positive-weight links as direct strokes with optional disabled dashes.
353
+ context.strokeStyle = weightedConnectionScene.connectionColor;
354
+ context.lineWidth = FLAPPY_NETWORK_LEGEND_CONNECTION_LINE_WIDTH_PX;
355
+ context.setLineDash(weightedConnectionScene.dashPattern);
356
+ context.beginPath();
357
+ context.moveTo(
358
+ weightedConnectionScene.fromPosition.xPx,
359
+ weightedConnectionScene.fromPosition.yPx,
360
+ );
361
+ context.lineTo(
362
+ weightedConnectionScene.toPosition.xPx,
363
+ weightedConnectionScene.toPosition.yPx,
364
+ );
365
+ context.stroke();
366
+ }
367
+
368
+ /**
369
+ * Resolves all paint attributes needed to render a single node.
370
+ *
371
+ * @param context - Render context.
372
+ * @param positionedNode - Positioned node payload.
373
+ * @param nodeDimensions - Shared node dimensions.
374
+ * @param halfNodeWidthPx - Cached half node width.
375
+ * @param biasScale - Bias color scale.
376
+ * @returns Paint-ready node scene.
377
+ */
378
+ function resolveBiasNodeScene(
379
+ context: CanvasRenderingContext2D,
380
+ positionedNode: PositionedNetworkNodeLike,
381
+ nodeDimensions: NetworkNodeDimensionsLike,
382
+ halfNodeWidthPx: number,
383
+ biasScale: DynamicColorScale,
384
+ ): BiasNodeScene {
385
+ // Step 1: Resolve color and emphasis rules from node type and bias value.
386
+ const nodeLabel = formatNodeBiasLabel(positionedNode.node.bias);
387
+ const biasNodePaintStyle = resolveBiasNodePaintStyle(
388
+ positionedNode,
389
+ biasScale,
390
+ );
391
+
392
+ // Step 2: Measure the label so the node rectangle can fit readable text.
393
+ const biasNodeLabelMetrics = resolveBiasNodeLabelMetrics(
394
+ context,
395
+ nodeLabel,
396
+ nodeDimensions,
397
+ );
398
+ const resolvedNodeHeightPx = resolveBiasNodeHeightPx(
399
+ nodeDimensions,
400
+ biasNodeLabelMetrics,
401
+ biasNodePaintStyle.isOutputNode,
402
+ );
403
+ const halfNodeHeightPx = resolvedNodeHeightPx * 0.5;
404
+
405
+ // Step 3: Convert center-based node positions into top-left render coordinates.
406
+ return {
407
+ ...biasNodePaintStyle,
408
+ positionedNode,
409
+ nodeLabel,
410
+ nodeRectLeftPx: positionedNode.xPx - halfNodeWidthPx,
411
+ nodeRectTopPx: positionedNode.yPx - halfNodeHeightPx,
412
+ resolvedNodeHeightPx,
413
+ labelBaselineYPx:
414
+ positionedNode.yPx +
415
+ (biasNodeLabelMetrics.labelAscentPx -
416
+ biasNodeLabelMetrics.labelDescentPx) *
417
+ 0.5,
418
+ labelFont: biasNodeLabelMetrics.labelFont,
419
+ };
420
+ }
421
+
422
+ /**
423
+ * Resolves node fill, stroke, and glow styling.
424
+ *
425
+ * @param positionedNode - Positioned node payload.
426
+ * @param biasScale - Bias color scale.
427
+ * @returns Node paint style.
428
+ */
429
+ function resolveBiasNodePaintStyle(
430
+ positionedNode: PositionedNetworkNodeLike,
431
+ biasScale: DynamicColorScale,
432
+ ): BiasNodePaintStyle {
433
+ // Step 1: Determine whether the node is an output node, which changes both color and emphasis.
434
+ const isOutputNode =
435
+ positionedNode.node.type === FLAPPY_NETWORK_OUTPUT_NODE_TYPE;
436
+
437
+ // Step 2: Resolve the fill and stroke styles for the active node category.
438
+ return {
439
+ isOutputNode,
440
+ nodeFillColor: isOutputNode
441
+ ? FLAPPY_NEON_PALETTE.currentRunText
442
+ : resolveTierColor(
443
+ positionedNode.node.bias,
444
+ biasScale.tiers,
445
+ biasScale.aboveTierColor,
446
+ ),
447
+ nodeStrokeColor: isOutputNode
448
+ ? FLAPPY_NETWORK_OUTPUT_NODE_STROKE_COLOR
449
+ : FLAPPY_NETWORK_HIDDEN_NODE_STROKE_COLOR,
450
+ nodeStrokeWidthPx: isOutputNode
451
+ ? FLAPPY_NETWORK_OUTPUT_NODE_STROKE_WIDTH_PX
452
+ : FLAPPY_NETWORK_HIDDEN_NODE_STROKE_WIDTH_PX,
453
+ nodeShadowBlurPx: isOutputNode
454
+ ? FLAPPY_NETWORK_OUTPUT_NODE_SHADOW_BLUR_PX
455
+ : 0,
456
+ nodeShadowColor: isOutputNode
457
+ ? FLAPPY_NETWORK_OUTPUT_NODE_GLOW_COLOR
458
+ : FLAPPY_TRANSPARENT_CANVAS_COLOR,
459
+ };
460
+ }
461
+
462
+ /**
463
+ * Measures a bias label and resolves its font declaration.
464
+ *
465
+ * @param context - Render context.
466
+ * @param nodeLabel - Bias label string.
467
+ * @param nodeDimensions - Shared node dimensions.
468
+ * @returns Measured label metrics.
469
+ */
470
+ function resolveBiasNodeLabelMetrics(
471
+ context: CanvasRenderingContext2D,
472
+ nodeLabel: string,
473
+ nodeDimensions: NetworkNodeDimensionsLike,
474
+ ): BiasNodeLabelMetrics {
475
+ // Step 1: Clamp label height into the readable range for the current node size.
476
+ const maximumReadableLabelHeightPx = Math.max(
477
+ FLAPPY_NETWORK_MIN_LABEL_HEIGHT_PX,
478
+ Math.floor(nodeDimensions.heightPx),
479
+ );
480
+ const computedLabelHeightPx = Math.floor(
481
+ nodeDimensions.heightPx * FLAPPY_NETWORK_NODE_LABEL_SIZE_RATIO,
482
+ );
483
+ const labelHeightPx = clamp(
484
+ computedLabelHeightPx,
485
+ FLAPPY_NETWORK_MIN_LABEL_HEIGHT_PX,
486
+ maximumReadableLabelHeightPx,
487
+ );
488
+ const labelFont = `${FLAPPY_NETWORK_NODE_LABEL_FONT_WEIGHT} ${labelHeightPx}px ${FLAPPY_MONOSPACE_FONT_FAMILY}`;
489
+
490
+ // Step 2: Measure the label using the resolved font so baseline math stays stable.
491
+ context.font = labelFont;
492
+ context.textBaseline = FLAPPY_CANVAS_TEXT_BASELINE_ALPHABETIC;
493
+ const labelMetrics = context.measureText(nodeLabel);
494
+ const labelAscentPx =
495
+ labelMetrics.actualBoundingBoxAscent || labelHeightPx * 0.72;
496
+ const labelDescentPx =
497
+ labelMetrics.actualBoundingBoxDescent || labelHeightPx * 0.28;
498
+
499
+ return {
500
+ labelAscentPx,
501
+ labelDescentPx,
502
+ measuredLabelHeightPx: Math.max(
503
+ FLAPPY_NETWORK_MIN_LABEL_HEIGHT_PX,
504
+ Math.ceil(labelAscentPx + labelDescentPx),
505
+ ),
506
+ labelFont,
507
+ };
508
+ }
509
+
510
+ /**
511
+ * Resolves node rectangle height from label metrics and node role.
512
+ *
513
+ * @param nodeDimensions - Shared node dimensions.
514
+ * @param biasNodeLabelMetrics - Measured label metrics.
515
+ * @param isOutputNode - Whether the node is an output node.
516
+ * @returns Render height for the node rectangle.
517
+ */
518
+ function resolveBiasNodeHeightPx(
519
+ nodeDimensions: NetworkNodeDimensionsLike,
520
+ biasNodeLabelMetrics: BiasNodeLabelMetrics,
521
+ isOutputNode: boolean,
522
+ ): number {
523
+ // Step 1: Use a slightly tighter output-node box while enforcing a hard minimum height.
524
+ if (isOutputNode) {
525
+ return Math.max(
526
+ FLAPPY_NETWORK_MIN_RENDER_NODE_HEIGHT_PX,
527
+ nodeDimensions.heightPx - FLAPPY_NETWORK_OUTPUT_NODE_HEIGHT_REDUCTION_PX,
528
+ );
529
+ }
530
+
531
+ // Step 2: Fit hidden and input nodes around the measured label height plus inner padding.
532
+ return Math.max(
533
+ FLAPPY_NETWORK_MIN_RENDER_NODE_HEIGHT_PX,
534
+ Math.min(
535
+ nodeDimensions.heightPx,
536
+ biasNodeLabelMetrics.measuredLabelHeightPx +
537
+ FLAPPY_NETWORK_HIDDEN_NODE_VERTICAL_PADDING_PX,
538
+ ),
539
+ );
540
+ }
541
+
542
+ /**
543
+ * Draws a resolved node rectangle and optional bias label.
544
+ *
545
+ * @param context - Render context.
546
+ * @param biasNodeScene - Paint-ready node scene.
547
+ * @param nodeWidthPx - Shared node width.
548
+ * @returns Nothing.
549
+ */
550
+ function drawBiasNodeScene(
551
+ context: CanvasRenderingContext2D,
552
+ biasNodeScene: BiasNodeScene,
553
+ nodeWidthPx: number,
554
+ ): void {
555
+ // Step 1: Paint the node rectangle with its resolved fill, stroke, and glow emphasis.
556
+ context.fillStyle = biasNodeScene.nodeFillColor;
557
+ context.strokeStyle = biasNodeScene.nodeStrokeColor;
558
+ context.lineWidth = biasNodeScene.nodeStrokeWidthPx;
559
+ context.shadowBlur = biasNodeScene.nodeShadowBlurPx;
560
+ context.shadowColor = biasNodeScene.nodeShadowColor;
561
+ context.fillRect(
562
+ biasNodeScene.nodeRectLeftPx,
563
+ biasNodeScene.nodeRectTopPx,
564
+ nodeWidthPx,
565
+ biasNodeScene.resolvedNodeHeightPx,
566
+ );
567
+ context.strokeRect(
568
+ biasNodeScene.nodeRectLeftPx,
569
+ biasNodeScene.nodeRectTopPx,
570
+ nodeWidthPx,
571
+ biasNodeScene.resolvedNodeHeightPx,
572
+ );
573
+
574
+ // Step 2: Reset shadow state before any later text rendering.
575
+ context.shadowBlur = 0;
576
+ context.shadowColor = FLAPPY_TRANSPARENT_CANVAS_COLOR;
577
+
578
+ // Step 3: Skip label painting for output nodes, which render as emphasized solid blocks.
579
+ if (biasNodeScene.isOutputNode) {
580
+ return;
581
+ }
582
+
583
+ // Step 4: Paint the centered bias label inside the node rectangle.
584
+ context.fillStyle = FLAPPY_NETWORK_NODE_LABEL_FILL_COLOR;
585
+ context.font = biasNodeScene.labelFont;
586
+ context.textAlign = FLAPPY_CANVAS_TEXT_ALIGN_CENTER;
587
+ context.textBaseline = FLAPPY_CANVAS_TEXT_BASELINE_ALPHABETIC;
588
+ context.fillText(
589
+ biasNodeScene.nodeLabel,
590
+ biasNodeScene.positionedNode.xPx,
591
+ biasNodeScene.labelBaselineYPx,
592
+ );
593
+ }
594
+
595
+ /**
596
+ * Determines whether the responsive viewport intentionally hides the overlay legend.
597
+ *
598
+ * @param context - Render context.
599
+ * @returns True when the legend should be omitted.
600
+ */
601
+ function shouldHideNetworkColorLegend(
602
+ context: CanvasRenderingContext2D,
603
+ ): boolean {
604
+ // Step 1: Prefer the live window width when available, otherwise fall back to canvas width.
605
+ const viewportWidthPx =
606
+ context.canvas.ownerDocument?.defaultView?.innerWidth ??
607
+ context.canvas.width;
608
+ return viewportWidthPx < FLAPPY_VIEWPORT_NETWORK_OVERLAY_HIDDEN_BREAKPOINT_PX;
609
+ }
610
+
611
+ /**
612
+ * Resolves the legend rows, layout, and architecture label bounds.
613
+ *
614
+ * @param context - Render context.
615
+ * @param architectureLabel - Multiline architecture label.
616
+ * @param colorScales - Connection and bias color scales.
617
+ * @returns Legend scene context.
618
+ */
619
+ function resolveLegendSceneContext(
620
+ context: CanvasRenderingContext2D,
621
+ architectureLabel: string,
622
+ colorScales: NetworkVisualizationColorScales,
623
+ ): LegendSceneContext {
624
+ // Step 1: Build the connection and bias legend rows from the active scales.
625
+ const connectionLegendRows = createColorLegendRows(
626
+ colorScales.connectionScale,
627
+ FLAPPY_CONNECTION_LEGEND_SYMBOL,
628
+ );
629
+ const biasLegendRows = createColorLegendRows(
630
+ colorScales.biasScale,
631
+ FLAPPY_BIAS_LEGEND_SYMBOL,
632
+ );
633
+ const networkLegendLayout = resolveNetworkLegendLayout(
634
+ context,
635
+ connectionLegendRows,
636
+ biasLegendRows,
637
+ );
638
+
639
+ // Step 2: Resolve the multiline architecture label bounds above the legend box.
640
+ const architectureLines = architectureLabel.split(
641
+ FLAPPY_MULTILINE_LABEL_SEPARATOR,
642
+ );
643
+ const architectureTextTopPx = Math.max(
644
+ FLAPPY_NETWORK_LEGEND_MIN_ARCHITECTURE_TOP_PX,
645
+ networkLegendLayout.legendTopPx -
646
+ architectureLines.length * FLAPPY_NETWORK_HEADER_LINE_HEIGHT_PX -
647
+ FLAPPY_NETWORK_LEGEND_ARCHITECTURE_GAP_PX,
648
+ );
649
+
650
+ return {
651
+ ...networkLegendLayout,
652
+ architectureLines,
653
+ architectureTextTopPx,
654
+ connectionLegendRows,
655
+ biasLegendRows,
656
+ };
657
+ }
658
+
659
+ /**
660
+ * Draws the architecture label block above the legend frame.
661
+ *
662
+ * @param context - Render context.
663
+ * @param legendSceneContext - Legend scene context.
664
+ * @returns Nothing.
665
+ */
666
+ function drawLegendArchitectureLabel(
667
+ context: CanvasRenderingContext2D,
668
+ legendSceneContext: LegendSceneContext,
669
+ ): void {
670
+ // Step 1: Paint the architecture lines aligned to the legend frame left edge.
671
+ drawLeftAlignedTextRows(context, {
672
+ lines: legendSceneContext.architectureLines,
673
+ leftPx:
674
+ legendSceneContext.legendLeftPx + FLAPPY_NETWORK_LEGEND_BOX_PADDING_PX,
675
+ topPx: legendSceneContext.architectureTextTopPx,
676
+ lineHeightPx: FLAPPY_NETWORK_HEADER_LINE_HEIGHT_PX,
677
+ font: `${FLAPPY_NETWORK_HEADER_FONT_SIZE_PX}px ${FLAPPY_MONOSPACE_FONT_FAMILY}`,
678
+ fillStyle: FLAPPY_NETWORK_HEADER_TEXT_COLOR,
679
+ });
680
+ }
681
+
682
+ /**
683
+ * Draws the legend container frame.
684
+ *
685
+ * @param context - Render context.
686
+ * @param legendSceneContext - Legend scene context.
687
+ * @returns Nothing.
688
+ */
689
+ function drawLegendFrame(
690
+ context: CanvasRenderingContext2D,
691
+ legendSceneContext: LegendSceneContext,
692
+ ): void {
693
+ // Step 1: Paint the legend background block.
694
+ context.fillStyle = FLAPPY_NETWORK_LEGEND_BACKGROUND;
695
+ context.strokeStyle = FLAPPY_NEON_PALETTE.statusText;
696
+ context.lineWidth = 1;
697
+ context.fillRect(
698
+ legendSceneContext.legendLeftPx,
699
+ legendSceneContext.legendTopPx,
700
+ legendSceneContext.legendWidthPx,
701
+ legendSceneContext.legendHeightPx,
702
+ );
703
+ context.strokeRect(
704
+ legendSceneContext.legendLeftPx,
705
+ legendSceneContext.legendTopPx,
706
+ legendSceneContext.legendWidthPx,
707
+ legendSceneContext.legendHeightPx,
708
+ );
709
+ }
710
+
711
+ /**
712
+ * Draws the legend title row.
713
+ *
714
+ * @param context - Render context.
715
+ * @param legendSceneContext - Legend scene context.
716
+ * @returns Nothing.
717
+ */
718
+ function drawLegendHeader(
719
+ context: CanvasRenderingContext2D,
720
+ legendSceneContext: LegendSceneContext,
721
+ ): void {
722
+ // Step 1: Resolve the legend title font based on the compact layout mode.
723
+ const legendFontSizePx = legendSceneContext.compactLegend
724
+ ? FLAPPY_NETWORK_LEGEND_COMPACT_FONT_SIZE_PX
725
+ : FLAPPY_NETWORK_LEGEND_REGULAR_FONT_SIZE_PX;
726
+
727
+ // Step 2: Paint the title inside the legend frame.
728
+ context.fillStyle = FLAPPY_NETWORK_LEGEND_HEADER_COLOR;
729
+ context.font = `${legendFontSizePx}px ${FLAPPY_MONOSPACE_FONT_FAMILY}`;
730
+ context.textAlign = FLAPPY_CANVAS_TEXT_ALIGN_LEFT;
731
+ context.textBaseline = FLAPPY_CANVAS_TEXT_BASELINE_TOP;
732
+ context.fillText(
733
+ FLAPPY_NETWORK_LEGEND_TITLE,
734
+ legendSceneContext.legendLeftPx + FLAPPY_NETWORK_LEGEND_BOX_PADDING_PX,
735
+ legendSceneContext.legendTopPx +
736
+ FLAPPY_NETWORK_LEGEND_HEADER_TOP_PADDING_PX,
737
+ );
738
+ }
739
+
740
+ /**
741
+ * Draws the connection-weight legend section.
742
+ *
743
+ * @param context - Render context.
744
+ * @param legendSceneContext - Legend scene context.
745
+ * @returns Nothing.
746
+ */
747
+ function drawLegendConnectionSection(
748
+ context: CanvasRenderingContext2D,
749
+ legendSceneContext: LegendSceneContext,
750
+ ): void {
751
+ // Step 1: Paint the section title just below the legend header.
752
+ const connectionSectionTopPx =
753
+ legendSceneContext.legendTopPx + legendSceneContext.legendHeaderHeightPx;
754
+ context.fillStyle = FLAPPY_NETWORK_LEGEND_CONNECTION_TITLE_COLOR;
755
+ context.font = `${legendSceneContext.compactLegend ? FLAPPY_NETWORK_LEGEND_COMPACT_FONT_SIZE_PX : FLAPPY_NETWORK_LEGEND_REGULAR_FONT_SIZE_PX}px ${FLAPPY_MONOSPACE_FONT_FAMILY}`;
756
+ context.textAlign = FLAPPY_CANVAS_TEXT_ALIGN_LEFT;
757
+ context.textBaseline = FLAPPY_CANVAS_TEXT_BASELINE_TOP;
758
+ context.fillText(
759
+ FLAPPY_NETWORK_CONNECTION_SECTION_TITLE,
760
+ legendSceneContext.legendLeftPx + FLAPPY_NETWORK_LEGEND_BOX_PADDING_PX,
761
+ connectionSectionTopPx,
762
+ );
763
+
764
+ // Step 2: Paint each legend row sample and label.
765
+ legendSceneContext.connectionLegendRows.forEach(
766
+ (connectionLegendRow, connectionLegendRowIndex) => {
767
+ const connectionRowTopPx =
768
+ connectionSectionTopPx +
769
+ legendSceneContext.legendSectionTitleHeightPx +
770
+ connectionLegendRowIndex * legendSceneContext.legendRowHeightPx;
771
+ drawLegendConnectionRow(
772
+ context,
773
+ legendSceneContext,
774
+ connectionLegendRow,
775
+ connectionRowTopPx,
776
+ );
777
+ },
778
+ );
779
+ }
780
+
781
+ /**
782
+ * Draws a single connection legend row.
783
+ *
784
+ * @param context - Render context.
785
+ * @param legendSceneContext - Legend scene context.
786
+ * @param connectionLegendRow - Legend row.
787
+ * @param connectionRowTopPx - Row top coordinate.
788
+ * @returns Nothing.
789
+ */
790
+ function drawLegendConnectionRow(
791
+ context: CanvasRenderingContext2D,
792
+ legendSceneContext: LegendSceneContext,
793
+ connectionLegendRow: ColorLegendRow,
794
+ connectionRowTopPx: number,
795
+ ): void {
796
+ // Step 1: Resolve the sample line coordinates for the row.
797
+ const sampleStartXPx =
798
+ legendSceneContext.legendLeftPx + FLAPPY_NETWORK_LEGEND_BOX_PADDING_PX;
799
+ const sampleEndXPx =
800
+ legendSceneContext.legendLeftPx +
801
+ FLAPPY_NETWORK_LEGEND_CONNECTION_SAMPLE_END_X_PX;
802
+ const sampleYPx =
803
+ connectionRowTopPx + FLAPPY_NETWORK_LEGEND_CONNECTION_SAMPLE_Y_OFFSET_PX;
804
+
805
+ // Step 2: Paint negative ranges with dots and positive ranges with solid strokes.
806
+ if (connectionLegendRow.maximumValue <= 0) {
807
+ drawSquareDottedConnection(context, {
808
+ fromXPx: sampleStartXPx,
809
+ fromYPx: sampleYPx,
810
+ toXPx: sampleEndXPx,
811
+ toYPx: sampleYPx,
812
+ color: connectionLegendRow.color,
813
+ lineWidthPx: FLAPPY_NETWORK_LEGEND_CONNECTION_LINE_WIDTH_PX,
814
+ });
815
+ } else {
816
+ context.strokeStyle = connectionLegendRow.color;
817
+ context.lineWidth = FLAPPY_NETWORK_LEGEND_CONNECTION_LINE_WIDTH_PX;
818
+ context.beginPath();
819
+ context.moveTo(sampleStartXPx, sampleYPx);
820
+ context.lineTo(sampleEndXPx, sampleYPx);
821
+ context.stroke();
822
+ }
823
+
824
+ // Step 3: Paint the row label beside the sample.
825
+ context.fillStyle = FLAPPY_NETWORK_LEGEND_ROW_TEXT_COLOR;
826
+ context.fillText(
827
+ connectionLegendRow.label,
828
+ legendSceneContext.legendLeftPx +
829
+ FLAPPY_NETWORK_LEGEND_CONNECTION_LABEL_X_PX,
830
+ connectionRowTopPx - 1,
831
+ );
832
+ }
833
+
834
+ /**
835
+ * Draws the bias legend section.
836
+ *
837
+ * @param context - Render context.
838
+ * @param legendSceneContext - Legend scene context.
839
+ * @returns Nothing.
840
+ */
841
+ function drawLegendBiasSection(
842
+ context: CanvasRenderingContext2D,
843
+ legendSceneContext: LegendSceneContext,
844
+ ): void {
845
+ // Step 1: Resolve the bias section top from the connection section height.
846
+ const biasSectionTopPx =
847
+ legendSceneContext.legendTopPx +
848
+ legendSceneContext.legendHeaderHeightPx +
849
+ legendSceneContext.legendSectionTitleHeightPx +
850
+ legendSceneContext.connectionLegendRows.length *
851
+ legendSceneContext.legendRowHeightPx +
852
+ legendSceneContext.legendSectionGapPx;
853
+ context.fillStyle = FLAPPY_NETWORK_LEGEND_BIAS_TITLE_COLOR;
854
+ context.font = `${legendSceneContext.compactLegend ? FLAPPY_NETWORK_LEGEND_COMPACT_FONT_SIZE_PX : FLAPPY_NETWORK_LEGEND_REGULAR_FONT_SIZE_PX}px ${FLAPPY_MONOSPACE_FONT_FAMILY}`;
855
+ context.textAlign = FLAPPY_CANVAS_TEXT_ALIGN_LEFT;
856
+ context.textBaseline = FLAPPY_CANVAS_TEXT_BASELINE_TOP;
857
+ context.fillText(
858
+ FLAPPY_NETWORK_BIAS_SECTION_TITLE,
859
+ legendSceneContext.legendLeftPx + FLAPPY_NETWORK_LEGEND_BOX_PADDING_PX,
860
+ biasSectionTopPx,
861
+ );
862
+
863
+ // Step 2: Paint each bias swatch row beneath the section title.
864
+ legendSceneContext.biasLegendRows.forEach(
865
+ (biasLegendRow, biasLegendRowIndex) => {
866
+ const biasRowTopPx =
867
+ biasSectionTopPx +
868
+ legendSceneContext.legendSectionTitleHeightPx +
869
+ biasLegendRowIndex * legendSceneContext.legendRowHeightPx;
870
+ drawLegendBiasRow(
871
+ context,
872
+ legendSceneContext,
873
+ biasLegendRow,
874
+ biasRowTopPx,
875
+ );
876
+ },
877
+ );
878
+ }
879
+
880
+ /**
881
+ * Draws a single bias legend row.
882
+ *
883
+ * @param context - Render context.
884
+ * @param legendSceneContext - Legend scene context.
885
+ * @param biasLegendRow - Legend row.
886
+ * @param biasRowTopPx - Row top coordinate.
887
+ * @returns Nothing.
888
+ */
889
+ function drawLegendBiasRow(
890
+ context: CanvasRenderingContext2D,
891
+ legendSceneContext: LegendSceneContext,
892
+ biasLegendRow: ColorLegendRow,
893
+ biasRowTopPx: number,
894
+ ): void {
895
+ // Step 1: Paint the color swatch that represents the bias bucket.
896
+ context.fillStyle = biasLegendRow.color;
897
+ context.beginPath();
898
+ context.rect(
899
+ legendSceneContext.legendLeftPx + FLAPPY_NETWORK_LEGEND_BIAS_SWATCH_X_PX,
900
+ biasRowTopPx + FLAPPY_NETWORK_LEGEND_BIAS_SWATCH_Y_PX,
901
+ FLAPPY_NETWORK_LEGEND_BIAS_SWATCH_SIZE_PX,
902
+ FLAPPY_NETWORK_LEGEND_BIAS_SWATCH_SIZE_PX,
903
+ );
904
+ context.fill();
905
+
906
+ // Step 2: Paint the descriptive label beside the swatch.
907
+ context.fillStyle = FLAPPY_NETWORK_LEGEND_ROW_TEXT_COLOR;
908
+ context.fillText(
909
+ biasLegendRow.label,
910
+ legendSceneContext.legendLeftPx + FLAPPY_NETWORK_LEGEND_BIAS_LABEL_X_PX,
911
+ biasRowTopPx - 1,
912
+ );
913
+ }
914
+
915
+ /**
916
+ * Draws multiline text rows aligned to a fixed left edge.
917
+ *
918
+ * @param context - Render context.
919
+ * @param request - Multiline text draw request.
920
+ * @returns Nothing.
921
+ */
922
+ function drawLeftAlignedTextRows(
923
+ context: CanvasRenderingContext2D,
924
+ request: {
925
+ lines: string[];
926
+ leftPx: number;
927
+ topPx: number;
928
+ lineHeightPx: number;
929
+ font: string;
930
+ fillStyle: string;
931
+ },
932
+ ): void {
933
+ // Step 1: Configure the shared text state used by every row.
934
+ context.fillStyle = request.fillStyle;
935
+ context.font = request.font;
936
+ context.textAlign = FLAPPY_CANVAS_TEXT_ALIGN_LEFT;
937
+ context.textBaseline = FLAPPY_CANVAS_TEXT_BASELINE_TOP;
938
+
939
+ // Step 2: Paint the rows with fixed vertical spacing.
940
+ request.lines.forEach((lineText, lineIndex) => {
941
+ context.fillText(
942
+ lineText,
943
+ request.leftPx,
944
+ request.topPx + lineIndex * request.lineHeightPx,
945
+ );
946
+ });
947
+ }
948
+
949
+ /**
950
+ * Draws a square-dotted connection stroke for negative weights.
951
+ *
952
+ * @param context - Render context.
953
+ * @param input - Dotted-stroke endpoints and style.
954
+ * @returns Nothing.
955
+ */
956
+ function drawSquareDottedConnection(
957
+ context: CanvasRenderingContext2D,
958
+ input: {
959
+ fromXPx: number;
960
+ fromYPx: number;
961
+ toXPx: number;
962
+ toYPx: number;
963
+ color: string;
964
+ lineWidthPx: number;
965
+ },
966
+ ): void {
967
+ // Step 1: Resolve the normalized segment direction and exit early for zero-length links.
968
+ const deltaXPx = input.toXPx - input.fromXPx;
969
+ const deltaYPx = input.toYPx - input.fromYPx;
970
+ const segmentLengthPx = Math.hypot(deltaXPx, deltaYPx);
971
+ if (segmentLengthPx <= 0) {
972
+ return;
973
+ }
974
+
975
+ // Step 2: Resolve the dot cadence and alignment helpers for the segment.
976
+ const squareSidePx = FLAPPY_NETWORK_DOTTED_CONNECTION_SQUARE_SIDE_PX;
977
+ const stepDistancePx = Math.max(
978
+ (squareSidePx + 2) * FLAPPY_NETWORK_DOTTED_CONNECTION_STEP_COMPACT_RATIO,
979
+ input.lineWidthPx *
980
+ FLAPPY_NETWORK_DOTTED_CONNECTION_WIDTH_SPACING_RATIO *
981
+ FLAPPY_NETWORK_DOTTED_CONNECTION_STEP_COMPACT_RATIO,
982
+ );
983
+ const directionXPx = deltaXPx / segmentLengthPx;
984
+ const directionYPx = deltaYPx / segmentLengthPx;
985
+ const halfSquareSidePx = squareSidePx * 0.5;
986
+
987
+ context.fillStyle = input.color;
988
+
989
+ // Step 3: Paint each aligned square sample along the connection path.
990
+ for (
991
+ let traveledDistancePx = 0;
992
+ traveledDistancePx <= segmentLengthPx;
993
+ traveledDistancePx += stepDistancePx
994
+ ) {
995
+ const centerXPx = input.fromXPx + directionXPx * traveledDistancePx;
996
+ const centerYPx = input.fromYPx + directionYPx * traveledDistancePx;
997
+
998
+ const axisAlignedCenterXPx =
999
+ Math.round(
1000
+ centerXPx +
1001
+ directionXPx * FLAPPY_NETWORK_DOTTED_CONNECTION_ALIGNMENT_EPSILON,
1002
+ ) -
1003
+ Math.round(
1004
+ directionXPx * FLAPPY_NETWORK_DOTTED_CONNECTION_ALIGNMENT_EPSILON,
1005
+ );
1006
+ const axisAlignedCenterYPx =
1007
+ Math.round(
1008
+ centerYPx +
1009
+ directionYPx * FLAPPY_NETWORK_DOTTED_CONNECTION_ALIGNMENT_EPSILON,
1010
+ ) -
1011
+ Math.round(
1012
+ directionYPx * FLAPPY_NETWORK_DOTTED_CONNECTION_ALIGNMENT_EPSILON,
1013
+ );
1014
+
1015
+ context.fillRect(
1016
+ axisAlignedCenterXPx - halfSquareSidePx,
1017
+ axisAlignedCenterYPx - halfSquareSidePx,
1018
+ squareSidePx,
1019
+ squareSidePx,
1020
+ );
1021
+ }
1022
+
1023
+ // Step 4: Paint the destination endpoint to avoid a visible trailing gap.
1024
+ context.fillRect(
1025
+ Math.round(input.toXPx - halfSquareSidePx),
1026
+ Math.round(input.toYPx - halfSquareSidePx),
1027
+ squareSidePx,
1028
+ squareSidePx,
1029
+ );
70
1030
  }