@classytic/labs 0.1.0 → 0.2.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 (455) hide show
  1. package/dist/_virtual/_rolldown/runtime.mjs +18 -0
  2. package/dist/biology/enzyme-rate/preset.mjs +5 -5
  3. package/dist/biology/genetic-cross/core.d.mts +7 -7
  4. package/dist/biology/genetic-cross/core.mjs +4 -4
  5. package/dist/biology/genetic-cross/grid.mjs +132 -123
  6. package/dist/biology/genetic-cross/preset.mjs +4 -4
  7. package/dist/biology/genetic-cross/sex-linked.mjs +7 -7
  8. package/dist/biology/photosynthesis-factors/preset.mjs +8 -8
  9. package/dist/biology/respiration/preset.mjs +4 -4
  10. package/dist/biology/sequence/central-dogma.mjs +5 -5
  11. package/dist/biology/sequence/core.d.mts +6 -6
  12. package/dist/biology/sequence/core.mjs +1 -1
  13. package/dist/biology/sequence/preset.mjs +6 -6
  14. package/dist/blocks/authoring.mjs +5 -5
  15. package/dist/blocks/biology.d.mts +2 -2
  16. package/dist/blocks/biology.mjs +5 -5
  17. package/dist/blocks/chem.d.mts +2 -2
  18. package/dist/blocks/chem.mjs +13 -13
  19. package/dist/blocks/circuits.d.mts +293 -4
  20. package/dist/blocks/circuits.mjs +874 -9
  21. package/dist/blocks/discrete.d.mts +30 -2
  22. package/dist/blocks/discrete.mjs +74 -4
  23. package/dist/blocks/economics.mjs +1 -1
  24. package/dist/blocks/geography.mjs +1 -1
  25. package/dist/blocks/geometry.mjs +3 -3
  26. package/dist/blocks/ict.d.mts +90 -1
  27. package/dist/blocks/ict.mjs +217 -7
  28. package/dist/blocks/index.d.mts +3064 -657
  29. package/dist/blocks/index.mjs +9 -9
  30. package/dist/blocks/lab-block.mjs +5 -5
  31. package/dist/blocks/lab-config.d.mts +1 -1
  32. package/dist/blocks/lab-config.mjs +4 -4
  33. package/dist/blocks/lab-gallery.d.mts +1 -1
  34. package/dist/blocks/lab-gallery.mjs +2 -2
  35. package/dist/blocks/language.d.mts +6 -6
  36. package/dist/blocks/language.mjs +12 -12
  37. package/dist/blocks/lesson.mjs +4 -4
  38. package/dist/blocks/math.d.mts +577 -11
  39. package/dist/blocks/math.mjs +1648 -14
  40. package/dist/blocks/ml.d.mts +45 -1
  41. package/dist/blocks/ml.mjs +80 -6
  42. package/dist/blocks/physics.d.mts +9 -4
  43. package/dist/blocks/physics.mjs +75 -28
  44. package/dist/blocks/statistics.mjs +1 -1
  45. package/dist/build/CircuitEditor.d.mts +14 -0
  46. package/dist/build/CircuitEditor.mjs +787 -0
  47. package/dist/build/CircuitPlayer.d.mts +19 -0
  48. package/dist/build/CircuitPlayer.mjs +53 -0
  49. package/dist/build/CircuitScene.d.mts +61 -0
  50. package/dist/build/CircuitScene.mjs +383 -0
  51. package/dist/build/contract.d.mts +116 -0
  52. package/dist/build/editor-ops.d.mts +65 -0
  53. package/dist/build/editor-ops.mjs +306 -0
  54. package/dist/build/editor-ui.mjs +121 -0
  55. package/dist/build/flow.d.mts +14 -0
  56. package/dist/build/flow.mjs +119 -0
  57. package/dist/build/index.d.mts +10 -0
  58. package/dist/build/index.mjs +10 -0
  59. package/dist/build/parts/ammeter.d.mts +1 -0
  60. package/dist/build/parts/ammeter.mjs +32 -0
  61. package/dist/build/parts/bulb.d.mts +1 -0
  62. package/dist/build/parts/bulb.mjs +50 -0
  63. package/dist/build/parts/capacitor.d.mts +1 -0
  64. package/dist/build/parts/capacitor.mjs +26 -0
  65. package/dist/build/parts/cell.d.mts +1 -0
  66. package/dist/build/parts/cell.mjs +40 -0
  67. package/dist/build/parts/diode.d.mts +1 -0
  68. package/dist/build/parts/diode.mjs +33 -0
  69. package/dist/build/parts/index.d.mts +5 -0
  70. package/dist/build/parts/index.mjs +43 -0
  71. package/dist/build/parts/mosfet.d.mts +1 -0
  72. package/dist/build/parts/mosfet.mjs +95 -0
  73. package/dist/build/parts/node.d.mts +1 -0
  74. package/dist/build/parts/node.mjs +32 -0
  75. package/dist/build/parts/resistor.d.mts +1 -0
  76. package/dist/build/parts/resistor.mjs +49 -0
  77. package/dist/build/parts/shared.mjs +42 -0
  78. package/dist/build/parts/switch.d.mts +1 -0
  79. package/dist/build/parts/switch.mjs +34 -0
  80. package/dist/build/parts.d.mts +1 -0
  81. package/dist/build/registry.d.mts +9 -0
  82. package/dist/build/registry.mjs +15 -0
  83. package/dist/build/solve.d.mts +8 -0
  84. package/dist/build/solve.mjs +0 -0
  85. package/dist/catalog/registry.d.mts +2 -2
  86. package/dist/catalog/templates.d.mts +1 -1
  87. package/dist/catalog/templates.mjs +6 -6
  88. package/dist/chem/battery.mjs +1 -1
  89. package/dist/chem/bohr-atom.mjs +4 -4
  90. package/dist/chem/diffusion/preset.mjs +4 -4
  91. package/dist/chem/electrochem/preset.mjs +5 -5
  92. package/dist/chem/equilibrium/preset.d.mts +1 -1
  93. package/dist/chem/equilibrium/preset.mjs +6 -6
  94. package/dist/chem/gas-box/preset.mjs +6 -6
  95. package/dist/chem/kinetics/preset.mjs +6 -6
  96. package/dist/chem/periodic-trends/preset.mjs +7 -7
  97. package/dist/chem/reaction-lab.mjs +3 -3
  98. package/dist/chem/reaction-profile.mjs +4 -4
  99. package/dist/chem/solution/dilution.mjs +6 -6
  100. package/dist/chem/solution/field.d.mts +2 -2
  101. package/dist/chem/solution/field.mjs +2 -2
  102. package/dist/chem/solution/solution-box.mjs +7 -7
  103. package/dist/chem/stoichiometry/preset.mjs +5 -5
  104. package/dist/chem/titration/preset.mjs +5 -5
  105. package/dist/circuits/ac-dc/asset.mjs +1 -1
  106. package/dist/circuits/ac-dc/preset.mjs +2 -2
  107. package/dist/circuits/brownout/index.d.mts +1 -0
  108. package/dist/circuits/brownout/preset.d.mts +22 -0
  109. package/dist/circuits/brownout/preset.mjs +399 -0
  110. package/dist/circuits/capacitor-leak/preset.mjs +13 -8
  111. package/dist/circuits/circuit/asset.mjs +71 -14
  112. package/dist/circuits/circuit/preset.mjs +23 -5
  113. package/dist/circuits/circuit-builder.mjs +60 -132
  114. package/dist/circuits/circuit-lab.mjs +60 -40
  115. package/dist/circuits/cmos-gate/index.d.mts +1 -0
  116. package/dist/circuits/cmos-gate/preset.d.mts +75 -0
  117. package/dist/circuits/cmos-gate/preset.mjs +1491 -0
  118. package/dist/circuits/diode/index.d.mts +1 -0
  119. package/dist/circuits/diode/preset.d.mts +25 -0
  120. package/dist/circuits/diode/preset.mjs +266 -0
  121. package/dist/circuits/index.d.mts +7 -1
  122. package/dist/circuits/index.mjs +7 -1
  123. package/dist/circuits/rc-charging/index.d.mts +1 -0
  124. package/dist/circuits/rc-charging/preset.d.mts +27 -0
  125. package/dist/circuits/rc-charging/preset.mjs +329 -0
  126. package/dist/circuits/semiconductor/index.d.mts +1 -0
  127. package/dist/circuits/semiconductor/preset.d.mts +102 -0
  128. package/dist/circuits/semiconductor/preset.mjs +1855 -0
  129. package/dist/circuits/transistor/index.d.mts +1 -0
  130. package/dist/circuits/transistor/preset.d.mts +27 -0
  131. package/dist/circuits/transistor/preset.mjs +329 -0
  132. package/dist/commerce/accounting/core.d.mts +1 -1
  133. package/dist/commerce/accounting/equation-balance.mjs +5 -5
  134. package/dist/commerce/accounting/journal-poster.mjs +8 -8
  135. package/dist/commerce/accounting/statement-sorter.mjs +4 -4
  136. package/dist/commerce/economics/core.d.mts +2 -2
  137. package/dist/commerce/economics/core.mjs +1 -1
  138. package/dist/commerce/economics/demand-shift-vs-move.mjs +6 -6
  139. package/dist/commerce/economics/elasticity-revenue.mjs +6 -6
  140. package/dist/commerce/economics/market-equilibrium.mjs +5 -5
  141. package/dist/core/easing.d.mts +3 -3
  142. package/dist/core/easing.mjs +2 -2
  143. package/dist/core/numeric.d.mts +3 -3
  144. package/dist/core/numeric.mjs +2 -2
  145. package/dist/core/reactive.d.mts +1 -1
  146. package/dist/core/reactive.mjs +1 -1
  147. package/dist/core/rng.d.mts +2 -2
  148. package/dist/core/rng.mjs +1 -1
  149. package/dist/core/tex.mjs +2 -2
  150. package/dist/core/timeline.d.mts +1 -1
  151. package/dist/core/timeline.mjs +1 -1
  152. package/dist/core/util.mjs +11 -1
  153. package/dist/discrete/arrangements/preset.mjs +6 -14
  154. package/dist/discrete/bayes/preset.mjs +43 -35
  155. package/dist/discrete/binomial/preset.mjs +2 -2
  156. package/dist/discrete/combination-studio/figure.d.mts +6 -0
  157. package/dist/discrete/combination-studio/figure.mjs +159 -0
  158. package/dist/discrete/combination-studio/index.d.mts +2 -0
  159. package/dist/discrete/combination-studio/preset.d.mts +44 -0
  160. package/dist/discrete/combination-studio/preset.mjs +440 -0
  161. package/dist/discrete/core/combinatorics.d.mts +23 -3
  162. package/dist/discrete/core/combinatorics.mjs +38 -3
  163. package/dist/discrete/core/pigeonhole.d.mts +1 -1
  164. package/dist/discrete/core/pigeonhole.mjs +1 -1
  165. package/dist/discrete/core/probability.d.mts +3 -3
  166. package/dist/discrete/core/probability.mjs +3 -3
  167. package/dist/discrete/core/sets.d.mts +1 -1
  168. package/dist/discrete/counting-slots/preset.mjs +8 -8
  169. package/dist/discrete/counting-tree/preset.mjs +65 -8
  170. package/dist/discrete/expected-value/preset.mjs +13 -13
  171. package/dist/discrete/hypergeometric/preset.mjs +48 -15
  172. package/dist/discrete/index.d.mts +4 -2
  173. package/dist/discrete/index.mjs +4 -2
  174. package/dist/discrete/karnaugh/preset.mjs +26 -20
  175. package/dist/discrete/lln/preset.mjs +44 -3
  176. package/dist/discrete/logic-circuit/preset.mjs +2 -2
  177. package/dist/discrete/monte-carlo/preset.d.mts +1 -1
  178. package/dist/discrete/monte-carlo/preset.mjs +14 -14
  179. package/dist/discrete/monty-hall/preset.mjs +6 -6
  180. package/dist/discrete/outcome-builder/preset.mjs +3 -3
  181. package/dist/discrete/pascal/preset.mjs +37 -7
  182. package/dist/discrete/rules.d.mts +20 -0
  183. package/dist/discrete/rules.mjs +264 -0
  184. package/dist/discrete/sample-space/preset.mjs +4 -4
  185. package/dist/discrete/selection/preset.mjs +7 -13
  186. package/dist/discrete/truth-table/preset.mjs +32 -9
  187. package/dist/discrete/venn/preset.mjs +71 -48
  188. package/dist/geography/cycle-lab/preset.mjs +17 -10
  189. package/dist/geography/cycles.d.mts +3 -3
  190. package/dist/geography/cycles.mjs +3 -3
  191. package/dist/geometry/board/preset.d.mts +2 -0
  192. package/dist/geometry/board/preset.mjs +11 -15
  193. package/dist/geometry/builder.mjs +3 -3
  194. package/dist/ict/number-systems/base-odometer.mjs +4 -4
  195. package/dist/ict/number-systems/bit-grouper.d.mts +1 -1
  196. package/dist/ict/number-systems/bit-grouper.mjs +4 -4
  197. package/dist/ict/number-systems/place-value-dial.d.mts +1 -1
  198. package/dist/ict/number-systems/place-value-dial.mjs +3 -3
  199. package/dist/ict/number-systems/wheel.d.mts +3 -3
  200. package/dist/ict/number-systems/wheel.mjs +4 -4
  201. package/dist/kit/annotate.mjs +3 -3
  202. package/dist/kit/answer-check.d.mts +2 -2
  203. package/dist/kit/answer-check.mjs +34 -6
  204. package/dist/kit/ask.d.mts +19 -0
  205. package/dist/kit/ask.mjs +46 -0
  206. package/dist/kit/bar-strip.mjs +53 -0
  207. package/dist/kit/calc.d.mts +33 -0
  208. package/dist/kit/calc.mjs +27 -0
  209. package/dist/kit/carrier-engine.mjs +147 -0
  210. package/dist/kit/clue-scene.d.mts +86 -0
  211. package/dist/kit/clue-scene.mjs +424 -0
  212. package/dist/kit/cluster.d.mts +30 -0
  213. package/dist/kit/cluster.mjs +81 -0
  214. package/dist/kit/controls.mjs +11 -5
  215. package/dist/kit/coords.d.mts +88 -0
  216. package/dist/kit/coords.mjs +192 -0
  217. package/dist/kit/data-scene.d.mts +27 -0
  218. package/dist/kit/data-scene.mjs +199 -0
  219. package/dist/kit/diagram.mjs +111 -50
  220. package/dist/kit/electronics.mjs +552 -60
  221. package/dist/kit/expr-analysis.d.mts +18 -6
  222. package/dist/kit/expr-analysis.mjs +82 -28
  223. package/dist/kit/frame.d.mts +3 -3
  224. package/dist/kit/frame.mjs +25 -6
  225. package/dist/kit/gameshow.mjs +2 -2
  226. package/dist/kit/index.d.mts +14 -2
  227. package/dist/kit/index.mjs +13 -1
  228. package/dist/kit/logic-gates.mjs +246 -47
  229. package/dist/kit/palette.mjs +26 -0
  230. package/dist/kit/pedagogy.d.mts +4 -4
  231. package/dist/kit/pedagogy.mjs +21 -17
  232. package/dist/kit/play.d.mts +1 -1
  233. package/dist/kit/play.mjs +3 -3
  234. package/dist/kit/predict.d.mts +55 -0
  235. package/dist/kit/predict.mjs +146 -0
  236. package/dist/kit/receipt.d.mts +29 -0
  237. package/dist/kit/receipt.mjs +94 -0
  238. package/dist/kit/rich.mjs +2 -2
  239. package/dist/kit/rule.d.mts +59 -0
  240. package/dist/kit/rule.mjs +215 -0
  241. package/dist/kit/scale.mjs +1 -1
  242. package/dist/kit/scene-library.d.mts +22 -0
  243. package/dist/kit/scene-library.mjs +311 -0
  244. package/dist/kit/scene-studio.d.mts +14 -0
  245. package/dist/kit/scene-studio.mjs +338 -0
  246. package/dist/kit/scenes.d.mts +31 -0
  247. package/dist/kit/scenes.mjs +569 -0
  248. package/dist/kit/slot-fill.d.mts +61 -0
  249. package/dist/kit/slot-fill.mjs +207 -0
  250. package/dist/kit/solve.d.mts +21 -0
  251. package/dist/kit/solve.mjs +75 -0
  252. package/dist/kit/space.mjs +5 -5
  253. package/dist/kit/steps.mjs +2 -2
  254. package/dist/kit/vessel.d.mts +69 -0
  255. package/dist/kit/vessel.mjs +191 -0
  256. package/dist/language/agreement/preset.mjs +2 -2
  257. package/dist/language/article-lens/preset.d.mts +1 -1
  258. package/dist/language/article-lens/preset.mjs +8 -8
  259. package/dist/language/deck.d.mts +3 -3
  260. package/dist/language/deck.mjs +1 -1
  261. package/dist/language/preposition-scene/preset.d.mts +2 -2
  262. package/dist/language/preposition-scene/preset.mjs +5 -5
  263. package/dist/language/sentence-builder/preset.mjs +5 -5
  264. package/dist/language/transform/preset.d.mts +2 -2
  265. package/dist/language/transform/preset.mjs +5 -5
  266. package/dist/language/ui.d.mts +3 -3
  267. package/dist/language/ui.mjs +4 -4
  268. package/dist/language/word-match/preset.mjs +3 -3
  269. package/dist/logic/LogicBuildLab.d.mts +22 -0
  270. package/dist/logic/LogicBuildLab.mjs +170 -0
  271. package/dist/logic/LogicEditScene.d.mts +31 -0
  272. package/dist/logic/LogicEditScene.mjs +276 -0
  273. package/dist/logic/LogicEditor.d.mts +14 -0
  274. package/dist/logic/LogicEditor.mjs +413 -0
  275. package/dist/logic/LogicScene.d.mts +32 -0
  276. package/dist/logic/LogicScene.mjs +277 -0
  277. package/dist/logic/contract.d.mts +72 -0
  278. package/dist/logic/display.d.mts +24 -0
  279. package/dist/logic/display.mjs +127 -0
  280. package/dist/logic/edit-ops.d.mts +44 -0
  281. package/dist/logic/edit-ops.mjs +177 -0
  282. package/dist/logic/evaluate.d.mts +11 -0
  283. package/dist/logic/evaluate.mjs +93 -0
  284. package/dist/logic/gates.d.mts +10 -0
  285. package/dist/logic/gates.mjs +73 -0
  286. package/dist/logic/index.d.mts +14 -0
  287. package/dist/logic/index.mjs +13 -0
  288. package/dist/logic/lab.d.mts +32 -0
  289. package/dist/logic/lab.mjs +160 -0
  290. package/dist/logic/presets.d.mts +12 -0
  291. package/dist/logic/presets.mjs +241 -0
  292. package/dist/logic/registry.d.mts +9 -0
  293. package/dist/logic/registry.mjs +15 -0
  294. package/dist/math/area-model/preset.mjs +70 -44
  295. package/dist/math/balance-algebra/preset.mjs +14 -5
  296. package/dist/math/broken-tree/index.d.mts +1 -0
  297. package/dist/math/broken-tree/preset.d.mts +26 -0
  298. package/dist/math/broken-tree/preset.mjs +435 -0
  299. package/dist/math/circle/index.d.mts +1 -0
  300. package/dist/math/circle/preset.d.mts +44 -0
  301. package/dist/math/circle/preset.mjs +198 -0
  302. package/dist/math/complex/core.d.mts +93 -0
  303. package/dist/math/complex/core.mjs +239 -0
  304. package/dist/math/complex/index.d.mts +3 -0
  305. package/dist/math/complex/preset.d.mts +31 -0
  306. package/dist/math/complex/preset.mjs +377 -0
  307. package/dist/math/complex/rules.d.mts +6 -0
  308. package/dist/math/complex/rules.mjs +121 -0
  309. package/dist/math/conic/index.d.mts +1 -0
  310. package/dist/math/conic/preset.d.mts +30 -0
  311. package/dist/math/conic/preset.mjs +334 -0
  312. package/dist/math/derivation.mjs +5 -5
  313. package/dist/math/derivative-explorer/preset.mjs +5 -5
  314. package/dist/math/domain-range/index.d.mts +1 -0
  315. package/dist/math/domain-range/preset.d.mts +33 -0
  316. package/dist/math/domain-range/preset.mjs +313 -0
  317. package/dist/math/fraction-bar/index.d.mts +1 -0
  318. package/dist/math/fraction-bar/preset.d.mts +27 -0
  319. package/dist/math/fraction-bar/preset.mjs +197 -0
  320. package/dist/math/function-machine/preset.mjs +2 -2
  321. package/dist/math/gradient-descent.mjs +6 -6
  322. package/dist/math/grapher/preset.d.mts +1 -1
  323. package/dist/math/grapher/preset.mjs +4 -4
  324. package/dist/math/harmonic-form/preset.mjs +6 -6
  325. package/dist/math/index.d.mts +27 -2
  326. package/dist/math/index.mjs +27 -2
  327. package/dist/math/integral-explorer/preset.mjs +2 -2
  328. package/dist/math/interactive/preset.mjs +3 -3
  329. package/dist/math/limit-explorer/preset.mjs +4 -4
  330. package/dist/math/linear-model/index.d.mts +1 -0
  331. package/dist/math/linear-model/preset.d.mts +53 -0
  332. package/dist/math/linear-model/preset.mjs +164 -0
  333. package/dist/math/linear-system/preset.mjs +4 -4
  334. package/dist/math/mystery-bucket/preset.mjs +6 -6
  335. package/dist/math/number-line/preset.mjs +3 -2
  336. package/dist/math/parabola/preset.mjs +4 -3
  337. package/dist/math/pattern/preset.mjs +7 -13
  338. package/dist/math/percent-bar/index.d.mts +1 -0
  339. package/dist/math/percent-bar/preset.d.mts +37 -0
  340. package/dist/math/percent-bar/preset.mjs +275 -0
  341. package/dist/math/poly/core.d.mts +50 -0
  342. package/dist/math/poly/core.mjs +268 -0
  343. package/dist/math/poly/index.d.mts +3 -0
  344. package/dist/math/poly/preset.d.mts +23 -0
  345. package/dist/math/poly/preset.mjs +141 -0
  346. package/dist/math/poly/steps.d.mts +10 -0
  347. package/dist/math/poly/steps.mjs +99 -0
  348. package/dist/math/rate-machine/index.d.mts +1 -0
  349. package/dist/math/rate-machine/preset.d.mts +35 -0
  350. package/dist/math/rate-machine/preset.mjs +281 -0
  351. package/dist/math/ratio-share/index.d.mts +1 -0
  352. package/dist/math/ratio-share/preset.d.mts +24 -0
  353. package/dist/math/ratio-share/preset.mjs +204 -0
  354. package/dist/math/receipt/index.d.mts +1 -0
  355. package/dist/math/receipt/preset.d.mts +22 -0
  356. package/dist/math/receipt/preset.mjs +137 -0
  357. package/dist/math/sequence-predict/index.d.mts +1 -0
  358. package/dist/math/sequence-predict/preset.d.mts +34 -0
  359. package/dist/math/sequence-predict/preset.mjs +130 -0
  360. package/dist/math/straight-line/index.d.mts +1 -0
  361. package/dist/math/straight-line/preset.d.mts +37 -0
  362. package/dist/math/straight-line/preset.mjs +374 -0
  363. package/dist/math/system-solve/index.d.mts +1 -0
  364. package/dist/math/system-solve/preset.d.mts +21 -0
  365. package/dist/math/system-solve/preset.mjs +227 -0
  366. package/dist/math/transform/index.d.mts +1 -0
  367. package/dist/math/transform/preset.d.mts +41 -0
  368. package/dist/math/transform/preset.mjs +343 -0
  369. package/dist/math/triangle-trig/preset.mjs +12 -9
  370. package/dist/math/trig/core.d.mts +37 -0
  371. package/dist/math/trig/core.mjs +150 -0
  372. package/dist/math/trig/diagrams.d.mts +1 -0
  373. package/dist/math/trig/diagrams.mjs +321 -0
  374. package/dist/math/trig/index.d.mts +3 -0
  375. package/dist/math/trig/preset.d.mts +25 -0
  376. package/dist/math/trig/preset.mjs +260 -0
  377. package/dist/math/trig/rules.d.mts +6 -0
  378. package/dist/math/trig/rules.mjs +159 -0
  379. package/dist/math/trig-explorer.mjs +4 -4
  380. package/dist/ml/boundary/preset.mjs +8 -3
  381. package/dist/ml/classifier/preset.mjs +31 -6
  382. package/dist/ml/kmeans/preset.mjs +4 -4
  383. package/dist/ml/knn/preset.mjs +47 -12
  384. package/dist/ml/regression/preset.mjs +36 -7
  385. package/dist/physics/atwood/preset.mjs +4 -4
  386. package/dist/physics/bullet-walls/preset.d.mts +1 -1
  387. package/dist/physics/bullet-walls/preset.mjs +7 -7
  388. package/dist/physics/carnot/preset.mjs +6 -6
  389. package/dist/physics/circular-motion/preset.mjs +36 -5
  390. package/dist/physics/collision-track/preset.mjs +35 -5
  391. package/dist/physics/efficiency/preset.d.mts +2 -2
  392. package/dist/physics/efficiency/preset.mjs +5 -5
  393. package/dist/physics/electric-field/preset.mjs +32 -23
  394. package/dist/physics/electric-flux/index.d.mts +1 -0
  395. package/dist/physics/electric-flux/preset.d.mts +29 -0
  396. package/dist/physics/electric-flux/preset.mjs +264 -0
  397. package/dist/physics/energy-skate/preset.mjs +5 -5
  398. package/dist/physics/entropy/preset.mjs +7 -7
  399. package/dist/physics/expansion/preset.mjs +13 -13
  400. package/dist/physics/exponential/asset.mjs +1 -1
  401. package/dist/physics/exponential/preset.mjs +4 -4
  402. package/dist/physics/gas-process/preset.mjs +3 -3
  403. package/dist/physics/gauss-law/index.d.mts +1 -0
  404. package/dist/physics/gauss-law/preset.d.mts +20 -0
  405. package/dist/physics/gauss-law/preset.mjs +286 -0
  406. package/dist/physics/gravitation/preset.mjs +7 -7
  407. package/dist/physics/gravity-drop.mjs +4 -4
  408. package/dist/physics/heat-transfer/preset.d.mts +1 -1
  409. package/dist/physics/heat-transfer/preset.mjs +10 -10
  410. package/dist/physics/impulse/preset.mjs +5 -5
  411. package/dist/physics/index.d.mts +4 -1
  412. package/dist/physics/index.mjs +4 -1
  413. package/dist/physics/kepler/preset.mjs +4 -4
  414. package/dist/physics/lever/preset.mjs +3 -3
  415. package/dist/physics/lorentz/preset.mjs +11 -11
  416. package/dist/physics/magnetism/preset.mjs +3 -3
  417. package/dist/physics/optics/preset.mjs +2 -2
  418. package/dist/physics/orbit-lab.mjs +5 -5
  419. package/dist/physics/projectile-lab.mjs +4 -4
  420. package/dist/physics/rain-relative/preset.mjs +5 -5
  421. package/dist/physics/ramp-forces/preset.mjs +5 -5
  422. package/dist/physics/river-boat.mjs +6 -6
  423. package/dist/physics/shm/preset.mjs +9 -9
  424. package/dist/physics/stopping-distance/preset.mjs +3 -3
  425. package/dist/physics/temperature-scales/preset.mjs +105 -116
  426. package/dist/physics/terminal-velocity/preset.mjs +5 -5
  427. package/dist/physics/thermal/preset.mjs +5 -5
  428. package/dist/physics/vector-board/preset.mjs +7 -6
  429. package/dist/physics/vector-scene.mjs +2 -2
  430. package/dist/physics/vector-types/preset.mjs +37 -3
  431. package/dist/physics/water-density/preset.mjs +12 -12
  432. package/dist/physics/waves/doppler.mjs +2 -2
  433. package/dist/physics/waves/preset.mjs +4 -4
  434. package/dist/physics/waves/ripple.mjs +6 -6
  435. package/dist/physics/waves/string.mjs +5 -5
  436. package/dist/physics/work-energy/preset.mjs +4 -4
  437. package/dist/physics/work-potential/index.d.mts +1 -0
  438. package/dist/physics/work-potential/preset.d.mts +20 -0
  439. package/dist/physics/work-potential/preset.mjs +276 -0
  440. package/dist/schemas/index.d.mts +6 -6
  441. package/dist/schemas/index.mjs +3 -3
  442. package/dist/statistics/center-spread/preset.mjs +3 -3
  443. package/dist/statistics/core/descriptive.d.mts +2 -2
  444. package/dist/statistics/core/descriptive.mjs +2 -2
  445. package/dist/statistics/core/normal.d.mts +3 -3
  446. package/dist/statistics/core/normal.mjs +3 -3
  447. package/dist/statistics/core/sequences.d.mts +1 -1
  448. package/dist/statistics/galton/preset.mjs +2 -2
  449. package/dist/statistics/histogram/preset.mjs +6 -6
  450. package/dist/statistics/normal/preset.mjs +2 -2
  451. package/dist/statistics/sampling/preset.mjs +4 -4
  452. package/dist/statistics/sequence/preset.mjs +58 -15
  453. package/dist/statistics/z-table/preset.mjs +17 -4
  454. package/package.json +11 -3
  455. package/styles.css +4 -1
