@almadar/std 16.173.0 → 16.175.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 (220) hide show
  1. package/behaviors/lolo/infra/atoms/std-cross-reference.lolo +2 -4
  2. package/behaviors/lolo/ui/core/atoms/std-board.lolo +5 -9
  3. package/behaviors/lolo/ui/core/atoms/std-browse.lolo +8 -1
  4. package/behaviors/lolo/ui/core/atoms/std-esign-flow.lolo +2 -2
  5. package/behaviors/lolo/ui/core/atoms/std-event-log.lolo +3 -7
  6. package/behaviors/lolo/ui/core/atoms/std-form-advanced.lolo +1 -1
  7. package/behaviors/lolo/ui/core/atoms/std-image-upload-multi.lolo +1 -1
  8. package/behaviors/lolo/ui/core/atoms/std-list.lolo +3 -3
  9. package/behaviors/lolo/ui/core/atoms/std-modal.lolo +2 -2
  10. package/behaviors/lolo/ui/core/atoms/std-multi-party-flow.lolo +2 -4
  11. package/behaviors/lolo/ui/core/atoms/std-rating-review.lolo +5 -13
  12. package/behaviors/lolo/ui/core/atoms/std-recurrence.lolo +3 -7
  13. package/behaviors/lolo/ui/core/atoms/std-saved-search.lolo +1 -1
  14. package/behaviors/lolo/ui/core/atoms/std-signature-capture.lolo +2 -2
  15. package/behaviors/lolo/ui/core/atoms/std-step-flow.lolo +2 -4
  16. package/behaviors/lolo/ui/core/atoms/std-tag-taxonomy.lolo +1 -1
  17. package/behaviors/lolo/ui/core/atoms/std-thread.lolo +2 -2
  18. package/behaviors/lolo/ui/core/atoms/std-vote.lolo +2 -2
  19. package/behaviors/lolo/ui/core/atoms/std-wizard.lolo +3 -7
  20. package/behaviors/lolo/ui/core/atoms/ui-atlas-image.lolo +1 -1
  21. package/behaviors/lolo/ui/core/atoms/ui-atlas-panel.lolo +1 -1
  22. package/behaviors/lolo/ui/core/atoms/ui-center.lolo +1 -1
  23. package/behaviors/lolo/ui/core/atoms/ui-hstack.lolo +1 -1
  24. package/behaviors/lolo/ui/core/atoms/ui-icon.lolo +1 -1
  25. package/behaviors/lolo/ui/core/atoms/ui-stack.lolo +1 -1
  26. package/behaviors/lolo/ui/core/atoms/ui-typography.lolo +1 -1
  27. package/behaviors/lolo/ui/core/atoms/ui-vstack.lolo +1 -1
  28. package/behaviors/lolo/ui/core/molecules/ui-content-renderer.lolo +1 -1
  29. package/behaviors/lolo/ui/core/molecules/ui-form-layout.lolo +1 -1
  30. package/behaviors/lolo/ui/core/molecules/ui-grid.lolo +1 -1
  31. package/behaviors/lolo/ui/core/molecules/ui-table-view.lolo +4 -9
  32. package/behaviors/lolo/ui/core/organisms/ui-chat-bar.lolo +1 -1
  33. package/behaviors/lolo/ui/core/organisms/ui-dashboard-grid.lolo +1 -1
  34. package/behaviors/lolo/ui/core/organisms/ui-detail-panel.lolo +1 -1
  35. package/behaviors/lolo/ui/core/organisms/ui-entity-cards.lolo +1 -1
  36. package/behaviors/lolo/ui/core/organisms/ui-entity-list.lolo +1 -1
  37. package/behaviors/lolo/ui/core/organisms/ui-entity-table.lolo +1 -1
  38. package/behaviors/lolo/ui/core/organisms/ui-form-section.lolo +9 -3
  39. package/behaviors/lolo/ui/core/organisms/ui-form.lolo +9 -3
  40. package/behaviors/lolo/ui/core/organisms/ui-master-detail.lolo +1 -1
  41. package/behaviors/lolo/ui/core/organisms/ui-media-gallery.lolo +1 -1
  42. package/behaviors/lolo/ui/core/organisms/ui-segment-renderer.lolo +7 -1
  43. package/behaviors/lolo/ui/core/organisms/ui-stat-card.lolo +1 -1
  44. package/behaviors/lolo/ui/core/organisms/ui-subagent-trace-panel.lolo +1 -1
  45. package/behaviors/lolo/ui/game/atoms/ui-action-tile.lolo +1 -1
  46. package/behaviors/lolo/ui/game/atoms/ui-draw-shape-layer.lolo +41 -9
  47. package/behaviors/lolo/ui/game/atoms/ui-draw-shape.lolo +40 -8
  48. package/behaviors/lolo/ui/game/atoms/ui-draw-sprite-layer.lolo +13 -3
  49. package/behaviors/lolo/ui/game/atoms/ui-draw-sprite.lolo +12 -2
  50. package/behaviors/lolo/ui/game/atoms/ui-draw-text-layer.lolo +3 -3
  51. package/behaviors/lolo/ui/game/atoms/ui-draw-text.lolo +2 -2
  52. package/behaviors/lolo/ui/game/organisms/std-tactics-board-3d.lolo +32 -2
  53. package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +133 -23
  54. package/behaviors/lolo/ui/learning/atoms/std-learn-compare.lolo +397 -0
  55. package/behaviors/lolo/ui/learning/atoms/std-learn-coupled-sim.lolo +537 -0
  56. package/behaviors/lolo/ui/learning/atoms/std-learn-derivation.lolo +428 -0
  57. package/behaviors/lolo/ui/learning/atoms/std-learn-doser.lolo +520 -0
  58. package/behaviors/lolo/ui/learning/atoms/std-learn-estimate.lolo +419 -0
  59. package/behaviors/lolo/ui/learning/atoms/std-learn-goal-seek.lolo +591 -0
  60. package/behaviors/lolo/ui/learning/atoms/std-learn-grid-sim.lolo +425 -0
  61. package/behaviors/lolo/ui/learning/atoms/std-learn-hotspot.lolo +398 -0
  62. package/behaviors/lolo/ui/learning/atoms/std-learn-measure.lolo +325 -0
  63. package/behaviors/lolo/ui/learning/atoms/std-learn-perturb.lolo +494 -0
  64. package/behaviors/lolo/ui/learning/atoms/std-learn-predict.lolo +408 -0
  65. package/behaviors/lolo/ui/learning/atoms/std-learn-scatter-fit.lolo +805 -0
  66. package/behaviors/lolo/ui/learning/atoms/std-learn-scenario.lolo +307 -0
  67. package/behaviors/lolo/ui/learning/atoms/std-learn-sim-3d.lolo +318 -0
  68. package/behaviors/lolo/ui/learning/atoms/std-learn-speed-drill.lolo +336 -0
  69. package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +409 -0
  70. package/behaviors/lolo/ui/learning/atoms/std-learn-transport.lolo +186 -0
  71. package/behaviors/lolo/ui/learning/atoms/std-learn-tree.lolo +522 -0
  72. package/behaviors/lolo/ui/learning/atoms/ui-algo-graph-canvas.lolo +192 -0
  73. package/behaviors/lolo/ui/learning/atoms/ui-algorithm-canvas.lolo +111 -2
  74. package/behaviors/lolo/ui/learning/atoms/ui-biology-canvas.lolo +193 -5
  75. package/behaviors/lolo/ui/learning/atoms/ui-chemistry-canvas.lolo +173 -5
  76. package/behaviors/lolo/ui/learning/atoms/ui-learning-canvas.lolo +48 -1
  77. package/behaviors/lolo/ui/learning/atoms/ui-math-canvas.lolo +141 -1
  78. package/behaviors/lolo/ui/learning/atoms/ui-physics-canvas.lolo +242 -5
  79. package/behaviors/lolo/ui/marketing/organisms/ui-book-viewer.lolo +1 -1
  80. package/behaviors/lolo/ui/marketing/organisms/ui-case-study-organism.lolo +1 -1
  81. package/behaviors/lolo/ui/marketing/organisms/ui-feature-grid-organism.lolo +1 -1
  82. package/behaviors/lolo/ui/marketing/organisms/ui-hero-organism.lolo +1 -1
  83. package/behaviors/lolo/ui/marketing/organisms/ui-pricing-organism.lolo +1 -1
  84. package/behaviors/lolo/ui/marketing/organisms/ui-showcase-organism.lolo +1 -1
  85. package/behaviors/lolo/ui/marketing/organisms/ui-stats-organism.lolo +1 -1
  86. package/behaviors/lolo/ui/marketing/organisms/ui-step-flow-organism.lolo +1 -1
  87. package/behaviors/lolo/ui/marketing/organisms/ui-team-organism.lolo +1 -1
  88. package/behaviors/registry/agent/atoms/std-agent-session.orb +22 -1
  89. package/behaviors/registry/agent/atoms/std-agent-tool-call.orb +38 -2
  90. package/behaviors/registry/agent/atoms/std-agent-tool-loop.orb +38 -2
  91. package/behaviors/registry/agent/organisms/std-agent-rabit.orb +23 -1
  92. package/behaviors/registry/infra/atoms/std-approval-gate.orb +8 -16
  93. package/behaviors/registry/infra/atoms/std-cache-aside.orb +31 -62
  94. package/behaviors/registry/infra/atoms/std-circuit-breaker.orb +16 -32
  95. package/behaviors/registry/infra/atoms/std-cross-reference.orb +32 -4
  96. package/behaviors/registry/infra/atoms/std-data-erasure.orb +8 -16
  97. package/behaviors/registry/infra/atoms/std-mod-queue.orb +8 -16
  98. package/behaviors/registry/infra/atoms/std-rate-limiter.orb +14 -28
  99. package/behaviors/registry/infra/atoms/std-row-access-control.orb +30 -2
  100. package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +1 -2
  101. package/behaviors/registry/ui/core/atoms/std-board.orb +19 -5
  102. package/behaviors/registry/ui/core/atoms/std-browse.orb +29 -7
  103. package/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +1 -2
  104. package/behaviors/registry/ui/core/atoms/std-esign-flow.orb +3 -5
  105. package/behaviors/registry/ui/core/atoms/std-esign-request.orb +3 -6
  106. package/behaviors/registry/ui/core/atoms/std-event-log.orb +17 -3
  107. package/behaviors/registry/ui/core/atoms/std-export.orb +2 -4
  108. package/behaviors/registry/ui/core/atoms/std-form-advanced.orb +1 -1
  109. package/behaviors/registry/ui/core/atoms/std-gallery.orb +9 -18
  110. package/behaviors/registry/ui/core/atoms/std-graphs.orb +15 -1
  111. package/behaviors/registry/ui/core/atoms/std-import.orb +2 -4
  112. package/behaviors/registry/ui/core/atoms/std-list.orb +4 -6
  113. package/behaviors/registry/ui/core/atoms/std-modal.orb +2 -2
  114. package/behaviors/registry/ui/core/atoms/std-multi-party-flow.orb +16 -2
  115. package/behaviors/registry/ui/core/atoms/std-pagination.orb +15 -1
  116. package/behaviors/registry/ui/core/atoms/std-rating-review.orb +19 -5
  117. package/behaviors/registry/ui/core/atoms/std-recurrence.orb +17 -3
  118. package/behaviors/registry/ui/core/atoms/std-rich-editor.orb +15 -1
  119. package/behaviors/registry/ui/core/atoms/std-scatter.orb +15 -1
  120. package/behaviors/registry/ui/core/atoms/std-signature-capture.orb +2 -2
  121. package/behaviors/registry/ui/core/atoms/std-stats.orb +15 -1
  122. package/behaviors/registry/ui/core/atoms/std-step-flow.orb +16 -2
  123. package/behaviors/registry/ui/core/atoms/std-thread.orb +2 -2
  124. package/behaviors/registry/ui/core/atoms/std-vote.orb +2 -2
  125. package/behaviors/registry/ui/core/atoms/std-wizard.orb +17 -3
  126. package/behaviors/registry/ui/core/atoms/ui-atlas-image.orb +4 -1
  127. package/behaviors/registry/ui/core/atoms/ui-atlas-panel.orb +4 -1
  128. package/behaviors/registry/ui/core/atoms/ui-center.orb +4 -1
  129. package/behaviors/registry/ui/core/atoms/ui-hstack.orb +4 -1
  130. package/behaviors/registry/ui/core/atoms/ui-icon.orb +4 -1
  131. package/behaviors/registry/ui/core/atoms/ui-stack.orb +4 -1
  132. package/behaviors/registry/ui/core/atoms/ui-typography.orb +4 -1
  133. package/behaviors/registry/ui/core/atoms/ui-vstack.orb +4 -1
  134. package/behaviors/registry/ui/core/molecules/ui-accordion.orb +3 -3
  135. package/behaviors/registry/ui/core/molecules/ui-content-renderer.orb +4 -1
  136. package/behaviors/registry/ui/core/molecules/ui-form-layout.orb +4 -1
  137. package/behaviors/registry/ui/core/molecules/ui-grid.orb +4 -1
  138. package/behaviors/registry/ui/core/molecules/ui-table-view.orb +4 -39
  139. package/behaviors/registry/ui/core/molecules/ui-tabs.orb +2 -2
  140. package/behaviors/registry/ui/core/molecules/ui-wizard-container.orb +1 -1
  141. package/behaviors/registry/ui/core/organisms/ui-chat-bar.orb +4 -1
  142. package/behaviors/registry/ui/core/organisms/ui-dashboard-grid.orb +5 -2
  143. package/behaviors/registry/ui/core/organisms/ui-detail-panel.orb +5 -2
  144. package/behaviors/registry/ui/core/organisms/ui-entity-cards.orb +4 -1
  145. package/behaviors/registry/ui/core/organisms/ui-entity-list.orb +4 -1
  146. package/behaviors/registry/ui/core/organisms/ui-entity-table.orb +4 -1
  147. package/behaviors/registry/ui/core/organisms/ui-form-section.orb +34 -2
  148. package/behaviors/registry/ui/core/organisms/ui-form.orb +34 -2
  149. package/behaviors/registry/ui/core/organisms/ui-master-detail.orb +4 -1
  150. package/behaviors/registry/ui/core/organisms/ui-media-gallery.orb +4 -1
  151. package/behaviors/registry/ui/core/organisms/ui-segment-renderer.orb +24 -2
  152. package/behaviors/registry/ui/core/organisms/ui-stat-card.orb +4 -1
  153. package/behaviors/registry/ui/core/organisms/ui-subagent-trace-panel.orb +4 -1
  154. package/behaviors/registry/ui/core/organisms/ui-tabbed-container.orb +1 -1
  155. package/behaviors/registry/ui/core/organisms/ui-ui-slot-renderer.orb +1 -1
  156. package/behaviors/registry/ui/game/atoms/std-fx-particles.orb +35 -1
  157. package/behaviors/registry/ui/game/atoms/ui-action-tile.orb +4 -1
  158. package/behaviors/registry/ui/game/atoms/ui-draw-shape-layer.orb +183 -13
  159. package/behaviors/registry/ui/game/atoms/ui-draw-shape.orb +104 -9
  160. package/behaviors/registry/ui/game/atoms/ui-draw-sprite-layer.orb +56 -2
  161. package/behaviors/registry/ui/game/atoms/ui-draw-sprite.orb +36 -4
  162. package/behaviors/registry/ui/game/atoms/ui-draw-text-layer.orb +21 -2
  163. package/behaviors/registry/ui/game/atoms/ui-draw-text.orb +19 -4
  164. package/behaviors/registry/ui/game/organisms/std-tactics-board-3d.orb +173 -2
  165. package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +1288 -197
  166. package/behaviors/registry/ui/learning/atoms/std-learn-compare.orb +2814 -0
  167. package/behaviors/registry/ui/learning/atoms/std-learn-coupled-sim.orb +3183 -0
  168. package/behaviors/registry/ui/learning/atoms/std-learn-derivation.orb +2512 -0
  169. package/behaviors/registry/ui/learning/atoms/std-learn-doser.orb +3678 -0
  170. package/behaviors/registry/ui/learning/atoms/std-learn-estimate.orb +2394 -0
  171. package/behaviors/registry/ui/learning/atoms/std-learn-goal-seek.orb +3951 -0
  172. package/behaviors/registry/ui/learning/atoms/std-learn-grid-sim.orb +3458 -0
  173. package/behaviors/registry/ui/learning/atoms/std-learn-hotspot.orb +2646 -0
  174. package/behaviors/registry/ui/learning/atoms/std-learn-measure.orb +1979 -0
  175. package/behaviors/registry/ui/learning/atoms/std-learn-perturb.orb +2994 -0
  176. package/behaviors/registry/ui/learning/atoms/std-learn-predict.orb +2682 -0
  177. package/behaviors/registry/ui/learning/atoms/std-learn-scatter-fit.orb +6539 -0
  178. package/behaviors/registry/ui/learning/atoms/std-learn-scenario.orb +1842 -0
  179. package/behaviors/registry/ui/learning/atoms/std-learn-sim-3d.orb +1648 -0
  180. package/behaviors/registry/ui/learning/atoms/std-learn-speed-drill.orb +2307 -0
  181. package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +3118 -0
  182. package/behaviors/registry/ui/learning/atoms/std-learn-transport.orb +986 -0
  183. package/behaviors/registry/ui/learning/atoms/std-learn-tree.orb +3333 -0
  184. package/behaviors/registry/ui/learning/atoms/ui-algo-graph-canvas.orb +716 -0
  185. package/behaviors/registry/ui/learning/atoms/ui-algorithm-canvas.orb +679 -5
  186. package/behaviors/registry/ui/learning/atoms/ui-biology-canvas.orb +962 -18
  187. package/behaviors/registry/ui/learning/atoms/ui-chemistry-canvas.orb +919 -30
  188. package/behaviors/registry/ui/learning/atoms/ui-learning-canvas.orb +372 -1
  189. package/behaviors/registry/ui/learning/atoms/ui-math-canvas.orb +936 -16
  190. package/behaviors/registry/ui/learning/atoms/ui-physics-canvas.orb +1328 -43
  191. package/behaviors/registry/ui/marketing/atoms/std-marketing-article.orb +1 -2
  192. package/behaviors/registry/ui/marketing/atoms/std-marketing-footer.orb +1 -2
  193. package/behaviors/registry/ui/marketing/atoms/std-marketing-split.orb +1 -2
  194. package/behaviors/registry/ui/marketing/atoms/std-marketing-stats.orb +1 -2
  195. package/behaviors/registry/ui/marketing/atoms/std-marketing-steps.orb +1 -2
  196. package/behaviors/registry/ui/marketing/atoms/std-marketing-team.orb +1 -2
  197. package/behaviors/registry/ui/marketing/organisms/ui-book-viewer.orb +4 -1
  198. package/behaviors/registry/ui/marketing/organisms/ui-case-study-organism.orb +4 -1
  199. package/behaviors/registry/ui/marketing/organisms/ui-feature-grid-organism.orb +4 -1
  200. package/behaviors/registry/ui/marketing/organisms/ui-hero-organism.orb +4 -1
  201. package/behaviors/registry/ui/marketing/organisms/ui-pricing-organism.orb +4 -1
  202. package/behaviors/registry/ui/marketing/organisms/ui-showcase-organism.orb +4 -1
  203. package/behaviors/registry/ui/marketing/organisms/ui-stats-organism.orb +4 -1
  204. package/behaviors/registry/ui/marketing/organisms/ui-step-flow-organism.orb +4 -1
  205. package/behaviors/registry/ui/marketing/organisms/ui-team-organism.orb +4 -1
  206. package/dist/behaviors/exports-reader.js +3704 -2189
  207. package/dist/behaviors/functions/index.d.ts +2638 -254
  208. package/dist/behaviors/functions/index.js +3648 -2190
  209. package/dist/behaviors/index.d.ts +1 -1
  210. package/dist/behaviors/index.js +3705 -2190
  211. package/dist/behaviors/query.js +3704 -2189
  212. package/dist/behaviors-embeddings.hash.json +3 -3
  213. package/dist/behaviors-embeddings.json +289 -213
  214. package/dist/behaviors-registry.json +7900 -587
  215. package/dist/index.d.ts +1 -1
  216. package/dist/index.js +3705 -2190
  217. package/dist/knob-embeddings.hash.json +3 -3
  218. package/dist/knob-embeddings.json +1 -1
  219. package/dist/registry/factory-signatures.json +1 -1
  220. package/package.json +2 -2
