@classytic/stage 0.2.0 → 0.4.0

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 (458) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +5 -1
  3. package/dist/_virtual/_rolldown/runtime.mjs +18 -0
  4. package/dist/assets/index.mjs +0 -1
  5. package/dist/atomic/aufbau.d.mts +144 -0
  6. package/dist/atomic/aufbau.mjs +384 -0
  7. package/dist/atomic/elements.d.mts +67 -0
  8. package/dist/atomic/elements.mjs +785 -0
  9. package/dist/atomic/emission.d.mts +112 -0
  10. package/dist/atomic/emission.mjs +179 -0
  11. package/dist/atomic/index.d.mts +8 -0
  12. package/dist/atomic/index.mjs +9 -0
  13. package/dist/atomic/periodic.d.mts +82 -0
  14. package/dist/atomic/periodic.mjs +231 -0
  15. package/dist/atomic/properties.d.mts +100 -0
  16. package/dist/atomic/properties.mjs +574 -0
  17. package/dist/atomic/shells.d.mts +137 -0
  18. package/dist/atomic/shells.mjs +170 -0
  19. package/dist/atomic/trends.d.mts +148 -0
  20. package/dist/atomic/trends.mjs +225 -0
  21. package/dist/builder/Palette.mjs +50 -89
  22. package/dist/builder/SceneBuilder.mjs +15 -73
  23. package/dist/chem/chromatography.d.mts +66 -0
  24. package/dist/chem/chromatography.mjs +63 -0
  25. package/dist/chem/complex.d.mts +114 -0
  26. package/dist/chem/complex.mjs +234 -0
  27. package/dist/chem/cycle.d.mts +75 -0
  28. package/dist/chem/cycle.mjs +145 -0
  29. package/dist/chem/equation.d.mts +86 -0
  30. package/dist/chem/equation.mjs +168 -0
  31. package/dist/chem/formula.d.mts +90 -0
  32. package/dist/chem/formula.mjs +201 -0
  33. package/dist/chem/functional-groups.d.mts +31 -0
  34. package/dist/chem/functional-groups.mjs +187 -0
  35. package/dist/chem/hazard.d.mts +94 -0
  36. package/dist/chem/hazard.mjs +329 -0
  37. package/dist/chem/index.d.mts +34 -33
  38. package/dist/chem/index.mjs +34 -28
  39. package/dist/chem/ionic.d.mts +98 -0
  40. package/dist/chem/ionic.mjs +156 -0
  41. package/dist/chem/layout.d.mts +46 -0
  42. package/dist/chem/layout.mjs +342 -0
  43. package/dist/chem/lewis.d.mts +79 -0
  44. package/dist/chem/lewis.mjs +163 -0
  45. package/dist/chem/molecular-orbital.d.mts +114 -0
  46. package/dist/chem/molecular-orbital.mjs +201 -0
  47. package/dist/chem/molecule.d.mts +154 -0
  48. package/dist/chem/molecule.mjs +282 -0
  49. package/dist/chem/nmr.d.mts +153 -0
  50. package/dist/chem/nmr.mjs +165 -0
  51. package/dist/chem/polymer.d.mts +65 -0
  52. package/dist/chem/polymer.mjs +179 -0
  53. package/dist/chem/redox.d.mts +138 -0
  54. package/dist/chem/redox.mjs +328 -0
  55. package/dist/chem/relations.d.mts +183 -0
  56. package/dist/chem/relations.mjs +299 -0
  57. package/dist/chem/skeleton.d.mts +51 -0
  58. package/dist/chem/skeleton.mjs +186 -0
  59. package/dist/chem/solution.d.mts +116 -0
  60. package/dist/chem/solution.mjs +186 -0
  61. package/dist/chem/species.d.mts +47 -0
  62. package/dist/chem/species.mjs +128 -0
  63. package/dist/chem/stereo.d.mts +108 -0
  64. package/dist/chem/stereo.mjs +286 -0
  65. package/dist/chem/stoichiometry.d.mts +95 -0
  66. package/dist/chem/stoichiometry.mjs +145 -0
  67. package/dist/chem/synthesis.d.mts +113 -0
  68. package/dist/chem/synthesis.mjs +281 -0
  69. package/dist/chem/vsepr.d.mts +80 -0
  70. package/dist/chem/vsepr.mjs +247 -0
  71. package/dist/circuit/ac.d.mts +163 -0
  72. package/dist/circuit/ac.mjs +452 -0
  73. package/dist/circuit/dc.d.mts +92 -0
  74. package/dist/circuit/dc.mjs +333 -0
  75. package/dist/circuit/index.d.mts +5 -92
  76. package/dist/circuit/index.mjs +5 -332
  77. package/dist/circuit/network.d.mts +99 -0
  78. package/dist/circuit/network.mjs +440 -0
  79. package/dist/circuit/topology.d.mts +102 -0
  80. package/dist/circuit/topology.mjs +143 -0
  81. package/dist/colour/absorption.d.mts +79 -0
  82. package/dist/colour/absorption.mjs +137 -0
  83. package/dist/colour/index.d.mts +6 -0
  84. package/dist/colour/index.mjs +7 -0
  85. package/dist/colour/name.d.mts +14 -0
  86. package/dist/colour/name.mjs +76 -0
  87. package/dist/colour/observer.d.mts +31 -0
  88. package/dist/colour/observer.mjs +46 -0
  89. package/dist/colour/spectrum.d.mts +61 -0
  90. package/dist/colour/spectrum.mjs +108 -0
  91. package/dist/colour/srgb.d.mts +49 -0
  92. package/dist/colour/srgb.mjs +108 -0
  93. package/dist/core/constants.d.mts +138 -0
  94. package/dist/core/constants.mjs +139 -0
  95. package/dist/core/index.d.mts +4 -1
  96. package/dist/core/index.mjs +4 -1
  97. package/dist/core/math.d.mts +26 -0
  98. package/dist/core/math.mjs +37 -0
  99. package/dist/core/vec3.d.mts +94 -0
  100. package/dist/core/vec3.mjs +123 -0
  101. package/dist/core/worked.d.mts +35 -0
  102. package/dist/core/worked.mjs +44 -0
  103. package/dist/electro/cell.d.mts +102 -0
  104. package/dist/electro/cell.mjs +170 -0
  105. package/dist/electro/conductivity.d.mts +157 -0
  106. package/dist/electro/conductivity.mjs +156 -0
  107. package/dist/electro/couples.d.mts +114 -0
  108. package/dist/electro/couples.mjs +334 -0
  109. package/dist/electro/electrolysis.d.mts +96 -0
  110. package/dist/electro/electrolysis.mjs +274 -0
  111. package/dist/electro/faraday.d.mts +150 -0
  112. package/dist/electro/faraday.mjs +118 -0
  113. package/dist/electro/index.d.mts +6 -0
  114. package/dist/electro/index.mjs +7 -0
  115. package/dist/finance/accounting/adjustments.d.mts +17 -0
  116. package/dist/finance/accounting/adjustments.mjs +202 -0
  117. package/dist/finance/accounting/analysis.d.mts +8 -0
  118. package/dist/finance/accounting/analysis.mjs +205 -0
  119. package/dist/finance/accounting/clubs.d.mts +21 -0
  120. package/dist/finance/accounting/clubs.mjs +217 -0
  121. package/dist/finance/accounting/company-statements.d.mts +7 -0
  122. package/dist/finance/accounting/company-statements.mjs +150 -0
  123. package/dist/finance/accounting/harness.d.mts +7 -0
  124. package/dist/finance/accounting/harness.mjs +45 -0
  125. package/dist/finance/accounting/index.d.mts +18 -0
  126. package/dist/finance/accounting/index.mjs +18 -0
  127. package/dist/finance/accounting/journalize.d.mts +16 -0
  128. package/dist/finance/accounting/journalize.mjs +128 -0
  129. package/dist/finance/accounting/manufacturing.d.mts +8 -0
  130. package/dist/finance/accounting/manufacturing.mjs +236 -0
  131. package/dist/finance/accounting/partnership.d.mts +7 -0
  132. package/dist/finance/accounting/partnership.mjs +152 -0
  133. package/dist/finance/accounting/period-case.d.mts +6 -0
  134. package/dist/finance/accounting/period-case.mjs +71 -0
  135. package/dist/finance/accounting/post.d.mts +9 -0
  136. package/dist/finance/accounting/post.mjs +84 -0
  137. package/dist/finance/accounting/precision.d.mts +9 -0
  138. package/dist/finance/accounting/precision.mjs +15 -0
  139. package/dist/finance/accounting/rules.d.mts +21 -0
  140. package/dist/finance/accounting/rules.mjs +37 -0
  141. package/dist/finance/accounting/solve.d.mts +28 -0
  142. package/dist/finance/accounting/solve.mjs +92 -0
  143. package/dist/finance/accounting/statements.d.mts +7 -0
  144. package/dist/finance/accounting/statements.mjs +142 -0
  145. package/dist/finance/accounting/types.d.mts +649 -0
  146. package/dist/finance/accounting/validate.d.mts +7 -0
  147. package/dist/finance/accounting/validate.mjs +67 -0
  148. package/dist/finance/accounting/verification.d.mts +24 -0
  149. package/dist/finance/accounting/verification.mjs +386 -0
  150. package/dist/finance/bizsim.d.mts +75 -0
  151. package/dist/finance/bizsim.mjs +296 -0
  152. package/dist/finance/index.d.mts +95 -0
  153. package/dist/finance/index.mjs +145 -0
  154. package/dist/finance/investment-math.d.mts +7 -0
  155. package/dist/finance/investment-math.mjs +18 -0
  156. package/dist/finance/management-accounting/budgeting.d.mts +7 -0
  157. package/dist/finance/management-accounting/budgeting.mjs +189 -0
  158. package/dist/finance/management-accounting/cash-budget.d.mts +7 -0
  159. package/dist/finance/management-accounting/cash-budget.mjs +154 -0
  160. package/dist/finance/management-accounting/costing.d.mts +8 -0
  161. package/dist/finance/management-accounting/costing.mjs +175 -0
  162. package/dist/finance/management-accounting/decisions.d.mts +8 -0
  163. package/dist/finance/management-accounting/decisions.mjs +164 -0
  164. package/dist/finance/management-accounting/index.d.mts +13 -0
  165. package/dist/finance/management-accounting/index.mjs +13 -0
  166. package/dist/finance/management-accounting/integrated-budget.d.mts +8 -0
  167. package/dist/finance/management-accounting/integrated-budget.mjs +67 -0
  168. package/dist/finance/management-accounting/inventory-costing.d.mts +7 -0
  169. package/dist/finance/management-accounting/inventory-costing.mjs +173 -0
  170. package/dist/finance/management-accounting/investment-appraisal.d.mts +7 -0
  171. package/dist/finance/management-accounting/investment-appraisal.mjs +148 -0
  172. package/dist/finance/management-accounting/management-case.d.mts +8 -0
  173. package/dist/finance/management-accounting/management-case.mjs +91 -0
  174. package/dist/finance/management-accounting/master-budget.d.mts +7 -0
  175. package/dist/finance/management-accounting/master-budget.mjs +137 -0
  176. package/dist/finance/management-accounting/production-costing.d.mts +8 -0
  177. package/dist/finance/management-accounting/production-costing.mjs +214 -0
  178. package/dist/finance/management-accounting/types.d.mts +644 -0
  179. package/dist/finance/management-accounting/variances.d.mts +7 -0
  180. package/dist/finance/management-accounting/variances.mjs +150 -0
  181. package/dist/index.d.mts +9 -8
  182. package/dist/index.mjs +5 -5
  183. package/dist/interaction/MovableDot.mjs +19 -0
  184. package/dist/interaction/useDraggable.mjs +24 -4
  185. package/dist/math/analysis.d.mts +61 -0
  186. package/dist/math/analysis.mjs +187 -0
  187. package/dist/math/answer-check.d.mts +50 -0
  188. package/dist/math/answer-check.mjs +106 -0
  189. package/dist/math/calculus.d.mts +17 -1
  190. package/dist/math/calculus.mjs +365 -2
  191. package/dist/math/complex.d.mts +93 -0
  192. package/dist/math/complex.mjs +239 -0
  193. package/dist/math/expr.d.mts +23 -0
  194. package/dist/math/expr.mjs +36 -0
  195. package/dist/math/index.d.mts +9 -23
  196. package/dist/math/index.mjs +9 -38
  197. package/dist/math/latex.mjs +10 -1
  198. package/dist/math/parse.mjs +1 -1
  199. package/dist/math/poly.d.mts +55 -0
  200. package/dist/math/poly.mjs +273 -0
  201. package/dist/math/rearrange.d.mts +14 -0
  202. package/dist/math/rearrange.mjs +125 -0
  203. package/dist/math/solve.d.mts +21 -0
  204. package/dist/math/solve.mjs +75 -0
  205. package/dist/networking/addressing.d.mts +26 -0
  206. package/dist/networking/addressing.mjs +33 -0
  207. package/dist/networking/cloud/access.d.mts +36 -0
  208. package/dist/networking/cloud/access.mjs +95 -0
  209. package/dist/networking/cloud/containers.d.mts +43 -0
  210. package/dist/networking/cloud/containers.mjs +54 -0
  211. package/dist/networking/cloud/edge.d.mts +17 -0
  212. package/dist/networking/cloud/edge.mjs +31 -0
  213. package/dist/networking/cloud/failure-domains.d.mts +19 -0
  214. package/dist/networking/cloud/failure-domains.mjs +22 -0
  215. package/dist/networking/cloud/geo-routing.d.mts +22 -0
  216. package/dist/networking/cloud/geo-routing.mjs +64 -0
  217. package/dist/networking/cloud/index.d.mts +18 -0
  218. package/dist/networking/cloud/index.mjs +19 -0
  219. package/dist/networking/cloud/inference-reliability.d.mts +30 -0
  220. package/dist/networking/cloud/inference-reliability.mjs +76 -0
  221. package/dist/networking/cloud/inference.d.mts +118 -0
  222. package/dist/networking/cloud/inference.mjs +179 -0
  223. package/dist/networking/cloud/infrastructure.d.mts +45 -0
  224. package/dist/networking/cloud/infrastructure.mjs +96 -0
  225. package/dist/networking/cloud/observability.d.mts +24 -0
  226. package/dist/networking/cloud/observability.mjs +34 -0
  227. package/dist/networking/cloud/orchestration.d.mts +32 -0
  228. package/dist/networking/cloud/orchestration.mjs +38 -0
  229. package/dist/networking/cloud/recovery.d.mts +21 -0
  230. package/dist/networking/cloud/recovery.mjs +27 -0
  231. package/dist/networking/cloud/releases.d.mts +42 -0
  232. package/dist/networking/cloud/releases.mjs +135 -0
  233. package/dist/networking/cloud/scaling.d.mts +25 -0
  234. package/dist/networking/cloud/scaling.mjs +65 -0
  235. package/dist/networking/cloud/secrets.d.mts +39 -0
  236. package/dist/networking/cloud/secrets.mjs +68 -0
  237. package/dist/networking/cloud/server-deployment.d.mts +29 -0
  238. package/dist/networking/cloud/server-deployment.mjs +72 -0
  239. package/dist/networking/cloud/storage.d.mts +16 -0
  240. package/dist/networking/cloud/storage.mjs +58 -0
  241. package/dist/networking/cloud/vpc.d.mts +18 -0
  242. package/dist/networking/cloud/vpc.mjs +100 -0
  243. package/dist/networking/delivery.d.mts +34 -0
  244. package/dist/networking/delivery.mjs +52 -0
  245. package/dist/networking/index.d.mts +47 -0
  246. package/dist/networking/index.mjs +47 -0
  247. package/dist/networking/ipv6.d.mts +28 -0
  248. package/dist/networking/ipv6.mjs +17 -0
  249. package/dist/networking/journeys.d.mts +83 -0
  250. package/dist/networking/journeys.mjs +161 -0
  251. package/dist/networking/models/airtime.d.mts +87 -0
  252. package/dist/networking/models/airtime.mjs +130 -0
  253. package/dist/networking/models/cellular.d.mts +157 -0
  254. package/dist/networking/models/cellular.mjs +252 -0
  255. package/dist/networking/models/encapsulation.d.mts +98 -0
  256. package/dist/networking/models/encapsulation.mjs +132 -0
  257. package/dist/networking/models/handover.d.mts +116 -0
  258. package/dist/networking/models/handover.mjs +209 -0
  259. package/dist/networking/models/handshake.d.mts +90 -0
  260. package/dist/networking/models/handshake.mjs +122 -0
  261. package/dist/networking/models/index.d.mts +14 -0
  262. package/dist/networking/models/index.mjs +15 -0
  263. package/dist/networking/models/media.d.mts +82 -0
  264. package/dist/networking/models/media.mjs +188 -0
  265. package/dist/networking/models/satellite.d.mts +86 -0
  266. package/dist/networking/models/satellite.mjs +121 -0
  267. package/dist/networking/models/spectrum.d.mts +83 -0
  268. package/dist/networking/models/spectrum.mjs +235 -0
  269. package/dist/networking/models/switch.d.mts +80 -0
  270. package/dist/networking/models/switch.mjs +155 -0
  271. package/dist/networking/models/vlan.d.mts +62 -0
  272. package/dist/networking/models/vlan.mjs +100 -0
  273. package/dist/networking/models/vpn.d.mts +66 -0
  274. package/dist/networking/models/vpn.mjs +125 -0
  275. package/dist/networking/models/wifi-privacy.d.mts +82 -0
  276. package/dist/networking/models/wifi-privacy.mjs +130 -0
  277. package/dist/networking/models/wifi.d.mts +240 -0
  278. package/dist/networking/models/wifi.mjs +562 -0
  279. package/dist/networking/nat-traversal.d.mts +24 -0
  280. package/dist/networking/nat-traversal.mjs +38 -0
  281. package/dist/networking/protocols.d.mts +17 -0
  282. package/dist/networking/protocols.mjs +57 -0
  283. package/dist/networking/queue.d.mts +12 -0
  284. package/dist/networking/queue.mjs +18 -0
  285. package/dist/networking/route-learning.d.mts +23 -0
  286. package/dist/networking/route-learning.mjs +43 -0
  287. package/dist/networking/routing.d.mts +11 -0
  288. package/dist/networking/routing.mjs +25 -0
  289. package/dist/networking/signals.d.mts +26 -0
  290. package/dist/networking/signals.mjs +35 -0
  291. package/dist/networking/subnet.d.mts +49 -0
  292. package/dist/networking/subnet.mjs +89 -0
  293. package/dist/networking/tcp-reliability.d.mts +24 -0
  294. package/dist/networking/tcp-reliability.mjs +38 -0
  295. package/dist/networking/topology/contract.d.mts +123 -0
  296. package/dist/networking/topology/index.d.mts +4 -0
  297. package/dist/networking/topology/index.mjs +4 -0
  298. package/dist/networking/topology/presets.d.mts +7 -0
  299. package/dist/networking/topology/presets.mjs +302 -0
  300. package/dist/networking/topology/simulation.d.mts +8 -0
  301. package/dist/networking/topology/simulation.mjs +179 -0
  302. package/dist/networking/wireless.d.mts +11 -0
  303. package/dist/networking/wireless.mjs +17 -0
  304. package/dist/optics/imaging.d.mts +218 -0
  305. package/dist/optics/imaging.mjs +276 -0
  306. package/dist/optics/index.d.mts +7 -0
  307. package/dist/optics/index.mjs +8 -0
  308. package/dist/optics/laws.d.mts +70 -0
  309. package/dist/optics/laws.mjs +195 -0
  310. package/dist/optics/prism.d.mts +148 -0
  311. package/dist/optics/prism.mjs +197 -0
  312. package/dist/optics/refraction.d.mts +201 -0
  313. package/dist/optics/refraction.mjs +270 -0
  314. package/dist/optics/wave-optics.d.mts +171 -0
  315. package/dist/optics/wave-optics.mjs +259 -0
  316. package/dist/physics/ac/index.d.mts +183 -0
  317. package/dist/physics/ac/index.mjs +203 -0
  318. package/dist/physics/dynamics/forces.d.mts +98 -0
  319. package/dist/physics/dynamics/forces.mjs +153 -0
  320. package/dist/physics/dynamics/friction.d.mts +76 -0
  321. package/dist/physics/dynamics/friction.mjs +119 -0
  322. package/dist/physics/dynamics/system.d.mts +68 -0
  323. package/dist/physics/dynamics/system.mjs +125 -0
  324. package/dist/physics/electrostatics/index.d.mts +169 -0
  325. package/dist/physics/electrostatics/index.mjs +249 -0
  326. package/dist/physics/energy/centre-of-mass.d.mts +115 -0
  327. package/dist/physics/energy/centre-of-mass.mjs +180 -0
  328. package/dist/physics/energy/conservation.d.mts +81 -0
  329. package/dist/physics/energy/conservation.mjs +129 -0
  330. package/dist/physics/energy/power.d.mts +90 -0
  331. package/dist/physics/energy/power.mjs +167 -0
  332. package/dist/physics/energy/spring.d.mts +73 -0
  333. package/dist/physics/energy/spring.mjs +129 -0
  334. package/dist/physics/energy/store.d.mts +63 -0
  335. package/dist/physics/energy/store.mjs +108 -0
  336. package/dist/physics/energy/theorem.d.mts +65 -0
  337. package/dist/physics/energy/theorem.mjs +113 -0
  338. package/dist/physics/energy/work.d.mts +114 -0
  339. package/dist/physics/energy/work.mjs +219 -0
  340. package/dist/physics/gravitation/body.d.mts +85 -0
  341. package/dist/physics/gravitation/body.mjs +168 -0
  342. package/dist/physics/gravitation/kepler.d.mts +127 -0
  343. package/dist/physics/gravitation/kepler.mjs +216 -0
  344. package/dist/physics/gravitation/law.d.mts +117 -0
  345. package/dist/physics/gravitation/law.mjs +175 -0
  346. package/dist/physics/gravitation/orbit.d.mts +119 -0
  347. package/dist/physics/gravitation/orbit.mjs +202 -0
  348. package/dist/physics/gravitation/potential.d.mts +114 -0
  349. package/dist/physics/gravitation/potential.mjs +202 -0
  350. package/dist/physics/gravitation/variation.d.mts +97 -0
  351. package/dist/physics/gravitation/variation.mjs +175 -0
  352. package/dist/physics/index.d.mts +35 -0
  353. package/dist/physics/index.mjs +36 -0
  354. package/dist/physics/integral-sweep.d.mts +63 -0
  355. package/dist/physics/integral-sweep.mjs +126 -0
  356. package/dist/physics/kinematics/equations.d.mts +47 -0
  357. package/dist/physics/kinematics/equations.mjs +126 -0
  358. package/dist/physics/kinematics/free-fall.d.mts +53 -0
  359. package/dist/physics/kinematics/free-fall.mjs +71 -0
  360. package/dist/physics/kinematics/given.d.mts +16 -0
  361. package/dist/physics/kinematics/given.mjs +55 -0
  362. package/dist/physics/kinematics/roots.d.mts +41 -0
  363. package/dist/physics/kinematics/roots.mjs +72 -0
  364. package/dist/physics/kinematics/solve.d.mts +52 -0
  365. package/dist/physics/kinematics/solve.mjs +172 -0
  366. package/dist/physics/magnetism/biot-savart.d.mts +161 -0
  367. package/dist/physics/magnetism/biot-savart.mjs +353 -0
  368. package/dist/physics/magnetism/flux.d.mts +94 -0
  369. package/dist/physics/magnetism/flux.mjs +313 -0
  370. package/dist/physics/magnetism/geomagnetism.d.mts +151 -0
  371. package/dist/physics/magnetism/geomagnetism.mjs +184 -0
  372. package/dist/physics/magnetism/index.d.mts +12 -0
  373. package/dist/physics/magnetism/index.mjs +74 -0
  374. package/dist/physics/magnetism/lorentz.d.mts +28 -0
  375. package/dist/physics/magnetism/lorentz.mjs +88 -0
  376. package/dist/physics/magnetism/magnetic-force.d.mts +102 -0
  377. package/dist/physics/magnetism/magnetic-force.mjs +169 -0
  378. package/dist/physics/momentum/collide.d.mts +77 -0
  379. package/dist/physics/momentum/collide.mjs +115 -0
  380. package/dist/physics/momentum/impulse.d.mts +89 -0
  381. package/dist/physics/momentum/impulse.mjs +159 -0
  382. package/dist/physics/momentum/planar.d.mts +60 -0
  383. package/dist/physics/momentum/planar.mjs +110 -0
  384. package/dist/physics/projectile.d.mts +107 -0
  385. package/dist/physics/projectile.mjs +185 -0
  386. package/dist/physics/rotation/angular.d.mts +85 -0
  387. package/dist/physics/rotation/angular.mjs +130 -0
  388. package/dist/physics/rotation/circular.d.mts +79 -0
  389. package/dist/physics/rotation/circular.mjs +131 -0
  390. package/dist/physics/rotation/inertia.d.mts +164 -0
  391. package/dist/physics/rotation/inertia.mjs +332 -0
  392. package/dist/physics/rotation/torque.d.mts +65 -0
  393. package/dist/physics/rotation/torque.mjs +134 -0
  394. package/dist/physics/vectors.d.mts +64 -0
  395. package/dist/physics/vectors.mjs +114 -0
  396. package/dist/primitives/Dot.d.mts +2 -15
  397. package/dist/primitives/Dot.mjs +6 -4
  398. package/dist/primitives/Grid.d.mts +33 -17
  399. package/dist/primitives/Grid.mjs +89 -17
  400. package/dist/primitives/Label.d.mts +1 -14
  401. package/dist/primitives/Label.mjs +3 -2
  402. package/dist/primitives/Lines.d.mts +4 -32
  403. package/dist/primitives/Lines.mjs +10 -8
  404. package/dist/primitives/Shapes.d.mts +5 -43
  405. package/dist/primitives/Shapes.mjs +12 -10
  406. package/dist/primitives/index.d.mts +2 -2
  407. package/dist/primitives/index.mjs +2 -2
  408. package/dist/primitives/props.mjs +31 -0
  409. package/dist/quantity/index.d.mts +119 -0
  410. package/dist/quantity/index.mjs +417 -0
  411. package/dist/relation/derive.d.mts +74 -0
  412. package/dist/relation/derive.mjs +73 -0
  413. package/dist/relation/index.d.mts +56 -0
  414. package/dist/relation/index.mjs +234 -0
  415. package/dist/scene/Scene.d.mts +6 -1
  416. package/dist/scene/Scene.mjs +15 -40
  417. package/dist/scene/evaluators.mjs +1 -1
  418. package/dist/sim/thermal.mjs +45 -20
  419. package/dist/thermo/calorimetry.d.mts +207 -0
  420. package/dist/thermo/calorimetry.mjs +345 -0
  421. package/dist/thermo/cycles.d.mts +142 -0
  422. package/dist/thermo/cycles.mjs +256 -0
  423. package/dist/thermo/entropy.d.mts +87 -0
  424. package/dist/thermo/entropy.mjs +140 -0
  425. package/dist/thermo/expansion.d.mts +130 -0
  426. package/dist/thermo/expansion.mjs +178 -0
  427. package/dist/thermo/gas.d.mts +197 -0
  428. package/dist/thermo/gas.mjs +279 -0
  429. package/dist/thermo/heat-transfer.d.mts +164 -0
  430. package/dist/thermo/heat-transfer.mjs +217 -0
  431. package/dist/thermo/index.d.mts +9 -82
  432. package/dist/thermo/index.mjs +9 -132
  433. package/dist/thermo/processes.d.mts +95 -0
  434. package/dist/thermo/processes.mjs +154 -0
  435. package/dist/thermo/temperature.d.mts +174 -0
  436. package/dist/thermo/temperature.mjs +236 -0
  437. package/dist/view/Stage.mjs +4 -11
  438. package/dist/waves/damping.d.mts +224 -0
  439. package/dist/waves/damping.mjs +317 -0
  440. package/dist/waves/doppler.d.mts +72 -0
  441. package/dist/waves/doppler.mjs +125 -0
  442. package/dist/waves/index.d.mts +10 -0
  443. package/dist/waves/index.mjs +10 -0
  444. package/dist/waves/pendulum.d.mts +130 -0
  445. package/dist/waves/pendulum.mjs +211 -0
  446. package/dist/waves/pitch.d.mts +125 -0
  447. package/dist/waves/pitch.mjs +158 -0
  448. package/dist/waves/shm.d.mts +170 -0
  449. package/dist/waves/shm.mjs +279 -0
  450. package/dist/waves/springs.d.mts +103 -0
  451. package/dist/waves/springs.mjs +176 -0
  452. package/dist/waves/superposition.d.mts +124 -0
  453. package/dist/waves/superposition.mjs +196 -0
  454. package/dist/waves/wave.d.mts +154 -0
  455. package/dist/waves/wave.mjs +178 -0
  456. package/package.json +103 -23
  457. package/styles.css +125 -8
  458. package/dist/assets/kit/index.mjs +0 -4
