@almadar/std 16.239.0 → 16.242.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 (167) hide show
  1. package/behaviors/behaviors-registry.json +4814 -553
  2. package/behaviors/lolo/agent/atoms/std-agent-memory.lolo +38 -1
  3. package/behaviors/lolo/agent/atoms/std-agent-session.lolo +1074 -47
  4. package/behaviors/lolo/agent/atoms/std-agent-tool-loop.lolo +8 -7
  5. package/behaviors/lolo/agent/organisms/std-agent-assistant.lolo +56 -37
  6. package/behaviors/lolo/infra/atoms/std-page-open.lolo +59 -0
  7. package/behaviors/lolo/infra/atoms/std-page-search.lolo +134 -0
  8. package/behaviors/lolo/infra/atoms/std-page-treat.lolo +59 -0
  9. package/behaviors/lolo/infra/atoms/std-page-watch.lolo +86 -0
  10. package/behaviors/lolo/ui/core/atoms/std-board.lolo +53 -6
  11. package/behaviors/lolo/ui/core/atoms/std-book-reader.lolo +25 -1
  12. package/behaviors/lolo/ui/core/atoms/std-card-grid.lolo +9 -0
  13. package/behaviors/lolo/ui/core/atoms/std-doc-page.lolo +153 -0
  14. package/behaviors/lolo/ui/core/atoms/std-site-header.lolo +15 -3
  15. package/behaviors/lolo/ui/core/atoms/std-tag-strip.lolo +2 -2
  16. package/behaviors/lolo/ui/core/atoms/std-tag-taxonomy.lolo +7 -7
  17. package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +1 -1
  18. package/behaviors/lolo/ui/core/molecules/ui-header.lolo +5 -0
  19. package/behaviors/lolo/ui/learning/atoms/std-learn-arrangement.lolo +124 -92
  20. package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +114 -55
  21. package/behaviors/lolo/ui/learning/atoms/std-learn-compare.lolo +40 -14
  22. package/behaviors/lolo/ui/learning/atoms/std-learn-coupled-sim.lolo +363 -123
  23. package/behaviors/lolo/ui/learning/atoms/std-learn-derivation.lolo +93 -48
  24. package/behaviors/lolo/ui/learning/atoms/std-learn-diagram.lolo +60 -28
  25. package/behaviors/lolo/ui/learning/atoms/std-learn-doser.lolo +193 -96
  26. package/behaviors/lolo/ui/learning/atoms/std-learn-estimate.lolo +75 -32
  27. package/behaviors/lolo/ui/learning/atoms/std-learn-goal-seek.lolo +142 -86
  28. package/behaviors/lolo/ui/learning/atoms/std-learn-grid-sim.lolo +266 -98
  29. package/behaviors/lolo/ui/learning/atoms/std-learn-hotspot.lolo +103 -65
  30. package/behaviors/lolo/ui/learning/atoms/std-learn-live-sim.lolo +223 -109
  31. package/behaviors/lolo/ui/learning/atoms/std-learn-measure.lolo +232 -64
  32. package/behaviors/lolo/ui/learning/atoms/std-learn-param-explorer.lolo +69 -29
  33. package/behaviors/lolo/ui/learning/atoms/std-learn-perturb.lolo +363 -123
  34. package/behaviors/lolo/ui/learning/atoms/std-learn-predict.lolo +73 -41
  35. package/behaviors/lolo/ui/learning/atoms/std-learn-sample-accumulator.lolo +96 -38
  36. package/behaviors/lolo/ui/learning/atoms/std-learn-sandbox.lolo +65 -45
  37. package/behaviors/lolo/ui/learning/atoms/std-learn-scatter-fit.lolo +243 -117
  38. package/behaviors/lolo/ui/learning/atoms/std-learn-scenario.lolo +76 -28
  39. package/behaviors/lolo/ui/learning/atoms/std-learn-sim-3d.lolo +228 -109
  40. package/behaviors/lolo/ui/learning/atoms/std-learn-speed-drill.lolo +95 -44
  41. package/behaviors/lolo/ui/learning/atoms/std-learn-stepper.lolo +199 -125
  42. package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +92 -48
  43. package/behaviors/lolo/ui/learning/atoms/std-learn-trace-player.lolo +239 -78
  44. package/behaviors/lolo/ui/learning/atoms/std-learn-transport.lolo +147 -38
  45. package/behaviors/lolo/ui/learning/atoms/std-learn-tree.lolo +123 -58
  46. package/behaviors/lolo/ui/learning/atoms/ui-algo-graph-canvas.lolo +22 -0
  47. package/behaviors/lolo/ui/learning/atoms/ui-algorithm-canvas.lolo +26 -0
  48. package/behaviors/lolo/ui/learning/atoms/ui-biology-canvas.lolo +26 -0
  49. package/behaviors/lolo/ui/learning/atoms/ui-chemistry-canvas.lolo +27 -1
  50. package/behaviors/lolo/ui/learning/atoms/ui-learning-canvas.lolo +27 -1
  51. package/behaviors/lolo/ui/learning/atoms/ui-math-canvas.lolo +29 -9
  52. package/behaviors/lolo/ui/learning/atoms/ui-physics-canvas.lolo +26 -0
  53. package/behaviors/lolo-behavior-contracts.json +1 -1
  54. package/behaviors/registry/agent/atoms/std-agent-memory.orb +372 -2
  55. package/behaviors/registry/agent/atoms/std-agent-session.orb +4752 -348
  56. package/behaviors/registry/agent/atoms/std-agent-tool-loop.orb +54 -42
  57. package/behaviors/registry/agent/organisms/std-agent-assistant.orb +356 -122
  58. package/behaviors/registry/factory-signatures.json +1 -1
  59. package/behaviors/registry/infra/atoms/std-page-open.orb +435 -0
  60. package/behaviors/registry/infra/atoms/std-page-search.orb +867 -0
  61. package/behaviors/registry/infra/atoms/std-page-treat.orb +494 -0
  62. package/behaviors/registry/infra/atoms/std-page-watch.orb +554 -0
  63. package/behaviors/registry/ui/core/atoms/std-board.orb +110 -14
  64. package/behaviors/registry/ui/core/atoms/std-book-reader.orb +119 -0
  65. package/behaviors/registry/ui/core/atoms/std-card-grid.orb +61 -0
  66. package/behaviors/registry/ui/core/atoms/std-doc-page.orb +594 -0
  67. package/behaviors/registry/ui/core/atoms/std-site-header.orb +40 -2
  68. package/behaviors/registry/ui/core/atoms/std-tag-strip.orb +2 -0
  69. package/behaviors/registry/ui/core/atoms/std-tag-taxonomy.orb +24 -0
  70. package/behaviors/registry/ui/core/atoms/std-version-history.orb +8 -1
  71. package/behaviors/registry/ui/core/molecules/ui-header.orb +7 -0
  72. package/behaviors/registry/ui/learning/atoms/std-learn-arrangement.orb +903 -369
  73. package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +1002 -232
  74. package/behaviors/registry/ui/learning/atoms/std-learn-compare.orb +253 -74
  75. package/behaviors/registry/ui/learning/atoms/std-learn-coupled-sim.orb +3624 -921
  76. package/behaviors/registry/ui/learning/atoms/std-learn-derivation.orb +685 -144
  77. package/behaviors/registry/ui/learning/atoms/std-learn-diagram.orb +404 -52
  78. package/behaviors/registry/ui/learning/atoms/std-learn-doser.orb +1787 -210
  79. package/behaviors/registry/ui/learning/atoms/std-learn-estimate.orb +699 -71
  80. package/behaviors/registry/ui/learning/atoms/std-learn-goal-seek.orb +1102 -578
  81. package/behaviors/registry/ui/learning/atoms/std-learn-grid-sim.orb +2760 -872
  82. package/behaviors/registry/ui/learning/atoms/std-learn-hotspot.orb +689 -152
  83. package/behaviors/registry/ui/learning/atoms/std-learn-live-sim.orb +1431 -633
  84. package/behaviors/registry/ui/learning/atoms/std-learn-measure.orb +2485 -512
  85. package/behaviors/registry/ui/learning/atoms/std-learn-param-explorer.orb +501 -197
  86. package/behaviors/registry/ui/learning/atoms/std-learn-perturb.orb +3642 -928
  87. package/behaviors/registry/ui/learning/atoms/std-learn-predict.orb +705 -125
  88. package/behaviors/registry/ui/learning/atoms/std-learn-sample-accumulator.orb +624 -114
  89. package/behaviors/registry/ui/learning/atoms/std-learn-sandbox.orb +405 -153
  90. package/behaviors/registry/ui/learning/atoms/std-learn-scatter-fit.orb +1911 -611
  91. package/behaviors/registry/ui/learning/atoms/std-learn-scenario.orb +436 -128
  92. package/behaviors/registry/ui/learning/atoms/std-learn-sim-3d.orb +2482 -485
  93. package/behaviors/registry/ui/learning/atoms/std-learn-speed-drill.orb +896 -192
  94. package/behaviors/registry/ui/learning/atoms/std-learn-stepper.orb +2001 -759
  95. package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +734 -228
  96. package/behaviors/registry/ui/learning/atoms/std-learn-trace-player.orb +2501 -764
  97. package/behaviors/registry/ui/learning/atoms/std-learn-transport.orb +1591 -173
  98. package/behaviors/registry/ui/learning/atoms/std-learn-tree.orb +883 -122
  99. package/behaviors/registry/ui/learning/atoms/ui-algo-graph-canvas.orb +176 -0
  100. package/behaviors/registry/ui/learning/atoms/ui-algorithm-canvas.orb +211 -0
  101. package/behaviors/registry/ui/learning/atoms/ui-biology-canvas.orb +211 -0
  102. package/behaviors/registry/ui/learning/atoms/ui-chemistry-canvas.orb +212 -1
  103. package/behaviors/registry/ui/learning/atoms/ui-learning-canvas.orb +212 -1
  104. package/behaviors/registry/ui/learning/atoms/ui-math-canvas.orb +203 -9
  105. package/behaviors/registry/ui/learning/atoms/ui-physics-canvas.orb +211 -0
  106. package/behaviors/signatures.lolo +325 -655
  107. package/canonical-operators.json +35 -1
  108. package/dist/behaviors/exports-reader.js +41980 -35508
  109. package/dist/behaviors/functions/index.d.ts +1063 -140
  110. package/dist/behaviors/functions/index.js +41951 -35506
  111. package/dist/behaviors/index.d.ts +1 -1
  112. package/dist/behaviors/index.js +41981 -35509
  113. package/dist/behaviors/query.js +41980 -35508
  114. package/dist/behaviors-embeddings.hash.json +3 -3
  115. package/dist/behaviors-embeddings.json +47 -27
  116. package/dist/behaviors-registry.json +4814 -553
  117. package/dist/index.d.ts +4 -4
  118. package/dist/index.js +42019 -35509
  119. package/dist/knob-embeddings.hash.json +3 -3
  120. package/dist/knob-embeddings.json +623 -263
  121. package/dist/modules/anim.d.ts +1 -1
  122. package/dist/modules/array.d.ts +1 -1
  123. package/dist/modules/async.d.ts +1 -1
  124. package/dist/modules/behavior.d.ts +1 -1
  125. package/dist/modules/browser.d.ts +1 -1
  126. package/dist/modules/composition.d.ts +1 -1
  127. package/dist/modules/contract.d.ts +1 -1
  128. package/dist/modules/core.d.ts +1 -1
  129. package/dist/modules/core.js +2 -0
  130. package/dist/modules/data.d.ts +1 -1
  131. package/dist/modules/ease.d.ts +1 -1
  132. package/dist/modules/format.d.ts +1 -1
  133. package/dist/modules/geo.d.ts +1 -1
  134. package/dist/modules/graph.d.ts +1 -1
  135. package/dist/modules/grid.d.ts +1 -1
  136. package/dist/modules/i18n.d.ts +17 -0
  137. package/dist/modules/i18n.js +22 -0
  138. package/dist/modules/index.d.ts +2 -1
  139. package/dist/modules/index.js +24 -1
  140. package/dist/modules/integration.d.ts +1 -1
  141. package/dist/modules/json.d.ts +1 -1
  142. package/dist/modules/llm.d.ts +1 -1
  143. package/dist/modules/math.d.ts +1 -1
  144. package/dist/modules/memory.d.ts +1 -1
  145. package/dist/modules/nn.d.ts +1 -1
  146. package/dist/modules/noise.d.ts +1 -1
  147. package/dist/modules/object.d.ts +1 -1
  148. package/dist/modules/os.d.ts +1 -1
  149. package/dist/modules/path.d.ts +1 -1
  150. package/dist/modules/prob.d.ts +1 -1
  151. package/dist/modules/session.d.ts +1 -1
  152. package/dist/modules/str.d.ts +1 -1
  153. package/dist/modules/tensor.d.ts +1 -1
  154. package/dist/modules/time.d.ts +1 -1
  155. package/dist/modules/trace.d.ts +1 -1
  156. package/dist/modules/train.d.ts +1 -1
  157. package/dist/modules/validate.d.ts +1 -1
  158. package/dist/modules/vector.d.ts +1 -1
  159. package/dist/modules/workspace.d.ts +1 -1
  160. package/dist/registry/factory-signatures.json +1 -1
  161. package/dist/registry.d.ts +10 -3
  162. package/dist/registry.js +31 -1
  163. package/dist/{types-DLx38UFZ.d.ts → types-Cdb321uM.d.ts} +9 -3
  164. package/i18n/ar.json +1 -0
  165. package/i18n/en.json +1 -0
  166. package/i18n/sl.json +1 -0
  167. package/package.json +3 -3
