@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,307 @@
1
+ app std-learn-scenario "1.0.0"
2
+ "Generic branching decision scenario — a config-declared node graph of situations and choices plays out as a choose-your-path story with modeled consequences: every choice shifts two config-named metrics, appends to a visible decision log, and moves to its declared next node, until a node with no choices ends the run with the final metrics and the full path. This simulates decisions rather than browsing records: the learner feels trade-offs compound. All vocabulary is config-declared, so the same mechanism serves any consequence-chain lesson: fiscal policy choices, ecosystem interventions, historical what-ifs, medical triage, engineering trade-offs. Standalone by default; behind a lab router set matchMode and it renders only when MODE_SELECTED matches."
3
+ orbital ScenarioOrbital {
4
+ type ScenarioChoice = {
5
+ label : string!
6
+ next : string!
7
+ deltaA : number
8
+ deltaB : number
9
+ note : string
10
+ }
11
+ type ScenarioNode = {
12
+ id : string!
13
+ situation : string!
14
+ choices : [ScenarioChoice]
15
+ }
16
+
17
+ entity ScenarioScene [runtime] {
18
+ id : string!
19
+ active : boolean = false
20
+ title : string
21
+ currentId : string
22
+ situation : string
23
+ choices : [ScenarioChoice]
24
+ metricA : number = 0
25
+ metricB : number = 0
26
+ stepCount : number = 0
27
+ path : [string]
28
+ done : boolean = false
29
+ feedback : string
30
+ view : [node]
31
+ }
32
+
33
+ trait ScenarioSim -> @rebindable ScenarioScene [interaction, instance] {
34
+ initial: idle
35
+
36
+ state idle {
37
+ INIT -> idle when (== @config.matchMode "")
38
+ (let (
39
+ (start (array/find @config.nodes (fn n (== (object/get @n id) @config.startId))))
40
+ )
41
+ (do
42
+ (set @entity.active true)
43
+ (set @entity.title @config.title)
44
+ (set @entity.currentId @config.startId)
45
+ (set @entity.situation (object/get @start situation))
46
+ (set @entity.choices (object/get @start choices []))
47
+ (set @entity.metricA @config.metricAStart)
48
+ (set @entity.metricB @config.metricBStart)
49
+ (set @entity.stepCount 0)
50
+ (set @entity.path [])
51
+ (set @entity.done false)
52
+ (set @entity.feedback "")
53
+ (set @entity.view [
54
+ { type: card, variant: bordered, padding: md, children: [
55
+ { type: typography, variant: body, content: @entity.situation }
56
+ ] }
57
+ ])))
58
+ (render-ui main {
59
+ type: box
60
+ padding: md
61
+ children: [
62
+ { type: stack, direction: vertical, gap: md, children: [
63
+ { type: typography, variant: h4, content: @entity.title },
64
+ { type: typography, variant: body, content: (str/concat @config.metricALabel ": " (math/round @entity.metricA 0) " · " @config.metricBLabel ": " (math/round @entity.metricB 0) " · decisions: " (str/concat "" @entity.stepCount)) },
65
+ { type: stack, direction: vertical, gap: md, children: @entity.view },
66
+ { type: stack, direction: vertical, gap: xs, children: (array/map (array/range 0 (array/len @entity.choices)) (fn (i)
67
+ { type: button, action: CHOOSE, actionPayload: { index: @i }, label: (object/get (array/nth @entity.choices @i) label), variant: secondary, size: sm, disabled: (if @entity.done true false) })) },
68
+ { type: typography, variant: caption, content: @entity.feedback },
69
+ { type: stack, direction: vertical, gap: xs, children: (array/map @entity.path (fn (line)
70
+ { type: typography, variant: caption, color: muted, content: @line })) },
71
+ { type: stack, direction: horizontal, gap: sm, children: [
72
+ { type: button, action: RESTART, label: @config.restartLabel, variant: primary, disabled: (if @entity.done false true) }
73
+ ] }
74
+ ] }
75
+ ]
76
+ })
77
+
78
+ MODE_SELECTED -> idle when (== ?mode @config.matchMode)
79
+ (let (
80
+ (start (array/find @config.nodes (fn n (== (object/get @n id) @config.startId))))
81
+ )
82
+ (do
83
+ (set @entity.active true)
84
+ (set @entity.title @config.title)
85
+ (set @entity.currentId @config.startId)
86
+ (set @entity.situation (object/get @start situation))
87
+ (set @entity.choices (object/get @start choices []))
88
+ (set @entity.metricA @config.metricAStart)
89
+ (set @entity.metricB @config.metricBStart)
90
+ (set @entity.stepCount 0)
91
+ (set @entity.path [])
92
+ (set @entity.done false)
93
+ (set @entity.feedback "")
94
+ (set @entity.view [
95
+ { type: card, variant: bordered, padding: md, children: [
96
+ { type: typography, variant: body, content: @entity.situation }
97
+ ] }
98
+ ])))
99
+ (render-ui main {
100
+ type: box
101
+ padding: md
102
+ children: [
103
+ { type: stack, direction: vertical, gap: md, children: [
104
+ { type: typography, variant: h4, content: @entity.title },
105
+ { type: typography, variant: body, content: (str/concat @config.metricALabel ": " (math/round @entity.metricA 0) " · " @config.metricBLabel ": " (math/round @entity.metricB 0) " · decisions: " (str/concat "" @entity.stepCount)) },
106
+ { type: stack, direction: vertical, gap: md, children: @entity.view },
107
+ { type: stack, direction: vertical, gap: xs, children: (array/map (array/range 0 (array/len @entity.choices)) (fn (i)
108
+ { type: button, action: CHOOSE, actionPayload: { index: @i }, label: (object/get (array/nth @entity.choices @i) label), variant: secondary, size: sm, disabled: (if @entity.done true false) })) },
109
+ { type: typography, variant: caption, content: @entity.feedback },
110
+ { type: stack, direction: vertical, gap: xs, children: (array/map @entity.path (fn (line)
111
+ { type: typography, variant: caption, color: muted, content: @line })) },
112
+ { type: stack, direction: horizontal, gap: sm, children: [
113
+ { type: button, action: RESTART, label: @config.restartLabel, variant: primary, disabled: (if @entity.done false true) }
114
+ ] }
115
+ ] }
116
+ ]
117
+ })
118
+
119
+ CHOOSE -> idle when (and (== @entity.active true) (== @entity.done false))
120
+ (let (
121
+ (choice (array/nth @entity.choices ?index))
122
+ (nextId (object/get @choice next))
123
+ (nextNode (array/find @config.nodes (fn n (== (object/get @n id) @nextId))))
124
+ (nextChoices (object/get @nextNode choices []))
125
+ (ended (== (array/len @nextChoices) 0))
126
+ (note (object/get @choice note ""))
127
+ (newA (+ @entity.metricA (object/get @choice deltaA 0)))
128
+ (newB (+ @entity.metricB (object/get @choice deltaB 0)))
129
+ )
130
+ (do
131
+ (set @entity.currentId @nextId)
132
+ (set @entity.situation (object/get @nextNode situation))
133
+ (set @entity.choices @nextChoices)
134
+ (set @entity.metricA @newA)
135
+ (set @entity.metricB @newB)
136
+ (set @entity.stepCount (+ @entity.stepCount 1))
137
+ (set @entity.path (array/append @entity.path (str/concat (str/concat "" @entity.stepCount) ". " (object/get @choice label) (if (!= @note "") (str/concat " — " @note) ""))))
138
+ (set @entity.done (if @ended true false))
139
+ (set @entity.feedback (if (!= @note "") @note ""))
140
+ (set @entity.view (if @ended
141
+ [
142
+ { type: card, variant: bordered, padding: md, children: [
143
+ { type: typography, variant: h4, content: "Outcome" },
144
+ { type: typography, variant: body, content: @entity.situation },
145
+ { type: typography, variant: body, content: (str/concat "Final " @config.metricALabel ": " (math/round @entity.metricA 0) " · final " @config.metricBLabel ": " (math/round @entity.metricB 0) " after " (str/concat "" @entity.stepCount) " decisions.") }
146
+ ] }
147
+ ]
148
+ [
149
+ { type: card, variant: bordered, padding: md, children: [
150
+ { type: typography, variant: body, content: @entity.situation }
151
+ ] }
152
+ ])))
153
+ )
154
+ (render-ui main {
155
+ type: box
156
+ padding: md
157
+ children: [
158
+ { type: stack, direction: vertical, gap: md, children: [
159
+ { type: typography, variant: h4, content: @entity.title },
160
+ { type: typography, variant: body, content: (str/concat @config.metricALabel ": " (math/round @entity.metricA 0) " · " @config.metricBLabel ": " (math/round @entity.metricB 0) " · decisions: " (str/concat "" @entity.stepCount)) },
161
+ { type: stack, direction: vertical, gap: md, children: @entity.view },
162
+ { type: stack, direction: vertical, gap: xs, children: (array/map (array/range 0 (array/len @entity.choices)) (fn (i)
163
+ { type: button, action: CHOOSE, actionPayload: { index: @i }, label: (object/get (array/nth @entity.choices @i) label), variant: secondary, size: sm, disabled: (if @entity.done true false) })) },
164
+ { type: typography, variant: caption, content: @entity.feedback },
165
+ { type: stack, direction: vertical, gap: xs, children: (array/map @entity.path (fn (line)
166
+ { type: typography, variant: caption, color: muted, content: @line })) },
167
+ { type: stack, direction: horizontal, gap: sm, children: [
168
+ { type: button, action: RESTART, label: @config.restartLabel, variant: primary, disabled: (if @entity.done false true) }
169
+ ] }
170
+ ] }
171
+ ]
172
+ })
173
+ (emit CHOICE_MADE { nodeId: @entity.currentId, metricA: @entity.metricA, metricB: @entity.metricB, ended: @entity.done })
174
+
175
+ RESTART -> idle when (and (== @entity.active true) (== @entity.done true))
176
+ (let (
177
+ (start (array/find @config.nodes (fn n (== (object/get @n id) @config.startId))))
178
+ )
179
+ (do
180
+ (set @entity.active true)
181
+ (set @entity.title @config.title)
182
+ (set @entity.currentId @config.startId)
183
+ (set @entity.situation (object/get @start situation))
184
+ (set @entity.choices (object/get @start choices []))
185
+ (set @entity.metricA @config.metricAStart)
186
+ (set @entity.metricB @config.metricBStart)
187
+ (set @entity.stepCount 0)
188
+ (set @entity.path [])
189
+ (set @entity.done false)
190
+ (set @entity.feedback "")
191
+ (set @entity.view [
192
+ { type: card, variant: bordered, padding: md, children: [
193
+ { type: typography, variant: body, content: @entity.situation }
194
+ ] }
195
+ ])))
196
+ (render-ui main {
197
+ type: box
198
+ padding: md
199
+ children: [
200
+ { type: stack, direction: vertical, gap: md, children: [
201
+ { type: typography, variant: h4, content: @entity.title },
202
+ { type: typography, variant: body, content: (str/concat @config.metricALabel ": " (math/round @entity.metricA 0) " · " @config.metricBLabel ": " (math/round @entity.metricB 0) " · decisions: " (str/concat "" @entity.stepCount)) },
203
+ { type: stack, direction: vertical, gap: md, children: @entity.view },
204
+ { type: stack, direction: vertical, gap: xs, children: (array/map (array/range 0 (array/len @entity.choices)) (fn (i)
205
+ { type: button, action: CHOOSE, actionPayload: { index: @i }, label: (object/get (array/nth @entity.choices @i) label), variant: secondary, size: sm, disabled: (if @entity.done true false) })) },
206
+ { type: typography, variant: caption, content: @entity.feedback },
207
+ { type: stack, direction: vertical, gap: xs, children: (array/map @entity.path (fn (line)
208
+ { type: typography, variant: caption, color: muted, content: @line })) },
209
+ { type: stack, direction: horizontal, gap: sm, children: [
210
+ { type: button, action: RESTART, label: @config.restartLabel, variant: primary, disabled: (if @entity.done false true) }
211
+ ] }
212
+ ] }
213
+ ]
214
+ })
215
+ (emit SCENARIO_RESTARTED { startId: @config.startId })
216
+ }
217
+
218
+ listens {
219
+ MODE_SELECTED {
220
+ mode : string
221
+ }
222
+ @description "Lab router broadcast naming the active mode; this instrument renders when the mode equals matchMode, otherwise renders null."
223
+ @tier "domain"
224
+ CHOOSE {
225
+ index : number
226
+ }
227
+ @description "Learner picked one of the current node's choices — index selects it; its metric deltas apply, the decision log grows, and the story moves to the declared next node."
228
+ @tier "domain"
229
+ RESTART {
230
+ }
231
+ @description "Learner restarted the scenario from the start node with fresh metrics and an empty decision log."
232
+ @tier "domain"
233
+ }
234
+
235
+ emits {
236
+ CHOICE_MADE -> external {
237
+ nodeId : string
238
+ metricA : number
239
+ metricB : number
240
+ ended : boolean
241
+ }
242
+ @description "A decision was taken: the node reached, both metric values after the deltas, and whether the run just ended — a composed listener (coach, grader) can subscribe; a harmless no-op when the atom runs standalone."
243
+ @synonyms "branching scenario, choose your path, decision tree, consequences, trade-offs, what-if"
244
+ @tier "domain"
245
+ SCENARIO_RESTARTED -> external {
246
+ startId : string
247
+ }
248
+ @description "The learner restarted the scenario from scratch."
249
+ @tier "domain"
250
+ }
251
+
252
+ config {
253
+ matchMode : string = ""
254
+ @label "Match Mode"
255
+ @description "When composed behind a lab router, the MODE_SELECTED mode value this instrument responds to. Empty string means standalone — render immediately on INIT."
256
+ @tier "presentation"
257
+ title : string = "Decision Scenario"
258
+ @label "Title"
259
+ @description "Title shown above the scenario."
260
+ @tier "presentation"
261
+ metricALabel : string = "Budget"
262
+ @label "Metric A Label"
263
+ @description "Display name of the first tracked consequence metric."
264
+ @tier "domain"
265
+ metricBLabel : string = "Support"
266
+ @label "Metric B Label"
267
+ @description "Display name of the second tracked consequence metric."
268
+ @tier "domain"
269
+ metricAStart : number = 100
270
+ @label "Metric A Start"
271
+ @tier "domain"
272
+ metricBStart : number = 50
273
+ @label "Metric B Start"
274
+ @tier "domain"
275
+ startId : string = "start"
276
+ @label "Start Node"
277
+ @description "Id of the node the scenario opens on."
278
+ @tier "domain"
279
+ nodes : [ScenarioNode] = [
280
+ { id: "start", situation: "Your town's river floods every spring. The council asks you to lead the response.", choices: [
281
+ { label: "Build a levee now", next: "levee", deltaA: -60, deltaB: 10, note: "Fast protection, most of the budget gone." },
282
+ { label: "Restore the upstream wetlands", next: "wetlands", deltaA: -25, deltaB: -5, note: "Cheaper and greener, but slower to work." }
283
+ ] },
284
+ { id: "levee", situation: "The levee holds the first spring, but silt builds up and the river rises higher each year.", choices: [
285
+ { label: "Raise the levee again", next: "end-lock-in", deltaA: -30, deltaB: -10, note: "Costs keep compounding." },
286
+ { label: "Add wetlands as a second line", next: "end-hybrid", deltaA: -20, deltaB: 15, note: "Late, but it eases the pressure." }
287
+ ] },
288
+ { id: "wetlands", situation: "Two springs pass; the wetlands now absorb most floods, and wildlife is returning.", choices: [
289
+ { label: "Expand the wetland corridor", next: "end-green", deltaA: -15, deltaB: 25, note: "The town rallies behind the project." },
290
+ { label: "Bank the savings", next: "end-thrifty", deltaA: 10, deltaB: 5, note: "Prudent, though the corridor stays small." }
291
+ ] },
292
+ { id: "end-lock-in", situation: "You are locked into an arms race with the river: protected, but at ever-growing cost.", choices: [] },
293
+ { id: "end-hybrid", situation: "The hybrid defense works: the levee buys time and the wetlands cut the peak floods.", choices: [] },
294
+ { id: "end-green", situation: "The restored river corridor protects the town and becomes its most loved public space.", choices: [] },
295
+ { id: "end-thrifty", situation: "The town stays safe in normal years, with savings in reserve for a bigger flood.", choices: [] }
296
+ ]
297
+ @label "Nodes"
298
+ @description "The decision graph; each node carries a situation and its choices, each choice naming the next node, metric deltas, and a consequence note. A node with no choices ends the run. Realizations declare their own subject's decision tree here."
299
+ @tier "domain"
300
+ restartLabel : string = "Play Again"
301
+ @label "Restart Label"
302
+ @tier "presentation"
303
+ }
304
+ }
305
+
306
+ page "/learn-scenario" as ScenarioPage -> ScenarioSim
307
+ }
@@ -0,0 +1,318 @@
1
+ app std-learn-sim-3d "1.0.0"
2
+ "Generic 3D simulation-transport (clock) instrument. Contract: the atom owns the transport and the clock but NOT the physics — Run/Pause/Reset plus a 0.5x/1x/2x/4x speed row drive a running flag and speed multiplier, and a 32ms tick advances steps and simTime by dt x speedMultiplier, emitting SIM_3D_TICK { dt, simTime } each step WITHOUT rendering; a composed sibling trait transitions directly on the SIM_3D_TICK / SIM_3D_UPDATED broadcasts (the same bare-event cascade the lab routers use), applies the domain physics, and owns the scene render — this atom renders no canvas. A caption readout shows steps, sim time, and current speed. Standalone by default; behind a lab router set matchMode and it renders only when MODE_SELECTED matches."
3
+ orbital LearnSim3DOrbital {
4
+ entity Learn3DSimScene [runtime] {
5
+ id : string!
6
+ active : boolean = false
7
+ title : string
8
+ running : boolean = false
9
+ speedMultiplier : number = 1
10
+ steps : int = 0
11
+ simTime : number = 0
12
+ }
13
+
14
+ trait Learn3DSimTransport -> @rebindable Learn3DSimScene [interaction, instance] {
15
+ initial: idle
16
+
17
+ state idle {
18
+ INIT -> idle when (== @config.matchMode "")
19
+ (set @entity.active true)
20
+ (set @entity.title @config.title)
21
+ (set @entity.running @config.running)
22
+ (set @entity.speedMultiplier 1)
23
+ (set @entity.steps 0)
24
+ (set @entity.simTime 0)
25
+ (render-ui main {
26
+ type: box
27
+ padding: md
28
+ children: [
29
+ { type: stack, direction: vertical, gap: md, children: [
30
+ { type: typography, variant: h4, content: @entity.title },
31
+ { type: stack, direction: horizontal, gap: sm, children: [
32
+ { type: button, action: RUN, label: "Run", variant: primary },
33
+ { type: button, action: PAUSE, label: "Pause", variant: secondary },
34
+ { type: button, action: RESET, label: "Reset", variant: ghost }
35
+ ] },
36
+ { type: stack, direction: horizontal, gap: sm, children: [
37
+ { type: button, action: SPEED_HALF, label: "0.5x", variant: ghost },
38
+ { type: button, action: SPEED_NORMAL, label: "1x", variant: ghost },
39
+ { type: button, action: SPEED_DOUBLE, label: "2x", variant: ghost },
40
+ { type: button, action: SPEED_QUAD, label: "4x", variant: ghost }
41
+ ] },
42
+ { type: typography, variant: caption, content: (str/concat "Steps: " @entity.steps " — sim time: " (math/round @entity.simTime 1) " s — speed: " @entity.speedMultiplier "x") }
43
+ ] }
44
+ ]
45
+ })
46
+ (emit SIM_3D_UPDATED { running: @entity.running, speedMultiplier: @entity.speedMultiplier, steps: @entity.steps, simTime: @entity.simTime })
47
+
48
+ MODE_SELECTED -> idle when (== ?mode @config.matchMode)
49
+ (set @entity.active true)
50
+ (set @entity.title @config.title)
51
+ (set @entity.running @config.running)
52
+ (set @entity.speedMultiplier 1)
53
+ (set @entity.steps 0)
54
+ (set @entity.simTime 0)
55
+ (render-ui main {
56
+ type: box
57
+ padding: md
58
+ children: [
59
+ { type: stack, direction: vertical, gap: md, children: [
60
+ { type: typography, variant: h4, content: @entity.title },
61
+ { type: stack, direction: horizontal, gap: sm, children: [
62
+ { type: button, action: RUN, label: "Run", variant: primary },
63
+ { type: button, action: PAUSE, label: "Pause", variant: secondary },
64
+ { type: button, action: RESET, label: "Reset", variant: ghost }
65
+ ] },
66
+ { type: stack, direction: horizontal, gap: sm, children: [
67
+ { type: button, action: SPEED_HALF, label: "0.5x", variant: ghost },
68
+ { type: button, action: SPEED_NORMAL, label: "1x", variant: ghost },
69
+ { type: button, action: SPEED_DOUBLE, label: "2x", variant: ghost },
70
+ { type: button, action: SPEED_QUAD, label: "4x", variant: ghost }
71
+ ] },
72
+ { type: typography, variant: caption, content: (str/concat "Steps: " @entity.steps " — sim time: " (math/round @entity.simTime 1) " s — speed: " @entity.speedMultiplier "x") }
73
+ ] }
74
+ ]
75
+ })
76
+ (emit SIM_3D_UPDATED { running: @entity.running, speedMultiplier: @entity.speedMultiplier, steps: @entity.steps, simTime: @entity.simTime })
77
+
78
+ RUN -> idle when (== @entity.active true)
79
+ (set @entity.running true)
80
+ (render-ui main {
81
+ type: box
82
+ padding: md
83
+ children: [
84
+ { type: stack, direction: vertical, gap: md, children: [
85
+ { type: typography, variant: h4, content: @entity.title },
86
+ { type: stack, direction: horizontal, gap: sm, children: [
87
+ { type: button, action: RUN, label: "Run", variant: primary },
88
+ { type: button, action: PAUSE, label: "Pause", variant: secondary },
89
+ { type: button, action: RESET, label: "Reset", variant: ghost }
90
+ ] },
91
+ { type: stack, direction: horizontal, gap: sm, children: [
92
+ { type: button, action: SPEED_HALF, label: "0.5x", variant: ghost },
93
+ { type: button, action: SPEED_NORMAL, label: "1x", variant: ghost },
94
+ { type: button, action: SPEED_DOUBLE, label: "2x", variant: ghost },
95
+ { type: button, action: SPEED_QUAD, label: "4x", variant: ghost }
96
+ ] },
97
+ { type: typography, variant: caption, content: (str/concat "Steps: " @entity.steps " — sim time: " (math/round @entity.simTime 1) " s — speed: " @entity.speedMultiplier "x") }
98
+ ] }
99
+ ]
100
+ })
101
+ (emit SIM_3D_UPDATED { running: @entity.running, speedMultiplier: @entity.speedMultiplier, steps: @entity.steps, simTime: @entity.simTime })
102
+
103
+ PAUSE -> idle when (== @entity.active true)
104
+ (set @entity.running false)
105
+ (render-ui main {
106
+ type: box
107
+ padding: md
108
+ children: [
109
+ { type: stack, direction: vertical, gap: md, children: [
110
+ { type: typography, variant: h4, content: @entity.title },
111
+ { type: stack, direction: horizontal, gap: sm, children: [
112
+ { type: button, action: RUN, label: "Run", variant: primary },
113
+ { type: button, action: PAUSE, label: "Pause", variant: secondary },
114
+ { type: button, action: RESET, label: "Reset", variant: ghost }
115
+ ] },
116
+ { type: stack, direction: horizontal, gap: sm, children: [
117
+ { type: button, action: SPEED_HALF, label: "0.5x", variant: ghost },
118
+ { type: button, action: SPEED_NORMAL, label: "1x", variant: ghost },
119
+ { type: button, action: SPEED_DOUBLE, label: "2x", variant: ghost },
120
+ { type: button, action: SPEED_QUAD, label: "4x", variant: ghost }
121
+ ] },
122
+ { type: typography, variant: caption, content: (str/concat "Steps: " @entity.steps " — sim time: " (math/round @entity.simTime 1) " s — speed: " @entity.speedMultiplier "x") }
123
+ ] }
124
+ ]
125
+ })
126
+ (emit SIM_3D_UPDATED { running: @entity.running, speedMultiplier: @entity.speedMultiplier, steps: @entity.steps, simTime: @entity.simTime })
127
+
128
+ RESET -> idle when (== @entity.active true)
129
+ (set @entity.running @config.running)
130
+ (set @entity.speedMultiplier 1)
131
+ (set @entity.steps 0)
132
+ (set @entity.simTime 0)
133
+ (render-ui main {
134
+ type: box
135
+ padding: md
136
+ children: [
137
+ { type: stack, direction: vertical, gap: md, children: [
138
+ { type: typography, variant: h4, content: @entity.title },
139
+ { type: stack, direction: horizontal, gap: sm, children: [
140
+ { type: button, action: RUN, label: "Run", variant: primary },
141
+ { type: button, action: PAUSE, label: "Pause", variant: secondary },
142
+ { type: button, action: RESET, label: "Reset", variant: ghost }
143
+ ] },
144
+ { type: stack, direction: horizontal, gap: sm, children: [
145
+ { type: button, action: SPEED_HALF, label: "0.5x", variant: ghost },
146
+ { type: button, action: SPEED_NORMAL, label: "1x", variant: ghost },
147
+ { type: button, action: SPEED_DOUBLE, label: "2x", variant: ghost },
148
+ { type: button, action: SPEED_QUAD, label: "4x", variant: ghost }
149
+ ] },
150
+ { type: typography, variant: caption, content: (str/concat "Steps: " @entity.steps " — sim time: " (math/round @entity.simTime 1) " s — speed: " @entity.speedMultiplier "x") }
151
+ ] }
152
+ ]
153
+ })
154
+ (emit SIM_3D_UPDATED { running: @entity.running, speedMultiplier: @entity.speedMultiplier, steps: @entity.steps, simTime: @entity.simTime })
155
+
156
+ SPEED_HALF -> idle when (== @entity.active true)
157
+ (set @entity.speedMultiplier 0.5)
158
+ (render-ui main {
159
+ type: box
160
+ padding: md
161
+ children: [
162
+ { type: stack, direction: vertical, gap: md, children: [
163
+ { type: typography, variant: h4, content: @entity.title },
164
+ { type: stack, direction: horizontal, gap: sm, children: [
165
+ { type: button, action: RUN, label: "Run", variant: primary },
166
+ { type: button, action: PAUSE, label: "Pause", variant: secondary },
167
+ { type: button, action: RESET, label: "Reset", variant: ghost }
168
+ ] },
169
+ { type: stack, direction: horizontal, gap: sm, children: [
170
+ { type: button, action: SPEED_HALF, label: "0.5x", variant: ghost },
171
+ { type: button, action: SPEED_NORMAL, label: "1x", variant: ghost },
172
+ { type: button, action: SPEED_DOUBLE, label: "2x", variant: ghost },
173
+ { type: button, action: SPEED_QUAD, label: "4x", variant: ghost }
174
+ ] },
175
+ { type: typography, variant: caption, content: (str/concat "Steps: " @entity.steps " — sim time: " (math/round @entity.simTime 1) " s — speed: " @entity.speedMultiplier "x") }
176
+ ] }
177
+ ]
178
+ })
179
+ (emit SIM_3D_UPDATED { running: @entity.running, speedMultiplier: @entity.speedMultiplier, steps: @entity.steps, simTime: @entity.simTime })
180
+
181
+ SPEED_NORMAL -> idle when (== @entity.active true)
182
+ (set @entity.speedMultiplier 1)
183
+ (render-ui main {
184
+ type: box
185
+ padding: md
186
+ children: [
187
+ { type: stack, direction: vertical, gap: md, children: [
188
+ { type: typography, variant: h4, content: @entity.title },
189
+ { type: stack, direction: horizontal, gap: sm, children: [
190
+ { type: button, action: RUN, label: "Run", variant: primary },
191
+ { type: button, action: PAUSE, label: "Pause", variant: secondary },
192
+ { type: button, action: RESET, label: "Reset", variant: ghost }
193
+ ] },
194
+ { type: stack, direction: horizontal, gap: sm, children: [
195
+ { type: button, action: SPEED_HALF, label: "0.5x", variant: ghost },
196
+ { type: button, action: SPEED_NORMAL, label: "1x", variant: ghost },
197
+ { type: button, action: SPEED_DOUBLE, label: "2x", variant: ghost },
198
+ { type: button, action: SPEED_QUAD, label: "4x", variant: ghost }
199
+ ] },
200
+ { type: typography, variant: caption, content: (str/concat "Steps: " @entity.steps " — sim time: " (math/round @entity.simTime 1) " s — speed: " @entity.speedMultiplier "x") }
201
+ ] }
202
+ ]
203
+ })
204
+ (emit SIM_3D_UPDATED { running: @entity.running, speedMultiplier: @entity.speedMultiplier, steps: @entity.steps, simTime: @entity.simTime })
205
+
206
+ SPEED_DOUBLE -> idle when (== @entity.active true)
207
+ (set @entity.speedMultiplier 2)
208
+ (render-ui main {
209
+ type: box
210
+ padding: md
211
+ children: [
212
+ { type: stack, direction: vertical, gap: md, children: [
213
+ { type: typography, variant: h4, content: @entity.title },
214
+ { type: stack, direction: horizontal, gap: sm, children: [
215
+ { type: button, action: RUN, label: "Run", variant: primary },
216
+ { type: button, action: PAUSE, label: "Pause", variant: secondary },
217
+ { type: button, action: RESET, label: "Reset", variant: ghost }
218
+ ] },
219
+ { type: stack, direction: horizontal, gap: sm, children: [
220
+ { type: button, action: SPEED_HALF, label: "0.5x", variant: ghost },
221
+ { type: button, action: SPEED_NORMAL, label: "1x", variant: ghost },
222
+ { type: button, action: SPEED_DOUBLE, label: "2x", variant: ghost },
223
+ { type: button, action: SPEED_QUAD, label: "4x", variant: ghost }
224
+ ] },
225
+ { type: typography, variant: caption, content: (str/concat "Steps: " @entity.steps " — sim time: " (math/round @entity.simTime 1) " s — speed: " @entity.speedMultiplier "x") }
226
+ ] }
227
+ ]
228
+ })
229
+ (emit SIM_3D_UPDATED { running: @entity.running, speedMultiplier: @entity.speedMultiplier, steps: @entity.steps, simTime: @entity.simTime })
230
+
231
+ SPEED_QUAD -> idle when (== @entity.active true)
232
+ (set @entity.speedMultiplier 4)
233
+ (render-ui main {
234
+ type: box
235
+ padding: md
236
+ children: [
237
+ { type: stack, direction: vertical, gap: md, children: [
238
+ { type: typography, variant: h4, content: @entity.title },
239
+ { type: stack, direction: horizontal, gap: sm, children: [
240
+ { type: button, action: RUN, label: "Run", variant: primary },
241
+ { type: button, action: PAUSE, label: "Pause", variant: secondary },
242
+ { type: button, action: RESET, label: "Reset", variant: ghost }
243
+ ] },
244
+ { type: stack, direction: horizontal, gap: sm, children: [
245
+ { type: button, action: SPEED_HALF, label: "0.5x", variant: ghost },
246
+ { type: button, action: SPEED_NORMAL, label: "1x", variant: ghost },
247
+ { type: button, action: SPEED_DOUBLE, label: "2x", variant: ghost },
248
+ { type: button, action: SPEED_QUAD, label: "4x", variant: ghost }
249
+ ] },
250
+ { type: typography, variant: caption, content: (str/concat "Steps: " @entity.steps " — sim time: " (math/round @entity.simTime 1) " s — speed: " @entity.speedMultiplier "x") }
251
+ ] }
252
+ ]
253
+ })
254
+ (emit SIM_3D_UPDATED { running: @entity.running, speedMultiplier: @entity.speedMultiplier, steps: @entity.steps, simTime: @entity.simTime })
255
+ }
256
+
257
+ ticks {
258
+ simTick every 32ms
259
+ when (== @entity.running true)
260
+ (let (
261
+ (dtEff (* @config.dt @entity.speedMultiplier))
262
+ )
263
+ (do
264
+ (set @entity.steps (+ @entity.steps 1))
265
+ (set @entity.simTime (+ @entity.simTime @dtEff))
266
+ (emit SIM_3D_TICK { dt: @dtEff, simTime: @entity.simTime })
267
+ ))
268
+ }
269
+
270
+ listens {
271
+ MODE_SELECTED {
272
+ mode : string
273
+ }
274
+ @description "Lab router broadcast naming the active mode; this instrument renders when the mode equals matchMode, otherwise renders null."
275
+ @tier "domain"
276
+ }
277
+
278
+ emits {
279
+ SIM_3D_TICK -> external {
280
+ dt : number
281
+ simTime : number
282
+ }
283
+ @description "Fires every 32ms tick while running with the effective timestep (config dt x speed multiplier) and accumulated sim time — the clock a composed physics trait subscribes to; a harmless no-op when the atom runs standalone."
284
+ @synonyms "tick, frame, step, clock, sim update, telemetry"
285
+ @tier "domain"
286
+ SIM_3D_UPDATED -> external {
287
+ running : boolean
288
+ speedMultiplier : number
289
+ steps : int
290
+ simTime : number
291
+ }
292
+ @description "Fires on INIT/MODE_SELECTED/Run/Pause/Reset/speed change with the transport state — running flag, speed multiplier, step counter, and sim time — a composed listener (readout, graph) can subscribe; a harmless no-op when the atom runs standalone."
293
+ @synonyms "run, pause, reset, speed, transport, sim state"
294
+ @tier "domain"
295
+ }
296
+
297
+ config {
298
+ matchMode : string = ""
299
+ @label "Match Mode"
300
+ @description "When composed behind a lab router, the MODE_SELECTED mode value this instrument responds to. Empty string means standalone — render immediately on INIT."
301
+ @tier "presentation"
302
+ title : string = "3D Simulation"
303
+ @label "Title"
304
+ @description "Title shown above the transport controls."
305
+ @tier "presentation"
306
+ dt : number = 0.02
307
+ @label "Timestep"
308
+ @description "Simulated seconds per tick before the speed multiplier; the effective step emitted on SIM_3D_TICK is dt x speedMultiplier."
309
+ @tier "domain"
310
+ running : boolean = true
311
+ @label "Running"
312
+ @description "When true the clock starts ticking on INIT; the Reset arm restores this value."
313
+ @tier "domain"
314
+ }
315
+ }
316
+
317
+ page "/learn-sim-3d" as Learn3DSimPage -> Learn3DSimTransport
318
+ }