@@ -0,0 +1,805 @@
1
+ app std-learn-scatter-fit "1.0.0"
2
+ "Generic scatter-plot fitting instrument — points on a math-canvas (config-declared axis ranges/labels/colors) with a live least-squares refit: slope, intercept, r, and R² recompute as points are added (button or auto-add tick, up to maxPoints) or dragged. An optional guess-then-reveal mode lets the learner drag slope/intercept sliders to propose a line, scored against the fit until Reveal Fit; dashed residual segments and the r/R² readout are config-toggleable. Run/Pause drives a fixed-interval auto-add tick, Reset reseeds from the configured point roster. SCATTER_FIT_UPDATED broadcasts the fit after every learner action, tick, or reset. Every button label, caption, curve label/color, and narration phrase is config-declared so the same mechanism serves any scatter-fit domain (correlation, physics data fitting, trend lines, and beyond) on a math-canvas. Standalone by default; behind a lab router set matchMode and it renders only when MODE_SELECTED matches."
3
+ orbital LearnScatterFitOrbital {
4
+ type LearnScatterFitPoint = {
5
+ id : string
6
+ x : number!
7
+ y : number!
8
+ label : string
9
+ color : string
10
+ radius : number
11
+ }
12
+ type LearnScatterFitCurve = {
13
+ label : string
14
+ color : string
15
+ dash : "dashed" | "dotted"
16
+ samples : [LearnScatterFitCurvePoint]
17
+ }
18
+ type LearnScatterFitCurvePoint = {
19
+ x : number!
20
+ y : number!
21
+ }
22
+
23
+ entity LearnScatterFitScene [runtime] {
24
+ id : string!
25
+ active : boolean = false
26
+ title : string
27
+ points : [LearnScatterFitPoint]
28
+ slope : number
29
+ intercept : number
30
+ r : number
31
+ rSquared : number
32
+ guessSlope : number
33
+ guessIntercept : number
34
+ guessScore : number
35
+ revealed : boolean = false
36
+ running : boolean = false
37
+ done : boolean = false
38
+ curves : [LearnScatterFitCurve]
39
+ narration : string
40
+ }
41
+
42
+ trait LearnScatterFitSim -> @rebindable LearnScatterFitScene [interaction, instance] {
43
+ initial: idle
44
+
45
+ state idle {
46
+ INIT -> idle when (== @config.matchMode "")
47
+ (set @entity.active true)
48
+ (set @entity.title @config.title)
49
+ (set @entity.points @config.initialPoints)
50
+ (set @entity.guessSlope 0)
51
+ (set @entity.guessIntercept (/ (+ @config.axisYMin @config.axisYMax) 2))
52
+ (set @entity.revealed false)
53
+ (set @entity.running @config.autoRun)
54
+ (let (
55
+ (pts @config.initialPoints)
56
+ (n (array/len @pts))
57
+ (meanX (if (> @n 0) (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p x)))) @n) 0))
58
+ (meanY (if (> @n 0) (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p y)))) @n) 0))
59
+ (sxx (array/reduce @pts 0 (fn (acc p) (let ((dx (- (object/get @p x) @meanX))) (+ @acc (* @dx @dx))))))
60
+ (syy (array/reduce @pts 0 (fn (acc p) (let ((dy (- (object/get @p y) @meanY))) (+ @acc (* @dy @dy))))))
61
+ (sxy (array/reduce @pts 0 (fn (acc p) (+ @acc (* (- (object/get @p x) @meanX) (- (object/get @p y) @meanY))))))
62
+ (newSlope (if (> @sxx 0) (/ @sxy @sxx) 0))
63
+ (newIntercept (- @meanY (* @newSlope @meanX)))
64
+ (newR (if (and (> @sxx 0) (> @syy 0)) (/ @sxy (math/sqrt (* @sxx @syy))) 0))
65
+ (fitCurve { label: @config.fitLabel, color: @config.fitColor, samples: [
66
+ { x: @config.axisXMin, y: (math/clamp @newIntercept @config.axisYMin @config.axisYMax) },
67
+ { x: @config.axisXMax, y: (math/clamp (+ (* @newSlope @config.axisXMax) @newIntercept) @config.axisYMin @config.axisYMax) }
68
+ ]})
69
+ (residualCurves (if @config.showResiduals
70
+ (array/map @pts (fn p { label: "", color: @config.residualColor, dash: @config.residualDash, samples: [ { x: (object/get @p x), y: (object/get @p y) }, { x: (object/get @p x), y: (+ (* @newSlope (object/get @p x)) @newIntercept) } ] }))
71
+ []))
72
+ )
73
+ (do
74
+ (set @entity.slope @newSlope)
75
+ (set @entity.intercept @newIntercept)
76
+ (set @entity.r @newR)
77
+ (set @entity.rSquared (* @newR @newR))
78
+ (set @entity.guessScore 0)
79
+ (set @entity.curves (array/concat (array/append [] @fitCurve) @residualCurves))
80
+ (set @entity.done (>= @n @config.maxPoints))
81
+ (set @entity.narration (str/concat (str/concat "" @n) " " @config.narrationLoadedTemplate " — r = " (str/concat "" (math/round @newR 3)) ", R² = " (str/concat "" (math/round (* @newR @newR) 3))))
82
+ )
83
+ )
84
+ (render-ui main {
85
+ type: box
86
+ padding: md
87
+ children: [
88
+ { type: stack, direction: vertical, gap: md, children: [
89
+ { type: typography, variant: h4, content: @entity.title },
90
+ { type: stack, direction: horizontal, gap: sm, children: [
91
+ { type: button, action: ADD_POINT, label: @config.addPointLabel, variant: primary },
92
+ { type: button, action: REVEAL_FIT, label: @config.revealLabel, variant: secondary },
93
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running @config.pauseLabel @config.runLabel), variant: secondary },
94
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
95
+ ] },
96
+ { type: typography, variant: caption, content: (str/concat @config.guessSlopeCaption (math/round @entity.guessSlope 2)) },
97
+ { type: range-slider, min: @config.guessSlopeMin, max: @config.guessSlopeMax, step: @config.guessSlopeStep, value: @entity.guessSlope, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "slope" } },
98
+ { type: typography, variant: caption, content: (str/concat @config.guessInterceptCaption (math/round @entity.guessIntercept 1)) },
99
+ { type: range-slider, min: @config.guessInterceptMin, max: @config.guessInterceptMax, step: @config.guessInterceptStep, value: @entity.guessIntercept, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "intercept" } },
100
+ { type: math-canvas, width: @config.width, height: @config.height, xMin: @config.axisXMin, xMax: @config.axisXMax, yMin: @config.axisYMin, yMax: @config.axisYMax, showAxes: @config.showAxes, showGrid: @config.showGrid, gridStep: @config.gridStep, showTickLabels: @config.showTickLabels, curves: @entity.curves, points: @entity.points, vectors: [], interactive: @config.interactive, animate: @config.animate },
101
+ { type: typography, variant: body, content: (if @config.showR (str/concat "r = " (math/round @entity.r 3) " — R² = " (math/round @entity.rSquared 3) " — guess score " @entity.guessScore "%") "") },
102
+ { type: typography, variant: body, content: @entity.narration }
103
+ ] }
104
+ ]
105
+ })
106
+ (emit LEARN_SCATTER_FIT_UPDATED { slope: @entity.slope, intercept: @entity.intercept, r: @entity.r, rSquared: @entity.rSquared, pointCount: (array/len @entity.points), narration: @entity.narration })
107
+
108
+ MODE_SELECTED -> idle when (== ?mode @config.matchMode)
109
+ (set @entity.active true)
110
+ (set @entity.title @config.title)
111
+ (set @entity.points @config.initialPoints)
112
+ (set @entity.guessSlope 0)
113
+ (set @entity.guessIntercept (/ (+ @config.axisYMin @config.axisYMax) 2))
114
+ (set @entity.revealed false)
115
+ (set @entity.running @config.autoRun)
116
+ (let (
117
+ (pts @config.initialPoints)
118
+ (n (array/len @pts))
119
+ (meanX (if (> @n 0) (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p x)))) @n) 0))
120
+ (meanY (if (> @n 0) (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p y)))) @n) 0))
121
+ (sxx (array/reduce @pts 0 (fn (acc p) (let ((dx (- (object/get @p x) @meanX))) (+ @acc (* @dx @dx))))))
122
+ (syy (array/reduce @pts 0 (fn (acc p) (let ((dy (- (object/get @p y) @meanY))) (+ @acc (* @dy @dy))))))
123
+ (sxy (array/reduce @pts 0 (fn (acc p) (+ @acc (* (- (object/get @p x) @meanX) (- (object/get @p y) @meanY))))))
124
+ (newSlope (if (> @sxx 0) (/ @sxy @sxx) 0))
125
+ (newIntercept (- @meanY (* @newSlope @meanX)))
126
+ (newR (if (and (> @sxx 0) (> @syy 0)) (/ @sxy (math/sqrt (* @sxx @syy))) 0))
127
+ (fitCurve { label: @config.fitLabel, color: @config.fitColor, samples: [
128
+ { x: @config.axisXMin, y: (math/clamp @newIntercept @config.axisYMin @config.axisYMax) },
129
+ { x: @config.axisXMax, y: (math/clamp (+ (* @newSlope @config.axisXMax) @newIntercept) @config.axisYMin @config.axisYMax) }
130
+ ]})
131
+ (residualCurves (if @config.showResiduals
132
+ (array/map @pts (fn p { label: "", color: @config.residualColor, dash: @config.residualDash, samples: [ { x: (object/get @p x), y: (object/get @p y) }, { x: (object/get @p x), y: (+ (* @newSlope (object/get @p x)) @newIntercept) } ] }))
133
+ []))
134
+ )
135
+ (do
136
+ (set @entity.slope @newSlope)
137
+ (set @entity.intercept @newIntercept)
138
+ (set @entity.r @newR)
139
+ (set @entity.rSquared (* @newR @newR))
140
+ (set @entity.guessScore 0)
141
+ (set @entity.curves (array/concat (array/append [] @fitCurve) @residualCurves))
142
+ (set @entity.done (>= @n @config.maxPoints))
143
+ (set @entity.narration (str/concat (str/concat "" @n) " " @config.narrationLoadedTemplate " — r = " (str/concat "" (math/round @newR 3)) ", R² = " (str/concat "" (math/round (* @newR @newR) 3))))
144
+ )
145
+ )
146
+ (render-ui main {
147
+ type: box
148
+ padding: md
149
+ children: [
150
+ { type: stack, direction: vertical, gap: md, children: [
151
+ { type: typography, variant: h4, content: @entity.title },
152
+ { type: stack, direction: horizontal, gap: sm, children: [
153
+ { type: button, action: ADD_POINT, label: @config.addPointLabel, variant: primary },
154
+ { type: button, action: REVEAL_FIT, label: @config.revealLabel, variant: secondary },
155
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running @config.pauseLabel @config.runLabel), variant: secondary },
156
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
157
+ ] },
158
+ { type: typography, variant: caption, content: (str/concat @config.guessSlopeCaption (math/round @entity.guessSlope 2)) },
159
+ { type: range-slider, min: @config.guessSlopeMin, max: @config.guessSlopeMax, step: @config.guessSlopeStep, value: @entity.guessSlope, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "slope" } },
160
+ { type: typography, variant: caption, content: (str/concat @config.guessInterceptCaption (math/round @entity.guessIntercept 1)) },
161
+ { type: range-slider, min: @config.guessInterceptMin, max: @config.guessInterceptMax, step: @config.guessInterceptStep, value: @entity.guessIntercept, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "intercept" } },
162
+ { type: math-canvas, width: @config.width, height: @config.height, xMin: @config.axisXMin, xMax: @config.axisXMax, yMin: @config.axisYMin, yMax: @config.axisYMax, showAxes: @config.showAxes, showGrid: @config.showGrid, gridStep: @config.gridStep, showTickLabels: @config.showTickLabels, curves: @entity.curves, points: @entity.points, vectors: [], interactive: @config.interactive, animate: @config.animate },
163
+ { type: typography, variant: body, content: (if @config.showR (str/concat "r = " (math/round @entity.r 3) " — R² = " (math/round @entity.rSquared 3) " — guess score " @entity.guessScore "%") "") },
164
+ { type: typography, variant: body, content: @entity.narration }
165
+ ] }
166
+ ]
167
+ })
168
+ (emit LEARN_SCATTER_FIT_UPDATED { slope: @entity.slope, intercept: @entity.intercept, r: @entity.r, rSquared: @entity.rSquared, pointCount: (array/len @entity.points), narration: @entity.narration })
169
+
170
+ ADD_POINT -> idle when (and @entity.active (not @entity.done))
171
+ (let (
172
+ (marginX (* (- @config.axisXMax @config.axisXMin) @config.pointMarginPct))
173
+ (usableX (- (- @config.axisXMax @config.axisXMin) (* @marginX 2)))
174
+ (marginY (* (- @config.axisYMax @config.axisYMin) @config.pointMarginPct))
175
+ (usableY (- (- @config.axisYMax @config.axisYMin) (* @marginY 2)))
176
+ (newPoint { id: (str/concat "p" (array/len @entity.points)), x: (math/round (+ @config.axisXMin (+ @marginX (* (math/random) @usableX)))), y: (math/round (+ @config.axisYMin (+ @marginY (* (math/random) @usableY)))), label: "", color: @config.newPointColor, radius: @config.newPointRadius })
177
+ (pts (array/append @entity.points @newPoint))
178
+ (n (array/len @pts))
179
+ (meanX (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p x)))) @n))
180
+ (meanY (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p y)))) @n))
181
+ (sxx (array/reduce @pts 0 (fn (acc p) (let ((dx (- (object/get @p x) @meanX))) (+ @acc (* @dx @dx))))))
182
+ (syy (array/reduce @pts 0 (fn (acc p) (let ((dy (- (object/get @p y) @meanY))) (+ @acc (* @dy @dy))))))
183
+ (sxy (array/reduce @pts 0 (fn (acc p) (+ @acc (* (- (object/get @p x) @meanX) (- (object/get @p y) @meanY))))))
184
+ (newSlope (if (> @sxx 0) (/ @sxy @sxx) 0))
185
+ (newIntercept (- @meanY (* @newSlope @meanX)))
186
+ (newR (if (and (> @sxx 0) (> @syy 0)) (/ @sxy (math/sqrt (* @sxx @syy))) 0))
187
+ (fitCurve { label: @config.fitLabel, color: @config.fitColor, samples: [
188
+ { x: @config.axisXMin, y: (math/clamp @newIntercept @config.axisYMin @config.axisYMax) },
189
+ { x: @config.axisXMax, y: (math/clamp (+ (* @newSlope @config.axisXMax) @newIntercept) @config.axisYMin @config.axisYMax) }
190
+ ]})
191
+ (guessCurve { label: @config.guessLabel, color: @config.guessColor, samples: [
192
+ { x: @config.axisXMin, y: (math/clamp @entity.guessIntercept @config.axisYMin @config.axisYMax) },
193
+ { x: @config.axisXMax, y: (math/clamp (+ (* @entity.guessSlope @config.axisXMax) @entity.guessIntercept) @config.axisYMin @config.axisYMax) }
194
+ ]})
195
+ (residualCurves (if @config.showResiduals
196
+ (array/map @pts (fn p { label: "", color: @config.residualColor, dash: @config.residualDash, samples: [ { x: (object/get @p x), y: (object/get @p y) }, { x: (object/get @p x), y: (+ (* @newSlope (object/get @p x)) @newIntercept) } ] }))
197
+ []))
198
+ (baseCurves (if (and @config.guessMode (not @entity.revealed))
199
+ (array/append [] @guessCurve)
200
+ (if @config.guessMode
201
+ (array/append (array/append [] @guessCurve) @fitCurve)
202
+ (array/append [] @fitCurve))))
203
+ (nowDone (>= @n @config.maxPoints))
204
+ )
205
+ (do
206
+ (set @entity.points @pts)
207
+ (set @entity.slope @newSlope)
208
+ (set @entity.intercept @newIntercept)
209
+ (set @entity.r @newR)
210
+ (set @entity.rSquared (* @newR @newR))
211
+ (set @entity.curves (array/concat @baseCurves @residualCurves))
212
+ (set @entity.done @nowDone)
213
+ (set @entity.narration (str/concat @config.narrationAddedTemplate " " (str/concat "" @n) " — r = " (str/concat "" (math/round @newR 3)) ", R² = " (str/concat "" (math/round (* @newR @newR) 3)) (if @nowDone @config.narrationMaxReachedSuffix "")))
214
+ )
215
+ )
216
+ (render-ui main {
217
+ type: box
218
+ padding: md
219
+ children: [
220
+ { type: stack, direction: vertical, gap: md, children: [
221
+ { type: typography, variant: h4, content: @entity.title },
222
+ { type: stack, direction: horizontal, gap: sm, children: [
223
+ { type: button, action: ADD_POINT, label: @config.addPointLabel, variant: primary },
224
+ { type: button, action: REVEAL_FIT, label: @config.revealLabel, variant: secondary },
225
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running @config.pauseLabel @config.runLabel), variant: secondary },
226
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
227
+ ] },
228
+ { type: typography, variant: caption, content: (str/concat @config.guessSlopeCaption (math/round @entity.guessSlope 2)) },
229
+ { type: range-slider, min: @config.guessSlopeMin, max: @config.guessSlopeMax, step: @config.guessSlopeStep, value: @entity.guessSlope, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "slope" } },
230
+ { type: typography, variant: caption, content: (str/concat @config.guessInterceptCaption (math/round @entity.guessIntercept 1)) },
231
+ { type: range-slider, min: @config.guessInterceptMin, max: @config.guessInterceptMax, step: @config.guessInterceptStep, value: @entity.guessIntercept, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "intercept" } },
232
+ { type: math-canvas, width: @config.width, height: @config.height, xMin: @config.axisXMin, xMax: @config.axisXMax, yMin: @config.axisYMin, yMax: @config.axisYMax, showAxes: @config.showAxes, showGrid: @config.showGrid, gridStep: @config.gridStep, showTickLabels: @config.showTickLabels, curves: @entity.curves, points: @entity.points, vectors: [], interactive: @config.interactive, animate: @config.animate },
233
+ { type: typography, variant: body, content: (if @config.showR (str/concat "r = " (math/round @entity.r 3) " — R² = " (math/round @entity.rSquared 3) " — guess score " @entity.guessScore "%") "") },
234
+ { type: typography, variant: body, content: @entity.narration }
235
+ ] }
236
+ ]
237
+ })
238
+ (emit LEARN_SCATTER_FIT_UPDATED { slope: @entity.slope, intercept: @entity.intercept, r: @entity.r, rSquared: @entity.rSquared, pointCount: (array/len @entity.points), narration: @entity.narration })
239
+
240
+ DRAG_POINT -> idle when (== @entity.active true)
241
+ (let (
242
+ (pts (array/map @entity.points (fn p
243
+ (if (== (object/get @p id) ?id)
244
+ (object/merge @p { x: (math/clamp ?x @config.axisXMin @config.axisXMax), y: (math/clamp ?y @config.axisYMin @config.axisYMax) })
245
+ @p))))
246
+ (n (array/len @pts))
247
+ (meanX (if (> @n 0) (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p x)))) @n) 0))
248
+ (meanY (if (> @n 0) (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p y)))) @n) 0))
249
+ (sxx (array/reduce @pts 0 (fn (acc p) (let ((dx (- (object/get @p x) @meanX))) (+ @acc (* @dx @dx))))))
250
+ (syy (array/reduce @pts 0 (fn (acc p) (let ((dy (- (object/get @p y) @meanY))) (+ @acc (* @dy @dy))))))
251
+ (sxy (array/reduce @pts 0 (fn (acc p) (+ @acc (* (- (object/get @p x) @meanX) (- (object/get @p y) @meanY))))))
252
+ (newSlope (if (> @sxx 0) (/ @sxy @sxx) 0))
253
+ (newIntercept (- @meanY (* @newSlope @meanX)))
254
+ (newR (if (and (> @sxx 0) (> @syy 0)) (/ @sxy (math/sqrt (* @sxx @syy))) 0))
255
+ (fitCurve { label: @config.fitLabel, color: @config.fitColor, samples: [
256
+ { x: @config.axisXMin, y: (math/clamp @newIntercept @config.axisYMin @config.axisYMax) },
257
+ { x: @config.axisXMax, y: (math/clamp (+ (* @newSlope @config.axisXMax) @newIntercept) @config.axisYMin @config.axisYMax) }
258
+ ]})
259
+ (guessCurve { label: @config.guessLabel, color: @config.guessColor, samples: [
260
+ { x: @config.axisXMin, y: (math/clamp @entity.guessIntercept @config.axisYMin @config.axisYMax) },
261
+ { x: @config.axisXMax, y: (math/clamp (+ (* @entity.guessSlope @config.axisXMax) @entity.guessIntercept) @config.axisYMin @config.axisYMax) }
262
+ ]})
263
+ (residualCurves (if @config.showResiduals
264
+ (array/map @pts (fn p { label: "", color: @config.residualColor, dash: @config.residualDash, samples: [ { x: (object/get @p x), y: (object/get @p y) }, { x: (object/get @p x), y: (+ (* @newSlope (object/get @p x)) @newIntercept) } ] }))
265
+ []))
266
+ (baseCurves (if (and @config.guessMode (not @entity.revealed))
267
+ (array/append [] @guessCurve)
268
+ (if @config.guessMode
269
+ (array/append (array/append [] @guessCurve) @fitCurve)
270
+ (array/append [] @fitCurve))))
271
+ )
272
+ (do
273
+ (set @entity.points @pts)
274
+ (set @entity.slope @newSlope)
275
+ (set @entity.intercept @newIntercept)
276
+ (set @entity.r @newR)
277
+ (set @entity.rSquared (* @newR @newR))
278
+ (set @entity.curves (array/concat @baseCurves @residualCurves))
279
+ (set @entity.narration (str/concat @config.narrationMovedTemplate " — r = " (str/concat "" (math/round @newR 3)) ", R² = " (str/concat "" (math/round (* @newR @newR) 3))))
280
+ )
281
+ )
282
+ (render-ui main {
283
+ type: box
284
+ padding: md
285
+ children: [
286
+ { type: stack, direction: vertical, gap: md, children: [
287
+ { type: typography, variant: h4, content: @entity.title },
288
+ { type: stack, direction: horizontal, gap: sm, children: [
289
+ { type: button, action: ADD_POINT, label: @config.addPointLabel, variant: primary },
290
+ { type: button, action: REVEAL_FIT, label: @config.revealLabel, variant: secondary },
291
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running @config.pauseLabel @config.runLabel), variant: secondary },
292
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
293
+ ] },
294
+ { type: typography, variant: caption, content: (str/concat @config.guessSlopeCaption (math/round @entity.guessSlope 2)) },
295
+ { type: range-slider, min: @config.guessSlopeMin, max: @config.guessSlopeMax, step: @config.guessSlopeStep, value: @entity.guessSlope, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "slope" } },
296
+ { type: typography, variant: caption, content: (str/concat @config.guessInterceptCaption (math/round @entity.guessIntercept 1)) },
297
+ { type: range-slider, min: @config.guessInterceptMin, max: @config.guessInterceptMax, step: @config.guessInterceptStep, value: @entity.guessIntercept, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "intercept" } },
298
+ { type: math-canvas, width: @config.width, height: @config.height, xMin: @config.axisXMin, xMax: @config.axisXMax, yMin: @config.axisYMin, yMax: @config.axisYMax, showAxes: @config.showAxes, showGrid: @config.showGrid, gridStep: @config.gridStep, showTickLabels: @config.showTickLabels, curves: @entity.curves, points: @entity.points, vectors: [], interactive: @config.interactive, animate: @config.animate },
299
+ { type: typography, variant: body, content: (if @config.showR (str/concat "r = " (math/round @entity.r 3) " — R² = " (math/round @entity.rSquared 3) " — guess score " @entity.guessScore "%") "") },
300
+ { type: typography, variant: body, content: @entity.narration }
301
+ ] }
302
+ ]
303
+ })
304
+ (emit LEARN_SCATTER_FIT_UPDATED { slope: @entity.slope, intercept: @entity.intercept, r: @entity.r, rSquared: @entity.rSquared, pointCount: (array/len @entity.points), narration: @entity.narration })
305
+
306
+ GUESS_LINE -> idle when (== @entity.active true)
307
+ (let (
308
+ (gSlope (if (== ?param "slope") ?value @entity.guessSlope))
309
+ (gIntercept (if (== ?param "intercept") ?value @entity.guessIntercept))
310
+ (pts @entity.points)
311
+ (n (array/len @pts))
312
+ (meanX (if (> @n 0) (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p x)))) @n) 0))
313
+ (meanY (if (> @n 0) (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p y)))) @n) 0))
314
+ (sxx (array/reduce @pts 0 (fn (acc p) (let ((dx (- (object/get @p x) @meanX))) (+ @acc (* @dx @dx))))))
315
+ (sxy (array/reduce @pts 0 (fn (acc p) (+ @acc (* (- (object/get @p x) @meanX) (- (object/get @p y) @meanY))))))
316
+ (newSlope (if (> @sxx 0) (/ @sxy @sxx) 0))
317
+ (newIntercept (- @meanY (* @newSlope @meanX)))
318
+ (ssFit (array/reduce @pts 0 (fn (acc p) (let ((e (- (object/get @p y) (+ (* @newSlope (object/get @p x)) @newIntercept)))) (+ @acc (* @e @e))))))
319
+ (ssGuess (array/reduce @pts 0 (fn (acc p) (let ((e (- (object/get @p y) (+ (* @gSlope (object/get @p x)) @gIntercept)))) (+ @acc (* @e @e))))))
320
+ (newScore (if (> @ssGuess 0) (math/round (math/clamp (* 100 (/ @ssFit @ssGuess)) 0 100)) 100))
321
+ (guessCurve { label: @config.guessLabel, color: @config.guessColor, samples: [
322
+ { x: @config.axisXMin, y: (math/clamp @gIntercept @config.axisYMin @config.axisYMax) },
323
+ { x: @config.axisXMax, y: (math/clamp (+ (* @gSlope @config.axisXMax) @gIntercept) @config.axisYMin @config.axisYMax) }
324
+ ]})
325
+ (fitCurve { label: @config.fitLabel, color: @config.fitColor, samples: [
326
+ { x: @config.axisXMin, y: (math/clamp @newIntercept @config.axisYMin @config.axisYMax) },
327
+ { x: @config.axisXMax, y: (math/clamp (+ (* @newSlope @config.axisXMax) @newIntercept) @config.axisYMin @config.axisYMax) }
328
+ ]})
329
+ )
330
+ (do
331
+ (set @entity.guessSlope @gSlope)
332
+ (set @entity.guessIntercept @gIntercept)
333
+ (set @entity.guessScore @newScore)
334
+ (set @entity.curves (if (and @config.guessMode (not @entity.revealed))
335
+ (array/append [] @guessCurve)
336
+ (if @config.guessMode
337
+ (array/append (array/append [] @guessCurve) @fitCurve)
338
+ (array/append [] @fitCurve))))
339
+ (set @entity.narration (str/concat @config.narrationGuessUpdatedTemplate " " (str/concat "" @newScore) "%"))
340
+ )
341
+ )
342
+ (render-ui main {
343
+ type: box
344
+ padding: md
345
+ children: [
346
+ { type: stack, direction: vertical, gap: md, children: [
347
+ { type: typography, variant: h4, content: @entity.title },
348
+ { type: stack, direction: horizontal, gap: sm, children: [
349
+ { type: button, action: ADD_POINT, label: @config.addPointLabel, variant: primary },
350
+ { type: button, action: REVEAL_FIT, label: @config.revealLabel, variant: secondary },
351
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running @config.pauseLabel @config.runLabel), variant: secondary },
352
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
353
+ ] },
354
+ { type: typography, variant: caption, content: (str/concat @config.guessSlopeCaption (math/round @entity.guessSlope 2)) },
355
+ { type: range-slider, min: @config.guessSlopeMin, max: @config.guessSlopeMax, step: @config.guessSlopeStep, value: @entity.guessSlope, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "slope" } },
356
+ { type: typography, variant: caption, content: (str/concat @config.guessInterceptCaption (math/round @entity.guessIntercept 1)) },
357
+ { type: range-slider, min: @config.guessInterceptMin, max: @config.guessInterceptMax, step: @config.guessInterceptStep, value: @entity.guessIntercept, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "intercept" } },
358
+ { type: math-canvas, width: @config.width, height: @config.height, xMin: @config.axisXMin, xMax: @config.axisXMax, yMin: @config.axisYMin, yMax: @config.axisYMax, showAxes: @config.showAxes, showGrid: @config.showGrid, gridStep: @config.gridStep, showTickLabels: @config.showTickLabels, curves: @entity.curves, points: @entity.points, vectors: [], interactive: @config.interactive, animate: @config.animate },
359
+ { type: typography, variant: body, content: (if @config.showR (str/concat "r = " (math/round @entity.r 3) " — R² = " (math/round @entity.rSquared 3) " — guess score " @entity.guessScore "%") "") },
360
+ { type: typography, variant: body, content: @entity.narration }
361
+ ] }
362
+ ]
363
+ })
364
+ (emit LEARN_SCATTER_FIT_UPDATED { slope: @entity.slope, intercept: @entity.intercept, r: @entity.r, rSquared: @entity.rSquared, pointCount: (array/len @entity.points), narration: @entity.narration })
365
+
366
+ REVEAL_FIT -> idle when (== @entity.active true)
367
+ (set @entity.revealed true)
368
+ (let (
369
+ (fitCurve { label: @config.fitLabel, color: @config.fitColor, samples: [
370
+ { x: @config.axisXMin, y: (math/clamp @entity.intercept @config.axisYMin @config.axisYMax) },
371
+ { x: @config.axisXMax, y: (math/clamp (+ (* @entity.slope @config.axisXMax) @entity.intercept) @config.axisYMin @config.axisYMax) }
372
+ ]})
373
+ (guessCurve { label: @config.guessLabel, color: @config.guessColor, samples: [
374
+ { x: @config.axisXMin, y: (math/clamp @entity.guessIntercept @config.axisYMin @config.axisYMax) },
375
+ { x: @config.axisXMax, y: (math/clamp (+ (* @entity.guessSlope @config.axisXMax) @entity.guessIntercept) @config.axisYMin @config.axisYMax) }
376
+ ]})
377
+ )
378
+ (do
379
+ (set @entity.curves (if @config.guessMode
380
+ (array/append (array/append [] @guessCurve) @fitCurve)
381
+ (array/append [] @fitCurve)))
382
+ (set @entity.narration (str/concat @config.narrationFitRevealedTemplate " — r = " (str/concat "" (math/round @entity.r 3)) ", R² = " (str/concat "" (math/round @entity.rSquared 3)) " — guess score " (str/concat "" @entity.guessScore) "%"))
383
+ )
384
+ )
385
+ (render-ui main {
386
+ type: box
387
+ padding: md
388
+ children: [
389
+ { type: stack, direction: vertical, gap: md, children: [
390
+ { type: typography, variant: h4, content: @entity.title },
391
+ { type: stack, direction: horizontal, gap: sm, children: [
392
+ { type: button, action: ADD_POINT, label: @config.addPointLabel, variant: primary },
393
+ { type: button, action: REVEAL_FIT, label: @config.revealLabel, variant: secondary },
394
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running @config.pauseLabel @config.runLabel), variant: secondary },
395
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
396
+ ] },
397
+ { type: typography, variant: caption, content: (str/concat @config.guessSlopeCaption (math/round @entity.guessSlope 2)) },
398
+ { type: range-slider, min: @config.guessSlopeMin, max: @config.guessSlopeMax, step: @config.guessSlopeStep, value: @entity.guessSlope, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "slope" } },
399
+ { type: typography, variant: caption, content: (str/concat @config.guessInterceptCaption (math/round @entity.guessIntercept 1)) },
400
+ { type: range-slider, min: @config.guessInterceptMin, max: @config.guessInterceptMax, step: @config.guessInterceptStep, value: @entity.guessIntercept, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "intercept" } },
401
+ { type: math-canvas, width: @config.width, height: @config.height, xMin: @config.axisXMin, xMax: @config.axisXMax, yMin: @config.axisYMin, yMax: @config.axisYMax, showAxes: @config.showAxes, showGrid: @config.showGrid, gridStep: @config.gridStep, showTickLabels: @config.showTickLabels, curves: @entity.curves, points: @entity.points, vectors: [], interactive: @config.interactive, animate: @config.animate },
402
+ { type: typography, variant: body, content: (if @config.showR (str/concat "r = " (math/round @entity.r 3) " — R² = " (math/round @entity.rSquared 3) " — guess score " @entity.guessScore "%") "") },
403
+ { type: typography, variant: body, content: @entity.narration }
404
+ ] }
405
+ ]
406
+ })
407
+ (emit LEARN_SCATTER_FIT_UPDATED { slope: @entity.slope, intercept: @entity.intercept, r: @entity.r, rSquared: @entity.rSquared, pointCount: (array/len @entity.points), narration: @entity.narration })
408
+
409
+ TOGGLE_RUN -> idle when (== @entity.active true)
410
+ (set @entity.running (not @entity.running))
411
+ (set @entity.narration (if @entity.running @config.narrationAutoAddingTemplate @config.narrationPausedTemplate))
412
+ (render-ui main {
413
+ type: box
414
+ padding: md
415
+ children: [
416
+ { type: stack, direction: vertical, gap: md, children: [
417
+ { type: typography, variant: h4, content: @entity.title },
418
+ { type: stack, direction: horizontal, gap: sm, children: [
419
+ { type: button, action: ADD_POINT, label: @config.addPointLabel, variant: primary },
420
+ { type: button, action: REVEAL_FIT, label: @config.revealLabel, variant: secondary },
421
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running @config.pauseLabel @config.runLabel), variant: secondary },
422
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
423
+ ] },
424
+ { type: typography, variant: caption, content: (str/concat @config.guessSlopeCaption (math/round @entity.guessSlope 2)) },
425
+ { type: range-slider, min: @config.guessSlopeMin, max: @config.guessSlopeMax, step: @config.guessSlopeStep, value: @entity.guessSlope, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "slope" } },
426
+ { type: typography, variant: caption, content: (str/concat @config.guessInterceptCaption (math/round @entity.guessIntercept 1)) },
427
+ { type: range-slider, min: @config.guessInterceptMin, max: @config.guessInterceptMax, step: @config.guessInterceptStep, value: @entity.guessIntercept, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "intercept" } },
428
+ { type: math-canvas, width: @config.width, height: @config.height, xMin: @config.axisXMin, xMax: @config.axisXMax, yMin: @config.axisYMin, yMax: @config.axisYMax, showAxes: @config.showAxes, showGrid: @config.showGrid, gridStep: @config.gridStep, showTickLabels: @config.showTickLabels, curves: @entity.curves, points: @entity.points, vectors: [], interactive: @config.interactive, animate: @config.animate },
429
+ { type: typography, variant: body, content: (if @config.showR (str/concat "r = " (math/round @entity.r 3) " — R² = " (math/round @entity.rSquared 3) " — guess score " @entity.guessScore "%") "") },
430
+ { type: typography, variant: body, content: @entity.narration }
431
+ ] }
432
+ ]
433
+ })
434
+ (emit LEARN_SCATTER_FIT_UPDATED { slope: @entity.slope, intercept: @entity.intercept, r: @entity.r, rSquared: @entity.rSquared, pointCount: (array/len @entity.points), narration: @entity.narration })
435
+
436
+ RESET -> idle when (== @entity.active true)
437
+ (set @entity.points @config.initialPoints)
438
+ (set @entity.guessSlope 0)
439
+ (set @entity.guessIntercept (/ (+ @config.axisYMin @config.axisYMax) 2))
440
+ (set @entity.revealed false)
441
+ (set @entity.running @config.autoRun)
442
+ (let (
443
+ (pts @config.initialPoints)
444
+ (n (array/len @pts))
445
+ (meanX (if (> @n 0) (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p x)))) @n) 0))
446
+ (meanY (if (> @n 0) (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p y)))) @n) 0))
447
+ (sxx (array/reduce @pts 0 (fn (acc p) (let ((dx (- (object/get @p x) @meanX))) (+ @acc (* @dx @dx))))))
448
+ (syy (array/reduce @pts 0 (fn (acc p) (let ((dy (- (object/get @p y) @meanY))) (+ @acc (* @dy @dy))))))
449
+ (sxy (array/reduce @pts 0 (fn (acc p) (+ @acc (* (- (object/get @p x) @meanX) (- (object/get @p y) @meanY))))))
450
+ (newSlope (if (> @sxx 0) (/ @sxy @sxx) 0))
451
+ (newIntercept (- @meanY (* @newSlope @meanX)))
452
+ (newR (if (and (> @sxx 0) (> @syy 0)) (/ @sxy (math/sqrt (* @sxx @syy))) 0))
453
+ (fitCurve { label: @config.fitLabel, color: @config.fitColor, samples: [
454
+ { x: @config.axisXMin, y: (math/clamp @newIntercept @config.axisYMin @config.axisYMax) },
455
+ { x: @config.axisXMax, y: (math/clamp (+ (* @newSlope @config.axisXMax) @newIntercept) @config.axisYMin @config.axisYMax) }
456
+ ]})
457
+ (residualCurves (if @config.showResiduals
458
+ (array/map @pts (fn p { label: "", color: @config.residualColor, dash: @config.residualDash, samples: [ { x: (object/get @p x), y: (object/get @p y) }, { x: (object/get @p x), y: (+ (* @newSlope (object/get @p x)) @newIntercept) } ] }))
459
+ []))
460
+ )
461
+ (do
462
+ (set @entity.slope @newSlope)
463
+ (set @entity.intercept @newIntercept)
464
+ (set @entity.r @newR)
465
+ (set @entity.rSquared (* @newR @newR))
466
+ (set @entity.guessScore 0)
467
+ (set @entity.curves (array/concat (array/append [] @fitCurve) @residualCurves))
468
+ (set @entity.done (>= @n @config.maxPoints))
469
+ (set @entity.narration (str/concat (str/concat "" @n) " " @config.narrationLoadedTemplate " — r = " (str/concat "" (math/round @newR 3)) ", R² = " (str/concat "" (math/round (* @newR @newR) 3))))
470
+ )
471
+ )
472
+ (render-ui main {
473
+ type: box
474
+ padding: md
475
+ children: [
476
+ { type: stack, direction: vertical, gap: md, children: [
477
+ { type: typography, variant: h4, content: @entity.title },
478
+ { type: stack, direction: horizontal, gap: sm, children: [
479
+ { type: button, action: ADD_POINT, label: @config.addPointLabel, variant: primary },
480
+ { type: button, action: REVEAL_FIT, label: @config.revealLabel, variant: secondary },
481
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running @config.pauseLabel @config.runLabel), variant: secondary },
482
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
483
+ ] },
484
+ { type: typography, variant: caption, content: (str/concat @config.guessSlopeCaption (math/round @entity.guessSlope 2)) },
485
+ { type: range-slider, min: @config.guessSlopeMin, max: @config.guessSlopeMax, step: @config.guessSlopeStep, value: @entity.guessSlope, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "slope" } },
486
+ { type: typography, variant: caption, content: (str/concat @config.guessInterceptCaption (math/round @entity.guessIntercept 1)) },
487
+ { type: range-slider, min: @config.guessInterceptMin, max: @config.guessInterceptMax, step: @config.guessInterceptStep, value: @entity.guessIntercept, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "intercept" } },
488
+ { type: math-canvas, width: @config.width, height: @config.height, xMin: @config.axisXMin, xMax: @config.axisXMax, yMin: @config.axisYMin, yMax: @config.axisYMax, showAxes: @config.showAxes, showGrid: @config.showGrid, gridStep: @config.gridStep, showTickLabels: @config.showTickLabels, curves: @entity.curves, points: @entity.points, vectors: [], interactive: @config.interactive, animate: @config.animate },
489
+ { type: typography, variant: body, content: (if @config.showR (str/concat "r = " (math/round @entity.r 3) " — R² = " (math/round @entity.rSquared 3) " — guess score " @entity.guessScore "%") "") },
490
+ { type: typography, variant: body, content: @entity.narration }
491
+ ] }
492
+ ]
493
+ })
494
+ (emit LEARN_SCATTER_FIT_UPDATED { slope: @entity.slope, intercept: @entity.intercept, r: @entity.r, rSquared: @entity.rSquared, pointCount: (array/len @entity.points), narration: @entity.narration })
495
+ }
496
+
497
+ ticks {
498
+ scatterFitTick every 600ms
499
+ when (and @entity.active (and @entity.running (not @entity.done)))
500
+ (let (
501
+ (marginX (* (- @config.axisXMax @config.axisXMin) @config.pointMarginPct))
502
+ (usableX (- (- @config.axisXMax @config.axisXMin) (* @marginX 2)))
503
+ (marginY (* (- @config.axisYMax @config.axisYMin) @config.pointMarginPct))
504
+ (usableY (- (- @config.axisYMax @config.axisYMin) (* @marginY 2)))
505
+ (newPoint { id: (str/concat "p" (array/len @entity.points)), x: (math/round (+ @config.axisXMin (+ @marginX (* (math/random) @usableX)))), y: (math/round (+ @config.axisYMin (+ @marginY (* (math/random) @usableY)))), label: "", color: @config.newPointColor, radius: @config.newPointRadius })
506
+ (pts (array/append @entity.points @newPoint))
507
+ (n (array/len @pts))
508
+ (meanX (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p x)))) @n))
509
+ (meanY (/ (array/reduce @pts 0 (fn (acc p) (+ @acc (object/get @p y)))) @n))
510
+ (sxx (array/reduce @pts 0 (fn (acc p) (let ((dx (- (object/get @p x) @meanX))) (+ @acc (* @dx @dx))))))
511
+ (syy (array/reduce @pts 0 (fn (acc p) (let ((dy (- (object/get @p y) @meanY))) (+ @acc (* @dy @dy))))))
512
+ (sxy (array/reduce @pts 0 (fn (acc p) (+ @acc (* (- (object/get @p x) @meanX) (- (object/get @p y) @meanY))))))
513
+ (newSlope (if (> @sxx 0) (/ @sxy @sxx) 0))
514
+ (newIntercept (- @meanY (* @newSlope @meanX)))
515
+ (newR (if (and (> @sxx 0) (> @syy 0)) (/ @sxy (math/sqrt (* @sxx @syy))) 0))
516
+ (fitCurve { label: @config.fitLabel, color: @config.fitColor, samples: [
517
+ { x: @config.axisXMin, y: (math/clamp @newIntercept @config.axisYMin @config.axisYMax) },
518
+ { x: @config.axisXMax, y: (math/clamp (+ (* @newSlope @config.axisXMax) @newIntercept) @config.axisYMin @config.axisYMax) }
519
+ ]})
520
+ (guessCurve { label: @config.guessLabel, color: @config.guessColor, samples: [
521
+ { x: @config.axisXMin, y: (math/clamp @entity.guessIntercept @config.axisYMin @config.axisYMax) },
522
+ { x: @config.axisXMax, y: (math/clamp (+ (* @entity.guessSlope @config.axisXMax) @entity.guessIntercept) @config.axisYMin @config.axisYMax) }
523
+ ]})
524
+ (residualCurves (if @config.showResiduals
525
+ (array/map @pts (fn p { label: "", color: @config.residualColor, dash: @config.residualDash, samples: [ { x: (object/get @p x), y: (object/get @p y) }, { x: (object/get @p x), y: (+ (* @newSlope (object/get @p x)) @newIntercept) } ] }))
526
+ []))
527
+ (baseCurves (if (and @config.guessMode (not @entity.revealed))
528
+ (array/append [] @guessCurve)
529
+ (if @config.guessMode
530
+ (array/append (array/append [] @guessCurve) @fitCurve)
531
+ (array/append [] @fitCurve))))
532
+ (nowDone (>= @n @config.maxPoints))
533
+ )
534
+ (do
535
+ (set @entity.points @pts)
536
+ (set @entity.slope @newSlope)
537
+ (set @entity.intercept @newIntercept)
538
+ (set @entity.r @newR)
539
+ (set @entity.rSquared (* @newR @newR))
540
+ (set @entity.curves (array/concat @baseCurves @residualCurves))
541
+ (set @entity.done @nowDone)
542
+ (set @entity.narration (str/concat @config.narrationAddedTemplate " " (str/concat "" @n) " — r = " (str/concat "" (math/round @newR 3)) ", R² = " (str/concat "" (math/round (* @newR @newR) 3)) (if @nowDone @config.narrationMaxReachedSuffix "")))
543
+ )
544
+ )
545
+ (render-ui main {
546
+ type: box
547
+ padding: md
548
+ children: [
549
+ { type: stack, direction: vertical, gap: md, children: [
550
+ { type: typography, variant: h4, content: @entity.title },
551
+ { type: stack, direction: horizontal, gap: sm, children: [
552
+ { type: button, action: ADD_POINT, label: @config.addPointLabel, variant: primary },
553
+ { type: button, action: REVEAL_FIT, label: @config.revealLabel, variant: secondary },
554
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running @config.pauseLabel @config.runLabel), variant: secondary },
555
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
556
+ ] },
557
+ { type: typography, variant: caption, content: (str/concat @config.guessSlopeCaption (math/round @entity.guessSlope 2)) },
558
+ { type: range-slider, min: @config.guessSlopeMin, max: @config.guessSlopeMax, step: @config.guessSlopeStep, value: @entity.guessSlope, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "slope" } },
559
+ { type: typography, variant: caption, content: (str/concat @config.guessInterceptCaption (math/round @entity.guessIntercept 1)) },
560
+ { type: range-slider, min: @config.guessInterceptMin, max: @config.guessInterceptMax, step: @config.guessInterceptStep, value: @entity.guessIntercept, showTooltip: true, action: "GUESS_LINE", actionPayload: { param: "intercept" } },
561
+ { type: math-canvas, width: @config.width, height: @config.height, xMin: @config.axisXMin, xMax: @config.axisXMax, yMin: @config.axisYMin, yMax: @config.axisYMax, showAxes: @config.showAxes, showGrid: @config.showGrid, gridStep: @config.gridStep, showTickLabels: @config.showTickLabels, curves: @entity.curves, points: @entity.points, vectors: [], interactive: @config.interactive, animate: @config.animate },
562
+ { type: typography, variant: body, content: (if @config.showR (str/concat "r = " (math/round @entity.r 3) " — R² = " (math/round @entity.rSquared 3) " — guess score " @entity.guessScore "%") "") },
563
+ { type: typography, variant: body, content: @entity.narration }
564
+ ] }
565
+ ]
566
+ })
567
+ (emit LEARN_SCATTER_FIT_UPDATED { slope: @entity.slope, intercept: @entity.intercept, r: @entity.r, rSquared: @entity.rSquared, pointCount: (array/len @entity.points), narration: @entity.narration })
568
+ }
569
+
570
+ listens {
571
+ MODE_SELECTED {
572
+ mode : string
573
+ }
574
+ @description "Lab router broadcast naming the active mode; this instrument renders when the mode equals matchMode, otherwise renders null."
575
+ @tier "domain"
576
+ DRAG_POINT {
577
+ id : string
578
+ x : number
579
+ y : number
580
+ }
581
+ @description "A scatter point was dragged — id names the point, x/y its new coordinates (clamped to the axis ranges); the fit recomputes live."
582
+ @tier "domain"
583
+ GUESS_LINE {
584
+ param : string
585
+ value : number
586
+ }
587
+ @description "Learner dragged a guess-the-line slider — param is slope or intercept; the guess is scored against the least-squares fit."
588
+ @tier "domain"
589
+ }
590
+
591
+ emits {
592
+ LEARN_SCATTER_FIT_UPDATED -> external {
593
+ slope : number
594
+ intercept : number
595
+ r : number
596
+ rSquared : number
597
+ pointCount : number
598
+ narration : string
599
+ }
600
+ @description "The least-squares fit and narration after each learner action, auto-add tick, or reset — a composed listener (readout, grader) can subscribe; a harmless no-op when the atom runs standalone."
601
+ @synonyms "fit update, line of best fit, slope, intercept, correlation, r squared"
602
+ @tier "domain"
603
+ }
604
+
605
+ config {
606
+ matchMode : string = ""
607
+ @label "Match Mode"
608
+ @description "When composed behind a lab router, the MODE_SELECTED mode value this instrument responds to. Empty string means standalone — render immediately on INIT."
609
+ @tier "presentation"
610
+ autoRun : boolean = false
611
+ @label "Auto Run"
612
+ @description "When true, INIT/MODE_SELECTED start the tick auto-adding points up to Max Points; toggle live with the Run/Pause button."
613
+ @tier "domain"
614
+ title : string = "Scatter Fit"
615
+ @label "Title"
616
+ @description "Title shown above the canvas."
617
+ @tier "presentation"
618
+ initialPoints : [LearnScatterFitPoint] = [
619
+ { id: "p0", x: 15, y: 25, label: "", color: "#334155", radius: 5 },
620
+ { id: "p1", x: 32, y: 40, label: "", color: "#334155", radius: 5 },
621
+ { id: "p2", x: 48, y: 44, label: "", color: "#334155", radius: 5 },
622
+ { id: "p3", x: 63, y: 62, label: "", color: "#334155", radius: 5 },
623
+ { id: "p4", x: 80, y: 70, label: "", color: "#334155", radius: 5 }
624
+ ]
625
+ @label "Initial Points"
626
+ @description "Scatter points the canvas starts with, in axis data coordinates; Reset reseeds from this roster."
627
+ @synonyms "seed points, dataset, scatter data, sample"
628
+ @tier "domain"
629
+ guessMode : boolean = true
630
+ @label "Guess Mode"
631
+ @description "When true the learner's guessed line is shown instead of the fit until Reveal Fit, and the guess is scored against least squares."
632
+ @synonyms "guess the line, prediction, estimate the fit"
633
+ @tier "domain"
634
+ showResiduals : boolean = true
635
+ @label "Show Residuals"
636
+ @description "Draw a dashed segment from each point to the fit line — the errors least squares minimizes."
637
+ @synonyms "residuals, errors, deviations"
638
+ @tier "domain"
639
+ showR : boolean = true
640
+ @label "Show r and R²"
641
+ @description "Show the correlation coefficient r and coefficient of determination R² under the canvas."
642
+ @synonyms "correlation coefficient, r squared, goodness of fit"
643
+ @tier "domain"
644
+ maxPoints : number = 20
645
+ @label "Max Points"
646
+ @description "Cap on accumulated scatter points; Add Point and the auto-run tick stop once reached, until Reset."
647
+ @tier "domain"
648
+ width : number = 600
649
+ @label "Width"
650
+ @description "Canvas width in pixels."
651
+ @tier "presentation"
652
+ height : number = 400
653
+ @label "Height"
654
+ @description "Canvas height in pixels."
655
+ @tier "presentation"
656
+ axisXMin : number = 0
657
+ @label "X Axis Min"
658
+ @description "Lower bound of the canvas x axis, in data coordinates."
659
+ @tier "domain"
660
+ axisXMax : number = 100
661
+ @label "X Axis Max"
662
+ @description "Upper bound of the canvas x axis, in data coordinates; also the x endpoint the fit/guess lines are drawn to."
663
+ @tier "domain"
664
+ axisYMin : number = 0
665
+ @label "Y Axis Min"
666
+ @description "Lower bound of the canvas y axis; also the floor curve samples clamp to and the low end of the guess-intercept slider default range."
667
+ @tier "domain"
668
+ axisYMax : number = 100
669
+ @label "Y Axis Max"
670
+ @description "Upper bound of the canvas y axis; also the ceiling curve samples clamp to and the high end of the guess-intercept slider default range."
671
+ @tier "domain"
672
+ gridStep : number = 20
673
+ @label "Grid Step"
674
+ @description "Spacing between canvas gridlines, in data coordinates."
675
+ @tier "presentation"
676
+ showAxes : boolean = true
677
+ @label "Show Axes"
678
+ @tier "presentation"
679
+ showGrid : boolean = true
680
+ @label "Show Grid"
681
+ @tier "presentation"
682
+ showTickLabels : boolean = true
683
+ @label "Show Tick Labels"
684
+ @tier "presentation"
685
+ interactive : boolean = true
686
+ @label "Interactive"
687
+ @description "Enable pointer interaction on the canvas — required for dragging points."
688
+ @tier "presentation"
689
+ animate : boolean = false
690
+ @label "Animate"
691
+ @description "Enable continuous redraw loop."
692
+ @tier "presentation"
693
+ fitLabel : string = "fit"
694
+ @label "Fit Curve Label"
695
+ @description "Legend label for the computed least-squares fit line."
696
+ @tier "presentation"
697
+ fitColor : string = "#2563eb"
698
+ @label "Fit Curve Color"
699
+ @tier "presentation"
700
+ guessLabel : string = "your guess"
701
+ @label "Guess Curve Label"
702
+ @description "Legend label for the learner's proposed line, shown while guessMode is on and the fit isn't yet revealed."
703
+ @tier "presentation"
704
+ guessColor : string = "#dc2626"
705
+ @label "Guess Curve Color"
706
+ @tier "presentation"
707
+ residualColor : string = "#f59e0b"
708
+ @label "Residual Color"
709
+ @tier "presentation"
710
+ residualDash : "dashed" | "dotted" = "dashed"
711
+ @label "Residual Dash Style"
712
+ @tier "presentation"
713
+ newPointColor : string = "#334155"
714
+ @label "New Point Color"
715
+ @description "Fill color assigned to points created by Add Point or the auto-add tick."
716
+ @tier "presentation"
717
+ newPointRadius : number = 5
718
+ @label "New Point Radius"
719
+ @tier "presentation"
720
+ pointMarginPct : number = 0.05
721
+ @label "Point Margin Percent"
722
+ @description "Fraction of each axis span kept clear at both ends when Add Point/the tick place a new random point, so new points never land flush against the axis edge."
723
+ @tier "domain"
724
+ guessSlopeMin : number = -3
725
+ @label "Guess Slope Min"
726
+ @description "Lower bound of the guess-slope slider."
727
+ @tier "domain"
728
+ guessSlopeMax : number = 3
729
+ @label "Guess Slope Max"
730
+ @description "Upper bound of the guess-slope slider."
731
+ @tier "domain"
732
+ guessSlopeStep : number = 0.05
733
+ @label "Guess Slope Step"
734
+ @tier "domain"
735
+ guessInterceptMin : number = 0
736
+ @label "Guess Intercept Min"
737
+ @description "Lower bound of the guess-intercept slider; typically set to axisYMin."
738
+ @tier "domain"
739
+ guessInterceptMax : number = 100
740
+ @label "Guess Intercept Max"
741
+ @description "Upper bound of the guess-intercept slider; typically set to axisYMax."
742
+ @tier "domain"
743
+ guessInterceptStep : number = 1
744
+ @label "Guess Intercept Step"
745
+ @tier "domain"
746
+ addPointLabel : string = "Add Point"
747
+ @label "Add Point Button Label"
748
+ @tier "presentation"
749
+ revealLabel : string = "Reveal Fit"
750
+ @label "Reveal Fit Button Label"
751
+ @tier "presentation"
752
+ runLabel : string = "Run"
753
+ @label "Run Button Label"
754
+ @tier "presentation"
755
+ pauseLabel : string = "Pause"
756
+ @label "Pause Button Label"
757
+ @tier "presentation"
758
+ resetLabel : string = "Reset"
759
+ @label "Reset Button Label"
760
+ @tier "presentation"
761
+ guessSlopeCaption : string = "Guess slope: "
762
+ @label "Guess Slope Caption"
763
+ @description "Text prefixed to the current guess-slope value above its slider."
764
+ @tier "presentation"
765
+ guessInterceptCaption : string = "Guess intercept: "
766
+ @label "Guess Intercept Caption"
767
+ @description "Text prefixed to the current guess-intercept value above its slider."
768
+ @tier "presentation"
769
+ narrationLoadedTemplate : string = "point(s) loaded"
770
+ @label "Loaded Narration Template"
771
+ @description "Narration phrase after INIT/MODE_SELECTED/Reset load the seed points, assembled as \"<n> <template> — r = .., R² = ..\"."
772
+ @tier "domain"
773
+ narrationAddedTemplate : string = "point added"
774
+ @label "Added Narration Template"
775
+ @description "Narration phrase after a point is added (button or tick), assembled as \"<template> <n> — r = .., R² = ..\"."
776
+ @tier "domain"
777
+ narrationMovedTemplate : string = "point moved"
778
+ @label "Moved Narration Template"
779
+ @description "Narration phrase after a point is dragged, assembled as \"<template> — r = .., R² = ..\"."
780
+ @tier "domain"
781
+ narrationGuessUpdatedTemplate : string = "guess updated — score"
782
+ @label "Guess Updated Narration Template"
783
+ @description "Narration phrase after a guess slider moves, assembled as \"<template> <score>%\"."
784
+ @tier "domain"
785
+ narrationFitRevealedTemplate : string = "fit revealed"
786
+ @label "Fit Revealed Narration Template"
787
+ @description "Narration phrase after Reveal Fit, assembled as \"<template> — r = .., R² = .. — guess score <score>%\"."
788
+ @tier "domain"
789
+ narrationMaxReachedSuffix : string = " — max points reached"
790
+ @label "Max Reached Narration Suffix"
791
+ @description "Suffix appended to the added-point narration once maxPoints is hit."
792
+ @tier "domain"
793
+ narrationAutoAddingTemplate : string = "Auto-adding — tick adds a new point."
794
+ @label "Auto-Adding Narration"
795
+ @description "Narration shown after Run is pressed."
796
+ @tier "domain"
797
+ narrationPausedTemplate : string = "Paused — add manually with Add Point."
798
+ @label "Paused Narration"
799
+ @description "Narration shown after Pause is pressed."
800
+ @tier "domain"
801
+ }
802
+ }
803
+
804
+ page "/learn-scatter-fit" as LearnScatterFitPage -> LearnScatterFitSim
805
+ }