@@ -20,6 +20,7 @@ orbital PerturbOrbital {
20
20
  setpoint : number = 0
21
21
  rate : number = 0
22
22
  running : boolean = false
23
+ speed : number = 1
23
24
  traceSamples : [PerturbSample]
24
25
  curves : [PerturbCurve]
25
26
  }
@@ -46,29 +47,50 @@ orbital PerturbOrbital {
46
47
  padding: md
47
48
  children: [
48
49
  { type: stack, direction: vertical, gap: md, children: [
49
- { type: typography, variant: h4, content: @entity.title },
50
- { type: typography, variant: body, content: @config.taskText },
51
- { type: stack, direction: horizontal, gap: sm, children: [
52
- { type: button, action: RUN, label: "Run", variant: primary, disabled: (if (and (== @entity.running false) (< @entity.t @config.duration)) false true) },
53
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
54
- { type: button, action: RESET, label: "Reset", variant: ghost },
50
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
51
+ { type: stack, direction: vertical, gap: xs, children: [
52
+ { type: typography, variant: h4, content: @entity.title },
53
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
54
+ ] },
55
+ { type: badge, label: (if (>= @entity.t @config.duration) "Done" (if @entity.running "Running" "Paused")), variant: (if (>= @entity.t @config.duration) "success" (if @entity.running "primary" "neutral")) }
56
+ ] },
57
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
58
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (>= @entity.t @config.duration) }),
59
+ { type: button, action: RESET, label: "Reset", icon: "rotate-ccw", variant: ghost },
55
60
  { type: button, action: SHOCK, actionPayload: { delta: @config.shockSize }, label: @config.shockUpLabel, variant: secondary, size: sm },
56
- { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm }
61
+ { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm },
62
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
63
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
64
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
65
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
66
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
67
+ ] },
68
+ { type: typography, variant: caption, color: muted, content: (str/concat "t = " (math/round @entity.t 1) " of " @config.duration) }
57
69
  ] },
