@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
@@ -1,68 +1,3 @@
1
- import Network from '../../../../src/architecture/network';
2
- import {
3
- FLAPPY_MONOSPACE_FONT_FAMILY,
4
- FLAPPY_NEON_PALETTE,
5
- FLAPPY_NETWORK_HEADER_FONT_SIZE_PX,
6
- FLAPPY_NETWORK_HEADER_TEXT_COLOR,
7
- FLAPPY_NETWORK_HIDDEN_NODE_STROKE_COLOR,
8
- FLAPPY_NETWORK_LEGEND_BACKGROUND,
9
- FLAPPY_NETWORK_LEGEND_BIAS_TITLE_COLOR,
10
- FLAPPY_NETWORK_LEGEND_BOTTOM_PADDING_PX,
11
- FLAPPY_NETWORK_LEGEND_COMPACT_FONT_SIZE_PX,
12
- FLAPPY_NETWORK_LEGEND_COMPACT_HEIGHT_THRESHOLD_PX,
13
- FLAPPY_NETWORK_LEGEND_COMPACT_ROW_HEIGHT_PX,
14
- FLAPPY_NETWORK_LEGEND_COMPACT_SECTION_GAP_PX,
15
- FLAPPY_NETWORK_LEGEND_COMPACT_SECTION_TITLE_HEIGHT_PX,
16
- FLAPPY_NETWORK_LEGEND_COMPACT_WIDTH_PX,
17
- FLAPPY_NETWORK_LEGEND_COMPACT_WIDTH_THRESHOLD_PX,
18
- FLAPPY_NETWORK_LEGEND_CONNECTION_LINE_WIDTH_PX,
19
- FLAPPY_NETWORK_LEGEND_CONNECTION_TITLE_COLOR,
20
- FLAPPY_NETWORK_LEGEND_HEADER_COLOR,
21
- FLAPPY_NETWORK_LEGEND_HEADER_HEIGHT_PX,
22
- FLAPPY_NETWORK_LEGEND_MARGIN_PX,
23
- FLAPPY_NETWORK_LEGEND_MIN_TOP_PX,
24
- FLAPPY_NETWORK_LEGEND_REGULAR_FONT_SIZE_PX,
25
- FLAPPY_NETWORK_LEGEND_REGULAR_ROW_HEIGHT_PX,
26
- FLAPPY_NETWORK_LEGEND_REGULAR_SECTION_GAP_PX,
27
- FLAPPY_NETWORK_LEGEND_REGULAR_SECTION_TITLE_HEIGHT_PX,
28
- FLAPPY_NETWORK_LEGEND_REGULAR_WIDTH_PX,
29
- FLAPPY_NETWORK_LEGEND_ROW_TEXT_COLOR,
30
- FLAPPY_NETWORK_LEGEND_TARGET_TOP_PX,
31
- FLAPPY_NETWORK_LEGEND_TOP_LEFT_THRESHOLD_PX,
32
- FLAPPY_NETWORK_MIN_LABEL_HEIGHT_PX,
33
- FLAPPY_NETWORK_NODE_LABEL_FONT_WEIGHT,
34
- FLAPPY_NETWORK_NODE_LABEL_FILL_COLOR,
35
- FLAPPY_NETWORK_NODE_LABEL_SIZE_RATIO,
36
- FLAPPY_NETWORK_OUTPUT_NODE_GLOW_COLOR,
37
- FLAPPY_NETWORK_OUTPUT_NODE_STROKE_COLOR,
38
- FLAPPY_VIEWPORT_NETWORK_OVERLAY_HIDDEN_BREAKPOINT_PX,
39
- } from '../constants/constants';
40
- import { applyAlphaToHexColor, clamp } from './browser-entry.math.utils';
41
- import type {
42
- ColorLegendRow,
43
- NetworkLegendLayout,
44
- NetworkNodeDimensionsLike,
45
- PositionedNetworkNodeLike,
46
- VisualNetworkConnectionLike,
47
- VisualNetworkNodeLike,
48
- } from './browser-entry.types';
49
- import type {
50
- DynamicColorScale,
51
- NetworkVisualizationColorScales,
52
- } from './visualization/visualization.types';
53
- import {
54
- FLAPPY_NETWORK_DOTTED_CONNECTION_ALIGNMENT_EPSILON,
55
- FLAPPY_NETWORK_DOTTED_CONNECTION_SQUARE_SIDE_PX,
56
- FLAPPY_NETWORK_DOTTED_CONNECTION_STEP_COMPACT_RATIO,
57
- FLAPPY_NETWORK_DOTTED_CONNECTION_WIDTH_SPACING_RATIO,
58
- } from './visualization/visualization.constants';
59
- import { assertFiniteLegendBound } from './visualization/visualization.errors';
60
- import {
61
- resolveNetworkVisualizationColorScales,
62
- resolveTierColor,
63
- } from './visualization/visualization.colors.utils';
64
- import { formatNodeBiasLabel } from './visualization/visualization.topology.utils';
65
-
66
1
  export {
67
2
  createLogDivergingColorTiers,
68
3
  resolveBiasRangeColor,
@@ -70,831 +5,18 @@ export {
70
5
  resolveNetworkVisualizationColorScales,
71
6
  resolveTierColor,
72
7
  } from './visualization/visualization.colors.utils';
73
-
74
- /**
75
- * Creates legend rows from ordered tiers.
76
- *
77
- * @param scale - Dynamic color scale containing bounds, tiers, and overflow color.
78
- * @param symbol - Label symbol.
79
- * @returns Legend rows.
80
- */
81
- export function createColorLegendRows(
82
- scale: DynamicColorScale,
83
- symbol: 'w' | 'b',
84
- ): ColorLegendRow[] {
85
- return scale.tiers.map((tier, tierIndex) => {
86
- const lowerBound =
87
- tierIndex === 0
88
- ? scale.minimumValue
89
- : scale.tiers[tierIndex - 1].upperBound;
90
- return {
91
- label: `${formatLegendBound(lowerBound)} <= ${symbol} <= ${formatLegendBound(tier.upperBound)}`,
92
- color: tier.color,
93
- minimumValue: lowerBound,
94
- maximumValue: tier.upperBound,
95
- };
96
- });
97
- }
98
-
99
- /**
100
- * Resolves network legend layout from canvas constraints.
101
- *
102
- * @param context - Render context.
103
- * @param connectionLegendRows - Connection legend rows.
104
- * @param biasLegendRows - Bias legend rows.
105
- * @returns Computed legend layout.
106
- */
107
- export function resolveNetworkLegendLayout(
108
- context: CanvasRenderingContext2D,
109
- connectionLegendRows: ColorLegendRow[],
110
- biasLegendRows: ColorLegendRow[],
111
- ): NetworkLegendLayout {
112
- // Step 1: Resolve compact/regular legend mode from canvas constraints.
113
- const compactLegend =
114
- context.canvas.width < FLAPPY_NETWORK_LEGEND_COMPACT_WIDTH_THRESHOLD_PX ||
115
- context.canvas.height < FLAPPY_NETWORK_LEGEND_COMPACT_HEIGHT_THRESHOLD_PX;
116
- const legendWidthPx = compactLegend
117
- ? FLAPPY_NETWORK_LEGEND_COMPACT_WIDTH_PX
118
- : FLAPPY_NETWORK_LEGEND_REGULAR_WIDTH_PX;
119
- const legendHeaderHeightPx = FLAPPY_NETWORK_LEGEND_HEADER_HEIGHT_PX;
120
- const legendSectionTitleHeightPx = compactLegend
121
- ? FLAPPY_NETWORK_LEGEND_COMPACT_SECTION_TITLE_HEIGHT_PX
122
- : FLAPPY_NETWORK_LEGEND_REGULAR_SECTION_TITLE_HEIGHT_PX;
123
- const legendRowHeightPx = compactLegend
124
- ? FLAPPY_NETWORK_LEGEND_COMPACT_ROW_HEIGHT_PX
125
- : FLAPPY_NETWORK_LEGEND_REGULAR_ROW_HEIGHT_PX;
126
- const legendSectionGapPx = compactLegend
127
- ? FLAPPY_NETWORK_LEGEND_COMPACT_SECTION_GAP_PX
128
- : FLAPPY_NETWORK_LEGEND_REGULAR_SECTION_GAP_PX;
129
-
130
- // Step 2: Compute full legend panel height from section and row geometry.
131
- const legendBottomPaddingPx = FLAPPY_NETWORK_LEGEND_BOTTOM_PADDING_PX;
132
- const legendHeightPx =
133
- legendHeaderHeightPx +
134
- legendSectionTitleHeightPx +
135
- connectionLegendRows.length * legendRowHeightPx +
136
- legendSectionGapPx +
137
- legendSectionTitleHeightPx +
138
- biasLegendRows.length * legendRowHeightPx +
139
- legendBottomPaddingPx;
140
- const legendMarginPx = FLAPPY_NETWORK_LEGEND_MARGIN_PX;
141
- const preferTopLeft =
142
- context.canvas.width < FLAPPY_NETWORK_LEGEND_TOP_LEFT_THRESHOLD_PX;
143
-
144
- // Step 3: Clamp left/top placement to visible canvas bounds.
145
- const maximumLeftPx = Math.max(
146
- legendMarginPx,
147
- context.canvas.width - legendWidthPx - legendMarginPx,
148
- );
149
- const legendLeftPx = preferTopLeft ? legendMarginPx : maximumLeftPx;
150
-
151
- const maximumTopPx = Math.max(
152
- legendMarginPx,
153
- context.canvas.height - legendHeightPx - legendMarginPx,
154
- );
155
- const minimumTopPx = Math.min(FLAPPY_NETWORK_LEGEND_MIN_TOP_PX, maximumTopPx);
156
- const targetTopPx = FLAPPY_NETWORK_LEGEND_TARGET_TOP_PX;
157
- const legendTopPx = clamp(targetTopPx, minimumTopPx, maximumTopPx);
158
-
159
- return {
160
- compactLegend,
161
- legendLeftPx,
162
- legendTopPx,
163
- legendWidthPx,
164
- legendHeightPx,
165
- legendHeaderHeightPx,
166
- legendSectionTitleHeightPx,
167
- legendRowHeightPx,
168
- legendSectionGapPx,
169
- };
170
- }
171
-
172
- /**
173
- * Draws weighted connection lines.
174
- *
175
- * @param context - Render context.
176
- * @param runtimeConnections - Runtime connection list.
177
- * @param positionByNodeIndex - Node layout map.
178
- * @returns Nothing.
179
- */
180
- export function drawWeightedConnectionsLayerInternal(
181
- context: CanvasRenderingContext2D,
182
- runtimeConnections: VisualNetworkConnectionLike[],
183
- positionByNodeIndex: Map<number, PositionedNetworkNodeLike>,
184
- connectionScale: DynamicColorScale,
185
- ): void {
186
- runtimeConnections.forEach((runtimeConnection) => {
187
- const fromNodeIndex = runtimeConnection.from?.index;
188
- const toNodeIndex = runtimeConnection.to?.index;
189
- if (typeof fromNodeIndex !== 'number' || typeof toNodeIndex !== 'number') {
190
- return;
191
- }
192
-
193
- const fromPosition = positionByNodeIndex.get(fromNodeIndex);
194
- const toPosition = positionByNodeIndex.get(toNodeIndex);
195
- if (!fromPosition || !toPosition) {
196
- return;
197
- }
198
-
199
- const connectionWeight = runtimeConnection.weight ?? 0;
200
- const connectionEnabled = runtimeConnection.enabled !== false;
201
- const isNegativeConnection = connectionWeight < 0;
202
- const baseColor = resolveTierColor(
203
- connectionWeight,
204
- connectionScale.tiers,
205
- connectionScale.aboveTierColor,
206
- );
207
- const alphaValue = connectionEnabled ? 0.95 : 0.2;
208
- const connectionColor = applyAlphaToHexColor(baseColor, alphaValue);
209
- const resolvedLineWidthPx = FLAPPY_NETWORK_LEGEND_CONNECTION_LINE_WIDTH_PX;
210
-
211
- if (isNegativeConnection) {
212
- drawSquareDottedConnection(context, {
213
- fromXPx: fromPosition.xPx,
214
- fromYPx: fromPosition.yPx,
215
- toXPx: toPosition.xPx,
216
- toYPx: toPosition.yPx,
217
- color: connectionColor,
218
- lineWidthPx: resolvedLineWidthPx,
219
- });
220
- return;
221
- }
222
-
223
- context.strokeStyle = connectionColor;
224
- context.lineWidth = resolvedLineWidthPx;
225
- context.setLineDash(connectionEnabled ? [] : [5, 4]);
226
- context.beginPath();
227
- context.moveTo(fromPosition.xPx, fromPosition.yPx);
228
- context.lineTo(toPosition.xPx, toPosition.yPx);
229
- context.stroke();
230
- });
231
-
232
- context.setLineDash([]);
233
- }
234
-
235
- /**
236
- * Draws one connection as square dots along the segment.
237
- *
238
- * @param context - Render context.
239
- * @param input - Segment and style input.
240
- * @returns Nothing.
241
- */
242
- function drawSquareDottedConnection(
243
- context: CanvasRenderingContext2D,
244
- input: {
245
- fromXPx: number;
246
- fromYPx: number;
247
- toXPx: number;
248
- toYPx: number;
249
- color: string;
250
- lineWidthPx: number;
251
- },
252
- ): void {
253
- const deltaXPx = input.toXPx - input.fromXPx;
254
- const deltaYPx = input.toYPx - input.fromYPx;
255
- const segmentLengthPx = Math.hypot(deltaXPx, deltaYPx);
256
- if (segmentLengthPx <= 0) {
257
- return;
258
- }
259
-
260
- const squareSidePx = FLAPPY_NETWORK_DOTTED_CONNECTION_SQUARE_SIDE_PX;
261
- const stepDistancePx = Math.max(
262
- (squareSidePx + 2) * FLAPPY_NETWORK_DOTTED_CONNECTION_STEP_COMPACT_RATIO,
263
- input.lineWidthPx *
264
- FLAPPY_NETWORK_DOTTED_CONNECTION_WIDTH_SPACING_RATIO *
265
- FLAPPY_NETWORK_DOTTED_CONNECTION_STEP_COMPACT_RATIO,
266
- );
267
- const directionXPx = deltaXPx / segmentLengthPx;
268
- const directionYPx = deltaYPx / segmentLengthPx;
269
- const halfSquareSidePx = squareSidePx * 0.5;
270
-
271
- context.fillStyle = input.color;
272
-
273
- // Step 1: Stamp dots at fixed step distances so spacing is consistent
274
- // regardless of segment length.
275
- for (
276
- let traveledDistancePx = 0;
277
- traveledDistancePx <= segmentLengthPx;
278
- traveledDistancePx += stepDistancePx
279
- ) {
280
- const centerXPx = input.fromXPx + directionXPx * traveledDistancePx;
281
- const centerYPx = input.fromYPx + directionYPx * traveledDistancePx;
282
-
283
- const axisAlignedCenterXPx =
284
- Math.round(
285
- centerXPx +
286
- directionXPx * FLAPPY_NETWORK_DOTTED_CONNECTION_ALIGNMENT_EPSILON,
287
- ) -
288
- Math.round(
289
- directionXPx * FLAPPY_NETWORK_DOTTED_CONNECTION_ALIGNMENT_EPSILON,
290
- );
291
- const axisAlignedCenterYPx =
292
- Math.round(
293
- centerYPx +
294
- directionYPx * FLAPPY_NETWORK_DOTTED_CONNECTION_ALIGNMENT_EPSILON,
295
- ) -
296
- Math.round(
297
- directionYPx * FLAPPY_NETWORK_DOTTED_CONNECTION_ALIGNMENT_EPSILON,
298
- );
299
-
300
- context.fillRect(
301
- axisAlignedCenterXPx - halfSquareSidePx,
302
- axisAlignedCenterYPx - halfSquareSidePx,
303
- squareSidePx,
304
- squareSidePx,
305
- );
306
- }
307
-
308
- // Step 2: Ensure the segment endpoint is always represented.
309
- if (segmentLengthPx > 0) {
310
- context.fillRect(
311
- Math.round(input.toXPx - halfSquareSidePx),
312
- Math.round(input.toYPx - halfSquareSidePx),
313
- squareSidePx,
314
- squareSidePx,
315
- );
316
- }
317
- }
318
-
319
- /**
320
- * Draws all network nodes with bias labels.
321
- *
322
- * @param context - Render context.
323
- * @param positionedNodes - Positioned nodes.
324
- * @param nodeDimensions - Node dimensions.
325
- * @returns Nothing.
326
- */
327
- export function drawBiasNodesLayerInternal(
328
- context: CanvasRenderingContext2D,
329
- positionedNodes: PositionedNetworkNodeLike[],
330
- nodeDimensions: NetworkNodeDimensionsLike,
331
- biasScale: DynamicColorScale,
332
- ): void {
333
- const minimumLabelHeightPx = FLAPPY_NETWORK_MIN_LABEL_HEIGHT_PX;
334
- const halfNodeWidthPx = nodeDimensions.widthPx * 0.5;
335
- const hiddenNodeVerticalPaddingPx = 2;
336
-
337
- positionedNodes.forEach((positionedNode) => {
338
- const nodeBias = positionedNode.node.bias;
339
- const nodeLabel = formatNodeBiasLabel(nodeBias);
340
- const isOutputNode = positionedNode.node.type === 'output';
341
- const nodeFillColor = isOutputNode
342
- ? FLAPPY_NEON_PALETTE.currentRunText
343
- : resolveTierColor(nodeBias, biasScale.tiers, biasScale.aboveTierColor);
344
- const nodeStrokeColor = isOutputNode
345
- ? FLAPPY_NETWORK_OUTPUT_NODE_STROKE_COLOR
346
- : FLAPPY_NETWORK_HIDDEN_NODE_STROKE_COLOR;
347
- const nodeStrokeWidthPx = isOutputNode ? 2.1 : 1.3;
348
-
349
- const maximumReadableLabelHeightPx = Math.max(
350
- minimumLabelHeightPx,
351
- Math.floor(nodeDimensions.heightPx),
352
- );
353
- const computedLabelHeightPx = Math.floor(
354
- nodeDimensions.heightPx * FLAPPY_NETWORK_NODE_LABEL_SIZE_RATIO,
355
- );
356
- const labelHeightPx = clamp(
357
- computedLabelHeightPx,
358
- minimumLabelHeightPx,
359
- maximumReadableLabelHeightPx,
360
- );
361
- context.font = `${FLAPPY_NETWORK_NODE_LABEL_FONT_WEIGHT} ${labelHeightPx}px ${FLAPPY_MONOSPACE_FONT_FAMILY}`;
362
- // Set baseline BEFORE measuring so actualBoundingBoxAscent/Descent are
363
- // relative to the alphabetic baseline — not a stale 'top' from prior draws.
364
- context.textBaseline = 'alphabetic';
365
- const labelMetrics = context.measureText(nodeLabel);
366
- const labelAscentPx =
367
- labelMetrics.actualBoundingBoxAscent || labelHeightPx * 0.72;
368
- const labelDescentPx =
369
- labelMetrics.actualBoundingBoxDescent || labelHeightPx * 0.28;
370
- const measuredLabelHeightPx = Math.max(
371
- minimumLabelHeightPx,
372
- Math.ceil(labelAscentPx + labelDescentPx),
373
- );
374
- const resolvedNodeHeightPx = isOutputNode
375
- ? Math.max(4, nodeDimensions.heightPx - 2)
376
- : Math.max(
377
- 4,
378
- Math.min(
379
- nodeDimensions.heightPx,
380
- measuredLabelHeightPx + hiddenNodeVerticalPaddingPx,
381
- ),
382
- );
383
- const halfNodeHeightPx = resolvedNodeHeightPx * 0.5;
384
-
385
- context.fillStyle = nodeFillColor;
386
- context.strokeStyle = nodeStrokeColor;
387
- context.lineWidth = nodeStrokeWidthPx;
388
- context.shadowBlur = isOutputNode ? 7 : 0;
389
- context.shadowColor = isOutputNode
390
- ? FLAPPY_NETWORK_OUTPUT_NODE_GLOW_COLOR
391
- : 'transparent';
392
- context.fillRect(
393
- positionedNode.xPx - halfNodeWidthPx,
394
- positionedNode.yPx - halfNodeHeightPx,
395
- nodeDimensions.widthPx,
396
- resolvedNodeHeightPx,
397
- );
398
- context.strokeRect(
399
- positionedNode.xPx - halfNodeWidthPx,
400
- positionedNode.yPx - halfNodeHeightPx,
401
- nodeDimensions.widthPx,
402
- resolvedNodeHeightPx,
403
- );
404
- context.shadowBlur = 0;
405
- context.shadowColor = 'transparent';
406
-
407
- if (isOutputNode) {
408
- return;
409
- }
410
-
411
- context.fillStyle = FLAPPY_NETWORK_NODE_LABEL_FILL_COLOR;
412
- context.textAlign = 'center';
413
- const labelBaselineYPx =
414
- positionedNode.yPx + (labelAscentPx - labelDescentPx) * 0.5;
415
- context.fillText(nodeLabel, positionedNode.xPx, labelBaselineYPx);
416
- });
417
- }
418
-
419
- /**
420
- * Draws network architecture header text.
421
- *
422
- * @param context - Render context.
423
- * @param architectureLabel - Header label.
424
- * @returns Nothing.
425
- */
426
- export function drawNetworkVisualizationHeaderInternal(
427
- context: CanvasRenderingContext2D,
428
- architectureLabel: string,
429
- ): void {
430
- context.fillStyle = FLAPPY_NETWORK_HEADER_TEXT_COLOR;
431
- context.font = `${FLAPPY_NETWORK_HEADER_FONT_SIZE_PX}px ${FLAPPY_MONOSPACE_FONT_FAMILY}`;
432
- context.textAlign = 'left';
433
- context.textBaseline = 'top';
434
- const headerLines = architectureLabel.split('\n');
435
- headerLines.forEach((headerLine, lineIndex) => {
436
- context.fillText(headerLine, 8, 8 + lineIndex * 12);
437
- });
438
- }
439
-
440
- /**
441
- * Draws the color legend for connections and node bias values.
442
- *
443
- * @param context - Render context.
444
- * @returns Nothing.
445
- */
446
- export function drawNetworkColorLegendInternal(
447
- context: CanvasRenderingContext2D,
448
- architectureLabel: string,
449
- colorScales: NetworkVisualizationColorScales,
450
- ): void {
451
- const viewportWidthPx =
452
- context.canvas.ownerDocument?.defaultView?.innerWidth ??
453
- context.canvas.width;
454
- if (viewportWidthPx < FLAPPY_VIEWPORT_NETWORK_OVERLAY_HIDDEN_BREAKPOINT_PX) {
455
- return;
456
- }
457
-
458
- // Step 1: Build legend row models for connection and bias tiers.
459
- const connectionLegendRows = createColorLegendRows(
460
- colorScales.connectionScale,
461
- 'w',
462
- );
463
- const biasLegendRows = createColorLegendRows(colorScales.biasScale, 'b');
464
- const legendLayout = resolveNetworkLegendLayout(
465
- context,
466
- connectionLegendRows,
467
- biasLegendRows,
468
- );
469
-
470
- // Step 2: Draw legend panel frame and header.
471
- const {
472
- compactLegend,
473
- legendLeftPx,
474
- legendTopPx,
475
- legendWidthPx,
476
- legendHeightPx,
477
- legendHeaderHeightPx,
478
- legendSectionTitleHeightPx,
479
- legendRowHeightPx,
480
- legendSectionGapPx,
481
- } = legendLayout;
482
-
483
- // Step 2: Draw architecture description directly above legend panel.
484
- const architectureLines = architectureLabel.split('\n');
485
- const architectureLineHeightPx = 12;
486
- const architectureTextTopPx = Math.max(
487
- 4,
488
- legendTopPx - architectureLines.length * architectureLineHeightPx - 4,
489
- );
490
-
491
- context.fillStyle = FLAPPY_NETWORK_HEADER_TEXT_COLOR;
492
- context.font = `${FLAPPY_NETWORK_HEADER_FONT_SIZE_PX}px ${FLAPPY_MONOSPACE_FONT_FAMILY}`;
493
- context.textAlign = 'left';
494
- context.textBaseline = 'top';
495
- architectureLines.forEach((architectureLine, lineIndex) => {
496
- context.fillText(
497
- architectureLine,
498
- legendLeftPx + 8,
499
- architectureTextTopPx + lineIndex * architectureLineHeightPx,
500
- );
501
- });
502
-
503
- // Step 3: Draw legend panel frame and header.
504
- context.fillStyle = FLAPPY_NETWORK_LEGEND_BACKGROUND;
505
- context.strokeStyle = FLAPPY_NEON_PALETTE.statusText;
506
- context.lineWidth = 1;
507
- context.fillRect(legendLeftPx, legendTopPx, legendWidthPx, legendHeightPx);
508
- context.strokeRect(legendLeftPx, legendTopPx, legendWidthPx, legendHeightPx);
509
-
510
- context.fillStyle = FLAPPY_NETWORK_LEGEND_HEADER_COLOR;
511
- context.font = `${compactLegend ? FLAPPY_NETWORK_LEGEND_COMPACT_FONT_SIZE_PX : FLAPPY_NETWORK_LEGEND_REGULAR_FONT_SIZE_PX}px ${FLAPPY_MONOSPACE_FONT_FAMILY}`;
512
- context.textAlign = 'left';
513
- context.textBaseline = 'top';
514
- context.fillText('Legend', legendLeftPx + 8, legendTopPx + 6);
515
-
516
- // Step 4: Draw connection-weight section and row entries.
517
- context.fillStyle = FLAPPY_NETWORK_LEGEND_CONNECTION_TITLE_COLOR;
518
- const connectionSectionTopPx = legendTopPx + legendHeaderHeightPx;
519
- context.fillText(
520
- 'Connection weight',
521
- legendLeftPx + 8,
522
- connectionSectionTopPx,
523
- );
524
-
525
- connectionLegendRows.forEach((legendRow, legendRowIndex) => {
526
- const rowTopPx =
527
- connectionSectionTopPx +
528
- legendSectionTitleHeightPx +
529
- legendRowIndex * legendRowHeightPx;
530
- const sampleStartXPx = legendLeftPx + 8;
531
- const sampleEndXPx = legendLeftPx + 28;
532
- const sampleYPx = rowTopPx + 4;
533
-
534
- if (legendRow.maximumValue <= 0) {
535
- drawSquareDottedConnection(context, {
536
- fromXPx: sampleStartXPx,
537
- fromYPx: sampleYPx,
538
- toXPx: sampleEndXPx,
539
- toYPx: sampleYPx,
540
- color: legendRow.color,
541
- lineWidthPx: FLAPPY_NETWORK_LEGEND_CONNECTION_LINE_WIDTH_PX,
542
- });
543
- } else {
544
- context.strokeStyle = legendRow.color;
545
- context.lineWidth = FLAPPY_NETWORK_LEGEND_CONNECTION_LINE_WIDTH_PX;
546
- context.beginPath();
547
- context.moveTo(sampleStartXPx, sampleYPx);
548
- context.lineTo(sampleEndXPx, sampleYPx);
549
- context.stroke();
550
- }
551
-
552
- context.fillStyle = FLAPPY_NETWORK_LEGEND_ROW_TEXT_COLOR;
553
- context.fillText(legendRow.label, legendLeftPx + 32, rowTopPx - 1);
554
- });
555
-
556
- // Step 5: Draw node-bias section and row entries.
557
- context.fillStyle = FLAPPY_NETWORK_LEGEND_BIAS_TITLE_COLOR;
558
- const biasSectionTopPx =
559
- connectionSectionTopPx +
560
- legendSectionTitleHeightPx +
561
- connectionLegendRows.length * legendRowHeightPx +
562
- legendSectionGapPx;
563
- context.fillText('Node bias', legendLeftPx + 8, biasSectionTopPx);
564
-
565
- biasLegendRows.forEach((legendRow, legendRowIndex) => {
566
- const rowTopPx =
567
- biasSectionTopPx +
568
- legendSectionTitleHeightPx +
569
- legendRowIndex * legendRowHeightPx;
570
- context.fillStyle = legendRow.color;
571
- context.beginPath();
572
- context.rect(legendLeftPx + 10, rowTopPx + 1, 6, 6);
573
- context.fill();
574
-
575
- context.fillStyle = FLAPPY_NETWORK_LEGEND_ROW_TEXT_COLOR;
576
- context.fillText(legendRow.label, legendLeftPx + 22, rowTopPx - 1);
577
- });
578
- }
579
-
580
- /**
581
- * Resolves default legend layout from internal tier definitions.
582
- *
583
- * @param context - Render context.
584
- * @returns Legend layout.
585
- */
586
- export function resolveDefaultNetworkLegendLayoutInternal(
587
- context: CanvasRenderingContext2D,
588
- network: Network | undefined,
589
- ): NetworkLegendLayout {
590
- const colorScales = resolveNetworkVisualizationColorScales(network);
591
- const connectionLegendRows = createColorLegendRows(
592
- colorScales.connectionScale,
593
- 'w',
594
- );
595
- const biasLegendRows = createColorLegendRows(colorScales.biasScale, 'b');
596
- return resolveNetworkLegendLayout(
597
- context,
598
- connectionLegendRows,
599
- biasLegendRows,
600
- );
601
- }
602
-
603
- /**
604
- * Formats node bias labels with fixed sign and precision.
605
- *
606
- * @param nodeBias - Node bias value.
607
- * @returns Label text.
608
- */
609
- export function formatNodeBiasLabelInternal(nodeBias: number): string {
610
- const roundedBias = Number.isFinite(nodeBias) ? nodeBias : 0;
611
- return `${roundedBias >= 0 ? '+' : ''}${roundedBias.toFixed(2)}`;
612
- }
613
-
614
- /**
615
- * Resolves layered node groups for visualization.
616
- *
617
- * @param network - Runtime network instance.
618
- * @param inputSize - Input count fallback.
619
- * @param outputSize - Output count fallback.
620
- * @returns Layered nodes for rendering.
621
- */
622
- export function resolveNetworkVisualizationLayersInternal(
623
- network: Network | undefined,
624
- inputSize: number,
625
- outputSize: number,
626
- ): VisualNetworkNodeLike[][] {
627
- // Step 1: Build fallback two-layer structure when no network is available.
628
- if (!network) {
629
- return [
630
- Array.from({ length: inputSize }, (_unusedValue, inputNodeIndex) => ({
631
- index: inputNodeIndex,
632
- type: 'input',
633
- bias: 0,
634
- })),
635
- Array.from({ length: outputSize }, (_unusedValue, outputNodeIndex) => ({
636
- index: inputSize + outputNodeIndex,
637
- type: 'output',
638
- bias: 0,
639
- })),
640
- ];
641
- }
642
-
643
- // Step 2: Normalize runtime node shape into visualization node model.
644
- const runtimeNodes = (
645
- (network.nodes ?? []) as Array<{
646
- index?: number;
647
- type?: string;
648
- bias?: number;
649
- layer?: number;
650
- }>
651
- ).map((runtimeNode, fallbackNodeIndex) => ({
652
- index:
653
- typeof runtimeNode.index === 'number'
654
- ? runtimeNode.index
655
- : fallbackNodeIndex,
656
- type: runtimeNode.type ?? 'hidden',
657
- bias: runtimeNode.bias ?? 0,
658
- layer: runtimeNode.layer,
659
- }));
660
-
661
- const inputAndConstantNodes = runtimeNodes
662
- .filter(
663
- (runtimeNode) =>
664
- runtimeNode.type === 'input' || runtimeNode.type === 'constant',
665
- )
666
- .toSorted((leftNode, rightNode) => leftNode.index - rightNode.index);
667
-
668
- const outputNodes = runtimeNodes
669
- .filter((runtimeNode) => runtimeNode.type === 'output')
670
- .toSorted((leftNode, rightNode) => leftNode.index - rightNode.index);
671
-
672
- const hiddenNodes = runtimeNodes.filter(
673
- (runtimeNode) =>
674
- runtimeNode.type !== 'input' &&
675
- runtimeNode.type !== 'constant' &&
676
- runtimeNode.type !== 'output',
677
- );
678
-
679
- // Step 3: Resolve hidden-layer grouping (metadata first, topology fallback).
680
- const hiddenLayersByMetadata = groupHiddenNodesByLayerMetadata(hiddenNodes);
681
- const hiddenLayers =
682
- hiddenLayersByMetadata.length > 0
683
- ? hiddenLayersByMetadata
684
- : groupHiddenNodesByTopology(network, runtimeNodes, hiddenNodes);
685
-
686
- const layeredNodes = [
687
- inputAndConstantNodes,
688
- ...hiddenLayers,
689
- outputNodes,
690
- ].filter((layerNodes) => layerNodes.length > 0);
691
-
692
- // Step 4: Return layered representation with final fallback.
693
- return layeredNodes.length > 0 ? layeredNodes : [runtimeNodes];
694
- }
695
-
696
- /**
697
- * Groups hidden nodes by layer metadata.
698
- *
699
- * @param hiddenNodes - Hidden nodes.
700
- * @returns Layer groups.
701
- */
702
- function groupHiddenNodesByLayerMetadata(
703
- hiddenNodes: VisualNetworkNodeLike[],
704
- ): VisualNetworkNodeLike[][] {
705
- const hiddenNodesWithLayer = hiddenNodes.filter(
706
- (hiddenNode) => typeof hiddenNode.layer === 'number',
707
- );
708
- if (hiddenNodesWithLayer.length === 0) {
709
- return [];
710
- }
711
-
712
- const nodesByLayer = new Map<number, VisualNetworkNodeLike[]>();
713
- hiddenNodesWithLayer.forEach((hiddenNode) => {
714
- const layerIndex = hiddenNode.layer as number;
715
- const existingLayerNodes = nodesByLayer.get(layerIndex) ?? [];
716
- existingLayerNodes.push(hiddenNode);
717
- nodesByLayer.set(layerIndex, existingLayerNodes);
718
- });
719
-
720
- return [...nodesByLayer.entries()]
721
- .toSorted(
722
- (leftLayerEntry, rightLayerEntry) =>
723
- leftLayerEntry[0] - rightLayerEntry[0],
724
- )
725
- .map((layerEntry) =>
726
- layerEntry[1].toSorted(
727
- (leftNode, rightNode) => leftNode.index - rightNode.index,
728
- ),
729
- );
730
- }
731
-
732
- /**
733
- * Groups hidden nodes by topology-derived depth.
734
- *
735
- * @param network - Runtime network.
736
- * @param runtimeNodes - Runtime node list.
737
- * @param hiddenNodes - Hidden node list.
738
- * @returns Layer groups.
739
- */
740
- function groupHiddenNodesByTopology(
741
- network: Network,
742
- runtimeNodes: VisualNetworkNodeLike[],
743
- hiddenNodes: VisualNetworkNodeLike[],
744
- ): VisualNetworkNodeLike[][] {
745
- if (hiddenNodes.length === 0) {
746
- return [];
747
- }
748
-
749
- const hiddenDepthByNodeIndex = resolveHiddenNodeDepthByTopology(
750
- runtimeNodes,
751
- (network.connections ?? []) as VisualNetworkConnectionLike[],
752
- );
753
- const nodesByDepth = new Map<number, VisualNetworkNodeLike[]>();
754
-
755
- hiddenNodes.forEach((hiddenNode) => {
756
- const depth = hiddenDepthByNodeIndex.get(hiddenNode.index) ?? 1;
757
- const existingDepthNodes = nodesByDepth.get(depth) ?? [];
758
- existingDepthNodes.push(hiddenNode);
759
- nodesByDepth.set(depth, existingDepthNodes);
760
- });
761
-
762
- return [...nodesByDepth.entries()]
763
- .toSorted(
764
- (leftDepthEntry, rightDepthEntry) =>
765
- leftDepthEntry[0] - rightDepthEntry[0],
766
- )
767
- .map((depthEntry) =>
768
- depthEntry[1].toSorted(
769
- (leftNode, rightNode) => leftNode.index - rightNode.index,
770
- ),
771
- );
772
- }
773
-
774
- /**
775
- * Resolves hidden node depth map from topology.
776
- *
777
- * @param runtimeNodes - Runtime nodes.
778
- * @param runtimeConnections - Runtime connections.
779
- * @returns Depth map.
780
- */
781
- function resolveHiddenNodeDepthByTopology(
782
- runtimeNodes: VisualNetworkNodeLike[],
783
- runtimeConnections: VisualNetworkConnectionLike[],
784
- ): Map<number, number> {
785
- // Step 1: Initialize node map and adjacency bookkeeping containers.
786
- const nodeByIndex = new Map<number, VisualNetworkNodeLike>(
787
- runtimeNodes.map((runtimeNode) => [runtimeNode.index, runtimeNode]),
788
- );
789
- const outgoingTargetsByNode = new Map<number, number[]>();
790
- const incomingEdgeCountByNode = new Map<number, number>();
791
-
792
- nodeByIndex.forEach((_runtimeNode, runtimeNodeIndex) => {
793
- outgoingTargetsByNode.set(runtimeNodeIndex, []);
794
- incomingEdgeCountByNode.set(runtimeNodeIndex, 0);
795
- });
796
-
797
- // Step 2: Populate adjacency and incoming-degree counts from enabled edges.
798
- runtimeConnections.forEach((runtimeConnection) => {
799
- if (runtimeConnection.enabled === false) {
800
- return;
801
- }
802
-
803
- const fromNodeIndex = runtimeConnection.from?.index;
804
- const toNodeIndex = runtimeConnection.to?.index;
805
- if (
806
- typeof fromNodeIndex !== 'number' ||
807
- typeof toNodeIndex !== 'number' ||
808
- !nodeByIndex.has(fromNodeIndex) ||
809
- !nodeByIndex.has(toNodeIndex) ||
810
- fromNodeIndex === toNodeIndex
811
- ) {
812
- return;
813
- }
814
-
815
- const outgoingTargets = outgoingTargetsByNode.get(fromNodeIndex) ?? [];
816
- outgoingTargets.push(toNodeIndex);
817
- outgoingTargetsByNode.set(fromNodeIndex, outgoingTargets);
818
-
819
- incomingEdgeCountByNode.set(
820
- toNodeIndex,
821
- (incomingEdgeCountByNode.get(toNodeIndex) ?? 0) + 1,
822
- );
823
- });
824
-
825
- // Step 3: Run Kahn topological traversal to detect cycles and ordering.
826
- const topologicalQueue = [...incomingEdgeCountByNode.entries()]
827
- .filter((incomingEntry) => incomingEntry[1] === 0)
828
- .map((incomingEntry) => incomingEntry[0]);
829
- const topologicalOrder: number[] = [];
830
-
831
- while (topologicalQueue.length > 0) {
832
- const currentNodeIndex = topologicalQueue.shift();
833
- if (typeof currentNodeIndex !== 'number') {
834
- continue;
835
- }
836
-
837
- topologicalOrder.push(currentNodeIndex);
838
-
839
- const outgoingTargets = outgoingTargetsByNode.get(currentNodeIndex) ?? [];
840
- outgoingTargets.forEach((targetNodeIndex) => {
841
- const remainingIncomingCount =
842
- (incomingEdgeCountByNode.get(targetNodeIndex) ?? 0) - 1;
843
- incomingEdgeCountByNode.set(targetNodeIndex, remainingIncomingCount);
844
- if (remainingIncomingCount === 0) {
845
- topologicalQueue.push(targetNodeIndex);
846
- }
847
- });
848
- }
849
-
850
- // Step 4: Fallback to flat hidden depth when cycles exist.
851
- const hasCycles = topologicalOrder.length !== nodeByIndex.size;
852
- if (hasCycles) {
853
- return new Map<number, number>(
854
- runtimeNodes
855
- .filter((runtimeNode) => runtimeNode.type === 'hidden')
856
- .map((runtimeNode) => [runtimeNode.index, 1]),
857
- );
858
- }
859
-
860
- // Step 5: Propagate depth values through topological order.
861
- const depthByNodeIndex = new Map<number, number>();
862
- runtimeNodes.forEach((runtimeNode) => {
863
- const baseDepth =
864
- runtimeNode.type === 'input' || runtimeNode.type === 'constant' ? 0 : 1;
865
- depthByNodeIndex.set(runtimeNode.index, baseDepth);
866
- });
867
-
868
- topologicalOrder.forEach((fromNodeIndex) => {
869
- const fromDepth = depthByNodeIndex.get(fromNodeIndex) ?? 0;
870
- const outgoingTargets = outgoingTargetsByNode.get(fromNodeIndex) ?? [];
871
- outgoingTargets.forEach((toNodeIndex) => {
872
- const nextDepth = Math.max(
873
- depthByNodeIndex.get(toNodeIndex) ?? 1,
874
- fromDepth + 1,
875
- );
876
- depthByNodeIndex.set(toNodeIndex, nextDepth);
877
- });
878
- });
879
-
880
- // Step 6: Return hidden-node depth map only.
881
- return new Map<number, number>(
882
- runtimeNodes
883
- .filter((runtimeNode) => runtimeNode.type === 'hidden')
884
- .map((runtimeNode) => [
885
- runtimeNode.index,
886
- depthByNodeIndex.get(runtimeNode.index) ?? 1,
887
- ]),
888
- );
889
- }
890
-
891
- /**
892
- * Formats a legend bound with fixed precision.
893
- *
894
- * @param value - Numeric bound.
895
- * @returns Formatted bound string.
896
- */
897
- function formatLegendBound(value: number): string {
898
- assertFiniteLegendBound(value);
899
- return value.toFixed(2);
900
- }
8
+ export {
9
+ drawBiasNodesLayer,
10
+ drawNetworkColorLegend,
11
+ drawNetworkVisualizationHeader,
12
+ drawWeightedConnectionsLayer,
13
+ } from './visualization/visualization.draw.service';
14
+ export {
15
+ createColorLegendRows,
16
+ resolveDefaultNetworkLegendLayout,
17
+ resolveNetworkLegendLayout,
18
+ } from './visualization/visualization.legend.utils';
19
+ export {
20
+ formatNodeBiasLabel,
21
+ resolveNetworkVisualizationLayers,
22
+ } from './visualization/visualization.topology.utils';