@reicek/neataptic-ts 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/.github/agents/boundary-mapper.agent.md +29 -0
  2. package/.github/agents/docs-scout.agent.md +29 -0
  3. package/.github/agents/plan-scout.agent.md +29 -0
  4. package/.github/agents/solid-split.agent.md +138 -0
  5. package/.github/copilot-instructions.md +103 -0
  6. package/package.json +6 -3
  7. package/plans/ES2023 migration +13 -8
  8. package/plans/Evolution_Training_Interoperability_Contracts.md +1 -1
  9. package/plans/Interactive_Examples_and_Learning_Path.md +10 -2
  10. package/plans/Memory_Optimization.md +3 -3
  11. package/plans/README.md +63 -0
  12. package/plans/Roadmap.md +15 -3
  13. package/plans/asciiMaze_SOLID_split.done.md +130 -0
  14. package/plans/flappy_bird_SOLID_split.done.md +67 -0
  15. package/scripts/assets/theme.css +221 -34
  16. package/scripts/copy-examples.mjs +9 -5
  17. package/scripts/export-onnx.mjs +3 -3
  18. package/scripts/generate-bench-tables.mjs +10 -10
  19. package/scripts/generate-bench-tables.ts +10 -10
  20. package/scripts/generate-docs.ts +1415 -449
  21. package/scripts/render-docs-html.ts +15 -8
  22. package/src/README.md +101 -223
  23. package/src/architecture/README.md +57 -185
  24. package/src/architecture/layer/README.md +38 -38
  25. package/src/architecture/network/README.md +33 -31
  26. package/src/architecture/network/activate/README.md +77 -77
  27. package/src/architecture/network/connect/README.md +15 -13
  28. package/src/architecture/network/deterministic/README.md +7 -7
  29. package/src/architecture/network/evolve/README.md +44 -44
  30. package/src/architecture/network/gating/README.md +20 -20
  31. package/src/architecture/network/genetic/README.md +51 -51
  32. package/src/architecture/network/mutate/README.md +97 -97
  33. package/src/architecture/network/onnx/README.md +264 -264
  34. package/src/architecture/network/prune/README.md +39 -39
  35. package/src/architecture/network/remove/README.md +26 -26
  36. package/src/architecture/network/serialize/README.md +56 -56
  37. package/src/architecture/network/slab/README.md +61 -61
  38. package/src/architecture/network/standalone/README.md +24 -24
  39. package/src/architecture/network/stats/README.md +9 -9
  40. package/src/architecture/network/topology/README.md +46 -46
  41. package/src/architecture/network/training/README.md +21 -21
  42. package/src/methods/README.md +9 -87
  43. package/src/multithreading/README.md +8 -77
  44. package/src/multithreading/workers/README.md +2 -2
  45. package/src/multithreading/workers/browser/README.md +0 -6
  46. package/src/multithreading/workers/node/README.md +0 -3
  47. package/src/neat/README.md +562 -568
  48. package/src/utils/README.md +18 -18
  49. package/test/examples/asciiMaze/README.md +59 -59
  50. package/test/examples/asciiMaze/asciiMaze.e2e.test.ts +14 -9
  51. package/test/examples/asciiMaze/browser-entry/README.md +196 -0
  52. package/test/examples/asciiMaze/browser-entry/browser-entry.abort.services.ts +95 -0
  53. package/test/examples/asciiMaze/browser-entry/browser-entry.constants.ts +23 -0
  54. package/test/examples/asciiMaze/browser-entry/browser-entry.curriculum.services.ts +115 -0
  55. package/test/examples/asciiMaze/browser-entry/browser-entry.globals.services.ts +106 -0
  56. package/test/examples/asciiMaze/browser-entry/browser-entry.host.services.ts +157 -0
  57. package/test/examples/asciiMaze/browser-entry/browser-entry.services.ts +14 -0
  58. package/test/examples/asciiMaze/browser-entry/browser-entry.ts +129 -0
  59. package/test/examples/asciiMaze/browser-entry/browser-entry.types.ts +120 -0
  60. package/test/examples/asciiMaze/browser-entry/browser-entry.utils.ts +98 -0
  61. package/test/examples/asciiMaze/browser-entry.ts +10 -576
  62. package/test/examples/asciiMaze/dashboardManager/README.md +276 -0
  63. package/test/examples/asciiMaze/dashboardManager/archive/README.md +16 -0
  64. package/test/examples/asciiMaze/dashboardManager/archive/dashboardManager.archive.services.ts +267 -0
  65. package/test/examples/asciiMaze/dashboardManager/dashboardManager.constants.ts +35 -0
  66. package/test/examples/asciiMaze/dashboardManager/dashboardManager.services.ts +103 -0
  67. package/test/examples/asciiMaze/dashboardManager/dashboardManager.ts +181 -0
  68. package/test/examples/asciiMaze/dashboardManager/dashboardManager.types.ts +267 -0
  69. package/test/examples/asciiMaze/dashboardManager/dashboardManager.utils.ts +254 -0
  70. package/test/examples/asciiMaze/dashboardManager/live/README.md +14 -0
  71. package/test/examples/asciiMaze/dashboardManager/live/dashboardManager.live.services.ts +264 -0
  72. package/test/examples/asciiMaze/dashboardManager/telemetry/README.md +47 -0
  73. package/test/examples/asciiMaze/dashboardManager/telemetry/dashboardManager.telemetry.services.ts +513 -0
  74. package/test/examples/asciiMaze/dashboardManager.ts +13 -2335
  75. package/test/examples/asciiMaze/evolutionEngine/README.md +1058 -0
  76. package/test/examples/asciiMaze/evolutionEngine/curriculumPhase.ts +90 -0
  77. package/test/examples/asciiMaze/evolutionEngine/engineState.constants.ts +36 -0
  78. package/test/examples/asciiMaze/evolutionEngine/engineState.ts +58 -513
  79. package/test/examples/asciiMaze/evolutionEngine/engineState.types.ts +212 -0
  80. package/test/examples/asciiMaze/evolutionEngine/engineState.utils.ts +301 -0
  81. package/test/examples/asciiMaze/evolutionEngine/evolutionEngine.types.ts +445 -0
  82. package/test/examples/asciiMaze/evolutionEngine/evolutionLoop.ts +81 -50
  83. package/test/examples/asciiMaze/evolutionEngine/optionsAndSetup.ts +2 -4
  84. package/test/examples/asciiMaze/evolutionEngine/populationDynamics.ts +17 -33
  85. package/test/examples/asciiMaze/evolutionEngine/populationPruning.ts +1 -1
  86. package/test/examples/asciiMaze/evolutionEngine/rngAndTiming.ts +1 -2
  87. package/test/examples/asciiMaze/evolutionEngine/sampling.ts +1 -1
  88. package/test/examples/asciiMaze/evolutionEngine/scratchPools.ts +2 -5
  89. package/test/examples/asciiMaze/evolutionEngine/setupHelpers.ts +30 -37
  90. package/test/examples/asciiMaze/evolutionEngine/telemetryMetrics.ts +16 -58
  91. package/test/examples/asciiMaze/evolutionEngine/trainingWarmStart.ts +2 -2
  92. package/test/examples/asciiMaze/evolutionEngine.ts +55 -55
  93. package/test/examples/asciiMaze/fitness.ts +2 -2
  94. package/test/examples/asciiMaze/fitness.types.ts +65 -0
  95. package/test/examples/asciiMaze/interfaces.ts +64 -1352
  96. package/test/examples/asciiMaze/mazeMovement/README.md +356 -0
  97. package/test/examples/asciiMaze/mazeMovement/finalization/README.md +49 -0
  98. package/test/examples/asciiMaze/mazeMovement/finalization/mazeMovement.finalization.ts +138 -0
  99. package/test/examples/asciiMaze/mazeMovement/mazeMovement.constants.ts +101 -0
  100. package/test/examples/asciiMaze/mazeMovement/mazeMovement.services.ts +230 -0
  101. package/test/examples/asciiMaze/mazeMovement/mazeMovement.ts +299 -0
  102. package/test/examples/asciiMaze/mazeMovement/mazeMovement.types.ts +185 -0
  103. package/test/examples/asciiMaze/mazeMovement/mazeMovement.utils.ts +153 -0
  104. package/test/examples/asciiMaze/mazeMovement/policy/README.md +91 -0
  105. package/test/examples/asciiMaze/mazeMovement/policy/mazeMovement.policy.ts +467 -0
  106. package/test/examples/asciiMaze/mazeMovement/runtime/README.md +95 -0
  107. package/test/examples/asciiMaze/mazeMovement/runtime/mazeMovement.runtime.ts +354 -0
  108. package/test/examples/asciiMaze/mazeMovement/shaping/README.md +124 -0
  109. package/test/examples/asciiMaze/mazeMovement/shaping/mazeMovement.shaping.ts +459 -0
  110. package/test/examples/asciiMaze/mazeMovement.ts +12 -2978
  111. package/test/examples/flappy_bird/Trace-20260309T191949.json +24124 -0
  112. package/test/examples/flappy_bird/browser-entry/README.md +1129 -0
  113. package/test/examples/flappy_bird/browser-entry/browser-entry.host.utils.ts +4 -324
  114. package/test/examples/flappy_bird/browser-entry/browser-entry.network-view.utils.ts +6 -399
  115. package/test/examples/flappy_bird/browser-entry/browser-entry.playback.utils.ts +1 -717
  116. package/test/examples/flappy_bird/browser-entry/browser-entry.spawn.utils.ts +11 -31
  117. package/test/examples/flappy_bird/browser-entry/browser-entry.visualization.utils.ts +15 -893
  118. package/test/examples/flappy_bird/browser-entry/host/README.md +307 -0
  119. package/test/examples/flappy_bird/browser-entry/host/host.resize.service.ts +1 -295
  120. package/test/examples/flappy_bird/browser-entry/host/host.ts +562 -6
  121. package/test/examples/flappy_bird/browser-entry/host/resize/README.md +274 -0
  122. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.constants.ts +31 -0
  123. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.services.ts +360 -0
  124. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.ts +117 -0
  125. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.types.ts +63 -0
  126. package/test/examples/flappy_bird/browser-entry/host/resize/host.resize.service.utils.ts +250 -0
  127. package/test/examples/flappy_bird/browser-entry/network-view/README.md +399 -0
  128. package/test/examples/flappy_bird/browser-entry/network-view/network-view.topology.utils.ts +255 -0
  129. package/test/examples/flappy_bird/browser-entry/network-view/network-view.ts +802 -7
  130. package/test/examples/flappy_bird/browser-entry/playback/README.md +684 -0
  131. package/test/examples/flappy_bird/browser-entry/playback/background/README.md +277 -0
  132. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/README.md +770 -0
  133. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.cache.services.ts +178 -0
  134. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.constants.ts +107 -0
  135. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.geometry.utils.ts +518 -0
  136. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.math.utils.ts +117 -0
  137. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.pulse.utils.ts +233 -0
  138. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.services.ts +211 -0
  139. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.ts +48 -0
  140. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.types.ts +212 -0
  141. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/playback.background.ground-grid.utils.ts +81 -0
  142. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.cache.services.ts +96 -0
  143. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.constants.ts +62 -0
  144. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.services.ts +244 -0
  145. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.ts +53 -0
  146. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.types.ts +68 -0
  147. package/test/examples/flappy_bird/browser-entry/playback/background/playback.background.utils.ts +100 -0
  148. package/test/examples/flappy_bird/browser-entry/playback/frame-render/README.md +310 -0
  149. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.service.ts +92 -0
  150. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.services.ts +272 -0
  151. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.types.ts +39 -0
  152. package/test/examples/flappy_bird/browser-entry/playback/frame-render/playback.frame-render.utils.ts +493 -0
  153. package/test/examples/flappy_bird/browser-entry/playback/playback.constants.ts +1 -1
  154. package/test/examples/flappy_bird/browser-entry/playback/playback.frame-render.service.ts +4 -0
  155. package/test/examples/flappy_bird/browser-entry/playback/playback.snapshot.utils.ts +44 -0
  156. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.service.ts +39 -122
  157. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.services.ts +272 -0
  158. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.types.ts +62 -0
  159. package/test/examples/flappy_bird/browser-entry/playback/playback.starfield.utils.ts +11 -4
  160. package/test/examples/flappy_bird/browser-entry/playback/playback.ts +409 -8
  161. package/test/examples/flappy_bird/browser-entry/playback/playback.types.ts +4 -12
  162. package/test/examples/flappy_bird/browser-entry/runtime/README.md +235 -0
  163. package/test/examples/flappy_bird/browser-entry/runtime/runtime.evolution-launch.service.ts +45 -0
  164. package/test/examples/flappy_bird/browser-entry/runtime/runtime.lifecycle.service.ts +81 -0
  165. package/test/examples/flappy_bird/browser-entry/runtime/runtime.startup.service.ts +74 -0
  166. package/test/examples/flappy_bird/browser-entry/runtime/runtime.ts +31 -121
  167. package/test/examples/flappy_bird/browser-entry/runtime/runtime.types.ts +36 -0
  168. package/test/examples/flappy_bird/browser-entry/visualization/README.md +557 -0
  169. package/test/examples/flappy_bird/browser-entry/visualization/visualization.constants.ts +110 -0
  170. package/test/examples/flappy_bird/browser-entry/visualization/visualization.draw.service.ts +957 -19
  171. package/test/examples/flappy_bird/browser-entry/visualization/visualization.legend.utils.ts +138 -3
  172. package/test/examples/flappy_bird/browser-entry/visualization/visualization.topology.utils.ts +3 -27
  173. package/test/examples/flappy_bird/browser-entry/visualization/visualization.ts +1 -23
  174. package/test/examples/flappy_bird/browser-entry/worker-channel/README.md +156 -0
  175. package/test/examples/flappy_bird/constants/README.md +1179 -0
  176. package/test/examples/flappy_bird/constants/constants.network-view.ts +24 -0
  177. package/test/examples/flappy_bird/constants/constants.palette.ts +7 -0
  178. package/test/examples/flappy_bird/constants/constants.starfield.ts +78 -3
  179. package/test/examples/flappy_bird/environment/README.md +143 -0
  180. package/test/examples/flappy_bird/environment/environment.observation.utils.ts +1 -19
  181. package/test/examples/flappy_bird/environment/environment.step.service.ts +3 -66
  182. package/test/examples/flappy_bird/evaluation/README.md +130 -0
  183. package/test/examples/flappy_bird/evaluation/evaluation.fitness.utils.ts +1 -1
  184. package/test/examples/flappy_bird/evaluation/evaluation.rollout.service.ts +5 -375
  185. package/test/examples/flappy_bird/evaluation/rollout/README.md +291 -0
  186. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.constants.ts +30 -0
  187. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.service.ts +58 -0
  188. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.services.ts +310 -0
  189. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.types.ts +56 -0
  190. package/test/examples/flappy_bird/evaluation/rollout/evaluation.rollout.utils.ts +368 -0
  191. package/test/examples/flappy_bird/flappy-evolution-worker/README.md +618 -0
  192. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.playback.service.ts +7 -7
  193. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.simulation.frame.service.ts +364 -0
  194. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.simulation.types.ts +14 -0
  195. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.simulation.utils.ts +4 -201
  196. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.ts +184 -345
  197. package/test/examples/flappy_bird/flappy-evolution-worker/flappy-evolution-worker.warm-start.service.ts +291 -0
  198. package/test/examples/flappy_bird/flappy.simulation.shared.utils.ts +5 -0
  199. package/test/examples/flappy_bird/simulation-shared/README.md +417 -0
  200. package/test/examples/flappy_bird/simulation-shared/observation/README.md +183 -0
  201. package/test/examples/flappy_bird/simulation-shared/observation/observation.features.utils.ts +301 -0
  202. package/test/examples/flappy_bird/simulation-shared/observation/observation.ts +9 -0
  203. package/test/examples/flappy_bird/simulation-shared/observation/observation.vector.utils.ts +59 -0
  204. package/test/examples/flappy_bird/simulation-shared/simulation-shared.observation.utils.ts +5 -403
  205. package/test/examples/flappy_bird/simulation-shared/simulation-shared.spawn.utils.ts +20 -6
  206. package/test/examples/flappy_bird/{evaluation/evaluation.statistics.utils.ts → simulation-shared/simulation-shared.statistics.utils.ts} +23 -8
  207. package/test/examples/flappy_bird/trainer/README.md +563 -0
  208. package/test/examples/flappy_bird/trainer/evaluation/README.md +199 -0
  209. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.constants.ts +9 -0
  210. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.services.ts +73 -0
  211. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.ts +165 -0
  212. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.types.ts +25 -0
  213. package/test/examples/flappy_bird/trainer/evaluation/trainer.evaluation.service.utils.ts +161 -0
  214. package/test/examples/flappy_bird/trainer/trainer.evaluation.service.ts +13 -0
  215. package/test/examples/flappy_bird/trainer/trainer.report.service.services.ts +181 -0
  216. package/test/examples/flappy_bird/trainer/trainer.report.service.ts +126 -0
  217. package/test/examples/flappy_bird/trainer/trainer.selection.utils.ts +89 -0
  218. package/test/examples/flappy_bird/trainer/trainer.ts +11 -553
  219. package/test/examples/flappy_bird/browser-entry/browser-entry.utils.ts +0 -12
  220. package/test/examples/flappy_bird/environment/environment.ts +0 -7
  221. package/test/examples/flappy_bird/evaluation/evaluation.ts +0 -7
  222. package/test/examples/flappy_bird/simulation-shared/simulation-shared.ts +0 -15
  223. package/test/examples/flappy_bird/trainer/trainer.statistics.utils.ts +0 -78