58
- { type: stack, direction: vertical, gap: xs, children: [
59
- { type: typography, variant: caption, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
60
- { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
70
+ { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false, keyMap: { Space: TOGGLE_RUN, KeyR: RESET } },
71
+ { type: stack, direction: vertical, gap: sm, children: [
72
+ { type: stack, direction: vertical, gap: xs, children: [
73
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
74
+ { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
75
+ ] },
76
+ { type: stack, direction: vertical, gap: xs, children: [
77
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
78
+ { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
79
+ ] }
61
80
  ] },
62
81
  { type: stack, direction: vertical, gap: xs, children: [
63
- { type: typography, variant: caption, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
64
- { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
65
- ] },
66
- { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false },
67
- { type: typography, variant: body, content: (str/concat "t = " (math/round @entity.t 1) " · " @config.valueLabel " = " (math/round @entity.value 2) " · deviation: " (math/round (- @entity.value @entity.setpoint) 2)) }
82
+ { type: typography, variant: body, content: @config.taskText },
83
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.valueLabel " = " (math/round @entity.value 2) " · deviation " (math/round (- @entity.value @entity.setpoint) 2)) }
84
+ ] }
68
85
  ] }
69
86
  ]
70
87
  })
71
88
 
89
+ MODE_SELECTED -> idle when (and (not (== @config.matchMode "")) (not (== ?mode @config.matchMode)))
90
+ (set @entity.active false)
91
+ (set @entity.running false)
92
+ (render-ui main null)
93
+
72
94
  MODE_SELECTED -> idle when (== ?mode @config.matchMode)