@@ -0,0 +1,159 @@
1
+ import { toRad } from "../../core/util.mjs";
2
+ import { calc } from "../../kit/calc.mjs";
3
+ import { castLetter, castPositive, evalTrig, exactTex, isSpecial, normDeg, quadrant, radTex, referenceAngleDeg, sign } from "./core.mjs";
4
+ import { SpecialTriangles, UnitCircleMini } from "./diagrams.mjs";
5
+ import { Fragment, createElement } from "react";
6
+
7
+ //#region src/math/trig/rules.ts
8
+ /**
9
+ * The trig RULEBOOK: signs (CAST), exact values, the Pythagorean identity, and
10
+ * degree↔radian — as RuleDef data for the RuleCard concept engine (formula +
11
+ * analogy + live worked calculator + derivation + tricks). Calculators run on the
12
+ * trig teaching kernel.
13
+ */
14
+ const fmt = (n) => Number.isNaN(n) ? "\\text{undef}" : (Math.round(n * 1e3) / 1e3).toString();
15
+ /** Which functions are positive in the quadrant of `deg`, with the signs. */
16
+ function explainCast(deg) {
17
+ const q = quadrant(deg);
18
+ const pos = castPositive(deg);
19
+ const sg = (fn) => `\\${fn}: ${sign(fn, deg) > 0 ? "+" : sign(fn, deg) < 0 ? "-" : "0"}`;
20
+ return calc().step(`\\theta = ${normDeg(deg)}^\\circ \\Rightarrow \\text{Quadrant } ${q || "\\text{axis}"}`, "where the terminal ray lands").step(`${sg("sin")}, \\quad ${sg("cos")}, \\quad ${sg("tan")}`, q ? `Q${q}: only ${pos.join("/")} positive (${castLetter(deg)})` : "on an axis").done(q);
21
+ }
22
+ /** Exact sin/cos/tan at a special angle, via reference angle + sign. */
23
+ function explainExact(deg) {
24
+ const ref = referenceAngleDeg(deg);
25
+ const special = isSpecial(deg);
26
+ const c = calc().step(`\\theta = ${normDeg(deg)}^\\circ, \\quad \\text{reference } = ${ref}^\\circ`, "reduce to the acute angle");
27
+ if (special) c.step(`\\sin = ${exactTex("sin", deg)}, \\;\\; \\cos = ${exactTex("cos", deg)}, \\;\\; \\tan = ${exactTex("tan", deg)}`, "reference value × the quadrant sign");
28
+ else c.step(`\\sin \\approx ${fmt(evalTrig("sin", deg))}, \\;\\; \\cos \\approx ${fmt(evalTrig("cos", deg))}`, "not a standard angle, decimal");
29
+ return c.done(ref);
30
+ }
31
+ /** Degree → exact radian. */
32
+ function explainDegRad(deg) {
33
+ return calc().step(`${normDeg(deg)}^\\circ \\times \\dfrac{\\pi}{180^\\circ}`, "because 180° = π radians").step(`= ${radTex(deg)}`).done(toRad(deg));
34
+ }
35
+ const CAST_RULE = {
36
+ id: "cast",
37
+ name: "CAST: signs by quadrant",
38
+ formula: "\\text{Q1: All} \\;|\\; \\text{Q2: Sin} \\;|\\; \\text{Q3: Tan} \\;|\\; \\text{Q4: Cos}",
39
+ analogy: "\"All Students Take Calculus.\" Going anticlockwise from Q1: All positive, then only Sin, then only Tan, then only Cos.",
40
+ figure: (v) => createElement(UnitCircleMini, {
41
+ deg: v.deg ?? 0,
42
+ showLegs: true,
43
+ showCast: true
44
+ }),
45
+ inputs: [{
46
+ key: "deg",
47
+ label: "θ°",
48
+ default: 210,
49
+ min: 0,
50
+ max: 345,
51
+ step: 15
52
+ }],
53
+ compute: (v) => explainCast(v.deg ?? 0),
54
+ derivation: [
55
+ {
56
+ tex: "\\cos\\theta = x, \\quad \\sin\\theta = y",
57
+ note: "the point on the unit circle"
58
+ },
59
+ { tex: "\\tan\\theta = \\tfrac{\\sin\\theta}{\\cos\\theta} = \\tfrac{y}{x}" },
60
+ {
61
+ tex: "\\text{so the signs are just the signs of } x, y",
62
+ note: "Q2: x<0,y>0 ⇒ only sin +"
63
+ }
64
+ ],
65
+ tricks: [
66
+ "It is literally the sign of x (cos) and y (sin) in each quadrant.",
67
+ "tan is positive where sin and cos AGREE in sign (Q1, Q3).",
68
+ "Reference angle gives the size; CAST gives the sign."
69
+ ]
70
+ };
71
+ const EXACT_RULE = {
72
+ id: "exact-values",
73
+ name: "Exact special-angle values",
74
+ formula: "\\sin 30^\\circ=\\tfrac12,\\; \\cos 30^\\circ=\\tfrac{\\sqrt3}{2},\\; \\sin 45^\\circ=\\tfrac{\\sqrt2}{2}",
75
+ analogy: "They all come from two triangles: the 45-45-90 (sides 1,1,√2) and the 30-60-90 (sides 1,√3,2).",
76
+ figure: (v) => createElement(Fragment, null, createElement("div", { style: {
77
+ display: "grid",
78
+ gap: 6,
79
+ justifyItems: "center"
80
+ } }, createElement(UnitCircleMini, {
81
+ deg: v.deg ?? 0,
82
+ showLegs: true,
83
+ showValue: true
84
+ }), createElement(SpecialTriangles, {}))),
85
+ inputs: [{
86
+ key: "deg",
87
+ label: "θ°",
88
+ default: 150,
89
+ min: 0,
90
+ max: 330,
91
+ step: 30
92
+ }],
93
+ compute: (v) => explainExact(v.deg ?? 0),
94
+ tricks: [
95
+ "Only the multiples of 30° and 45° are \"exact\".",
96
+ "Find the reference angle, look up its value, then apply the CAST sign.",
97
+ "sin and cos swap between 30° and 60° (½ ↔ √3⁄2)."
98
+ ]
99
+ };
100
+ const PYTHAG_RULE = {
101
+ id: "pythagorean",
102
+ name: "The Pythagorean identity",
103
+ formula: "\\sin^2\\theta + \\cos^2\\theta = 1",
104
+ analogy: "It IS Pythagoras on the unit circle: the point (cosθ, sinθ) sits at distance 1 from the centre, so x² + y² = 1.",
105
+ figure: createElement(UnitCircleMini, {
106
+ deg: 52,
107
+ showLegs: true,
108
+ showHyp: true
109
+ }),
110
+ derivation: [
111
+ {
112
+ tex: "(\\cos\\theta,\\ \\sin\\theta) \\text{ is on the unit circle}",
113
+ note: "radius 1"
114
+ },
115
+ {
116
+ tex: "x^2 + y^2 = 1^2",
117
+ note: "distance from the origin"
118
+ },
119
+ { tex: "\\Rightarrow \\cos^2\\theta + \\sin^2\\theta = 1" }
120
+ ],
121
+ tricks: [
122
+ "Divide by cos²θ → 1 + tan²θ = sec²θ.",
123
+ "Divide by sin²θ → 1 + cot²θ = csc²θ.",
124
+ "Lets you get cos from sin (up to sign): cosθ = ±√(1 − sin²θ)."
125
+ ]
126
+ };
127
+ const DEG_RAD_RULE = {
128
+ id: "deg-rad",
129
+ name: "Degrees ↔ radians",
130
+ formula: "180^\\circ = \\pi \\text{ rad}, \\qquad 1^\\circ = \\tfrac{\\pi}{180}",
131
+ analogy: "A radian is the angle that wraps one radius-length of arc around the circle; a full turn (360°) is 2π of them.",
132
+ figure: (v) => createElement(UnitCircleMini, {
133
+ deg: v.deg ?? 0,
134
+ showLegs: false
135
+ }),
136
+ inputs: [{
137
+ key: "deg",
138
+ label: "θ°",
139
+ default: 30,
140
+ min: 0,
141
+ max: 360,
142
+ step: 15
143
+ }],
144
+ compute: (v) => explainDegRad(v.deg ?? 0),
145
+ tricks: [
146
+ "× π/180 to go deg → rad; × 180/π to go back.",
147
+ "Memorise the anchors: 180°=π, 90°=π/2, 60°=π/3, 45°=π/4, 30°=π/6.",
148
+ "Calculus needs radians (so d/dx sin x = cos x works)."
149
+ ]
150
+ };
151
+ const TRIG_RULES = [
152
+ CAST_RULE,
153
+ EXACT_RULE,
154
+ PYTHAG_RULE,
155
+ DEG_RAD_RULE
156
+ ];
157
+
158
+ //#endregion
159
+ export { TRIG_RULES };
@@ -10,14 +10,14 @@ import { Axes, Circle, Dot, Grid, MovableDot, Polyline, Segment, Stage, Vector,
10
10
 
11
11
  //#region src/math/trig-explorer.tsx
12
12
  /**
13
- * TrigExplorer — the unit circle ↔ sine wave connection (3Blue1Brown style),
13
+ * TrigExplorer, the unit circle ↔ sine wave connection (3Blue1Brown style),
14
14
  * but interactive: drag the angle (or press play) and watch sin/cos trace out.
15
15
  *
16
- * Now on the @classytic/stage engine (SVG, accessible, themed) — the unit circle,
16
+ * Now on the @classytic/stage engine (SVG, accessible, themed), the unit circle,
17
17
  * radius, projections, and unrolled wave are real primitives, the angle handle is
18
18
  * a `MovableDot` (keyboard + aria), and animation runs on the engine clock.
19
19
  *
20
- * Deliberately FOCUSED on one idea — "sin and cos are the shadows of a point
20
+ * Deliberately FOCUSED on one idea, "sin and cos are the shadows of a point
21
21
  * going around a circle." Plotting arbitrary trig (tan, y=a·sin(b·x), …) is the
22
22
  * job of the general `Grapher`; this widget stays clean. Configurable only in
23
23
  * which of sin/cos to show.
@@ -173,7 +173,7 @@ function TrigExplorer({ functions, startDeg = 30 } = {}) {
173
173
  });
174
174
  return /* @__PURE__ */ jsx(LabFrame, {
175
175
  title: "Trig Explorer",
176
- prompt: "Drag the angle on the circle (or press play) — sin and cos are just its shadows.",
176
+ prompt: "Drag the angle on the circle (or press play), sin and cos are just its shadows.",
177
177
  controls: /* @__PURE__ */ jsxs(ControlBar, { children: [
178
178
  /* @__PURE__ */ jsx(CheckButton, {
179
179
  onClick: () => setPlaying((p) => !p),
@@ -3,19 +3,19 @@
3
3
  import { gaussian, mulberry32 } from "../../core/rng.mjs";
4
4
  import { Chip } from "../../kit/controls.mjs";
5
5
  import { Callout, ControlBar, LabFrame } from "../../kit/frame.mjs";
6
- import { HintLadder, useHints } from "../../kit/pedagogy.mjs";
6
+ import { HintLadder, useCheckpoint, useHints } from "../../kit/pedagogy.mjs";
7
7
  import { useCallback, useMemo, useRef, useState } from "react";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
9
  import { Axes, Circle, Dot, Grid, MovableDot, Polygon, Segment, Stage, useControlSurface, useFrameLoop, useInView } from "@classytic/stage";
10
10
 
11
11
  //#region src/ml/boundary/preset.tsx
12
12
  /**
13
- * DecisionBoundaryLab — a linear classifier you can SEE think. Two classes of
13
+ * DecisionBoundaryLab, a linear classifier you can SEE think. Two classes of
14
14
  * points sit in the plane; a straight boundary splits it into two predicted
15
15
  * regions. Drag the boundary's two handles to separate the classes by hand
16
16
  * (misclassified points get a red ring, accuracy updates live), then hit "train"
17
17
  * and watch a PERCEPTRON nudge the same line into place on its own. The honest
18
- * twist: the XOR dataset can't be split by ANY straight line — accuracy stalls
18
+ * twist: the XOR dataset can't be split by ANY straight line, accuracy stalls
19
19
  * below 100%, the door to "why we need more than a line."
20
20
  *
21
21
  * Pure stage primitives (Stage + Polygon shading + MovableDot handles + frame-loop
@@ -96,6 +96,11 @@ function DecisionBoundaryLab({ dataset = "separable", seed = 11, title = "Draw t
96
96
  wrong: wr
97
97
  };
98
98
  }, [pts, weights]);
99
+ useCheckpoint({
100
+ solved: acc === 1,
101
+ activity: `boundary:${title}`,
102
+ hintsUsed: hints.count
103
+ });
99
104
  const regions = useMemo(() => {
100
105
  const corners = [
101
106
  {
@@ -3,17 +3,17 @@
3
3
  import { clamp } from "../../core/util.mjs";
4
4
  import { StatusPill } from "../../kit/controls.mjs";
5
5
  import { LabFrame, LiveRegion } from "../../kit/frame.mjs";
6
- import { useCheckpoint } from "../../kit/pedagogy.mjs";
6
+ import { ChallengeCard, useChallenge, useCheckpoint } from "../../kit/pedagogy.mjs";
7
7
  import { useState } from "react";
8
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
9
  import { Circle, Dot, Label, MovableDot, Polygon, Segment, Stage } from "@classytic/stage";
10
10
 
11
11
  //#region src/ml/classifier/preset.tsx
12
12
  /**
13
- * ClassifierThresholdLab — the precision/recall trade-off you can drag. Positive
13
+ * ClassifierThresholdLab, the precision/recall trade-off you can drag. Positive
14
14
  * and negative examples sit along a score axis (they OVERLAP, like any real
15
15
  * classifier). Drag the threshold: everything to its right is predicted positive.
16
- * The 2×2 confusion matrix and precision / recall / accuracy / F1 update live —
16
+ * The 2×2 confusion matrix and precision / recall / accuracy / F1 update live ,
17
17
  * slide right and precision climbs while recall falls; there's no setting that
18
18
  * maxes both. Misclassified points get a red ring.
19
19
  *
@@ -46,7 +46,27 @@ const DEFAULT_NEG = [
46
46
  5,
47
47
  6
48
48
  ];
49
- function ClassifierThresholdLab({ positives = DEFAULT_POS, negatives = DEFAULT_NEG, threshold = 5, span = 10, title = "The precision–recall trade-off", prompt = "Drag the threshold: everything to its right is called positive. Watch the confusion matrix — pushing precision up costs you recall.", objectives, height = 300 }) {
49
+ const PREDICT_Q = [{
50
+ id: "recall-vs-threshold",
51
+ prompt: "As you RAISE the threshold (label fewer things positive), what happens to RECALL — the share of true positives you catch?",
52
+ choices: [
53
+ {
54
+ value: "down",
55
+ label: "recall falls"
56
+ },
57
+ {
58
+ value: "up",
59
+ label: "recall rises"
60
+ },
61
+ {
62
+ value: "same",
63
+ label: "unchanged"
64
+ }
65
+ ],
66
+ answer: "down",
67
+ explain: "Raising the threshold means fewer points clear the bar, so you catch fewer of the real positives (more false negatives): recall falls. Precision usually rises in return, since the ones you do call positive are more likely correct. That is the trade-off, no single threshold maxes both."
68
+ }];
69
+ function ClassifierThresholdLab({ positives = DEFAULT_POS, negatives = DEFAULT_NEG, threshold = 5, span = 10, title = "The precision–recall trade-off", prompt = "Drag the threshold: everything to its right is called positive. Watch the confusion matrix, pushing precision up costs you recall.", objectives, height = 300 }) {
50
70
  const view = {
51
71
  xMin: 0,
52
72
  xMax: span,
@@ -55,8 +75,9 @@ function ClassifierThresholdLab({ positives = DEFAULT_POS, negatives = DEFAULT_N
55
75
  };
56
76
  const [t, setT] = useState(threshold);
57
77
  const [moved, setMoved] = useState(false);
78
+ const ch = useChallenge(PREDICT_Q);
58
79
  useCheckpoint({
59
- solved: moved,
80
+ solved: moved && ch.allCorrect,
60
81
  activity: "classifier-threshold"
61
82
  });
62
83
  const TP = positives.filter((s) => s >= t).length;
@@ -306,7 +327,11 @@ function ClassifierThresholdLab({ positives = DEFAULT_POS, negatives = DEFAULT_N
306
327
  })
307
328
  ]
308
329
  })] }),
309
- footer: /* @__PURE__ */ jsx(LiveRegion, { children: `Threshold ${t.toFixed(1)}. Precision ${(precision * 100).toFixed(0)} percent, recall ${(recall * 100).toFixed(0)} percent.` }),
330
+ footer: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ChallengeCard, {
331
+ questions: PREDICT_Q,
332
+ state: ch,
333
+ title: "Predict first"
334
+ }), /* @__PURE__ */ jsx(LiveRegion, { children: `Threshold ${t.toFixed(1)}. Precision ${(precision * 100).toFixed(0)} percent, recall ${(recall * 100).toFixed(0)} percent.` })] }),
310
335
  children: figure
311
336
  });
312
337
  }
@@ -9,12 +9,12 @@ import { Circle, Dot, Grid, MovableDot, Segment, Stage, useFrameLoop, useInView
9
9
 
10
10
  //#region src/ml/kmeans/preset.tsx
11
11
  /**
12
- * KMeansLab — unsupervised clustering you can watch converge. Points sit in a few
12
+ * KMeansLab, unsupervised clustering you can watch converge. Points sit in a few
13
13
  * blobs; you drag the k centroids to seed them, then Step (Lloyd's algorithm):
14
14
  * every point recolours to its nearest centroid, and each centroid jumps to the
15
- * mean of its cluster. Run animates it — centroids migrate into the blobs, the
15
+ * mean of its cluster. Run animates it, centroids migrate into the blobs, the
16
16
  * within-cluster error (inertia) drops, and it stops when nothing moves. Seed the
17
- * centroids badly on purpose and you land in a worse local minimum — the key
17
+ * centroids badly on purpose and you land in a worse local minimum, the key
18
18
  * k-means intuition that initialisation matters.
19
19
  *
20
20
  * Pure stage primitives: Dot (coloured by assignment) + MovableDot (centroids) +
@@ -59,7 +59,7 @@ const DEFAULT_SEEDS = [
59
59
  }
60
60
  ];
61
61
  const d2 = (a, b) => (a.x - b.x) ** 2 + (a.y - b.y) ** 2;
62
- function KMeansLab({ points = DEFAULT_POINTS, k = 3, seeds, span = 10, showLines = true, title = "k-means — watch the clusters form", prompt = "Drag the centroids to seed them, then Step: points recolour to the nearest centroid, centroids jump to their cluster mean. Seed them badly and you get stuck in a worse answer.", objectives, height = 380 }) {
62
+ function KMeansLab({ points = DEFAULT_POINTS, k = 3, seeds, span = 10, showLines = true, title = "k-means: watch the clusters form", prompt = "Drag the centroids to seed them, then Step: points recolour to the nearest centroid, centroids jump to their cluster mean. Seed them badly and you get stuck in a worse answer.", objectives, height = 380 }) {
63
63
  const view = {
64
64
  xMin: 0,
65
65
  xMax: span,
@@ -10,15 +10,15 @@ import { CanvasLayer, useControlSurface } from "@classytic/stage";
10
10
 
11
11
  //#region src/ml/knn/preset.tsx
12
12
  /**
13
- * KNNBoundaryLab — the answer to "a line can't split XOR." k-nearest-neighbours
13
+ * KNNBoundaryLab, the answer to "a line can't split XOR." k-nearest-neighbours
14
14
  * paints the whole plane by asking, at every point, "what are my k closest
15
15
  * labelled neighbours, and which class wins the vote?" The boundary it carves is
16
- * CURVY — it shrugs off XOR and even concentric rings that no straight line could
16
+ * CURVY, it shrugs off XOR and even concentric rings that no straight line could
17
17
  * touch. Drag the ✦ test point to watch its k neighbours light up and cast their
18
18
  * votes; slide k to feel the trade-off: k=1 memorises every island (jagged,
19
19
  * overfit), big k smooths everything (and starts ignoring real structure).
20
20
  *
21
- * The decision regions are a CanvasLayer heatmap (one vote per cell — exactly the
21
+ * The decision regions are a CanvasLayer heatmap (one vote per cell, exactly the
22
22
  * high-element-count job canvas is for); points + neighbour spokes draw on top.
23
23
  */
24
24
  const R = 5, A0 = "#1c7ed6", A1 = "#e8590c";
@@ -182,15 +182,50 @@ function KNNBoundaryLab({ dataset = "circles", k = 5, seed = 7, title = "k-neare
182
182
  set: setKk
183
183
  }
184
184
  });
185
- const figure = /* @__PURE__ */ jsx(CanvasLayer, {
186
- view: useMemo(() => view, []),
187
- height,
188
- draw,
189
- onPointerMath: (m) => setQuery({
190
- x: Math.max(-5, Math.min(R, m[0])),
191
- y: Math.max(-5, Math.min(R, m[1]))
192
- }),
193
- ariaLabel: `k-NN boundary, k ${kk}, accuracy ${(acc * 100).toFixed(0)}%`
185
+ const vw = useMemo(() => view, []);
186
+ const clampX = (x) => Math.max(view.xMin, Math.min(view.xMax, x));
187
+ const clampY = (y) => Math.max(view.yMin, Math.min(view.yMax, y));
188
+ const KEY_STEP = .25;
189
+ const onKeyDown = useCallback((e) => {
190
+ let dx = 0, dy = 0;
191
+ if (e.key === "ArrowLeft") dx = -.25;
192
+ else if (e.key === "ArrowRight") dx = KEY_STEP;
193
+ else if (e.key === "ArrowUp") dy = KEY_STEP;
194
+ else if (e.key === "ArrowDown") dy = -.25;
195
+ else return;
196
+ e.preventDefault();
197
+ setQuery((q) => ({
198
+ x: clampX(q.x + dx),
199
+ y: clampY(q.y + dy)
200
+ }));
201
+ }, []);
202
+ const figure = /* @__PURE__ */ jsx("div", {
203
+ tabIndex: 0,
204
+ role: "application",
205
+ "aria-label": `k-NN decision boundary, k ${kk}. Test point predicts class ${qCls ? "B" : "A"}. Accuracy ${(acc * 100).toFixed(0)} percent. Use arrow keys to move the test point.`,
206
+ onKeyDown,
207
+ style: {
208
+ outline: "none",
209
+ borderRadius: 6,
210
+ position: "relative"
211
+ },
212
+ onFocus: (e) => {
213
+ e.currentTarget.style.outline = "2px solid var(--stage-accent, #1c7ed6)";
214
+ e.currentTarget.style.outlineOffset = "2px";
215
+ },
216
+ onBlur: (e) => {
217
+ e.currentTarget.style.outline = "none";
218
+ },
219
+ children: /* @__PURE__ */ jsx(CanvasLayer, {
220
+ view: vw,
221
+ height,
222
+ draw,
223
+ onPointerMath: (m) => setQuery({
224
+ x: clampX(m[0]),
225
+ y: clampY(m[1])
226
+ }),
227
+ ariaLabel: `k-NN boundary, k ${kk}, accuracy ${(acc * 100).toFixed(0)}%`
228
+ })
194
229
  });
195
230
  return /* @__PURE__ */ jsx(LabFrame, {
196
231
  title,
@@ -3,26 +3,26 @@
3
3
  import { clamp } from "../../core/util.mjs";
4
4
  import { CheckButton, Slider, StatusPill } from "../../kit/controls.mjs";
5
5
  import { Callout, ControlBar, Field, LabFrame, LiveRegion } from "../../kit/frame.mjs";
6
- import { useCheckpoint } from "../../kit/pedagogy.mjs";
6
+ import { ChallengeCard, useChallenge, useCheckpoint } from "../../kit/pedagogy.mjs";
7
7
  import { useMemo, useRef, useState } from "react";
8
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
9
  import { Axes, Dot, Grid, Label, MovableDot, Polygon, Segment, Stage, useFrameLoop, useInView } from "@classytic/stage";
10
10
 
11
11
  //#region src/ml/regression/preset.tsx
12
12
  /**
13
- * RegressionLab — least squares you can FEEL, and gradient descent you can WATCH.
13
+ * RegressionLab, least squares you can FEEL, and gradient descent you can WATCH.
14
14
  *
15
15
  * Drag the line's two handles: every data point grows a SQUARE whose area is its
16
- * squared error, and the loss (mean squared error) updates live — "least squares"
16
+ * squared error, and the loss (mean squared error) updates live, "least squares"
17
17
  * is literally "make the total square area smallest". Then press Descend and watch
18
18
  * the line crawl downhill on its own, squares shrinking, loss tumbling. The
19
- * learning-rate slider lets you make it overshoot and DIVERGE — the #1 gradient-
19
+ * learning-rate slider lets you make it overshoot and DIVERGE, the #1 gradient-
20
20
  * descent intuition. Reveal snaps to the closed-form optimum to check yourself.
21
21
  *
22
22
  * The litmus test for ML/data labs on the stage engine: scatter + draggable fit +
23
23
  * frame-loop optimisation + live loss, all from the shared primitives. Isometric
24
24
  * view (preserveAspect + equal spans) so a data-unit square reads as a real
25
- * on-screen square — and stays SSR-deterministic.
25
+ * on-screen square, and stays SSR-deterministic.
26
26
  */
27
27
  const DEFAULT_DATA = [
28
28
  {
@@ -62,7 +62,16 @@ const DEFAULT_DATA = [
62
62
  y: 8.3
63
63
  }
64
64
  ];
65
- function RegressionLab({ data = DEFAULT_DATA, showSquares = true, learnRate = .006, m0 = .3, b0 = 3.2, span = 10, title = "Least squares — drag the line, watch the error", prompt = "Each point grows a square of its squared error. Make the total area smallest — then press Descend and watch gradient descent do it for you.", objectives, height = 380 }) {
65
+ const slopeSign = (data) => {
66
+ const n = data.length;
67
+ if (n === 0) return "up";
68
+ const xb = data.reduce((s, p) => s + p.x, 0) / n;
69
+ const yb = data.reduce((s, p) => s + p.y, 0) / n;
70
+ let num = 0;
71
+ for (const p of data) num += (p.x - xb) * (p.y - yb);
72
+ return num >= 0 ? "up" : "down";
73
+ };
74
+ function RegressionLab({ data = DEFAULT_DATA, showSquares = true, learnRate = .006, m0 = .3, b0 = 3.2, span = 10, title = "Least squares: drag the line, watch the error", prompt = "Each point grows a square of its squared error. Make the total area smallest, then press Descend and watch gradient descent do it for you.", objectives, height = 380 }) {
66
75
  const view = {
67
76
  xMin: 0,
68
77
  xMax: span,
@@ -118,6 +127,22 @@ function RegressionLab({ data = DEFAULT_DATA, showSquares = true, learnRate = .0
118
127
  solved: closeEnough,
119
128
  activity: "regression"
120
129
  });
130
+ const predictQ = useMemo(() => {
131
+ return [{
132
+ id: "slope-sign",
133
+ prompt: "Before you fit the line — looking at the cloud of points, will the best-fit line slope UP or DOWN?",
134
+ choices: [{
135
+ value: "up",
136
+ label: "slope up (positive)"
137
+ }, {
138
+ value: "down",
139
+ label: "slope down (negative)"
140
+ }],
141
+ answer: slopeSign(data),
142
+ explain: "Least squares follows the overall trend of the cloud: as x increases the points drift the same way, so the best-fit slope picks up that direction — no dragging needed to call its sign."
143
+ }];
144
+ }, [data]);
145
+ const ch = useChallenge(predictQ);
121
146
  const lineRef = useRef(line);
122
147
  lineRef.current = line;
123
148
  const iterRef = useRef(0);
@@ -385,7 +410,11 @@ function RegressionLab({ data = DEFAULT_DATA, showSquares = true, learnRate = .0
385
410
  children: ["step ", iter]
386
411
  })
387
412
  ] }),
388
- footer: /* @__PURE__ */ jsx(LiveRegion, { children: `Line y = ${line.m.toFixed(2)}x + ${line.b.toFixed(2)}. Mean squared error ${loss.toFixed(2)}. ${closeEnough ? "This is the best fit." : ""}` }),
413
+ footer: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ChallengeCard, {
414
+ questions: predictQ,
415
+ state: ch,
416
+ title: "Predict first"
417
+ }), /* @__PURE__ */ jsx(LiveRegion, { children: `Line y = ${line.m.toFixed(2)}x + ${line.b.toFixed(2)}. Mean squared error ${loss.toFixed(2)}. ${closeEnough ? "This is the best fit." : ""}` })] }),
389
418
  children: figure
390
419
  });