@@ -0,0 +1,132 @@
1
+ //#region src/networking/models/encapsulation.ts
2
+ /** Outermost last: this is the order the headers are ADDED going down the stack. */
3
+ const HEADERS = [
4
+ {
5
+ kind: "application",
6
+ protocol: "HTTP",
7
+ bytes: 0,
8
+ addressing: null,
9
+ answers: "What is being asked for. This is the message itself, not a wrapper."
10
+ },
11
+ {
12
+ kind: "transport",
13
+ protocol: "TCP",
14
+ bytes: 20,
15
+ addressing: "port",
16
+ answers: "Which program on the machine, and where this piece fits in the stream."
17
+ },
18
+ {
19
+ kind: "network",
20
+ protocol: "IPv4",
21
+ bytes: 20,
22
+ addressing: "IP address",
23
+ answers: "Which machine, anywhere. Set once by the sender and carried the whole way."
24
+ },
25
+ {
26
+ kind: "data-link",
27
+ protocol: "Ethernet",
28
+ bytes: 18,
29
+ trailerBytes: 4,
30
+ addressing: "MAC address",
31
+ answers: "Which box on THIS wire takes it next. Replaced at every routed hop."
32
+ }
33
+ ];
34
+ const OVERHEAD_BYTES = HEADERS.reduce((sum, header) => sum + header.bytes, 0);
35
+ /** Wrap a payload in the first `depth` headers. Full depth is the frame that goes on the wire. */
36
+ function encapsulate(payloadBytes, depth = HEADERS.length) {
37
+ const applied = HEADERS.slice(0, Math.max(0, Math.min(depth, HEADERS.length)));
38
+ const overheadBytes = applied.reduce((sum, header) => sum + header.bytes, 0);
39
+ const onWireBytes = payloadBytes + overheadBytes;
40
+ return {
41
+ payloadBytes,
42
+ headers: applied,
43
+ depth: applied.length,
44
+ onWireBytes,
45
+ overheadBytes,
46
+ overheadShare: onWireBytes === 0 ? 0 : overheadBytes / onWireBytes
47
+ };
48
+ }
49
+ /** Unwrap one layer. The payload is never touched, which is the point. */
50
+ const decapsulate = (state) => encapsulate(state.payloadBytes, Math.max(0, state.depth - 1));
51
+ /** How deep a device opens the frame before it can do its job. */
52
+ const opensTo = (kind) => kind === "switch" ? "data-link" : kind === "router" ? "network" : "application";
53
+ /**
54
+ * Split a path at its layer-3 boundaries. A switch forwards a frame without terminating it, so it
55
+ * does not start a new segment. A router does, because it builds a brand new frame for the next
56
+ * wire. The number of segments is therefore routers + 1, however many switches are in the way.
57
+ */
58
+ function segmentsOf(path) {
59
+ const boundaries = path.filter((node) => node.kind !== "switch");
60
+ const segments = [];
61
+ for (let index = 0; index < boundaries.length - 1; index++) {
62
+ const from = boundaries[index];
63
+ const to = boundaries[index + 1];
64
+ const through = path.slice(path.indexOf(from) + 1, path.indexOf(to)).filter((n) => n.kind === "switch");
65
+ segments.push({
66
+ from,
67
+ to,
68
+ fromMac: from.mac,
69
+ toMac: to.mac,
70
+ through
71
+ });
72
+ }
73
+ return segments;
74
+ }
75
+ function frameOn(segment, source, destination) {
76
+ const rebuilt = segment.from.kind === "router";
77
+ return {
78
+ segment,
79
+ mac: {
80
+ from: segment.fromMac,
81
+ to: segment.toMac
82
+ },
83
+ ip: {
84
+ from: source.ip ?? "",
85
+ to: destination.ip ?? ""
86
+ },
87
+ note: rebuilt ? `${segment.from.name} threw away the frame it received and built a new one for this wire. Same packet inside, new MAC addresses outside.` : `${segment.from.name} addresses the frame to ${segment.to.name}, the next box on this wire, not to the far end.`
88
+ };
89
+ }
90
+ /** Every frame the message travels in, end to end. */
91
+ const journey = (path) => {
92
+ const source = path[0];
93
+ const destination = path[path.length - 1];
94
+ return segmentsOf(path).map((segment) => frameOn(segment, source, destination));
95
+ };
96
+ const DEFAULT_PATH = [
97
+ {
98
+ id: "laptop",
99
+ name: "Laptop",
100
+ kind: "host",
101
+ mac: "AA:01",
102
+ ip: "10.1.0.20"
103
+ },
104
+ {
105
+ id: "switch",
106
+ name: "Switch",
107
+ kind: "switch",
108
+ mac: "SW:00"
109
+ },
110
+ {
111
+ id: "r1",
112
+ name: "Router 1",
113
+ kind: "router",
114
+ mac: "BB:11"
115
+ },
116
+ {
117
+ id: "r2",
118
+ name: "Router 2",
119
+ kind: "router",
120
+ mac: "CC:22"
121
+ },
122
+ {
123
+ id: "server",
124
+ name: "Server",
125
+ kind: "host",
126
+ mac: "DD:33",
127
+ ip: "10.9.0.7"
128
+ }
129
+ ];
130
+
131
+ //#endregion
132
+ export { DEFAULT_PATH, HEADERS, OVERHEAD_BYTES, decapsulate, encapsulate, frameOn, journey, opensTo, segmentsOf };
@@ -0,0 +1,116 @@
1
+ import { Terrain } from "./cellular.mjs";
2
+
3
+ //#region src/networking/models/handover.d.ts
4
+ /** A mast beside a straight road, positioned by how far along the road it stands. */
5
+ interface Mast {
6
+ id: string;
7
+ label: string;
8
+ /** Distance along the road, in km. */
9
+ atKm: number;
10
+ /** Perpendicular offset from the road, in km. Keeps the signal finite as the car passes. */
11
+ offsetKm?: number;
12
+ }
13
+ interface DriveOptions {
14
+ /** How much better a neighbour must be before the call moves, in dB. The whole lesson. */
15
+ marginDb: number;
16
+ /** Where the drive starts along the road, in km. Defaults to the beginning. */
17
+ fromKm?: number;
18
+ /** Where it ends, in km. */
19
+ roadKm: number;
20
+ megahertz: number;
21
+ terrain: Terrain;
22
+ /** Standard deviation of the shadow fading, in dB. 6 is a normal urban figure. */
23
+ shadowDb?: number;
24
+ /** How far the shadowing persists, in km. */
25
+ correlationKm?: number;
26
+ stepKm?: number;
27
+ seed?: number;
28
+ }
29
+ interface DriveSample {
30
+ xKm: number;
31
+ /** Signal from every mast at this point, in dBm, in the order the masts were given. */
32
+ rssi: number[];
33
+ /** Index of the mast currently holding the call. */
34
+ serving: number;
35
+ }
36
+ interface HandoverEvent {
37
+ atKm: number;
38
+ from: number;
39
+ to: number;
40
+ }
41
+ interface DriveResult {
42
+ samples: DriveSample[];
43
+ events: HandoverEvent[];
44
+ /** The weakest signal the call was actually held on. What the listener hears. */
45
+ worstServingDbm: number;
46
+ /**
47
+ * Times the call actually dropped: a SUSTAINED loss, not a momentary dip.
48
+ *
49
+ * Counting every sample below sensitivity makes a single shadow from a single building look like a
50
+ * dropped call, and then every setting of the margin looks equally broken. A call survives a brief
51
+ * fade; it dies when coverage is gone for long enough that the link cannot be maintained.
52
+ */
53
+ dropouts: number;
54
+ }
55
+ /**
56
+ * How far the car must travel with no usable signal before the call is counted as dropped, in km.
57
+ *
58
+ * A hundred metres at city speed is a few seconds of silence, which is where a caller hangs up. Any
59
+ * shorter and a single building's shadow registers as a dropped call; any longer and a genuine
60
+ * coverage hole is scored as a good link.
61
+ */
62
+ declare const DROP_AFTER_KM = 0.1;
63
+ declare const DEFAULT_DRIVE: Required<Pick<DriveOptions, "shadowDb" | "correlationKm" | "stepKm" | "seed">>;
64
+ /**
65
+ * Drive the road and hand the call over as the rule dictates.
66
+ *
67
+ * The rule is deliberately the real one and nothing more: switch when some neighbour beats the
68
+ * serving mast by `marginDb`. Real networks add a time-to-trigger as well, so a neighbour must stay
69
+ * better for a while before anything happens. Leaving that out keeps ONE control on screen, and the
70
+ * margin alone already produces both failure modes a learner needs to meet.
71
+ */
72
+ declare function drive(masts: readonly Mast[], options: DriveOptions): DriveResult;
73
+ /**
74
+ * A road with two masts at either end, which is the smallest arrangement that has a boundary.
75
+ *
76
+ * One boundary is all this lesson needs. Adding a third mast produces more handovers without adding
77
+ * an idea, and makes the figure harder to read at the exact place a learner is meant to be looking.
78
+ */
79
+ declare const TWO_MAST_ROAD: Mast[];
80
+ /**
81
+ * The stretch actually driven, in km, which is NOT the whole road.
82
+ *
83
+ * The masts are eleven kilometres apart so that the midpoint sits near the edge of both cells: any
84
+ * closer and an absurd margin merely degrades the call without ever dropping it, any further and the
85
+ * call drops at every margin, which says nothing about hysteresis.
86
+ *
87
+ * But the drive covers only the five kilometres around the boundary, because that is where the whole
88
+ * phenomenon lives and because of what happens when it is drawn. Shadowing decorrelates over roughly
89
+ * a hundred metres. Across the full eleven kilometres that is one percent of the figure, so the
90
+ * signals render as static and a learner sees noise instead of two curves passing each other. Across
91
+ * five kilometres the same physics reads as visible undulation, and the crossings that cause the
92
+ * ping-pong can actually be counted. A real drive test covers the boundary for the same reason: it
93
+ * is the only part where the answer is in doubt.
94
+ */
95
+ declare const DRIVE_WINDOW: {
96
+ readonly fromKm: 3.5;
97
+ readonly toKm: 8.5;
98
+ };
99
+ /** How a chosen margin actually performed, in the two terms it trades between. */
100
+ interface MarginVerdict {
101
+ handovers: number;
102
+ worstServingDbm: number;
103
+ dropouts: number;
104
+ /** Plain-language reading of the trade, which is what the learner should leave with. */
105
+ verdict: "ping-pong" | "balanced" | "clinging";
106
+ }
107
+ /**
108
+ * Judge a margin on both of the things it trades between at once.
109
+ *
110
+ * Reporting only the handover count would make a huge margin look perfect, and reporting only the
111
+ * signal would make a margin of zero look perfect. Both numbers together are the only honest summary,
112
+ * and they are what makes the middle findable.
113
+ */
114
+ declare function judgeMargin(masts: readonly Mast[], options: DriveOptions, runs?: number): MarginVerdict;
115
+ //#endregion
116
+ export { DEFAULT_DRIVE, DRIVE_WINDOW, DROP_AFTER_KM, DriveOptions, DriveResult, DriveSample, HandoverEvent, MarginVerdict, Mast, TWO_MAST_ROAD, drive, judgeMargin };
@@ -0,0 +1,209 @@
1
+ import { HANDSET_SENSITIVITY_DBM, rssiFromMast } from "./cellular.mjs";
2
+
3
+ //#region src/networking/models/handover.ts
4
+ /**
5
+ * Handover: how a call survives a moving car.
6
+ *
7
+ * Chapter 2 treats a phone as standing still, which is the one assumption that makes cellular look
8
+ * simple. Everything interesting starts when it moves. A call is held by ONE mast at a time, the
9
+ * phone drives out of that mast's reach, and the call has to be passed to the next one without a gap
10
+ * the listener can hear.
11
+ *
12
+ * The naive rule is "switch to whichever mast is strongest". It is the obvious rule and it fails
13
+ * badly, for a reason worth meeting: near a cell boundary the two signals are almost equal, and
14
+ * shadowing from buildings makes which one is stronger flip back and forth. A phone following that
15
+ * rule hands the call over dozens of times across a few hundred metres. Each handover costs
16
+ * signalling on both masts, and enough of them collapse a network that is otherwise fine. The name
17
+ * for it is the ping-pong effect.
18
+ *
19
+ * The fix is a margin, called hysteresis: do not switch until the neighbour is better by some number
20
+ * of decibels. That kills the ping-pong, and it is not free. A large margin means the phone clings
21
+ * to a mast it has already driven away from, so the signal it is actually holding gets worse and the
22
+ * call gets closer to dropping. There is no setting that is simply correct, which is the real lesson:
23
+ * this is a tuned trade between signalling load and call quality, and a learner should find the
24
+ * middle by overshooting in both directions.
25
+ *
26
+ * The fading here is spatially CORRELATED rather than redrawn at every sample. Shadowing comes from
27
+ * buildings and terrain, so it persists over tens of metres; independent noise per sample would
28
+ * produce a ping-pong that is an artefact of the sampling rate rather than of the physics, and would
29
+ * quietly teach that the effect is worse than it is.
30
+ */
31
+ /** Deterministic PRNG, because a lab that renders differently on the server than in the browser is broken. */
32
+ function mulberry32(seed) {
33
+ let a = seed >>> 0;
34
+ return () => {
35
+ a = a + 1831565813 >>> 0;
36
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
37
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
38
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
39
+ };
40
+ }
41
+ const smoothstep = (t) => t * t * (3 - 2 * t);
42
+ /**
43
+ * Shadow fading along the road: one value per correlation length, smoothly interpolated between.
44
+ *
45
+ * This is what a building casting a radio shadow looks like from a moving car. It matters that it is
46
+ * smooth: the ping-pong this lab teaches must come from two signals genuinely crossing each other
47
+ * several times, not from a value that is redrawn every time we happen to look at it.
48
+ */
49
+ function shadowField(seed, roadKm, correlationKm, sigmaDb) {
50
+ const random = mulberry32(seed);
51
+ const nodes = [];
52
+ const count = Math.ceil(roadKm / correlationKm) + 3;
53
+ for (let index = 0; index < count; index++) nodes.push((random() + random() + random() - 1.5) * 2 * sigmaDb);
54
+ return (x) => {
55
+ const scaled = Math.max(0, x) / correlationKm;
56
+ const index = Math.min(nodes.length - 2, Math.floor(scaled));
57
+ const t = smoothstep(Math.min(1, Math.max(0, scaled - index)));
58
+ return nodes[index] * (1 - t) + nodes[index + 1] * t;
59
+ };
60
+ }
61
+ /**
62
+ * How far the car must travel with no usable signal before the call is counted as dropped, in km.
63
+ *
64
+ * A hundred metres at city speed is a few seconds of silence, which is where a caller hangs up. Any
65
+ * shorter and a single building's shadow registers as a dropped call; any longer and a genuine
66
+ * coverage hole is scored as a good link.
67
+ */
68
+ const DROP_AFTER_KM = .1;
69
+ const DEFAULT_DRIVE = {
70
+ shadowDb: 6,
71
+ correlationKm: .12,
72
+ stepKm: .02,
73
+ seed: 20260915
74
+ };
75
+ /**
76
+ * Drive the road and hand the call over as the rule dictates.
77
+ *
78
+ * The rule is deliberately the real one and nothing more: switch when some neighbour beats the
79
+ * serving mast by `marginDb`. Real networks add a time-to-trigger as well, so a neighbour must stay
80
+ * better for a while before anything happens. Leaving that out keeps ONE control on screen, and the
81
+ * margin alone already produces both failure modes a learner needs to meet.
82
+ */
83
+ function drive(masts, options) {
84
+ const { shadowDb, correlationKm, stepKm, seed } = {
85
+ ...DEFAULT_DRIVE,
86
+ ...options
87
+ };
88
+ const fields = masts.map((_, index) => shadowField(seed + index * 7919, options.roadKm, correlationKm, shadowDb));
89
+ const levels = (x) => masts.map((mast, index) => {
90
+ const along = x - mast.atKm;
91
+ const offset = mast.offsetKm ?? .05;
92
+ return rssiFromMast(Math.hypot(along, offset), options.megahertz, options.terrain) + fields[index](x);
93
+ });
94
+ const samples = [];
95
+ const events = [];
96
+ let serving = 0;
97
+ let worstServingDbm = Infinity;
98
+ let dropouts = 0;
99
+ let belowRunKm = 0;
100
+ let counted = false;
101
+ const first = levels(options.fromKm ?? 0);
102
+ serving = first.indexOf(Math.max(...first));
103
+ const startKm = options.fromKm ?? 0;
104
+ for (let x = startKm; x <= options.roadKm + 1e-9; x += stepKm) {
105
+ const rssi = levels(x);
106
+ let challenger = serving;
107
+ for (let index = 0; index < rssi.length; index++) if (index !== serving && rssi[index] > rssi[challenger] + (challenger === serving ? options.marginDb : 0)) challenger = index;
108
+ if (challenger !== serving) {
109
+ events.push({
110
+ atKm: x,
111
+ from: serving,
112
+ to: challenger
113
+ });
114
+ serving = challenger;
115
+ }
116
+ const held = rssi[serving];
117
+ if (held < worstServingDbm) worstServingDbm = held;
118
+ if (held < -100) {
119
+ belowRunKm += stepKm;
120
+ if (!counted && belowRunKm >= .1) {
121
+ dropouts++;
122
+ counted = true;
123
+ }
124
+ } else {
125
+ belowRunKm = 0;
126
+ counted = false;
127
+ }
128
+ samples.push({
129
+ xKm: x,
130
+ rssi,
131
+ serving
132
+ });
133
+ }
134
+ return {
135
+ samples,
136
+ events,
137
+ worstServingDbm,
138
+ dropouts
139
+ };
140
+ }
141
+ /**
142
+ * A road with two masts at either end, which is the smallest arrangement that has a boundary.
143
+ *
144
+ * One boundary is all this lesson needs. Adding a third mast produces more handovers without adding
145
+ * an idea, and makes the figure harder to read at the exact place a learner is meant to be looking.
146
+ */
147
+ const TWO_MAST_ROAD = [{
148
+ id: "west",
149
+ label: "West mast",
150
+ atKm: .5,
151
+ offsetKm: .08
152
+ }, {
153
+ id: "east",
154
+ label: "East mast",
155
+ atKm: 11.5,
156
+ offsetKm: .08
157
+ }];
158
+ /**
159
+ * The stretch actually driven, in km, which is NOT the whole road.
160
+ *
161
+ * The masts are eleven kilometres apart so that the midpoint sits near the edge of both cells: any
162
+ * closer and an absurd margin merely degrades the call without ever dropping it, any further and the
163
+ * call drops at every margin, which says nothing about hysteresis.
164
+ *
165
+ * But the drive covers only the five kilometres around the boundary, because that is where the whole
166
+ * phenomenon lives and because of what happens when it is drawn. Shadowing decorrelates over roughly
167
+ * a hundred metres. Across the full eleven kilometres that is one percent of the figure, so the
168
+ * signals render as static and a learner sees noise instead of two curves passing each other. Across
169
+ * five kilometres the same physics reads as visible undulation, and the crossings that cause the
170
+ * ping-pong can actually be counted. A real drive test covers the boundary for the same reason: it
171
+ * is the only part where the answer is in doubt.
172
+ */
173
+ const DRIVE_WINDOW = {
174
+ fromKm: 3.5,
175
+ toKm: 8.5
176
+ };
177
+ /**
178
+ * Judge a margin on both of the things it trades between at once.
179
+ *
180
+ * Reporting only the handover count would make a huge margin look perfect, and reporting only the
181
+ * signal would make a margin of zero look perfect. Both numbers together are the only honest summary,
182
+ * and they are what makes the middle findable.
183
+ */
184
+ function judgeMargin(masts, options, runs = 5) {
185
+ let handovers = 0;
186
+ let dropouts = 0;
187
+ let worstServingDbm = 0;
188
+ for (let run = 0; run < runs; run++) {
189
+ const result = drive(masts, {
190
+ ...options,
191
+ seed: (options.seed ?? DEFAULT_DRIVE.seed) + run * 104729
192
+ });
193
+ handovers += result.events.length;
194
+ dropouts += result.dropouts;
195
+ worstServingDbm += result.worstServingDbm;
196
+ }
197
+ const meanHandovers = handovers / runs;
198
+ const meanDropouts = dropouts / runs;
199
+ const verdict = meanHandovers > 4 ? "ping-pong" : meanDropouts > .5 ? "clinging" : "balanced";
200
+ return {
201
+ handovers: Math.round(meanHandovers),
202
+ worstServingDbm: worstServingDbm / runs,
203
+ dropouts: Math.round(meanDropouts),
204
+ verdict
205
+ };
206
+ }
207
+
208
+ //#endregion
209
+ export { DEFAULT_DRIVE, DRIVE_WINDOW, DROP_AFTER_KM, TWO_MAST_ROAD, drive, judgeMargin };
@@ -0,0 +1,90 @@
1
+ //#region src/networking/models/handshake.d.ts
2
+ /**
3
+ * Opening a TCP connection, and what it costs before a single byte of yours moves.
4
+ *
5
+ * The three-way handshake is usually taught as a ritual: SYN, SYN-ACK, ACK, learn the order, move on.
6
+ * Taught that way it answers none of the three questions a student actually has, and every one of
7
+ * them has a good answer.
8
+ *
9
+ * WHY THREE. Because each side has a sequence number the other must confirm, and a confirmation can
10
+ * only ride on a message. Two messages confirm one direction. The client learns the server got its
11
+ * number when the SYN-ACK arrives; the server learns nothing until the third message. A two-way
12
+ * handshake leaves the server holding a connection it cannot know the client agreed to, which is
13
+ * also, incidentally, how a SYN flood works.
14
+ *
15
+ * WHAT IT COSTS. One full round trip before the first request can even be sent. On a link with a
16
+ * 200 ms round trip that is 200 ms of nothing, every time, and it is why QUIC and TLS 1.3 went to so
17
+ * much trouble to fold the handshake into fewer trips. A learner who has watched the ladder stretch
18
+ * understands the entire motivation for a decade of protocol design.
19
+ *
20
+ * WHAT HAPPENS WHEN ONE IS LOST. Nothing, for a long time. There is no third party to notice, so the
21
+ * sender waits out a retransmission timeout that starts around a second, which is five times the
22
+ * round trip on a good link and utterly dominates. That asymmetry, a lost packet costing far more
23
+ * than a slow one, is the thing that makes loss and latency different problems.
24
+ */
25
+ type Side = "client" | "server";
26
+ /** Which segment of the handshake to lose, or none. */
27
+ type DropWhich = "none" | "syn" | "syn-ack" | "ack";
28
+ interface TcpSegment {
29
+ id: string;
30
+ from: Side;
31
+ /** The flags a packet capture would show. */
32
+ flags: string[];
33
+ seq: number;
34
+ ack: number | null;
35
+ /** When it left, in ms from the first attempt. */
36
+ sentMs: number;
37
+ /** When it arrived, or null if it never did. */
38
+ arrivesMs: number | null;
39
+ /** True when this is a second attempt after a timeout. */
40
+ retransmit: boolean;
41
+ }
42
+ interface HandshakeResult {
43
+ segments: TcpSegment[];
44
+ /** When the client may send its first request, in ms. */
45
+ readyMs: number;
46
+ /** When the server considers the connection open, in ms. */
47
+ serverOpenMs: number;
48
+ /** Time lost purely to waiting out a timeout. */
49
+ timeoutCostMs: number;
50
+ /** State of each side over time, for the column labels. */
51
+ states: {
52
+ side: Side;
53
+ atMs: number;
54
+ state: string;
55
+ }[];
56
+ }
57
+ interface HandshakeOptions {
58
+ /** Round trip time of the link, in ms. */
59
+ rttMs: number;
60
+ drop?: DropWhich;
61
+ /** Initial retransmission timeout, in ms. One second is the specified starting value. */
62
+ rtoMs?: number;
63
+ }
64
+ /** The specified initial retransmission timeout. Deliberately long, and the reason loss hurts. */
65
+ declare const DEFAULT_RTO_MS = 1000;
66
+ /**
67
+ * Run the handshake and return every segment with the time it left and the time it landed.
68
+ *
69
+ * Times are exact rather than simulated tick by tick, because the whole point is the arithmetic: one
70
+ * way is half a round trip, so the client is ready after one full round trip and the server half a
71
+ * trip later. Drawing that from a clock rather than from a formula would invite the reader to think
72
+ * the numbers came from somewhere mysterious.
73
+ */
74
+ declare function handshake({
75
+ rttMs,
76
+ drop,
77
+ rtoMs
78
+ }: HandshakeOptions): HandshakeResult;
79
+ /** Total time before the first response could come back: the handshake, then the request and reply. */
80
+ declare const firstResponseMs: (result: HandshakeResult, rttMs: number) => number;
81
+ interface DropSpec {
82
+ id: DropWhich;
83
+ label: string;
84
+ /** Who notices the loss, which is the interesting part. */
85
+ noticedBy: string;
86
+ note: string;
87
+ }
88
+ declare const DROPS: Record<DropWhich, DropSpec>;
89
+ //#endregion
90
+ export { DEFAULT_RTO_MS, DROPS, DropSpec, DropWhich, HandshakeOptions, HandshakeResult, Side, TcpSegment, firstResponseMs, handshake };
@@ -0,0 +1,122 @@
1
+ //#region src/networking/models/handshake.ts
2
+ /** The specified initial retransmission timeout. Deliberately long, and the reason loss hurts. */
3
+ const DEFAULT_RTO_MS = 1e3;
4
+ /** Sequence numbers are random in practice; fixed here so the figure is readable and stable. */
5
+ const CLIENT_ISN = 1e3;
6
+ const SERVER_ISN = 5e3;
7
+ /**
8
+ * Run the handshake and return every segment with the time it left and the time it landed.
9
+ *
10
+ * Times are exact rather than simulated tick by tick, because the whole point is the arithmetic: one
11
+ * way is half a round trip, so the client is ready after one full round trip and the server half a
12
+ * trip later. Drawing that from a clock rather than from a formula would invite the reader to think
13
+ * the numbers came from somewhere mysterious.
14
+ */
15
+ function handshake({ rttMs, drop = "none", rtoMs = DEFAULT_RTO_MS }) {
16
+ const hop = rttMs / 2;
17
+ const segments = [];
18
+ const states = [{
19
+ side: "client",
20
+ atMs: 0,
21
+ state: "CLOSED"
22
+ }, {
23
+ side: "server",
24
+ atMs: 0,
25
+ state: "LISTEN"
26
+ }];
27
+ let timeoutCostMs = 0;
28
+ let clock = 0;
29
+ const send = (id, from, flags, seq, ack, sentMs, lost, retransmit = false) => {
30
+ const segment = {
31
+ id,
32
+ from,
33
+ flags,
34
+ seq,
35
+ ack,
36
+ sentMs,
37
+ arrivesMs: lost ? null : sentMs + hop,
38
+ retransmit
39
+ };
40
+ segments.push(segment);
41
+ return segment;
42
+ };
43
+ let syn = send("syn", "client", ["SYN"], CLIENT_ISN, null, clock, drop === "syn");
44
+ states.push({
45
+ side: "client",
46
+ atMs: clock,
47
+ state: "SYN-SENT"
48
+ });
49
+ if (drop === "syn") {
50
+ clock += rtoMs;
51
+ timeoutCostMs += rtoMs;
52
+ syn = send("syn-2", "client", ["SYN"], CLIENT_ISN, null, clock, false, true);
53
+ }
54
+ const synArrives = syn.arrivesMs;
55
+ states.push({
56
+ side: "server",
57
+ atMs: synArrives,
58
+ state: "SYN-RECEIVED"
59
+ });
60
+ let synAck = send("syn-ack", "server", ["SYN", "ACK"], SERVER_ISN, 1001, synArrives, drop === "syn-ack");
61
+ if (drop === "syn-ack") {
62
+ const retryAt = syn.sentMs + rtoMs;
63
+ timeoutCostMs += retryAt - syn.sentMs;
64
+ synAck = send("syn-ack-2", "server", ["SYN", "ACK"], SERVER_ISN, 1001, send("syn-2", "client", ["SYN"], CLIENT_ISN, null, retryAt, false, true).arrivesMs, false, true);
65
+ }
66
+ const synAckArrives = synAck.arrivesMs;
67
+ states.push({
68
+ side: "client",
69
+ atMs: synAckArrives,
70
+ state: "ESTABLISHED"
71
+ });
72
+ let ack = send("ack", "client", ["ACK"], 1001, 5001, synAckArrives, drop === "ack");
73
+ if (drop === "ack") {
74
+ const serverRetryAt = synAck.sentMs + rtoMs;
75
+ timeoutCostMs += serverRetryAt - synAck.sentMs;
76
+ ack = send("ack-2", "client", ["ACK"], 1001, 5001, send("syn-ack-2", "server", ["SYN", "ACK"], SERVER_ISN, 1001, serverRetryAt, false, true).arrivesMs, false, true);
77
+ }
78
+ const ackArrives = ack.arrivesMs;
79
+ states.push({
80
+ side: "server",
81
+ atMs: ackArrives,
82
+ state: "ESTABLISHED"
83
+ });
84
+ return {
85
+ segments,
86
+ readyMs: synAckArrives,
87
+ serverOpenMs: ackArrives,
88
+ timeoutCostMs,
89
+ states
90
+ };
91
+ }
92
+ /** Total time before the first response could come back: the handshake, then the request and reply. */
93
+ const firstResponseMs = (result, rttMs) => result.readyMs + rttMs;
94
+ const DROPS = {
95
+ none: {
96
+ id: "none",
97
+ label: "Nothing lost",
98
+ noticedBy: "nobody",
99
+ note: "Three segments, one and a half round trips, and the client may send its request as the SYN-ACK arrives."
100
+ },
101
+ syn: {
102
+ id: "syn",
103
+ label: "The SYN is lost",
104
+ noticedBy: "the client, after a timeout",
105
+ note: "The server never knew a connection was attempted. Only the client can notice, and it has nothing to go on but a clock."
106
+ },
107
+ "syn-ack": {
108
+ id: "syn-ack",
109
+ label: "The SYN-ACK is lost",
110
+ noticedBy: "the client, after a timeout",
111
+ note: "The server is now holding a half-open connection it thinks is progressing. The client sees only silence and retries its SYN."
112
+ },
113
+ ack: {
114
+ id: "ack",
115
+ label: "The final ACK is lost",
116
+ noticedBy: "the server, after a timeout",
117
+ note: "The client already believes it is connected and may start sending. The server does not, and re-sends its SYN-ACK until it hears something."
118
+ }
119
+ };
120
+
121
+ //#endregion
122
+ export { DEFAULT_RTO_MS, DROPS, firstResponseMs, handshake };