73
95
  (set @entity.active true)
74
96
  (set @entity.title @config.title)
@@ -87,25 +109,134 @@ orbital PerturbOrbital {
87
109
  padding: md
88
110
  children: [
89
111
  { type: stack, direction: vertical, gap: md, children: [
90
- { type: typography, variant: h4, content: @entity.title },
91
- { type: typography, variant: body, content: @config.taskText },
92
- { type: stack, direction: horizontal, gap: sm, children: [
93
- { type: button, action: RUN, label: "Run", variant: primary, disabled: (if (and (== @entity.running false) (< @entity.t @config.duration)) false true) },
94
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
95
- { type: button, action: RESET, label: "Reset", variant: ghost },
112
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
113
+ { type: stack, direction: vertical, gap: xs, children: [
114
+ { type: typography, variant: h4, content: @entity.title },
115
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
116
+ ] },
117
+ { type: badge, label: (if (>= @entity.t @config.duration) "Done" (if @entity.running "Running" "Paused")), variant: (if (>= @entity.t @config.duration) "success" (if @entity.running "primary" "neutral")) }
118
+ ] },
119
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
120
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (>= @entity.t @config.duration) }),
121
+ { type: button, action: RESET, label: "Reset", icon: "rotate-ccw", variant: ghost },
96
122
  { type: button, action: SHOCK, actionPayload: { delta: @config.shockSize }, label: @config.shockUpLabel, variant: secondary, size: sm },
97
- { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm }
123
+ { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm },
124
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
125
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
126
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
127
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
128
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
129
+ ] },
130
+ { type: typography, variant: caption, color: muted, content: (str/concat "t = " (math/round @entity.t 1) " of " @config.duration) }
131
+ ] },
132
+ { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false, keyMap: { Space: TOGGLE_RUN, KeyR: RESET } },
133
+ { type: stack, direction: vertical, gap: sm, children: [
134
+ { type: stack, direction: vertical, gap: xs, children: [
135
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
136
+ { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
137
+ ] },
138
+ { type: stack, direction: vertical, gap: xs, children: [
139
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
140
+ { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
141
+ ] }
98
142
  ] },
99
143
  { type: stack, direction: vertical, gap: xs, children: [
100
- { type: typography, variant: caption, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
101
- { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
144
+ { type: typography, variant: body, content: @config.taskText },
145
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.valueLabel " = " (math/round @entity.value 2) " · deviation " (math/round (- @entity.value @entity.setpoint) 2)) }
146
+ ] }
147
+ ] }
148
+ ]
149
+ })
150
+
151
+ TOGGLE_RUN -> idle when (and (== @entity.active true) (or (== @entity.running true) (< @entity.t @config.duration)))
152
+ (set @entity.running (not @entity.running))
153
+ (render-ui main {
154
+ type: box
155
+ padding: md
156
+ children: [
157
+ { type: stack, direction: vertical, gap: md, children: [
158
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
159
+ { type: stack, direction: vertical, gap: xs, children: [
160
+ { type: typography, variant: h4, content: @entity.title },
161
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
162
+ ] },
163
+ { type: badge, label: (if (>= @entity.t @config.duration) "Done" (if @entity.running "Running" "Paused")), variant: (if (>= @entity.t @config.duration) "success" (if @entity.running "primary" "neutral")) }
164
+ ] },
165
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
166
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (>= @entity.t @config.duration) }),
167
+ { type: button, action: RESET, label: "Reset", icon: "rotate-ccw", variant: ghost },
168
+ { type: button, action: SHOCK, actionPayload: { delta: @config.shockSize }, label: @config.shockUpLabel, variant: secondary, size: sm },
169
+ { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm },
170
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
171
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
172
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
173
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
174
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
175
+ ] },
176
+ { type: typography, variant: caption, color: muted, content: (str/concat "t = " (math/round @entity.t 1) " of " @config.duration) }
177
+ ] },
178
+ { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false, keyMap: { Space: TOGGLE_RUN, KeyR: RESET } },
179
+ { type: stack, direction: vertical, gap: sm, children: [
180
+ { type: stack, direction: vertical, gap: xs, children: [
181
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
182
+ { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
183
+ ] },
184
+ { type: stack, direction: vertical, gap: xs, children: [
185
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
186
+ { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
187
+ ] }
102
188
  ] },
103
189
  { type: stack, direction: vertical, gap: xs, children: [
104
- { type: typography, variant: caption, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
105
- { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
190
+ { type: typography, variant: body, content: @config.taskText },
191
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.valueLabel " = " (math/round @entity.value 2) " · deviation " (math/round (- @entity.value @entity.setpoint) 2)) }
192
+ ] }
193
+ ] }
194
+ ]
195
+ })
196
+ (emit RUN_TOGGLED { running: @entity.running })
197
+
198
+ SPEED_SET -> idle when (== @entity.active true)
199
+ (set @entity.speed ?speed)
200
+ (render-ui main {
201
+ type: box
202
+ padding: md
203
+ children: [
204
+ { type: stack, direction: vertical, gap: md, children: [
205
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
206
+ { type: stack, direction: vertical, gap: xs, children: [
207
+ { type: typography, variant: h4, content: @entity.title },
208
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
209
+ ] },
210
+ { type: badge, label: (if (>= @entity.t @config.duration) "Done" (if @entity.running "Running" "Paused")), variant: (if (>= @entity.t @config.duration) "success" (if @entity.running "primary" "neutral")) }
211
+ ] },
212
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
213
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (>= @entity.t @config.duration) }),
214
+ { type: button, action: RESET, label: "Reset", icon: "rotate-ccw", variant: ghost },
215
+ { type: button, action: SHOCK, actionPayload: { delta: @config.shockSize }, label: @config.shockUpLabel, variant: secondary, size: sm },
216
+ { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm },
217
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
218
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
219
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
220
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
221
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
222
+ ] },
223
+ { type: typography, variant: caption, color: muted, content: (str/concat "t = " (math/round @entity.t 1) " of " @config.duration) }
224
+ ] },
225
+ { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false, keyMap: { Space: TOGGLE_RUN, KeyR: RESET } },
226
+ { type: stack, direction: vertical, gap: sm, children: [
227
+ { type: stack, direction: vertical, gap: xs, children: [
228
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
229
+ { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
230
+ ] },
231
+ { type: stack, direction: vertical, gap: xs, children: [
232
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
233
+ { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
234
+ ] }
106
235
  ] },
107
- { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false },
108
- { type: typography, variant: body, content: (str/concat "t = " (math/round @entity.t 1) " · " @config.valueLabel " = " (math/round @entity.value 2) " · deviation: " (math/round (- @entity.value @entity.setpoint) 2)) }
236
+ { type: stack, direction: vertical, gap: xs, children: [
237
+ { type: typography, variant: body, content: @config.taskText },
238
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.valueLabel " = " (math/round @entity.value 2) " · deviation " (math/round (- @entity.value @entity.setpoint) 2)) }
239
+ ] }
109
240
  ] }