391
420
  }
@@ -11,7 +11,7 @@ import { Circle, Label, Polygon, Segment, Stage, useInView } from "@classytic/st
11
11
 
12
12
  //#region src/physics/atwood/preset.tsx
13
13
  /**
14
- * AtwoodLab — "Which way, and how fast?", two masses over a pulley.
14
+ * AtwoodLab, "Which way, and how fast?", two masses over a pulley.
15
15
  *
16
16
  * The classic Atwood machine: link two masses over a frictionless pulley and the
17
17
  * WHOLE system shares one acceleration. Gravity pulls each side (m·g), but only
@@ -27,7 +27,7 @@ import { Circle, Label, Polygon, Segment, Stage, useInView } from "@classytic/st
27
27
  const G = 9.8;
28
28
  const Y0 = .6;
29
29
  const TRAVEL = 2.6;
30
- function AtwoodLab({ m1 = 3, m2 = 2, title = "Atwood machine — which way, and how fast?", prompt = "Two masses share one rope over a pulley. Only the difference in weight drives them, while the total mass resists: a = (m₁ − m₂)g / (m₁ + m₂). Predict which side falls, then release.", objectives, controlConfig }) {
30
+ function AtwoodLab({ m1 = 3, m2 = 2, title = "Atwood machine: which way, and how fast?", prompt = "Two masses share one rope over a pulley. Only the difference in weight drives them, while the total mass resists: a = (m₁ − m₂)g / (m₁ + m₂). Predict which side falls, then release.", objectives, controlConfig }) {
31
31
  const [ma, setMa] = useState(m1);
32
32
  const [mb, setMb] = useState(m2);
33
33
  const [running, setRunning] = useState(false);
@@ -244,7 +244,7 @@ function AtwoodLab({ m1 = 3, m2 = 2, title = "Atwood machine — which way, and
244
244
  }),
245
245
  /* @__PURE__ */ jsx(StatusPill, {
246
246
  ok: !balanced,
247
- children: balanced ? "balanced — no motion" : `${ma > mb ? "left" : "right"} side falls`
247
+ children: balanced ? "balanced, no motion" : `${ma > mb ? "left" : "right"} side falls`
248
248
  }),