@@ -0,0 +1,233 @@
1
+ import {
2
+ FLAPPY_GROUND_GRID_APPROX_FRAME_DURATION_MS,
3
+ FLAPPY_GROUND_GRID_PULSE_ALPHA,
4
+ FLAPPY_GROUND_GRID_PULSE_GLOW_BLUR_PX,
5
+ FLAPPY_GROUND_GRID_PULSE_INTERVAL_MS,
6
+ FLAPPY_GROUND_GRID_PULSE_LIFETIME_MS,
7
+ FLAPPY_GROUND_GRID_PULSE_MAX_SIZE_PX,
8
+ FLAPPY_GROUND_GRID_PULSE_MIN_SIZE_PX,
9
+ FLAPPY_GROUND_GRID_UNSIGNED_NORMALIZATION_DIVISOR,
10
+ FLAPPY_GROUND_GRID_VERTICAL_PULSE_END_RATIO,
11
+ FLAPPY_GROUND_GRID_VERTICAL_PULSE_START_RATIO,
12
+ } from './playback.background.ground-grid.constants';
13
+ import {
14
+ interpolatePlaybackGroundGridPoint,
15
+ resolvePlaybackGroundGridDepthFromHorizonDistance,
16
+ resolvePlaybackGroundGridLineThickness,
17
+ } from './playback.background.ground-grid.math.utils';
18
+ import type {
19
+ PlaybackBackgroundGroundGridSceneContext,
20
+ PlaybackGroundGridPulse,
21
+ PlaybackGroundGridPulseInput,
22
+ PlaybackGroundGridPulseOrientation,
23
+ PlaybackGroundGridPulsePath,
24
+ PlaybackGroundGridPulseTrackThicknessInput,
25
+ PlaybackGroundGridPulseTravelRatioInput,
26
+ } from './playback.background.ground-grid.types';
27
+
28
+ /**
29
+ * Resolves one rare, deterministic pulse square for the current frame.
30
+ *
31
+ * @param input - Current frame timing and visible pulse path candidates.
32
+ * @returns Visible pulse square, or null when the current slot is inactive.
33
+ */
34
+ export function resolvePlaybackGroundGridPulse(
35
+ input: PlaybackGroundGridPulseInput,
36
+ ): PlaybackGroundGridPulse | null {
37
+ const currentTimeMs =
38
+ input.frameIndex * FLAPPY_GROUND_GRID_APPROX_FRAME_DURATION_MS;
39
+ const pulseSlotIndex = Math.floor(
40
+ currentTimeMs / FLAPPY_GROUND_GRID_PULSE_INTERVAL_MS,
41
+ );
42
+ const pulseElapsedMs =
43
+ currentTimeMs - pulseSlotIndex * FLAPPY_GROUND_GRID_PULSE_INTERVAL_MS;
44
+ if (pulseElapsedMs > FLAPPY_GROUND_GRID_PULSE_LIFETIME_MS) {
45
+ return null;
46
+ }
47
+
48
+ const pulseOrientation =
49
+ resolvePlaybackGroundGridPulseOrientation(pulseSlotIndex);
50
+ const selectedPulsePath =
51
+ pulseOrientation === 'horizontal'
52
+ ? resolvePlaybackGroundGridHorizontalPulsePath(
53
+ input.horizontalPulsePaths,
54
+ pulseSlotIndex,
55
+ )
56
+ : resolvePlaybackGroundGridVerticalPulsePath(
57
+ input.verticalPulsePaths,
58
+ input.visibleVerticalPulsePaths,
59
+ pulseSlotIndex,
60
+ );
61
+ if (!selectedPulsePath) {
62
+ return null;
63
+ }
64
+
65
+ const directionIsForward =
66
+ resolvePlaybackGroundGridUnitHash(pulseSlotIndex, 29) >= 0.5;
67
+ const lifetimeProgressRatio =
68
+ pulseElapsedMs / FLAPPY_GROUND_GRID_PULSE_LIFETIME_MS;
69
+ const travelProgressRatio = resolvePlaybackGroundGridPulseTravelRatio({
70
+ directionIsForward,
71
+ lifetimeProgressRatio,
72
+ orientation: pulseOrientation,
73
+ });
74
+ const pulseCenter = interpolatePlaybackGroundGridPoint(
75
+ selectedPulsePath.startXPx,
76
+ selectedPulsePath.startYPx,
77
+ selectedPulsePath.endXPx,
78
+ selectedPulsePath.endYPx,
79
+ travelProgressRatio,
80
+ );
81
+ const pulseTrackThicknessPx = resolvePlaybackGroundGridPulseTrackThickness({
82
+ pulseCenterYPx: pulseCenter.yPx,
83
+ pulsePath: selectedPulsePath,
84
+ sceneContext: input.sceneContext,
85
+ });
86
+
87
+ return {
88
+ centerXPx: pulseCenter.xPx,
89
+ centerYPx: pulseCenter.yPx,
90
+ sizePx: Math.max(
91
+ FLAPPY_GROUND_GRID_PULSE_MIN_SIZE_PX,
92
+ Math.min(FLAPPY_GROUND_GRID_PULSE_MAX_SIZE_PX, pulseTrackThicknessPx),
93
+ ),
94
+ alpha: FLAPPY_GROUND_GRID_PULSE_ALPHA,
95
+ glowBlurPx: FLAPPY_GROUND_GRID_PULSE_GLOW_BLUR_PX,
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Resolves pulse orientation for one deterministic pulse slot.
101
+ *
102
+ * @param pulseSlotIndex - Zero-based pulse slot index.
103
+ * @returns Horizontal or vertical pulse travel orientation.
104
+ */
105
+ function resolvePlaybackGroundGridPulseOrientation(
106
+ pulseSlotIndex: number,
107
+ ): PlaybackGroundGridPulseOrientation {
108
+ return resolvePlaybackGroundGridUnitHash(pulseSlotIndex, 11) >= 0.5
109
+ ? 'horizontal'
110
+ : 'vertical';
111
+ }
112
+
113
+ /**
114
+ * Selects one thick-enough horizontal band for the current pulse slot.
115
+ *
116
+ * @param horizontalPulsePaths - Cached horizontal pulse paths eligible for travel.
117
+ * @param pulseSlotIndex - Zero-based pulse slot index.
118
+ * @returns Horizontal pulse path, or null when none are suitable.
119
+ */
120
+ function resolvePlaybackGroundGridHorizontalPulsePath(
121
+ horizontalPulsePaths: readonly PlaybackGroundGridPulsePath[],
122
+ pulseSlotIndex: number,
123
+ ): PlaybackGroundGridPulsePath | null {
124
+ if (horizontalPulsePaths.length === 0) {
125
+ return null;
126
+ }
127
+
128
+ const selectedLineIndex = Math.min(
129
+ horizontalPulsePaths.length - 1,
130
+ Math.floor(
131
+ resolvePlaybackGroundGridUnitHash(pulseSlotIndex, 17) *
132
+ horizontalPulsePaths.length,
133
+ ),
134
+ );
135
+ return horizontalPulsePaths[selectedLineIndex];
136
+ }
137
+
138
+ /**
139
+ * Selects one sparse vertical pulse path for the current pulse slot.
140
+ *
141
+ * @param verticalPulsePaths - Full vertical ray paths.
142
+ * @param visibleVerticalPulsePaths - Visible subset preferred for on-screen pulses.
143
+ * @param pulseSlotIndex - Zero-based pulse slot index.
144
+ * @returns Vertical pulse path, or null when none are available.
145
+ */
146
+ function resolvePlaybackGroundGridVerticalPulsePath(
147
+ verticalPulsePaths: readonly PlaybackGroundGridPulsePath[],
148
+ visibleVerticalPulsePaths: readonly PlaybackGroundGridPulsePath[],
149
+ pulseSlotIndex: number,
150
+ ): PlaybackGroundGridPulsePath | null {
151
+ if (verticalPulsePaths.length === 0) {
152
+ return null;
153
+ }
154
+
155
+ const candidatePulsePaths =
156
+ visibleVerticalPulsePaths.length > 0
157
+ ? visibleVerticalPulsePaths
158
+ : verticalPulsePaths;
159
+
160
+ const selectedPathIndex = Math.min(
161
+ candidatePulsePaths.length - 1,
162
+ Math.floor(
163
+ resolvePlaybackGroundGridUnitHash(pulseSlotIndex, 23) *
164
+ candidatePulsePaths.length,
165
+ ),
166
+ );
167
+ return candidatePulsePaths[selectedPathIndex];
168
+ }
169
+
170
+ /**
171
+ * Resolves the pulse travel ratio along its chosen line.
172
+ *
173
+ * @param input - Pulse timing direction and orientation.
174
+ * @returns Normalized 0..1 travel ratio along the chosen line.
175
+ */
176
+ function resolvePlaybackGroundGridPulseTravelRatio(
177
+ input: PlaybackGroundGridPulseTravelRatioInput,
178
+ ): number {
179
+ const baseTravelProgressRatio = input.directionIsForward
180
+ ? input.lifetimeProgressRatio
181
+ : 1 - input.lifetimeProgressRatio;
182
+ if (input.orientation === 'horizontal') {
183
+ return baseTravelProgressRatio;
184
+ }
185
+
186
+ return (
187
+ FLAPPY_GROUND_GRID_VERTICAL_PULSE_START_RATIO +
188
+ (FLAPPY_GROUND_GRID_VERTICAL_PULSE_END_RATIO -
189
+ FLAPPY_GROUND_GRID_VERTICAL_PULSE_START_RATIO) *
190
+ baseTravelProgressRatio
191
+ );
192
+ }
193
+
194
+ /**
195
+ * Resolves the local track thickness at the pulse position.
196
+ *
197
+ * @param input - Pulse position, path, and scene geometry.
198
+ * @returns Thickness of the current line under the pulse.
199
+ */
200
+ function resolvePlaybackGroundGridPulseTrackThickness(
201
+ input: PlaybackGroundGridPulseTrackThicknessInput,
202
+ ): number {
203
+ if (input.pulsePath.orientation === 'horizontal') {
204
+ return input.pulsePath.thicknessPx;
205
+ }
206
+
207
+ const maximumDistanceToHorizonPx = Math.max(
208
+ 1,
209
+ input.sceneContext.lowerBandBottomYPx -
210
+ input.sceneContext.alignedHorizonYPx,
211
+ );
212
+ const depthRatio = resolvePlaybackGroundGridDepthFromHorizonDistance(
213
+ input.pulseCenterYPx - input.sceneContext.alignedHorizonYPx,
214
+ maximumDistanceToHorizonPx,
215
+ );
216
+ return resolvePlaybackGroundGridLineThickness(depthRatio);
217
+ }
218
+
219
+ /**
220
+ * Resolves a deterministic unit-interval hash from a slot index and salt.
221
+ *
222
+ * @param seed - Slot-local seed value.
223
+ * @param salt - Small integer salt used to pick a stable random stream.
224
+ * @returns Stable random value in the range 0..1.
225
+ */
226
+ function resolvePlaybackGroundGridUnitHash(seed: number, salt: number): number {
227
+ let hashedValue = (seed + 1) ^ Math.imul(salt + 1, 374_761_393);
228
+ hashedValue = Math.imul(hashedValue ^ (hashedValue >>> 13), 1_274_126_177);
229
+ hashedValue ^= hashedValue >>> 16;
230
+ return (
231
+ (hashedValue >>> 0) / FLAPPY_GROUND_GRID_UNSIGNED_NORMALIZATION_DIVISOR
232
+ );
233
+ }
@@ -0,0 +1,211 @@
1
+ import {
2
+ FLAPPY_GROUND_GRID_FOG_ALPHA,
3
+ FLAPPY_GROUND_GRID_FOG_HEIGHT_RATIO,
4
+ FLAPPY_GROUND_GRID_PULSE_GLOW_ALPHA_RATIO,
5
+ FLAPPY_GROUND_GRID_PULSE_GLOW_SIZE_MULTIPLIER,
6
+ } from './playback.background.ground-grid.constants';
7
+ import {
8
+ FLAPPY_BACKGROUND_COMPOSITE_LIGHTER,
9
+ FLAPPY_BACKGROUND_COMPOSITE_SOURCE_OVER,
10
+ } from '../playback.background.constants';
11
+ import {
12
+ resolveCachedGroundGridFogGradient,
13
+ resolveGroundGridSceneCacheKey,
14
+ } from './playback.background.ground-grid.cache.services';
15
+ import type {
16
+ PlaybackBackgroundGroundGridResolvedScene,
17
+ PlaybackGroundGridGeometry,
18
+ PlaybackGroundGridPulse,
19
+ PlaybackGroundGridSegmentBatch,
20
+ } from './playback.background.ground-grid.types';
21
+
22
+ /**
23
+ * Draws the resolved neon ground grid inside the lower background band.
24
+ *
25
+ * @param context - Canvas 2D drawing context.
26
+ * @param resolvedScene - Geometry and style for the current viewport.
27
+ * @param geometry - Precomputed horizontal and vertical line segments.
28
+ * @returns Nothing.
29
+ */
30
+ export function drawPlaybackGroundGrid(
31
+ context: CanvasRenderingContext2D,
32
+ resolvedScene: PlaybackBackgroundGroundGridResolvedScene,
33
+ geometry: PlaybackGroundGridGeometry,
34
+ ): void {
35
+ context.save();
36
+ context.translate(resolvedScene.sceneContext.viewportOffsetXPx, 0);
37
+ context.globalCompositeOperation = FLAPPY_BACKGROUND_COMPOSITE_SOURCE_OVER;
38
+ context.beginPath();
39
+ context.rect(
40
+ 0,
41
+ resolvedScene.sceneContext.lowerBandTopYPx,
42
+ resolvedScene.sceneContext.visibleWorldWidthPx,
43
+ resolvedScene.sceneContext.lowerBandHeightPx,
44
+ );
45
+ context.clip();
46
+
47
+ drawGroundGridFog(context, resolvedScene);
48
+ drawGroundGridSegmentBatches(
49
+ context,
50
+ geometry.verticalLineBatches,
51
+ resolvedScene.style.lineColor,
52
+ resolvedScene.style.glowColor,
53
+ );
54
+ drawGroundGridSegmentBatches(
55
+ context,
56
+ geometry.horizontalLineBatches,
57
+ resolvedScene.style.lineColor,
58
+ resolvedScene.style.glowColor,
59
+ );
60
+ drawGroundGridPulse(
61
+ context,
62
+ geometry.pulse,
63
+ resolvedScene.style.pulseFillColor,
64
+ resolvedScene.style.pulseGlowColor,
65
+ );
66
+ context.restore();
67
+ }
68
+
69
+ /**
70
+ * Draws the lower-band atmospheric wash behind the neon line work.
71
+ *
72
+ * @param context - Canvas 2D drawing context.
73
+ * @param resolvedScene - Geometry and style for the current viewport.
74
+ * @returns Nothing.
75
+ */
76
+ export function drawGroundGridFog(
77
+ context: CanvasRenderingContext2D,
78
+ resolvedScene: PlaybackBackgroundGroundGridResolvedScene,
79
+ ): void {
80
+ const fogHeightPx =
81
+ resolvedScene.sceneContext.lowerBandHeightPx *
82
+ FLAPPY_GROUND_GRID_FOG_HEIGHT_RATIO;
83
+ const sceneCacheKey = resolveGroundGridSceneCacheKey(
84
+ resolvedScene.sceneContext,
85
+ );
86
+ const fogGradient = resolveCachedGroundGridFogGradient(
87
+ context,
88
+ sceneCacheKey,
89
+ resolvedScene.sceneContext,
90
+ resolvedScene.style.fogColor,
91
+ );
92
+
93
+ context.save();
94
+ context.globalAlpha = FLAPPY_GROUND_GRID_FOG_ALPHA;
95
+ context.fillStyle = fogGradient;
96
+ context.fillRect(
97
+ 0,
98
+ resolvedScene.sceneContext.lowerBandTopYPx,
99
+ resolvedScene.sceneContext.visibleWorldWidthPx,
100
+ fogHeightPx,
101
+ );
102
+ context.restore();
103
+ }
104
+
105
+ /**
106
+ * Draws one ordered collection of neon segment batches.
107
+ *
108
+ * @param context - Canvas 2D drawing context.
109
+ * @param batches - Ordered line-segment batches to render.
110
+ * @param lineColor - Core neon stroke color.
111
+ * @param glowColor - Outer glow color used for bloom.
112
+ * @returns Nothing.
113
+ */
114
+ export function drawGroundGridSegmentBatches(
115
+ context: CanvasRenderingContext2D,
116
+ batches: readonly PlaybackGroundGridSegmentBatch[],
117
+ lineColor: string,
118
+ glowColor: string,
119
+ ): void {
120
+ context.save();
121
+ context.strokeStyle = lineColor;
122
+ context.shadowColor = glowColor;
123
+
124
+ for (const batch of batches) {
125
+ drawGroundGridSegmentBatch(context, batch);
126
+ }
127
+
128
+ context.restore();
129
+ }
130
+
131
+ /**
132
+ * Draws one batch of neon line segments that share one render style.
133
+ *
134
+ * @param context - Canvas 2D drawing context.
135
+ * @param batch - Ordered line-segment batch that shares one render style.
136
+ * @returns Nothing.
137
+ */
138
+ export function drawGroundGridSegmentBatch(
139
+ context: CanvasRenderingContext2D,
140
+ batch: PlaybackGroundGridSegmentBatch,
141
+ ): void {
142
+ context.shadowBlur = batch.blurPx;
143
+ context.globalAlpha = batch.alpha;
144
+ context.lineWidth = batch.thicknessPx;
145
+ context.beginPath();
146
+
147
+ for (const segment of batch.segments) {
148
+ context.moveTo(segment.startXPx, segment.startYPx);
149
+ context.lineTo(segment.endXPx, segment.endYPx);
150
+ }
151
+
152
+ context.stroke();
153
+
154
+ context.shadowBlur = 0;
155
+ context.globalAlpha = Math.min(1, batch.alpha + 0.18);
156
+ context.beginPath();
157
+
158
+ for (const segment of batch.segments) {
159
+ context.moveTo(segment.startXPx, segment.startYPx);
160
+ context.lineTo(segment.endXPx, segment.endYPx);
161
+ }
162
+
163
+ context.stroke();
164
+ }
165
+
166
+ /**
167
+ * Draws one pulse square above the grid lines and below gameplay entities.
168
+ *
169
+ * @param context - Canvas 2D drawing context.
170
+ * @param pulse - Visible pulse square for the current frame.
171
+ * @param fillColor - Core neon fill color.
172
+ * @param glowColor - Outer glow color used behind the pulse.
173
+ * @returns Nothing.
174
+ */
175
+ export function drawGroundGridPulse(
176
+ context: CanvasRenderingContext2D,
177
+ pulse: PlaybackGroundGridPulse | null,
178
+ fillColor: string,
179
+ glowColor: string,
180
+ ): void {
181
+ if (!pulse) {
182
+ return;
183
+ }
184
+
185
+ const halfSizePx = pulse.sizePx * 0.5;
186
+ const glowSizePx =
187
+ pulse.sizePx * FLAPPY_GROUND_GRID_PULSE_GLOW_SIZE_MULTIPLIER;
188
+ const glowHalfSizePx = glowSizePx * 0.5;
189
+
190
+ context.save();
191
+ context.globalCompositeOperation = FLAPPY_BACKGROUND_COMPOSITE_LIGHTER;
192
+ context.fillStyle = fillColor;
193
+ context.shadowColor = glowColor;
194
+ context.shadowBlur = pulse.glowBlurPx;
195
+ context.globalAlpha = pulse.alpha * FLAPPY_GROUND_GRID_PULSE_GLOW_ALPHA_RATIO;
196
+ context.fillRect(
197
+ pulse.centerXPx - glowHalfSizePx,
198
+ pulse.centerYPx - glowHalfSizePx,
199
+ glowSizePx,
200
+ glowSizePx,
201
+ );
202
+
203
+ context.globalAlpha = pulse.alpha;
204
+ context.fillRect(
205
+ pulse.centerXPx - halfSizePx,
206
+ pulse.centerYPx - halfSizePx,
207
+ pulse.sizePx,
208
+ pulse.sizePx,
209
+ );
210
+ context.restore();
211
+ }
@@ -0,0 +1,48 @@
1
+ import { drawPlaybackGroundGrid } from './playback.background.ground-grid.services';
2
+ import { FLAPPY_BACKGROUND_GROUND_GRID_STYLE } from './playback.background.ground-grid.constants';
3
+ import type {
4
+ PlaybackBackgroundGroundGridRequest,
5
+ PlaybackBackgroundGroundGridResolvedScene,
6
+ PlaybackBackgroundGroundGridSourceScene,
7
+ } from './playback.background.ground-grid.types';
8
+ import {
9
+ resolvePlaybackGroundGridGeometry,
10
+ resolvePlaybackGroundGridSceneContext,
11
+ } from './playback.background.ground-grid.utils';
12
+
13
+ /**
14
+ * Draws the neon lower-band ground grid beneath the horizon.
15
+ *
16
+ * The grid is intentionally stylized rather than physically realistic: fixed
17
+ * horizontal depth bands compress toward the horizon, while moving perspective
18
+ * rays slide sideways but still converge to the centered vanishing point.
19
+ *
20
+ * @param context - Canvas 2D drawing context.
21
+ * @param sourceScene - Shared lower-band geometry from the background module.
22
+ * @param request - Shared parallax scroll input for the current frame.
23
+ * @returns Nothing.
24
+ */
25
+ export function renderPlaybackBackgroundGroundGrid(
26
+ context: CanvasRenderingContext2D,
27
+ sourceScene: PlaybackBackgroundGroundGridSourceScene,
28
+ request: PlaybackBackgroundGroundGridRequest,
29
+ ): void {
30
+ // Step 1: Adapt the shared background scene into the narrow grid contract.
31
+ const sceneContext = resolvePlaybackGroundGridSceneContext(sourceScene);
32
+
33
+ // Step 2: Resolve theme-owned styling for the lower-band grid.
34
+ const resolvedScene: PlaybackBackgroundGroundGridResolvedScene = {
35
+ sceneContext,
36
+ style: FLAPPY_BACKGROUND_GROUND_GRID_STYLE,
37
+ };
38
+
39
+ // Step 3: Build all grid geometry before touching the canvas state.
40
+ const geometry = resolvePlaybackGroundGridGeometry(
41
+ sceneContext,
42
+ request.frameIndex,
43
+ request.scrollBasePx,
44
+ );
45
+
46
+ // Step 4: Draw the resolved lower-band neon grid.
47
+ drawPlaybackGroundGrid(context, resolvedScene, geometry);
48
+ }
@@ -0,0 +1,212 @@
1
+ import type { PlaybackBackgroundSceneContext } from '../playback.background.types';
2
+
3
+ /**
4
+ * Narrow request required to render the playback ground grid.
5
+ */
6
+ export type PlaybackBackgroundGroundGridRequest = {
7
+ frameIndex: number;
8
+ scrollBasePx: number;
9
+ };
10
+
11
+ /**
12
+ * Lazy builder used when one horizontal geometry cache entry is missing.
13
+ */
14
+ export type PlaybackGroundGridHorizontalGeometryFactory =
15
+ () => PlaybackGroundGridHorizontalGeometry;
16
+
17
+ /**
18
+ * Lazy builder used when one vertical geometry cache entry is missing.
19
+ */
20
+ export type PlaybackGroundGridVerticalGeometryFactory =
21
+ () => PlaybackGroundGridVerticalGeometry;
22
+
23
+ /**
24
+ * Immutable scene context resolved for one lower-band ground-grid pass.
25
+ */
26
+ export type PlaybackBackgroundGroundGridSceneContext = {
27
+ viewportOffsetXPx: number;
28
+ visibleWorldWidthPx: number;
29
+ alignedHorizonYPx: number;
30
+ lowerBandTopYPx: number;
31
+ lowerBandHeightPx: number;
32
+ lowerBandBottomYPx: number;
33
+ vanishingPointXPx: number;
34
+ vanishingPointYPx: number;
35
+ };
36
+
37
+ /**
38
+ * Theme-owned style contract for the neon ground grid.
39
+ */
40
+ export type PlaybackBackgroundGroundGridStyle = {
41
+ lineColor: string;
42
+ glowColor: string;
43
+ fogColor: string;
44
+ pulseFillColor: string;
45
+ pulseGlowColor: string;
46
+ };
47
+
48
+ /** Travel orientation used by lightweight pulse overlays. */
49
+ export type PlaybackGroundGridPulseOrientation = 'horizontal' | 'vertical';
50
+
51
+ /**
52
+ * Geometry and style package resolved before drawing the ground grid.
53
+ */
54
+ export type PlaybackBackgroundGroundGridResolvedScene = {
55
+ sceneContext: PlaybackBackgroundGroundGridSceneContext;
56
+ style: PlaybackBackgroundGroundGridStyle;
57
+ };
58
+
59
+ /**
60
+ * Declarative line segment model used by the ground-grid renderer.
61
+ */
62
+ export type PlaybackGroundGridLineSegment = {
63
+ startXPx: number;
64
+ startYPx: number;
65
+ endXPx: number;
66
+ endYPx: number;
67
+ alpha: number;
68
+ blurPx: number;
69
+ thicknessPx: number;
70
+ };
71
+
72
+ /** Ordered batch of line segments that share one render style. */
73
+ export type PlaybackGroundGridSegmentBatch = {
74
+ alpha: number;
75
+ blurPx: number;
76
+ thicknessPx: number;
77
+ segments: readonly PlaybackGroundGridLineSegment[];
78
+ };
79
+
80
+ /**
81
+ * Internal helper contract used while generating vertical-ray sub-segments.
82
+ */
83
+ export type PlaybackGroundGridVerticalRayInput = PlaybackGroundGridPulsePath & {
84
+ lineDepthRatio: number;
85
+ maximumDistanceToHorizonPx: number;
86
+ sceneContext: PlaybackBackgroundGroundGridSceneContext;
87
+ verticalSegmentCount: number;
88
+ };
89
+
90
+ /** Simplified path used by one visible pulse event. */
91
+ export type PlaybackGroundGridPulsePath = {
92
+ orientation: PlaybackGroundGridPulseOrientation;
93
+ startXPx: number;
94
+ startYPx: number;
95
+ endXPx: number;
96
+ endYPx: number;
97
+ thicknessPx: number;
98
+ };
99
+
100
+ /** One visible pulse square rendered above the grid lines. */
101
+ export type PlaybackGroundGridPulse = {
102
+ centerXPx: number;
103
+ centerYPx: number;
104
+ sizePx: number;
105
+ alpha: number;
106
+ glowBlurPx: number;
107
+ };
108
+
109
+ /**
110
+ * Pure geometry bundle generated before canvas drawing begins.
111
+ */
112
+ export type PlaybackGroundGridGeometry = {
113
+ horizontalLineBatches: readonly PlaybackGroundGridSegmentBatch[];
114
+ pulse: PlaybackGroundGridPulse | null;
115
+ verticalLineBatches: readonly PlaybackGroundGridSegmentBatch[];
116
+ };
117
+
118
+ /**
119
+ * Cached horizontal geometry bundle reused across matching scene sizes.
120
+ */
121
+ export type PlaybackGroundGridHorizontalGeometry = {
122
+ horizontalLineBatches: readonly PlaybackGroundGridSegmentBatch[];
123
+ horizontalLines: readonly PlaybackGroundGridLineSegment[];
124
+ preferredHorizontalPulsePaths: readonly PlaybackGroundGridPulsePath[];
125
+ };
126
+
127
+ /**
128
+ * Cached vertical geometry bundle reused across one wrapped scroll cycle.
129
+ */
130
+ export type PlaybackGroundGridVerticalGeometry = {
131
+ verticalLineBatches: readonly PlaybackGroundGridSegmentBatch[];
132
+ verticalPulsePaths: readonly PlaybackGroundGridPulsePath[];
133
+ visibleVerticalPulsePaths: readonly PlaybackGroundGridPulsePath[];
134
+ };
135
+
136
+ /**
137
+ * Visible horizon bounds projected onto the bottom anchor line.
138
+ */
139
+ export type PlaybackGroundGridAnchorBounds = {
140
+ leftAnchorXPx: number;
141
+ rightAnchorXPx: number;
142
+ anchorSpanPx: number;
143
+ };
144
+
145
+ /**
146
+ * Input used when projecting a visible horizon span onto anchor space.
147
+ */
148
+ export type PlaybackGroundGridAnchorBoundsInput = {
149
+ horizonLeftXPx: number;
150
+ horizonRightXPx: number;
151
+ sceneContext: PlaybackBackgroundGroundGridSceneContext;
152
+ };
153
+
154
+ /**
155
+ * Input used when projecting one horizon x-position onto the floor anchor line.
156
+ */
157
+ export type PlaybackGroundGridAnchorProjectionInput = {
158
+ horizonXPx: number;
159
+ sceneContext: PlaybackBackgroundGroundGridSceneContext;
160
+ };
161
+
162
+ /**
163
+ * Wrapped vertical-cycle state derived from scroll for one frame.
164
+ */
165
+ export type PlaybackGroundGridVerticalCycleContext = {
166
+ quantizedWrappedOffsetPx: number;
167
+ safeLaneSpacingPx: number;
168
+ };
169
+
170
+ /**
171
+ * Input contract used while resolving one deterministic pulse event.
172
+ */
173
+ export type PlaybackGroundGridPulseInput = {
174
+ frameIndex: number;
175
+ horizontalPulsePaths: readonly PlaybackGroundGridPulsePath[];
176
+ sceneContext: PlaybackBackgroundGroundGridSceneContext;
177
+ visibleVerticalPulsePaths: readonly PlaybackGroundGridPulsePath[];
178
+ verticalPulsePaths: readonly PlaybackGroundGridPulsePath[];
179
+ };
180
+
181
+ /**
182
+ * Direction and timing state used when resolving pulse travel progress.
183
+ */
184
+ export type PlaybackGroundGridPulseTravelRatioInput = {
185
+ directionIsForward: boolean;
186
+ lifetimeProgressRatio: number;
187
+ orientation: PlaybackGroundGridPulseOrientation;
188
+ };
189
+
190
+ /**
191
+ * Input used when adapting a pulse position into a local track thickness.
192
+ */
193
+ export type PlaybackGroundGridPulseTrackThicknessInput = {
194
+ pulseCenterYPx: number;
195
+ pulsePath: PlaybackGroundGridPulsePath;
196
+ sceneContext: PlaybackBackgroundGroundGridSceneContext;
197
+ };
198
+
199
+ /**
200
+ * Helper alias used when adapting the shared background scene context.
201
+ */
202
+ export type PlaybackBackgroundGroundGridSourceScene = Pick<
203
+ PlaybackBackgroundSceneContext,
204
+ | 'viewportLeftXPx'
205
+ | 'visibleWorldWidthPx'
206
+ | 'alignedHorizonYPx'
207
+ | 'lowerBandTopYPx'
208
+ | 'lowerBandHeightPx'
209
+ | 'lowerBandBottomYPx'
210
+ | 'vanishingPointXPx'
211
+ | 'vanishingPointYPx'
212
+ >;