110
241
  ]
111
242
  })
@@ -117,25 +248,41 @@ orbital PerturbOrbital {
117
248
  padding: md
118
249
  children: [
119
250
  { type: stack, direction: vertical, gap: md, children: [
120
- { type: typography, variant: h4, content: @entity.title },
121
- { type: typography, variant: body, content: @config.taskText },
122
- { type: stack, direction: horizontal, gap: sm, children: [
123
- { type: button, action: RUN, label: "Run", variant: primary, disabled: (if (and (== @entity.running false) (< @entity.t @config.duration)) false true) },
124
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
125
- { type: button, action: RESET, label: "Reset", variant: ghost },
251
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
252
+ { type: stack, direction: vertical, gap: xs, children: [
253
+ { type: typography, variant: h4, content: @entity.title },
254
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
255
+ ] },
256
+ { type: badge, label: (if (>= @entity.t @config.duration) "Done" (if @entity.running "Running" "Paused")), variant: (if (>= @entity.t @config.duration) "success" (if @entity.running "primary" "neutral")) }
257
+ ] },
258
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
259
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (>= @entity.t @config.duration) }),
260
+ { type: button, action: RESET, label: "Reset", icon: "rotate-ccw", variant: ghost },
126
261
  { type: button, action: SHOCK, actionPayload: { delta: @config.shockSize }, label: @config.shockUpLabel, variant: secondary, size: sm },
127
- { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm }
262
+ { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm },
263
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
264
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
265
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
266
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
267
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
268
+ ] },
269
+ { type: typography, variant: caption, color: muted, content: (str/concat "t = " (math/round @entity.t 1) " of " @config.duration) }
128
270
  ] },
129
- { type: stack, direction: vertical, gap: xs, children: [
130
- { type: typography, variant: caption, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
131
- { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
271
+ { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false, keyMap: { Space: TOGGLE_RUN, KeyR: RESET } },
272
+ { type: stack, direction: vertical, gap: sm, children: [
273
+ { type: stack, direction: vertical, gap: xs, children: [
274
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
275
+ { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
276
+ ] },
277
+ { type: stack, direction: vertical, gap: xs, children: [
278
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
279
+ { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
280
+ ] }
132
281
  ] },
133
282
  { type: stack, direction: vertical, gap: xs, children: [
134
- { type: typography, variant: caption, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
135
- { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
136
- ] },
137
- { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false },
138
- { type: typography, variant: body, content: (str/concat "t = " (math/round @entity.t 1) " · " @config.valueLabel " = " (math/round @entity.value 2) " · deviation: " (math/round (- @entity.value @entity.setpoint) 2)) }
283
+ { type: typography, variant: body, content: @config.taskText },
284
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.valueLabel " = " (math/round @entity.value 2) " · deviation " (math/round (- @entity.value @entity.setpoint) 2)) }
285
+ ] }
139
286
  ] }
140
287
  ]
141
288
  })
@@ -148,25 +295,41 @@ orbital PerturbOrbital {
148
295
  padding: md
149
296
  children: [
150
297
  { type: stack, direction: vertical, gap: md, children: [
151
- { type: typography, variant: h4, content: @entity.title },
152
- { type: typography, variant: body, content: @config.taskText },
153
- { type: stack, direction: horizontal, gap: sm, children: [
154
- { type: button, action: RUN, label: "Run", variant: primary, disabled: (if (and (== @entity.running false) (< @entity.t @config.duration)) false true) },
155
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
156
- { type: button, action: RESET, label: "Reset", variant: ghost },
298
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
299
+ { type: stack, direction: vertical, gap: xs, children: [
300
+ { type: typography, variant: h4, content: @entity.title },
301
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
302
+ ] },
303
+ { type: badge, label: (if (>= @entity.t @config.duration) "Done" (if @entity.running "Running" "Paused")), variant: (if (>= @entity.t @config.duration) "success" (if @entity.running "primary" "neutral")) }
304
+ ] },
305
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
306
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (>= @entity.t @config.duration) }),
307
+ { type: button, action: RESET, label: "Reset", icon: "rotate-ccw", variant: ghost },
157
308
  { type: button, action: SHOCK, actionPayload: { delta: @config.shockSize }, label: @config.shockUpLabel, variant: secondary, size: sm },
158
- { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm }
309
+ { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm },
310
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
311
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
312
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
313
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
314
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
315
+ ] },
316
+ { type: typography, variant: caption, color: muted, content: (str/concat "t = " (math/round @entity.t 1) " of " @config.duration) }
159
317
  ] },
