@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,235 @@
1
+ import { SPEED_OF_LIGHT } from "../../core/constants.mjs";
2
+
3
+ //#region src/networking/models/spectrum.ts
4
+ /**
5
+ * The invisible spectrum: why a remote control needs to be pointed and Wi-Fi does not.
6
+ *
7
+ * Radio, microwave, infrared and light are one thing, and are almost always taught as four. A
8
+ * student learns that Wi-Fi is "radio waves", that a remote uses "infrared", that 5G is "faster",
9
+ * and nothing connects those three facts, so none of them predicts anything. They are the same
10
+ * phenomenon at different frequencies, and almost every practical difference between them follows
11
+ * from one number.
12
+ *
13
+ * The number is the wavelength, and the reason it decides everything is SIZE. A wave interacts with
14
+ * an object according to how the object compares to its own wavelength. An FM broadcast is three
15
+ * metres long and treats a person as though they were not there. Wi-Fi is twelve centimetres and
16
+ * treats a brick wall as an obstacle it loses some energy to. A 5G millimetre wave is a centimetre
17
+ * and is stopped by a hand, a leaf, or coated window glass. Infrared is a thousandth of a
18
+ * millimetre and is stopped by anything at all, which is why the remote has to see the television.
19
+ *
20
+ * So this table exists to be sorted and compared rather than recited. The derived quantities are
21
+ * computed from the frequency instead of stored, because the whole claim of the lesson is that they
22
+ * FOLLOW from it: change the frequency and the wavelength, the antenna and the ability to cross a
23
+ * wall all move together, and a learner who has watched them move together owns the relationship.
24
+ *
25
+ * It also settles the single most common misconception about Wi-Fi in one line. A microwave oven
26
+ * runs at 2.45 GHz and a Wi-Fi router runs at 2.437 GHz. They are the same band. The oven is not
27
+ * interfering with the network by coincidence or by poor design: it is shouting on the channel.
28
+ */
29
+ /**
30
+ * Speed of light in a vacuum, m/s. The one constant the whole table is derived
31
+ * from, and re-exported rather than re-declared: it was a fourth independent
32
+ * copy of a number that is EXACT by definition of the metre.
33
+ */
34
+ const C = SPEED_OF_LIGHT;
35
+ /**
36
+ * Real centre frequencies, in the order a learner should meet them: longest wave first, because the
37
+ * story is what happens as the wave gets shorter.
38
+ */
39
+ const SPECTRUM = [
40
+ {
41
+ id: "am",
42
+ label: "AM radio",
43
+ hz: 1e6,
44
+ use: "Medium-wave broadcast",
45
+ penetration: "through-anything",
46
+ compare: "three football pitches end to end",
47
+ compareM: 300,
48
+ note: "A 300 m wave bends around hills and buildings as though they were pebbles, which is why one transmitter can cover a region and why AM still works in a valley."
49
+ },
50
+ {
51
+ id: "fm",
52
+ label: "FM radio",
53
+ hz: 1e8,
54
+ use: "VHF broadcast",
55
+ penetration: "through-anything",
56
+ compare: "a small car",
57
+ compareM: 3,
58
+ note: "Still long enough to ignore a person entirely. Notice the aerial on an older car: it is a metre or so, which is not decoration."
59
+ },
60
+ {
61
+ id: "tv",
62
+ label: "UHF television",
63
+ hz: 6e8,
64
+ use: "Terrestrial TV, and the low 4G bands beside it",
65
+ penetration: "through-walls",
66
+ compare: "a forearm",
67
+ compareM: .5,
68
+ note: "The band mobile operators fought hardest for, because at this wavelength one tower covers a large area and still reaches indoors."
69
+ },
70
+ {
71
+ id: "cell-low",
72
+ label: "4G / 5G low band",
73
+ hz: 8e8,
74
+ use: "Wide-area mobile coverage",
75
+ penetration: "through-walls",
76
+ compare: "a forearm",
77
+ compareM: .4,
78
+ note: "This is the band that gives a rural village a signal at all. It carries less data than the high bands and reaches many times further."
79
+ },
80
+ {
81
+ id: "cell-mid",
82
+ label: "4G / 5G mid band",
83
+ hz: 21e8,
84
+ use: "The usual city mobile band",
85
+ penetration: "through-one-wall",
86
+ compare: "the width of a hand",
87
+ compareM: .1,
88
+ note: "The working compromise: enough capacity for a crowded city, enough reach to serve it from a practical number of towers."
89
+ },
90
+ {
91
+ id: "wifi24",
92
+ label: "Wi-Fi 2.4 GHz",
93
+ hz: 2437e6,
94
+ use: "The long-range Wi-Fi band",
95
+ penetration: "through-one-wall",
96
+ compare: "the width of a hand",
97
+ compareM: .1,
98
+ note: "Only three non-overlapping channels, shared with every neighbour, every Bluetooth device, and the oven below."
99
+ },
100
+ {
101
+ id: "oven",
102
+ label: "Microwave oven",
103
+ hz: 245e7,
104
+ use: "Heating water in food",
105
+ penetration: "through-one-wall",
106
+ compare: "the width of a hand",
107
+ compareM: .1,
108
+ note: "The same band as Wi-Fi, which is the whole explanation for why a running oven slows the network. It is not interference by accident: it is a kilowatt transmitting on your channel behind a metal screen."
109
+ },
110
+ {
111
+ id: "wifi5",
112
+ label: "Wi-Fi 5 GHz",
113
+ hz: 518e7,
114
+ use: "The fast Wi-Fi band",
115
+ penetration: "through-one-wall",
116
+ compare: "a thumb",
117
+ compareM: .06,
118
+ note: "Far more channels and far more speed, and it pays for both in range. A shorter wave is absorbed harder by every wall it meets."
119
+ },
120
+ {
121
+ id: "satellite",
122
+ label: "Satellite Ku band",
123
+ hz: 12e9,
124
+ use: "Television and internet from orbit",
125
+ penetration: "blocked",
126
+ compare: "a walnut",
127
+ compareM: .025,
128
+ note: "Needs a clear view of the sky, and heavy rain absorbs it. This is what a dish is for: at this wavelength a reflector that size makes a very narrow beam."
129
+ },
130
+ {
131
+ id: "mmwave",
132
+ label: "5G millimetre wave",
133
+ hz: 28e9,
134
+ use: "Very high capacity over very short range",
135
+ penetration: "blocked",
136
+ compare: "a fingernail",
137
+ compareM: .012,
138
+ note: "Enormous capacity, and a hand over the phone can interrupt it. Deployed on stadium and street-corner scales rather than as coverage, which is why most people never meet it despite the advertising."
139
+ },
140
+ {
141
+ id: "infrared",
142
+ label: "Infrared remote",
143
+ hz: 319e12,
144
+ use: "Television and air-conditioner remotes",
145
+ penetration: "line-of-sight",
146
+ compare: "a bacterium",
147
+ compareM: 1e-6,
148
+ note: "A thousandth of a millimetre. It cannot pass a wall, a sofa, or your hand, which is why a remote must be pointed and Wi-Fi never is. Bouncing it off a white wall works, and off a dark curtain does not."
149
+ },
150
+ {
151
+ id: "visible",
152
+ label: "Visible light",
153
+ hz: 54e13,
154
+ use: "Seeing, and fibre optics just beyond it",
155
+ penetration: "line-of-sight",
156
+ compare: "a bacterium",
157
+ compareM: 1e-6,
158
+ note: "The same phenomenon, a little further along. The only reason this band feels different is that you have a detector for it in your head."
159
+ }
160
+ ];
161
+ /** Wavelength in metres. Everything visual about a band comes from this one division. */
162
+ const wavelengthM = (hz) => C / hz;
163
+ /**
164
+ * Quarter-wave antenna length in metres, the classic first-order rule.
165
+ *
166
+ * It is worth computing rather than asserting, because it explains hardware a learner has already
167
+ * seen and never questioned: the metre-long whip on an old car radio, the few centimetres inside a
168
+ * phone, and the fact that a 5G millimetre-wave antenna is small enough to put dozens on one chip.
169
+ */
170
+ const quarterWaveM = (hz) => wavelengthM(hz) / 4;
171
+ /** A length in the largest unit that keeps it readable, which is how a person would say it. */
172
+ function humanLength(metres) {
173
+ if (metres >= 1e3) return `${(metres / 1e3).toFixed(metres / 1e3 < 10 ? 1 : 0)} km`;
174
+ if (metres >= 1) return `${metres < 10 ? metres.toFixed(1) : metres.toFixed(0)} m`;
175
+ if (metres >= .01) return `${(metres * 100).toFixed(metres * 100 < 10 ? 1 : 0)} cm`;
176
+ if (metres >= .001) return `${(metres * 1e3).toFixed(1)} mm`;
177
+ if (metres >= 1e-6) return `${(metres * 1e6).toFixed(2)} µm`;
178
+ return `${(metres * 1e9).toFixed(0)} nm`;
179
+ }
180
+ /** A frequency in the unit it is normally quoted in. */
181
+ function humanFrequency(hz) {
182
+ if (hz >= 0xe8d4a51000) return `${(hz / 0xe8d4a51000).toFixed(0)} THz`;
183
+ if (hz >= 1e9) return `${(hz / 1e9).toFixed(hz / 1e9 < 10 ? 2 : 0)} GHz`;
184
+ if (hz >= 1e6) return `${(hz / 1e6).toFixed(0)} MHz`;
185
+ return `${(hz / 1e3).toFixed(0)} kHz`;
186
+ }
187
+ const PENETRATION = {
188
+ "through-anything": {
189
+ id: "through-anything",
190
+ label: "Passes almost everything",
191
+ stoppedBy: "Very little. It bends around hills and buildings."
192
+ },
193
+ "through-walls": {
194
+ id: "through-walls",
195
+ label: "Passes several walls",
196
+ stoppedBy: "Thick concrete and steel, eventually."
197
+ },
198
+ "through-one-wall": {
199
+ id: "through-one-wall",
200
+ label: "Passes a wall, weakened",
201
+ stoppedBy: "Each wall takes a bite. Two concrete walls is usually the end of it."
202
+ },
203
+ blocked: {
204
+ id: "blocked",
205
+ label: "Stopped by almost anything",
206
+ stoppedBy: "A wall, a window, a leaf, a hand."
207
+ },
208
+ "line-of-sight": {
209
+ id: "line-of-sight",
210
+ label: "Needs to see the receiver",
211
+ stoppedBy: "Anything opaque, including a sheet of paper."
212
+ }
213
+ };
214
+ /**
215
+ * Position on a logarithmic axis from 0 (left) to 1 (right).
216
+ *
217
+ * The axis has to be logarithmic because the table spans nine orders of magnitude: on a linear
218
+ * scale every radio band would pile into the first pixel and the lesson would be invisible. Worth
219
+ * saying to a learner rather than hiding, since "the axis multiplies instead of adding" is itself
220
+ * one of the more useful things this figure can teach.
221
+ */
222
+ function logPosition(hz, fromHz = 1e5, toHz = 0x38d7ea4c68000) {
223
+ const span = Math.log10(toHz) - Math.log10(fromHz);
224
+ return Math.min(1, Math.max(0, (Math.log10(hz) - Math.log10(fromHz)) / span));
225
+ }
226
+ /** The decade ticks an axis should carry between two frequencies. */
227
+ function decades(fromHz = 1e5, toHz = 0x38d7ea4c68000) {
228
+ const ticks = [];
229
+ for (let power = Math.ceil(Math.log10(fromHz)); power <= Math.log10(toHz); power++) ticks.push(10 ** power);
230
+ return ticks;
231
+ }
232
+ const bandById = (id) => SPECTRUM.find((band) => band.id === id) ?? SPECTRUM[0];
233
+
234
+ //#endregion
235
+ export { C, PENETRATION, SPECTRUM, bandById, decades, humanFrequency, humanLength, logPosition, quarterWaveM, wavelengthM };
@@ -0,0 +1,80 @@
1
+ //#region src/networking/models/switch.d.ts
2
+ /**
3
+ * What a switch actually does, which is smaller and stranger than most courses admit.
4
+ *
5
+ * A switch is not told where anything is. It starts knowing nothing, and it learns by watching
6
+ * the SOURCE address of every frame that arrives: "that device is reachable through port 3,
7
+ * because a frame from it just came in on port 3". Until it has learned a destination it floods,
8
+ * sending the frame everywhere except back the way it came.
9
+ *
10
+ * That is the whole mechanism, and it produces the behaviour students are usually asked to
11
+ * memorise instead: the first frame to a new device goes everywhere, the second goes to one port,
12
+ * and a switch with an empty table behaves exactly like the hub it replaced.
13
+ */
14
+ interface SwitchDevice {
15
+ name: string;
16
+ mac: string;
17
+ }
18
+ interface SwitchPort {
19
+ /** 1-based, as printed on real hardware. */
20
+ id: number;
21
+ /** A disabled port is administratively down: no link, no traffic, no learning. */
22
+ enabled: boolean;
23
+ device?: SwitchDevice;
24
+ }
25
+ interface MacEntry {
26
+ mac: string;
27
+ port: number;
28
+ }
29
+ interface SwitchState {
30
+ ports: SwitchPort[];
31
+ /** The forwarding table, learned entirely from traffic. */
32
+ table: MacEntry[];
33
+ }
34
+ type SwitchEvent = {
35
+ type: "ingress";
36
+ port: number;
37
+ srcMac: string;
38
+ dstMac: string;
39
+ message: string;
40
+ } | {
41
+ type: "learn";
42
+ mac: string;
43
+ port: number;
44
+ relearned: boolean;
45
+ message: string;
46
+ } | {
47
+ type: "flood";
48
+ ports: number[];
49
+ message: string;
50
+ } | {
51
+ type: "forward";
52
+ port: number;
53
+ message: string;
54
+ } | {
55
+ type: "drop";
56
+ reason: "port-down" | "no-link" | "same-port";
57
+ message: string;
58
+ };
59
+ interface FrameResult {
60
+ events: SwitchEvent[];
61
+ next: SwitchState;
62
+ /** Ports the frame actually left by. Empty when it was dropped. */
63
+ delivered: number[];
64
+ flooded: boolean;
65
+ }
66
+ declare const linkUp: (port: SwitchPort) => boolean;
67
+ /**
68
+ * Send one frame in through `fromPort` and resolve what the switch does with it.
69
+ *
70
+ * Pure: returns the next state rather than mutating, so a lesson can step forward and back.
71
+ */
72
+ declare function sendFrame(state: SwitchState, fromPort: number, dstMac: string): FrameResult;
73
+ /** Toggle a port up or down. A port going down forgets what was learned through it. */
74
+ declare function setPortEnabled(state: SwitchState, portId: number, enabled: boolean): SwitchState;
75
+ /** Clear the forwarding table, which returns the switch to the day it was unboxed. */
76
+ declare const clearTable: (state: SwitchState) => SwitchState;
77
+ /** A switch with `size` ports, the first `connected` of them holding a device. */
78
+ declare function makeSwitch(size?: number, connected?: number): SwitchState;
79
+ //#endregion
80
+ export { FrameResult, MacEntry, SwitchDevice, SwitchEvent, SwitchPort, SwitchState, clearTable, linkUp, makeSwitch, sendFrame, setPortEnabled };
@@ -0,0 +1,155 @@
1
+ //#region src/networking/models/switch.ts
2
+ const linkUp = (port) => port.enabled && !!port.device;
3
+ const portOf = (state, mac) => state.table.find((entry) => entry.mac === mac)?.port;
4
+ const deviceName = (state, mac) => state.ports.find((port) => port.device?.mac === mac)?.device?.name ?? mac;
5
+ /**
6
+ * Send one frame in through `fromPort` and resolve what the switch does with it.
7
+ *
8
+ * Pure: returns the next state rather than mutating, so a lesson can step forward and back.
9
+ */
10
+ function sendFrame(state, fromPort, dstMac) {
11
+ const events = [];
12
+ const ingress = state.ports.find((port) => port.id === fromPort);
13
+ const srcMac = ingress?.device?.mac ?? "";
14
+ if (!ingress || !ingress.enabled) {
15
+ events.push({
16
+ type: "drop",
17
+ reason: "port-down",
18
+ message: `Port ${fromPort} is disabled, so nothing can enter through it.`
19
+ });
20
+ return {
21
+ events,
22
+ next: state,
23
+ delivered: [],
24
+ flooded: false
25
+ };
26
+ }
27
+ if (!ingress.device) {
28
+ events.push({
29
+ type: "drop",
30
+ reason: "no-link",
31
+ message: `Port ${fromPort} has nothing plugged into it.`
32
+ });
33
+ return {
34
+ events,
35
+ next: state,
36
+ delivered: [],
37
+ flooded: false
38
+ };
39
+ }
40
+ events.push({
41
+ type: "ingress",
42
+ port: fromPort,
43
+ srcMac,
44
+ dstMac,
45
+ message: `A frame from ${ingress.device.name} arrives on port ${fromPort}, addressed to ${deviceName(state, dstMac)}.`
46
+ });
47
+ const known = state.table.find((entry) => entry.mac === srcMac);
48
+ const relearned = !!known && known.port !== fromPort;
49
+ const table = [...state.table.filter((entry) => entry.mac !== srcMac), {
50
+ mac: srcMac,
51
+ port: fromPort
52
+ }].sort((a, b) => a.port - b.port || a.mac.localeCompare(b.mac));
53
+ if (!known || relearned) events.push({
54
+ type: "learn",
55
+ mac: srcMac,
56
+ port: fromPort,
57
+ relearned,
58
+ message: relearned ? `${ingress.device.name} has moved. The table now says port ${fromPort}.` : `Learned: ${ingress.device.name} is reachable through port ${fromPort}.`
59
+ });
60
+ const next = {
61
+ ports: state.ports,
62
+ table
63
+ };
64
+ const target = portOf(next, dstMac);
65
+ if (target === fromPort) {
66
+ events.push({
67
+ type: "drop",
68
+ reason: "same-port",
69
+ message: `The destination is on the port the frame arrived from, so the switch does not send it back out.`
70
+ });
71
+ return {
72
+ events,
73
+ next,
74
+ delivered: [],
75
+ flooded: false
76
+ };
77
+ }
78
+ if (target !== void 0 && linkUp(state.ports.find((port) => port.id === target))) {
79
+ events.push({
80
+ type: "forward",
81
+ port: target,
82
+ message: `The table knows this address, so the frame goes out of port ${target} only. No other device sees it.`
83
+ });
84
+ return {
85
+ events,
86
+ next,
87
+ delivered: [target],
88
+ flooded: false
89
+ };
90
+ }
91
+ const flooded = next.ports.filter((port) => port.id !== fromPort && linkUp(port)).map((port) => port.id);
92
+ events.push({
93
+ type: "flood",
94
+ ports: flooded,
95
+ message: flooded.length ? `This address is not in the table, so the switch floods: out of every connected port except ${fromPort}.` : `This address is not in the table, and no other port has a link, so there is nowhere to flood.`
96
+ });
97
+ return {
98
+ events,
99
+ next,
100
+ delivered: flooded,
101
+ flooded: true
102
+ };
103
+ }
104
+ /** Toggle a port up or down. A port going down forgets what was learned through it. */
105
+ function setPortEnabled(state, portId, enabled) {
106
+ return {
107
+ ports: state.ports.map((port) => port.id === portId ? {
108
+ ...port,
109
+ enabled
110
+ } : port),
111
+ table: enabled ? state.table : state.table.filter((entry) => entry.port !== portId)
112
+ };
113
+ }
114
+ /** Clear the forwarding table, which returns the switch to the day it was unboxed. */
115
+ const clearTable = (state) => ({
116
+ ports: state.ports,
117
+ table: []
118
+ });
119
+ const MACS = [
120
+ "AA:01",
121
+ "BB:02",
122
+ "CC:03",
123
+ "DD:04",
124
+ "EE:05",
125
+ "FF:06",
126
+ "11:07",
127
+ "22:08"
128
+ ];
129
+ const NAMES = [
130
+ "Laptop",
131
+ "Desktop",
132
+ "Printer",
133
+ "Server",
134
+ "Phone",
135
+ "Camera",
136
+ "NAS",
137
+ "TV"
138
+ ];
139
+ /** A switch with `size` ports, the first `connected` of them holding a device. */
140
+ function makeSwitch(size = 8, connected = 4) {
141
+ return {
142
+ ports: Array.from({ length: size }, (_, index) => ({
143
+ id: index + 1,
144
+ enabled: true,
145
+ device: index < connected ? {
146
+ name: NAMES[index],
147
+ mac: MACS[index]
148
+ } : void 0
149
+ })),
150
+ table: []
151
+ };
152
+ }
153
+
154
+ //#endregion
155
+ export { clearTable, linkUp, makeSwitch, sendFrame, setPortEnabled };
@@ -0,0 +1,62 @@
1
+ //#region src/networking/models/vlan.d.ts
2
+ /**
3
+ * VLANs: one physical switch behaving as several separate ones.
4
+ *
5
+ * The idea students are given is "a VLAN is a virtual LAN", which explains nothing. The idea that
6
+ * works is narrower and testable: a VLAN is a BROADCAST DOMAIN. A frame sent to everyone reaches
7
+ * every port in its own VLAN and no others, even though all of them are in the same box on the
8
+ * same power supply.
9
+ *
10
+ * The second half is the part usually left out, and it is the interesting one. If VLANs are truly
11
+ * isolated at layer 2, how does anything ever cross between them? It cannot, until something that
12
+ * works at layer 3 is added, and then every crossing goes through that thing. Which is exactly why
13
+ * putting two departments on separate VLANs gives you a place to enforce a policy between them.
14
+ */
15
+ interface Vlan {
16
+ id: number;
17
+ name: string;
18
+ }
19
+ interface VlanPort {
20
+ id: number;
21
+ /** The VLAN this access port belongs to. */
22
+ vlan: number;
23
+ enabled: boolean;
24
+ device?: {
25
+ name: string;
26
+ };
27
+ }
28
+ interface VlanState {
29
+ vlans: Vlan[];
30
+ ports: VlanPort[];
31
+ /** A layer-3 interface that can route between VLANs. Off by default, which is the point. */
32
+ routing: boolean;
33
+ }
34
+ declare const isLive: (port: VlanPort) => boolean;
35
+ /** Ports in the same VLAN as `portId`, excluding itself. */
36
+ declare function domainOf(state: VlanState, portId: number): number[];
37
+ interface BroadcastResult {
38
+ reached: number[];
39
+ /** Live ports that did NOT receive it, because they are in another VLAN. */
40
+ blocked: number[];
41
+ message: string;
42
+ }
43
+ /** A broadcast reaches its own VLAN and stops there. Routing does not change that. */
44
+ declare function broadcastFrom(state: VlanState, portId: number): BroadcastResult;
45
+ interface DeliveryResult {
46
+ delivered: boolean;
47
+ viaRouter: boolean;
48
+ message: string;
49
+ }
50
+ /**
51
+ * Directed traffic between two ports. Within a VLAN the switch forwards it. Across VLANs it needs
52
+ * a layer-3 interface, and that interface is where a policy would sit.
53
+ */
54
+ declare function sendBetween(state: VlanState, fromPort: number, toPort: number): DeliveryResult;
55
+ declare function setPortVlan(state: VlanState, portId: number, vlan: number): VlanState;
56
+ declare const setRouting: (state: VlanState, routing: boolean) => VlanState;
57
+ /** How many separate broadcast domains this switch is currently carrying. */
58
+ declare const domainCount: (state: VlanState) => number;
59
+ /** Eight ports, the first `connected` holding a device, split evenly across two VLANs. */
60
+ declare function makeVlanSwitch(connected?: number): VlanState;
61
+ //#endregion
62
+ export { BroadcastResult, DeliveryResult, Vlan, VlanPort, VlanState, broadcastFrom, domainCount, domainOf, isLive, makeVlanSwitch, sendBetween, setPortVlan, setRouting };
@@ -0,0 +1,100 @@
1
+ //#region src/networking/models/vlan.ts
2
+ const isLive = (port) => port.enabled && !!port.device;
3
+ /** Ports in the same VLAN as `portId`, excluding itself. */
4
+ function domainOf(state, portId) {
5
+ const source = state.ports.find((port) => port.id === portId);
6
+ if (!source) return [];
7
+ return state.ports.filter((port) => port.id !== portId && port.vlan === source.vlan && isLive(port)).map((port) => port.id);
8
+ }
9
+ /** A broadcast reaches its own VLAN and stops there. Routing does not change that. */
10
+ function broadcastFrom(state, portId) {
11
+ const source = state.ports.find((port) => port.id === portId);
12
+ if (!source || !isLive(source)) return {
13
+ reached: [],
14
+ blocked: [],
15
+ message: `Port ${portId} has no live device to send from.`
16
+ };
17
+ const reached = domainOf(state, portId);
18
+ const blocked = state.ports.filter((port) => port.id !== portId && isLive(port) && port.vlan !== source.vlan).map((port) => port.id);
19
+ const vlanName = state.vlans.find((vlan) => vlan.id === source.vlan)?.name ?? `VLAN ${source.vlan}`;
20
+ return {
21
+ reached,
22
+ blocked,
23
+ message: blocked.length ? `The broadcast reaches every live port in ${vlanName} and stops. ${blocked.length} live port${blocked.length > 1 ? "s are" : " is"} in another VLAN and never sees it.` : `The broadcast reaches every live port in ${vlanName}.`
24
+ };
25
+ }
26
+ /**
27
+ * Directed traffic between two ports. Within a VLAN the switch forwards it. Across VLANs it needs
28
+ * a layer-3 interface, and that interface is where a policy would sit.
29
+ */
30
+ function sendBetween(state, fromPort, toPort) {
31
+ const source = state.ports.find((port) => port.id === fromPort);
32
+ const target = state.ports.find((port) => port.id === toPort);
33
+ if (!source || !target || !isLive(source) || !isLive(target)) return {
34
+ delivered: false,
35
+ viaRouter: false,
36
+ message: "Both ends need a live device."
37
+ };
38
+ if (source.vlan === target.vlan) return {
39
+ delivered: true,
40
+ viaRouter: false,
41
+ message: `Same VLAN, so the switch forwards it directly. Nothing outside the VLAN is involved.`
42
+ };
43
+ if (!state.routing) return {
44
+ delivered: false,
45
+ viaRouter: false,
46
+ message: `Different VLANs and no layer-3 interface, so there is no path at all. The switch will not carry it.`
47
+ };
48
+ return {
49
+ delivered: true,
50
+ viaRouter: true,
51
+ message: `Different VLANs, so it goes up to the layer-3 interface and back down. Every crossing passes that one point, which is where a rule can be applied.`
52
+ };
53
+ }
54
+ function setPortVlan(state, portId, vlan) {
55
+ return {
56
+ ...state,
57
+ ports: state.ports.map((port) => port.id === portId ? {
58
+ ...port,
59
+ vlan
60
+ } : port)
61
+ };
62
+ }
63
+ const setRouting = (state, routing) => ({
64
+ ...state,
65
+ routing
66
+ });
67
+ /** How many separate broadcast domains this switch is currently carrying. */
68
+ const domainCount = (state) => new Set(state.ports.filter(isLive).map((port) => port.vlan)).size;
69
+ const NAMES = [
70
+ "Sales",
71
+ "Finance",
72
+ "Reception",
73
+ "Store",
74
+ "Server A",
75
+ "Server B",
76
+ "Kiosk",
77
+ "Camera"
78
+ ];
79
+ /** Eight ports, the first `connected` holding a device, split evenly across two VLANs. */
80
+ function makeVlanSwitch(connected = 6) {
81
+ return {
82
+ vlans: [{
83
+ id: 10,
84
+ name: "VLAN 10"
85
+ }, {
86
+ id: 20,
87
+ name: "VLAN 20"
88
+ }],
89
+ routing: false,
90
+ ports: Array.from({ length: 8 }, (_, index) => ({
91
+ id: index + 1,
92
+ vlan: index < 4 ? 10 : 20,
93
+ enabled: true,
94
+ device: index < connected ? { name: NAMES[index] } : void 0
95
+ }))
96
+ };
97
+ }
98
+
99
+ //#endregion
100
+ export { broadcastFrom, domainCount, domainOf, isLive, makeVlanSwitch, sendBetween, setPortVlan, setRouting };