249
249
  /* @__PURE__ */ jsxs("p", {
250
250
  style: {
@@ -257,7 +257,7 @@ function AtwoodLab({ m1 = 3, m2 = 2, title = "Atwood machine — which way, and
257
257
  /* @__PURE__ */ jsx("strong", { children: "difference" }),
258
258
  " (m₁−m₂)g drives it; the ",
259
259
  /* @__PURE__ */ jsx("strong", { children: "total" }),
260
- " (m₁+m₂) resists. Big equal masses with a tiny difference give a slow, measurable a — how Atwood weighed gravity."
260
+ " (m₁+m₂) resists. Big equal masses with a tiny difference give a slow, measurable a, how Atwood weighed gravity."
261
261
  ]
262
262
  }),
263
263
  /* @__PURE__ */ jsx(LiveRegion, { children: balanced ? "Balanced, no motion." : `${ma > mb ? "Left" : "Right"} side falls at ${Math.abs(a).toFixed(2)} metres per second squared.` })
@@ -11,7 +11,7 @@ interface BulletWallsProps {
11
11
  title?: string;
12
12
  prompt?: string;
13
13
  objectives?: string[];
14
- /** Lock/hide any knob — e.g. `{ lock: ['plank toughness'] }` to fix the toughness. */
14
+ /** Lock/hide any knob, e.g. `{ lock: ['plank toughness'] }` to fix the toughness. */
15
15
  controlConfig?: ControlConfig;
16
16
  }
17
17
  declare function BulletWallsLab({
@@ -10,18 +10,18 @@ import { Label, Polygon, Segment, Stage, useInView, useLearner } from "@classyti
10
10
 
11
11
  //#region src/physics/bullet-walls/preset.tsx
12
12
  /**
13
- * BulletWallsLab — "How many planks?", the classic penetration problem made
13
+ * BulletWallsLab, "How many planks?", the classic penetration problem made
14
14
  * watchable and PREDICT-FIRST.
15
15
  *
16
16
  * A bullet fires into a stack of identical planks. Each plank pushes back with a
17
17
  * constant retarding force over its thickness, so it drains a FIXED chunk of
18
- * kinetic energy — equivalently a fixed Δ(v²) per plank. The bullet free-flies
18
+ * kinetic energy, equivalently a fixed Δ(v²) per plank. The bullet free-flies
19
19
  * between planks (constant v) and decelerates linearly in v² while inside one
20
20
  * (v²(x) = v²ₑ − (cost/width)·depth), the textbook v² = u² − 2as. It stops the
21
- * instant its kinetic energy runs out — embedding partway through a plank.
21
+ * instant its kinetic energy runs out, embedding partway through a plank.
22
22
  *
23
23
  * The learner GUESSES how many planks it punches through, then fires and watches
24
- * it slow plank-by-plank while the KE bar drains — turning "N = u²/(2as)" from a
24
+ * it slow plank-by-plank while the KE bar drains, turning "N = u²/(2as)" from a
25
25
  * formula into a bet you can win.
26
26
  *
27
27
  * Tokenized SVG; time-dependent integrator here; honours reduced-motion.
@@ -29,7 +29,7 @@ import { Label, Polygon, Segment, Stage, useInView, useLearner } from "@classyti
29
29
  const PLANK_W = .7;
30
30
  const START_X = -4;
31
31
  const STACK_X0 = 0;
32
- function BulletWallsLab({ speed = 30, toughness = 160, planks = 6, mass = .02, title = "How many planks? — bet, then fire", prompt = "Each plank steals the same chunk of energy. Guess how many the bullet smashes through, then fire and watch the speed (and the energy bar) drain plank by plank.", objectives, controlConfig }) {
32
+ function BulletWallsLab({ speed = 30, toughness = 160, planks = 6, mass = .02, title = "How many planks?: bet, then fire", prompt = "Each plank steals the same chunk of energy. Guess how many the bullet smashes through, then fire and watch the speed (and the energy bar) drain plank by plank.", objectives, controlConfig }) {
33
33
  const [v0, setV0] = useState(speed);
34
34
  const [cost, setCost] = useState(toughness);
35
35
  const [n, setN] = useState(planks);
@@ -253,7 +253,7 @@ function BulletWallsLab({ speed = 30, toughness = 160, planks = 6, mass = .02, t
253
253
  ]
254
254
  })
255
255
  });
256
- const verdict = settled ? guess === fullPenetrated ? `🎯 Spot on — ${fullPenetrated} planks` : `It broke ${fullPenetrated}, you guessed ${guess}` : null;
256
+ const verdict = settled ? guess === fullPenetrated ? `🎯 Spot on, ${fullPenetrated} planks` : `It broke ${fullPenetrated}, you guessed ${guess}` : null;
257
257
  return /* @__PURE__ */ jsx(LabFrame, {
258
258
  title,
259
259
  prompt,
@@ -303,7 +303,7 @@ function BulletWallsLab({ speed = 30, toughness = 160, planks = 6, mass = .02, t
303
303
  children: [
304
304
  "Predict with ",
305
305
  /* @__PURE__ */ jsx("strong", { children: "N = u² / (Δv² per plank)" }),
306
- " — then watch v² fall by the same step each plank (v² = u² − 2as). It lodges when the energy hits zero."
306
+ ", then watch v² fall by the same step each plank (v² = u² − 2as). It lodges when the energy hits zero."
307
307
  ]
308
308
  }),
309
309
  /* @__PURE__ */ jsx(LiveRegion, { children: settled ? `The bullet broke ${fullPenetrated} of ${n} planks. You guessed ${guess}.` : "" })
@@ -11,11 +11,11 @@ import { carnotCycle, gammaDiatomic, gammaMonatomic } from "@classytic/stage/the
11
11
 
12
12
  //#region src/physics/carnot/preset.tsx
13
13
  /**
14
- * CarnotCycleLab — the most efficient possible heat engine, shown two ways at once:
14
+ * CarnotCycleLab, the most efficient possible heat engine, shown two ways at once:
15
15
  * • P–V diagram: the four-leg loop (hot isothermal expansion → adiabatic expansion
16
16
  * → cold isothermal compression → adiabatic compression). The ENCLOSED area is
17
17
  * the net work the engine delivers per cycle.
18
- * • T–S diagram: the very same cycle is a RECTANGLE — heat in along the top (Th),
18
+ * • T–S diagram: the very same cycle is a RECTANGLE, heat in along the top (Th),
19
19
  * heat out along the bottom (Tc), entropy unchanged on the adiabatic sides. Its
20
20
  * area (ΔS·(Th−Tc)) equals the net work too, and it makes the entropy bookkeeping
21
21
  * obvious: ΔS = Qh/Th = Qc/Tc, so the gas returns to the same entropy (net ΔS = 0).
@@ -27,10 +27,10 @@ import { carnotCycle, gammaDiatomic, gammaMonatomic } from "@classytic/stage/the
27
27
  const W = 720, H = 380;
28
28
  const V1 = .02;
29
29
  const J = (x) => Math.round(x).toString();
30
- function CarnotCycleLab({ hotK = 500, coldK = 300, gas = "monatomic", expansionRatio = 2, title = "Carnot cycle — the best a heat engine can do", prompt = "A gas absorbs heat at a hot temperature, does work, and dumps the rest at a cold one. Watch the loop on the P–V diagram and the same cycle as a rectangle on the T–S diagram.", objectives = [
30
+ function CarnotCycleLab({ hotK = 500, coldK = 300, gas = "monatomic", expansionRatio = 2, title = "Carnot cycle: the best a heat engine can do", prompt = "A gas absorbs heat at a hot temperature, does work, and dumps the rest at a cold one. Watch the loop on the P–V diagram and the same cycle as a rectangle on the T–S diagram.", objectives = [
31
31
  "See the Carnot cycle as a P–V loop whose area is the net work",
32
32
  "Read the same cycle as a T–S rectangle (heat in at Th, out at Tc)",
33
- "Derive the efficiency η = 1 − Tc/Th — the ceiling no engine can beat"
33
+ "Derive the efficiency η = 1 − Tc/Th, the ceiling no engine can beat"
34
34
  ] } = {}) {
35
35
  const [Th, setTh] = useState(hotK);
36
36
  const [Tc, setTc] = useState(coldK);
@@ -345,7 +345,7 @@ function CarnotCycleLab({ hotK = 500, coldK = 300, gas = "monatomic", expansionR
345
345
  " K"
346
346
  ]
347
347
  }),
348
- ". The wider the temperature gap, the more efficient — but you can never reach 100% without Tc = 0."
348
+ ". The wider the temperature gap, the more efficient, but you can never reach 100% without Tc = 0."
349
349
  ]
350
350
  }),
351
351
  /* @__PURE__ */ jsxs("span", {
@@ -355,7 +355,7 @@ function CarnotCycleLab({ hotK = 500, coldK = 300, gas = "monatomic", expansionR
355
355
  /* @__PURE__ */ jsx(Tex, { tex: "\\Delta S = Q_h/T_h = Q_c/T_c" }),
356
356
  " = ",
357
357
  dS.toFixed(2),
358
- " J/K, so the gas comes back to the same entropy — ",
358
+ " J/K, so the gas comes back to the same entropy, ",
359
359
  /* @__PURE__ */ jsx("strong", {
360
360
  style: { color: "var(--stage-fg)" },
361
361
  children: "net ΔS = 0"