160
- { type: stack, direction: vertical, gap: xs, children: [
161
- { type: typography, variant: caption, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
162
- { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
318
+ { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false, keyMap: { Space: TOGGLE_RUN, KeyR: RESET } },
319
+ { type: stack, direction: vertical, gap: sm, children: [
320
+ { type: stack, direction: vertical, gap: xs, children: [
321
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
322
+ { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
323
+ ] },
324
+ { type: stack, direction: vertical, gap: xs, children: [
325
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
326
+ { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
327
+ ] }
163
328
  ] },
164
329
  { type: stack, direction: vertical, gap: xs, children: [
165
- { type: typography, variant: caption, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
166
- { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
167
- ] },
168
- { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false },
169
- { type: typography, variant: body, content: (str/concat "t = " (math/round @entity.t 1) " · " @config.valueLabel " = " (math/round @entity.value 2) " · deviation: " (math/round (- @entity.value @entity.setpoint) 2)) }
330
+ { type: typography, variant: body, content: @config.taskText },
331
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.valueLabel " = " (math/round @entity.value 2) " · deviation " (math/round (- @entity.value @entity.setpoint) 2)) }
332
+ ] }
170
333
  ] }
171
334
  ]
172
335
  })
@@ -188,25 +351,41 @@ orbital PerturbOrbital {
188
351
  padding: md
189
352
  children: [
190
353
  { type: stack, direction: vertical, gap: md, children: [
191
- { type: typography, variant: h4, content: @entity.title },
192
- { type: typography, variant: body, content: @config.taskText },
193
- { type: stack, direction: horizontal, gap: sm, children: [
194
- { type: button, action: RUN, label: "Run", variant: primary, disabled: (if (and (== @entity.running false) (< @entity.t @config.duration)) false true) },
195
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
196
- { type: button, action: RESET, label: "Reset", variant: ghost },
354
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
355
+ { type: stack, direction: vertical, gap: xs, children: [
356
+ { type: typography, variant: h4, content: @entity.title },
357
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
358
+ ] },
359
+ { type: badge, label: (if (>= @entity.t @config.duration) "Done" (if @entity.running "Running" "Paused")), variant: (if (>= @entity.t @config.duration) "success" (if @entity.running "primary" "neutral")) }
360
+ ] },
361
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
362
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (>= @entity.t @config.duration) }),
363
+ { type: button, action: RESET, label: "Reset", icon: "rotate-ccw", variant: ghost },
197
364
  { type: button, action: SHOCK, actionPayload: { delta: @config.shockSize }, label: @config.shockUpLabel, variant: secondary, size: sm },
198
- { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm }
365
+ { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm },
366
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
367
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
368
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
369
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
370
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
371
+ ] },
372
+ { type: typography, variant: caption, color: muted, content: (str/concat "t = " (math/round @entity.t 1) " of " @config.duration) }
199
373
  ] },
200
- { type: stack, direction: vertical, gap: xs, children: [
201
- { type: typography, variant: caption, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
202
- { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
374
+ { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false, keyMap: { Space: TOGGLE_RUN, KeyR: RESET } },
375
+ { type: stack, direction: vertical, gap: sm, children: [
376
+ { type: stack, direction: vertical, gap: xs, children: [
377
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
378
+ { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
379
+ ] },
380
+ { type: stack, direction: vertical, gap: xs, children: [
381
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
382
+ { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
383
+ ] }
203
384
  ] },
204
385
  { type: stack, direction: vertical, gap: xs, children: [
205
- { type: typography, variant: caption, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
206
- { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
207
- ] },
208
- { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false },
209
- { type: typography, variant: body, content: (str/concat "t = " (math/round @entity.t 1) " · " @config.valueLabel " = " (math/round @entity.value 2) " · deviation: " (math/round (- @entity.value @entity.setpoint) 2)) }
386
+ { type: typography, variant: body, content: @config.taskText },
387
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.valueLabel " = " (math/round @entity.value 2) " · deviation " (math/round (- @entity.value @entity.setpoint) 2)) }
388
+ ] }
210
389
  ] }
211
390
  ]
212
391
  })
@@ -223,25 +402,41 @@ orbital PerturbOrbital {
223
402
  padding: md
224
403
  children: [
225
404
  { type: stack, direction: vertical, gap: md, children: [
226
- { type: typography, variant: h4, content: @entity.title },
227
- { type: typography, variant: body, content: @config.taskText },
228
- { type: stack, direction: horizontal, gap: sm, children: [
229
- { type: button, action: RUN, label: "Run", variant: primary, disabled: (if (and (== @entity.running false) (< @entity.t @config.duration)) false true) },
230
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
231
- { type: button, action: RESET, label: "Reset", variant: ghost },
405
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
406
+ { type: stack, direction: vertical, gap: xs, children: [
407
+ { type: typography, variant: h4, content: @entity.title },
408
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
409
+ ] },
410
+ { type: badge, label: (if (>= @entity.t @config.duration) "Done" (if @entity.running "Running" "Paused")), variant: (if (>= @entity.t @config.duration) "success" (if @entity.running "primary" "neutral")) }
411
+ ] },
412
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
413
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (>= @entity.t @config.duration) }),
414
+ { type: button, action: RESET, label: "Reset", icon: "rotate-ccw", variant: ghost },
232
415
  { type: button, action: SHOCK, actionPayload: { delta: @config.shockSize }, label: @config.shockUpLabel, variant: secondary, size: sm },
233
- { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm }
416
+ { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm },
417
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
418
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
419
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
420
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
421
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
422
+ ] },
423
+ { type: typography, variant: caption, color: muted, content: (str/concat "t = " (math/round @entity.t 1) " of " @config.duration) }
234
424
  ] },
235
- { type: stack, direction: vertical, gap: xs, children: [
236
- { type: typography, variant: caption, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
237
- { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
425
+ { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false, keyMap: { Space: TOGGLE_RUN, KeyR: RESET } },
426
+ { type: stack, direction: vertical, gap: sm, children: [
427
+ { type: stack, direction: vertical, gap: xs, children: [
428
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
429
+ { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
430
+ ] },
431
+ { type: stack, direction: vertical, gap: xs, children: [
432
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
433
+ { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
434
+ ] }
238
435
  ] },
239
436
  { type: stack, direction: vertical, gap: xs, children: [
240
- { type: typography, variant: caption, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
241
- { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
242
- ] },
243
- { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false },
244
- { type: typography, variant: body, content: (str/concat "t = " (math/round @entity.t 1) " · " @config.valueLabel " = " (math/round @entity.value 2) " · deviation: " (math/round (- @entity.value @entity.setpoint) 2)) }
437
+ { type: typography, variant: body, content: @config.taskText },
438
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.valueLabel " = " (math/round @entity.value 2) " · deviation " (math/round (- @entity.value @entity.setpoint) 2)) }
439
+ ] }
245
440
  ] }
246
441
  ]
247
442
  })
@@ -254,25 +449,41 @@ orbital PerturbOrbital {
254
449
  padding: md
255
450
  children: [
256
451
  { type: stack, direction: vertical, gap: md, children: [
257
- { type: typography, variant: h4, content: @entity.title },
258
- { type: typography, variant: body, content: @config.taskText },
259
- { type: stack, direction: horizontal, gap: sm, children: [
260
- { type: button, action: RUN, label: "Run", variant: primary, disabled: (if (and (== @entity.running false) (< @entity.t @config.duration)) false true) },
261
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
262
- { type: button, action: RESET, label: "Reset", variant: ghost },
452
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
453
+ { type: stack, direction: vertical, gap: xs, children: [
454
+ { type: typography, variant: h4, content: @entity.title },
455
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
456
+ ] },
457
+ { type: badge, label: (if (>= @entity.t @config.duration) "Done" (if @entity.running "Running" "Paused")), variant: (if (>= @entity.t @config.duration) "success" (if @entity.running "primary" "neutral")) }
458
+ ] },
459
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
460
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (>= @entity.t @config.duration) }),
461
+ { type: button, action: RESET, label: "Reset", icon: "rotate-ccw", variant: ghost },
263
462
  { type: button, action: SHOCK, actionPayload: { delta: @config.shockSize }, label: @config.shockUpLabel, variant: secondary, size: sm },
264
- { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm }
463
+ { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm },
464
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
465
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
466
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
467
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
468
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
469
+ ] },
470
+ { type: typography, variant: caption, color: muted, content: (str/concat "t = " (math/round @entity.t 1) " of " @config.duration) }
265
471
  ] },
266
- { type: stack, direction: vertical, gap: xs, children: [
267
- { type: typography, variant: caption, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
268
- { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
472
+ { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false, keyMap: { Space: TOGGLE_RUN, KeyR: RESET } },
473
+ { type: stack, direction: vertical, gap: sm, children: [
474
+ { type: stack, direction: vertical, gap: xs, children: [
475
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
476
+ { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
477
+ ] },
478
+ { type: stack, direction: vertical, gap: xs, children: [
479
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
480
+ { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
481
+ ] }
269
482
  ] },
270
483
  { type: stack, direction: vertical, gap: xs, children: [
271
- { type: typography, variant: caption, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
272
- { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
273
- ] },
274
- { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false },
275
- { type: typography, variant: body, content: (str/concat "t = " (math/round @entity.t 1) " · " @config.valueLabel " = " (math/round @entity.value 2) " · deviation: " (math/round (- @entity.value @entity.setpoint) 2)) }
484
+ { type: typography, variant: body, content: @config.taskText },
485
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.valueLabel " = " (math/round @entity.value 2) " · deviation " (math/round (- @entity.value @entity.setpoint) 2)) }
486
+ ] }
276
487
  ] }
277
488
  ]
278
489
  })
@@ -290,25 +501,41 @@ orbital PerturbOrbital {
290
501
  padding: md
291
502
  children: [
292
503
  { type: stack, direction: vertical, gap: md, children: [
293
- { type: typography, variant: h4, content: @entity.title },
294
- { type: typography, variant: body, content: @config.taskText },
295
- { type: stack, direction: horizontal, gap: sm, children: [
296
- { type: button, action: RUN, label: "Run", variant: primary, disabled: (if (and (== @entity.running false) (< @entity.t @config.duration)) false true) },
297
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
298
- { type: button, action: RESET, label: "Reset", variant: ghost },
504
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
505
+ { type: stack, direction: vertical, gap: xs, children: [
506
+ { type: typography, variant: h4, content: @entity.title },
507
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
508
+ ] },
509
+ { type: badge, label: (if (>= @entity.t @config.duration) "Done" (if @entity.running "Running" "Paused")), variant: (if (>= @entity.t @config.duration) "success" (if @entity.running "primary" "neutral")) }
510
+ ] },
511
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
512
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (>= @entity.t @config.duration) }),
513
+ { type: button, action: RESET, label: "Reset", icon: "rotate-ccw", variant: ghost },
299
514
  { type: button, action: SHOCK, actionPayload: { delta: @config.shockSize }, label: @config.shockUpLabel, variant: secondary, size: sm },
300
- { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm }
515
+ { type: button, action: SHOCK, actionPayload: { delta: (- 0 @config.shockSize) }, label: @config.shockDownLabel, variant: secondary, size: sm },
516
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
517
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
518
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
519
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
520
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
521
+ ] },
522
+ { type: typography, variant: caption, color: muted, content: (str/concat "t = " (math/round @entity.t 1) " of " @config.duration) }
301
523
  ] },
302
- { type: stack, direction: vertical, gap: xs, children: [
303
- { type: typography, variant: caption, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
304
- { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
524
+ { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false, keyMap: { Space: TOGGLE_RUN, KeyR: RESET } },
525
+ { type: stack, direction: vertical, gap: sm, children: [
526
+ { type: stack, direction: vertical, gap: xs, children: [
527
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.setpointLabel ": " (math/round @entity.setpoint 1)) },
528
+ { type: range-slider, min: @config.setpointMin, max: @config.setpointMax, step: @config.setpointStep, value: @entity.setpoint, showTooltip: true, action: "SET_POINT" }
529
+ ] },
530
+ { type: stack, direction: vertical, gap: xs, children: [
531
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
532
+ { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
533
+ ] }
305
534
  ] },
306
535
  { type: stack, direction: vertical, gap: xs, children: [
307
- { type: typography, variant: caption, content: (str/concat @config.rateLabel ": " (math/round @entity.rate 2)) },
308
- { type: range-slider, min: @config.rateMin, max: @config.rateMax, step: @config.rateStep, value: @entity.rate, showTooltip: true, action: "SET_RATE" }
309
- ] },
310
- { type: math-canvas, width: @config.width, height: @config.height, title: "", xMin: 0, xMax: @config.duration, yMin: @config.valueMin, yMax: @config.valueMax, showAxes: true, showGrid: true, gridStep: @config.gridStep, curves: @entity.curves, points: [], vectors: [], shapes: [], interactive: false, animate: false },
311
- { type: typography, variant: body, content: (str/concat "t = " (math/round @entity.t 1) " · " @config.valueLabel " = " (math/round @entity.value 2) " · deviation: " (math/round (- @entity.value @entity.setpoint) 2)) }
536
+ { type: typography, variant: body, content: @config.taskText },
537
+ { type: typography, variant: caption, color: muted, content: (str/concat @config.valueLabel " = " (math/round @entity.value 2) " · deviation " (math/round (- @entity.value @entity.setpoint) 2)) }
538
+ ] }
312
539
  ] }
313
540
  ]
314
541
  })
@@ -318,8 +545,8 @@ orbital PerturbOrbital {
318
545
  ticks {
319
546
  perturbTick every 100ms
320
547
  when (and (== @entity.active true) (and (== @entity.running true) (< @entity.t @config.duration)))
321
- (set @entity.t (math/min @config.duration (+ @entity.t 0.1)))
322
- (set @entity.value (+ @entity.value (* @entity.rate (- @entity.setpoint @entity.value) 0.1)))
548
+ (set @entity.t (math/min @config.duration (+ @entity.t (* 0.1 @entity.speed))))
549
+ (set @entity.value (+ @entity.value (* (math/min 1 (* @entity.rate (* 0.1 @entity.speed))) (- @entity.setpoint @entity.value))))
323
550
  (set @entity.traceSamples (array/append @entity.traceSamples { x: @entity.t, y: @entity.value }))
324
551
  (set @entity.curves [
325
552
  { label: @config.setpointLabel, color: @config.setpointColor, samples: [ { x: 0, y: @entity.setpoint }, { x: @config.duration, y: @entity.setpoint } ] },
@@ -334,6 +561,15 @@ orbital PerturbOrbital {
334
561
  }
335
562
  @description "Lab router broadcast naming the active mode; this instrument renders when the mode equals matchMode, otherwise renders null."
336
563
  @tier "domain"
564
+ TOGGLE_RUN {
565
+ }
566
+ @description "Learner pressed the Play/Pause toggle — starts or freezes the regulation loop."
567
+ @tier "domain"
568
+ SPEED_SET {
569
+ speed : number
570
+ }
571
+ @description "The learner picked a playback speed; the loop relaxes that many times faster."
572
+ @tier "presentation"
337
573
  RUN {
338
574
  }
339
575
  @description "Learner started (or resumed) the regulation loop; the trace draws live until Pause or the configured duration."
@@ -401,7 +637,11 @@ orbital PerturbOrbital {
401
637
  @tier "presentation"
402
638
  title : string = "Equilibrium & Shock"
403
639
  @label "Title"
404
- @description "Title shown above the simulation."
640
+ @description "The instrument's name, the header's one hero."
641
+ @tier "presentation"
642
+ summary : string = ""
643
+ @label "Concept"
644
+ @description "One line under the title saying what the learner is looking at (keep it under ~65 characters). Empty shows no line."
405
645
  @tier "presentation"
406
646
  taskText : string = "Run the system, shock it off its setpoint, and watch it recover — then move the setpoint mid-run."
407
647
  @label "Task Text"
@@ -471,10 +711,10 @@ orbital PerturbOrbital {
471
711
  gridStep : number = 2
472
712
  @label "Grid Step"
473
713
  @tier "presentation"
474
- valueColor : string = "#dc2626"
714
+ valueColor : string = "highlight"
475
715
  @label "Value Color"
476
716
  @tier "presentation"
477
- setpointColor : string = "#94a3b8"
717
+ setpointColor : string = "guide"
478
718
  @label "Setpoint Color"
479
719
  @tier "presentation"
480
720
  autoRun : boolean = true