@almadar/std 16.178.0 → 16.179.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 (73) hide show
  1. package/behaviors/lolo/ui/core/molecules/ui-fx-overlay.lolo +63 -0
  2. package/behaviors/lolo/ui/core/molecules/ui-page-header.lolo +1 -1
  3. package/behaviors/lolo/ui/core/organisms/ui-form-section.lolo +2 -2
  4. package/behaviors/lolo/ui/core/organisms/ui-form.lolo +2 -2
  5. package/behaviors/lolo/ui/core/templates/ui-dashboard-layout.lolo +2 -2
  6. package/behaviors/lolo/ui/game/atoms/std-fx-particles.lolo +23 -4
  7. package/behaviors/lolo/ui/game/atoms/std-platformer-body.lolo +251 -0
  8. package/behaviors/lolo/ui/game/atoms/ui-draw-fx-layer.lolo +109 -0
  9. package/behaviors/lolo/ui/game/organisms/std-arcade-board-3d.lolo +32 -2
  10. package/behaviors/lolo/ui/game/organisms/std-crew-board-2d.lolo +32 -2
  11. package/behaviors/lolo/ui/game/organisms/std-dungeon-board-2d.lolo +32 -2
  12. package/behaviors/lolo/ui/game/organisms/std-market-board-3d.lolo +32 -2
  13. package/behaviors/lolo/ui/game/organisms/std-objective-board-3d.lolo +32 -2
  14. package/behaviors/lolo/ui/game/organisms/std-platformer-board-2d.lolo +391 -0
  15. package/behaviors/lolo/ui/game/organisms/std-survival-board-3d.lolo +32 -2
  16. package/behaviors/lolo/ui/game/organisms/std-tactics-board-2d.lolo +34 -6
  17. package/behaviors/lolo/ui/game/organisms/std-tactics-board-3d.lolo +34 -4
  18. package/behaviors/lolo/ui/learning/atoms/std-learn-arrangement.lolo +8 -0
  19. package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +8 -0
  20. package/behaviors/lolo/ui/learning/atoms/std-learn-derivation.lolo +8 -0
  21. package/behaviors/lolo/ui/learning/atoms/std-learn-doser.lolo +9 -0
  22. package/behaviors/lolo/ui/learning/atoms/std-learn-estimate.lolo +8 -0
  23. package/behaviors/lolo/ui/learning/atoms/std-learn-fx-cues.lolo +147 -0
  24. package/behaviors/lolo/ui/learning/atoms/std-learn-goal-seek.lolo +8 -0
  25. package/behaviors/lolo/ui/learning/atoms/std-learn-hotspot.lolo +8 -0
  26. package/behaviors/lolo/ui/learning/atoms/std-learn-predict.lolo +8 -0
  27. package/behaviors/lolo/ui/learning/atoms/std-learn-scatter-fit.lolo +9 -0
  28. package/behaviors/lolo/ui/learning/atoms/std-learn-speed-drill.lolo +8 -0
  29. package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +9 -0
  30. package/behaviors/registry/ui/core/molecules/ui-fx-overlay.orb +281 -0
  31. package/behaviors/registry/ui/core/molecules/ui-page-header.orb +1 -1
  32. package/behaviors/registry/ui/core/organisms/ui-form-section.orb +6 -0
  33. package/behaviors/registry/ui/core/organisms/ui-form.orb +6 -0
  34. package/behaviors/registry/ui/core/templates/ui-dashboard-layout.orb +2 -2
  35. package/behaviors/registry/ui/game/atoms/std-fx-particles.orb +265 -4
  36. package/behaviors/registry/ui/game/atoms/std-platformer-body.orb +2136 -0
  37. package/behaviors/registry/ui/game/atoms/ui-draw-fx-layer.orb +523 -0
  38. package/behaviors/registry/ui/game/organisms/std-arcade-board-3d.orb +159 -70
  39. package/behaviors/registry/ui/game/organisms/std-crew-board-2d.orb +148 -58
  40. package/behaviors/registry/ui/game/organisms/std-dungeon-board-2d.orb +169 -58
  41. package/behaviors/registry/ui/game/organisms/std-market-board-3d.orb +160 -70
  42. package/behaviors/registry/ui/game/organisms/std-objective-board-3d.orb +151 -60
  43. package/behaviors/registry/ui/game/organisms/std-platformer-board-2d.orb +2981 -0
  44. package/behaviors/registry/ui/game/organisms/std-survival-board-3d.orb +150 -60
  45. package/behaviors/registry/ui/game/organisms/std-tactics-board-2d.orb +163 -316
  46. package/behaviors/registry/ui/game/organisms/std-tactics-board-3d.orb +156 -184
  47. package/behaviors/registry/ui/learning/atoms/std-learn-arrangement.orb +62 -0
  48. package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +73 -0
  49. package/behaviors/registry/ui/learning/atoms/std-learn-derivation.orb +66 -0
  50. package/behaviors/registry/ui/learning/atoms/std-learn-doser.orb +71 -0
  51. package/behaviors/registry/ui/learning/atoms/std-learn-estimate.orb +72 -0
  52. package/behaviors/registry/ui/learning/atoms/std-learn-fx-cues.orb +1546 -0
  53. package/behaviors/registry/ui/learning/atoms/std-learn-goal-seek.orb +58 -0
  54. package/behaviors/registry/ui/learning/atoms/std-learn-hotspot.orb +72 -0
  55. package/behaviors/registry/ui/learning/atoms/std-learn-predict.orb +72 -0
  56. package/behaviors/registry/ui/learning/atoms/std-learn-scatter-fit.orb +71 -0
  57. package/behaviors/registry/ui/learning/atoms/std-learn-speed-drill.orb +72 -0
  58. package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +176 -0
  59. package/dist/behaviors/exports-reader.js +8369 -5005
  60. package/dist/behaviors/functions/index.d.ts +604 -14
  61. package/dist/behaviors/functions/index.js +8354 -5006
  62. package/dist/behaviors/index.d.ts +1 -1
  63. package/dist/behaviors/index.js +8370 -5006
  64. package/dist/behaviors/query.js +8369 -5005
  65. package/dist/behaviors-embeddings.hash.json +3 -3
  66. package/dist/behaviors-embeddings.json +351 -331
  67. package/dist/behaviors-registry.json +1879 -344
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.js +8370 -5006
  70. package/dist/knob-embeddings.hash.json +3 -3
  71. package/dist/knob-embeddings.json +1 -1
  72. package/dist/registry/factory-signatures.json +1 -1
  73. package/package.json +2 -2
@@ -51,6 +51,32 @@ orbital TacticsBoard2DOrbital {
51
51
  y : number
52
52
  message : string
53
53
  ttl : number!
54
+ maxTtl : number
55
+ bornAt : number
56
+ space : string
57
+ effect : string
58
+ color : string
59
+ size : number
60
+ vx : number
61
+ vy : number
62
+ vz : number
63
+ particleCount : number
64
+ }
65
+ type FxPreset = {
66
+ type : string!
67
+ color : string
68
+ color2 : string
69
+ size : number
70
+ shape : string
71
+ count : number
72
+ glow : number
73
+ gravity : number
74
+ vx : number
75
+ vy : number
76
+ vz : number
77
+ space : string
78
+ effect : string
79
+ particleCount : number
54
80
  }
55
81
  type ValidMove = { x : number! z : number! }
56
82
  type Feature = {
@@ -188,10 +214,9 @@ orbital TacticsBoard2DOrbital {
188
214
  { type: draw-shape-layer, items: (array/map (array/filter @entity.units (fn u (> (object/get @u health) 0))) (fn u { type: draw-shape, shape: "rect", position: (object/get @u position), anchor: "ground", offsetX: -0.25, offsetY: -0.6, width: 0.5, height: 0.12, fill: "rgba(0,0,0,0.6)" })) }
189
215
  { type: draw-shape-layer, items: (array/map (array/filter @entity.units (fn u (> (object/get @u health) 0))) (fn u { type: draw-shape, shape: "rect", position: (object/get @u position), anchor: "ground", offsetX: -0.25, offsetY: -0.6, width: (* 0.5 (/ (object/get @u health) (object/get @u maxHealth))), height: 0.12, fill: "#22c55e" })) }
190
216
  { type: draw-text-layer, items: (array/map (array/filter @entity.units (fn u (> (object/get @u health) 0))) (fn u { type: draw-text, text: (object/get @u name), position: (object/get @u position), offsetY: -0.85, color: "#ffffff" })) }
191
- { type: draw-text-layer, items: (array/map @entity.fx (fn f { type: draw-text, text: (object/get @f message), position: { x: (object/get @f x), y: (object/get @f z) }, color: "#ffe066" })) }
192
- { type: draw-sprite-layer, items: (array/filter (array/map (array/filter @entity.fx (fn f (not (object/get @entity.svgAssetArt (object/get @f type))))) (fn f { type: draw-sprite, position: { x: (object/get @f x), y: (object/get @f z) }, asset: (object/get (object/get @config.assetManifest effects) (object/get @f type)), anchor: "center", width: 0.6, height: 0.6 })) (fn s (object/get (object/get @s asset) url))) }
217
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets, art: @entity.svgAssetArt, sprites: (object/get @config.assetManifest effects) }
193
218
  ] (array/concat
194
- (array/map (array/filter @entity.fx (fn f (object/get @entity.svgAssetArt (object/get @f type)))) (fn f { type: draw-group, position: { x: (object/get @f x), y: (object/get @f z) }, items: (object/get (object/get @entity.svgAssetArt (object/get @f type)) "idle") }))
219
+ []
195
220
  [
196
221
  { type: draw-sprite-layer, items: (array/map (array/filter (array/flatten (array/map @entity.units (fn u (array/flatten (array/map [ ["idle", 4], ["walk", 2], ["attack", 1], ["hit", 1], ["death", 1] ] (fn st (array/map (array/range 0 (array/nth @st 1)) (fn i (if (and (object/get (object/get @u modelUrl) url) (str/includes (object/get (object/get @u modelUrl) url) ".svg")) (str/concat (array/nth (str/split (object/get (object/get @u modelUrl) url) ".svg") 0) (str/concat "." (str/concat (array/nth @st 0) (str/concat "-" (str/concat @i ".svg"))))) ""))))))))) (fn w (!= @w ""))) (fn warmUrl { type: draw-sprite, position: { x: 0, y: 0 }, asset: { url: @warmUrl }, opacity: 0, width: 0.5, height: 0.5 })) }
197
222
  ])))))))) } ]
@@ -285,9 +310,8 @@ orbital TacticsBoard2DOrbital {
285
310
  { type: draw-shape-layer, items: (array/map (array/filter @entity.units (fn u (> (object/get @u health) 0))) (fn u { type: draw-shape, shape: "rect", position: (object/get @u position), anchor: "ground", offsetX: -0.25, offsetY: -0.6, width: 0.5, height: 0.12, fill: "rgba(0,0,0,0.6)" })) }
286
311
  { type: draw-shape-layer, items: (array/map (array/filter @entity.units (fn u (> (object/get @u health) 0))) (fn u { type: draw-shape, shape: "rect", position: (object/get @u position), anchor: "ground", offsetX: -0.25, offsetY: -0.6, width: (* 0.5 (/ (object/get @u health) (object/get @u maxHealth))), height: 0.12, fill: "#22c55e" })) }
287
312
  { type: draw-text-layer, items: (array/map (array/filter @entity.units (fn u (> (object/get @u health) 0))) (fn u { type: draw-text, text: (object/get @u name), position: (object/get @u position), offsetY: -0.85, color: "#ffffff" })) }
288
- { type: draw-text-layer, items: (array/map @entity.fx (fn f { type: draw-text, text: (object/get @f message), position: { x: (object/get @f x), y: (object/get @f z) }, color: "#ffe066" })) }
289
- { type: draw-sprite-layer, items: (array/filter (array/map (array/filter @entity.fx (fn f (not (object/get @entity.svgAssetArt (object/get @f type))))) (fn f { type: draw-sprite, position: { x: (object/get @f x), y: (object/get @f z) }, asset: (object/get (object/get @config.assetManifest effects) (object/get @f type)), anchor: "center", width: 0.6, height: 0.6 })) (fn s (object/get (object/get @s asset) url))) }
290
- ] (array/map (array/filter @entity.fx (fn f (object/get @entity.svgAssetArt (object/get @f type)))) (fn f { type: draw-group, position: { x: (object/get @f x), y: (object/get @f z) }, items: (object/get (object/get @entity.svgAssetArt (object/get @f type)) "idle") }))))))))) } ]
313
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets, art: @entity.svgAssetArt, sprites: (object/get @config.assetManifest effects) }
314
+ ] []))))))) } ]
291
315
  })
292
316
  }
293
317
 
@@ -357,6 +381,10 @@ orbital TacticsBoard2DOrbital {
357
381
  @label "Asset Manifest"
358
382
  @description "Terrain/unit/feature/effect sprite manifest."
359
383
  @tier "presentation"
384
+ fxPresets : [FxPreset] = [ { type: "hit", color: "#ff6b6b", color2: "#ffb347", shape: "spark", size: 0.45, count: 6, glow: 0.12, vy: -0.3 } ]
385
+ @label "Fx Presets"
386
+ @description "This board's effect vocabulary — one look/recipe row per fx type its mechanics emit; unknown types fall back to a neutral spark."
387
+ @tier "presentation"
360
388
  projection : string = "flat"
361
389
  @label "Projection"
362
390
  @description "canvas-2d projection: flat or isometric."
@@ -80,6 +80,32 @@ orbital TacticsBoard3DOrbital {
80
80
  y : number
81
81
  message : string
82
82
  ttl : number!
83
+ maxTtl : number
84
+ bornAt : number
85
+ space : string
86
+ effect : string
87
+ color : string
88
+ size : number
89
+ vx : number
90
+ vy : number
91
+ vz : number
92
+ particleCount : number
93
+ }
94
+ type FxPreset = {
95
+ type : string!
96
+ color : string
97
+ color2 : string
98
+ size : number
99
+ shape : string
100
+ count : number
101
+ glow : number
102
+ gravity : number
103
+ vx : number
104
+ vy : number
105
+ vz : number
106
+ space : string
107
+ effect : string
108
+ particleCount : number
83
109
  }
84
110
  type ValidMove = { x : number! z : number! }
85
111
  type Feature = {
@@ -184,9 +210,9 @@ orbital TacticsBoard3DOrbital {
184
210
  { type: draw-shape-layer, items: (array/map @entity.units (fn u { type: draw-shape, shape: "rect", position: { x: (object/get @u x), y: (object/get @u y), z: (object/get @u z) }, offsetY: -0.6, width: (* 0.8 (/ (object/get @u health) (object/get @u maxHealth))), height: 0.12, fill: "#22c55e" })) }
185
211
  { type: draw-text-layer, items: (array/map @entity.units (fn u { type: draw-text, text: (object/get @u name), position: { x: (object/get @u x), y: (object/get @u y), z: (object/get @u z) }, offsetY: -0.85, color: "#ffffff" })) }
186
212
  ] (array/concat
187
- (array/map (array/filter @entity.fx (fn f (object/get @entity.meshAssetArt (object/get @f type)))) (fn f { type: draw-group, position: { x: (object/get @f x), y: (object/get @f y), z: (object/get @f z) }, items: (object/get (object/get @entity.meshAssetArt (object/get @f type)) "idle") }))
213
+ []
188
214
  [
189
- { type: draw-text-layer, items: (array/map @entity.fx (fn f { type: draw-text, text: (object/get @f message), position: { x: (object/get @f x), y: (object/get @f y), z: (object/get @f z) }, color: "#ffe066" })) }
215
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets, art: @entity.meshAssetArt }
190
216
  ])))))))) } ]
191
217
  })
192
218
 
@@ -252,9 +278,9 @@ orbital TacticsBoard3DOrbital {
252
278
  { type: draw-shape-layer, items: (array/map @entity.units (fn u { type: draw-shape, shape: "rect", position: { x: (object/get @u x), y: (object/get @u y), z: (object/get @u z) }, offsetY: -0.6, width: (* 0.8 (/ (object/get @u health) (object/get @u maxHealth))), height: 0.12, fill: "#22c55e" })) }
253
279
  { type: draw-text-layer, items: (array/map @entity.units (fn u { type: draw-text, text: (object/get @u name), position: { x: (object/get @u x), y: (object/get @u y), z: (object/get @u z) }, offsetY: -0.85, color: "#ffffff" })) }
254
280
  ] (array/concat
255
- (array/map (array/filter @entity.fx (fn f (object/get @entity.meshAssetArt (object/get @f type)))) (fn f { type: draw-group, position: { x: (object/get @f x), y: (object/get @f y), z: (object/get @f z) }, items: (object/get (object/get @entity.meshAssetArt (object/get @f type)) "idle") }))
281
+ []
256
282
  [
257
- { type: draw-text-layer, items: (array/map @entity.fx (fn f { type: draw-text, text: (object/get @f message), position: { x: (object/get @f x), y: (object/get @f y), z: (object/get @f z) }, color: "#ffe066" })) }
283
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets, art: @entity.meshAssetArt }
258
284
  ])))))))) } ]
259
285
  })
260
286
  }
@@ -315,6 +341,10 @@ orbital TacticsBoard3DOrbital {
315
341
  @label "Features"
316
342
  @description "Decorative environment features drawn on the board."
317
343
  @tier "presentation"
344
+ fxPresets : [FxPreset] = [ { type: "hit", color: "#ff5252", color2: "#7a1010", size: 0.5, shape: "spark", count: 6, glow: 0.12, gravity: 0.6, vy: -0.2 } ]
345
+ @label "Fx Presets"
346
+ @description "This board's effect vocabulary — one look/recipe row per fx type its mechanics emit; unknown types fall back to a neutral spark."
347
+ @tier "presentation"
318
348
  assetManifest : AssetManifest = { terrains: {}, units: {}, features: {}, effects: {} }
319
349
  @label "Asset Manifest"
320
350
  @description "Terrain/unit/feature/effect model manifest."
@@ -301,6 +301,7 @@ orbital LearnArrangementOrbital {
301
301
  ]
302
302
  })
303
303
  (emit ARRANGEMENT_CHECKED { correctCount: @entity.correctCount, total: @entity.totalItems })
304
+ (if (== @entity.correctCount @entity.totalItems) (emit LEARN_ROUND_COMPLETE { score: @entity.correctCount, total: @entity.totalItems }) null)
304
305
 
305
306
  RETRY -> idle when (== @entity.active true)
306
307
  (set @entity.placedIds [])
@@ -388,6 +389,13 @@ orbital LearnArrangementOrbital {
388
389
  @description "Signals the arrangement was cleared back to empty by Retry — a composed listener can subscribe; a harmless no-op when the atom runs standalone."
389
390
  @synonyms "arrangement reset, retry, restart"
390
391
  @tier "domain"
392
+ LEARN_ROUND_COMPLETE -> external {
393
+ score : number
394
+ total : number
395
+ }
396
+ @description "Fired when a Check grades the arrangement fully correct — the standard learning completion broadcast a celebration/progression sibling (fx cues, badges) rides."
397
+ @synonyms "round complete, finished, done, completion, all correct"
398
+ @tier "domain"
391
399
  }
392
400
 
393
401
  config {
@@ -382,6 +382,7 @@ orbital LearnClassifyOrbital {
382
382
  (emit CLASSIFY_UPDATED { itemIndex: @entity.itemIndex, correctCount: @entity.correctCount, attemptedCount: @entity.attemptedCount, selectedCategory: @entity.selectedCategory, verdict: @entity.verdict, roundComplete: @entity.roundComplete })
383
383
 
384
384
  ADVANCE -> idle when (and (== @entity.active true) (== @entity.roundComplete false))
385
+ (if (and (not @config.loopRound) (>= (+ @entity.itemIndex @config.orderStride) @entity.totalItems)) (emit LEARN_ROUND_COMPLETE { score: @entity.correctCount, total: @entity.totalItems }) null)
385
386
  (let (
386
387
  (isLast (and (not @config.loopRound) (>= (+ @entity.itemIndex @config.orderStride) @entity.totalItems)))
387
388
  )
@@ -650,6 +651,13 @@ orbital LearnClassifyOrbital {
650
651
  @description "The classification state after each learner action — a composed listener (readout, grader) can subscribe; a harmless no-op when the atom runs standalone."
651
652
  @synonyms "classify update, pick result, score, running score, verdict"
652
653
  @tier "domain"
654
+ LEARN_ROUND_COMPLETE -> external {
655
+ score : number
656
+ total : number
657
+ }
658
+ @description "Fired once when the round finishes (the advance past the final item) — the standard learning completion broadcast a celebration/progression sibling (fx cues, badges) rides."
659
+ @synonyms "round complete, finished, done, completion"
660
+ @tier "domain"
653
661
  }
654
662
 
655
663
  config {
@@ -146,6 +146,7 @@ orbital LearnDerivationOrbital {
146
146
  @candidate.noMatchFeedback)))
147
147
  (set @entity.goalReached (if @canDerive (== @derivedLabel @config.goalLabel) @entity.goalReached))
148
148
  (set @entity.narration @entity.feedback)
149
+ (if (and @canDerive (== @derivedLabel @config.goalLabel)) (emit LEARN_ROUND_COMPLETE { score: 1, total: 1 }) null)
149
150
  )
150
151
  )
151
152
  (set @entity.renderChildren [
@@ -345,6 +346,13 @@ orbital LearnDerivationOrbital {
345
346
  @description "The derivation state after each learner action — a composed listener (readout, grader) can subscribe; a harmless no-op when the atom runs standalone."
346
347
  @synonyms "derivation update, proof step, derived line, candidate applied, goal reached"
347
348
  @tier "domain"
349
+ LEARN_ROUND_COMPLETE -> external {
350
+ score : number
351
+ total : number
352
+ }
353
+ @description "Fired once when the step that derives the goal fact lands — the standard learning completion broadcast a celebration/progression sibling (fx cues, badges) rides."
354
+ @synonyms "round complete, finished, done, completion, goal reached"
355
+ @tier "domain"
348
356
  }
349
357
 
350
358
  config {
@@ -226,6 +226,7 @@ orbital LearnDoserOrbital {
226
226
  ]
227
227
  }))
228
228
  (emit DOSE_ADDED { dose: @entity.dose, bandLabel: @entity.bandLabel, bandColor: @entity.bandColor, done: @entity.done })
229
+ (if @entity.done (emit LEARN_ROUND_COMPLETE { score: 1, total: 1 }) null)
229
230
 
230
231
  ADD_LARGE -> idle when (and (== @entity.active true) (== @entity.done false))
231
232
  (set @entity.dose (math/min (+ @entity.dose @config.stepLarge) @config.capacity))
@@ -283,6 +284,7 @@ orbital LearnDoserOrbital {
283
284
  ]
284
285
  }))
285
286
  (emit DOSE_ADDED { dose: @entity.dose, bandLabel: @entity.bandLabel, bandColor: @entity.bandColor, done: @entity.done })
287
+ (if @entity.done (emit LEARN_ROUND_COMPLETE { score: 1, total: 1 }) null)
286
288
 
287
289
  TOGGLE_DRIP -> idle when (== @entity.active true)
288
290
  (set @entity.running (not @entity.running))
@@ -427,6 +429,13 @@ orbital LearnDoserOrbital {
427
429
  @description "Fires on INIT/MODE_SELECTED/auto-drip toggle with the transport-level state — running flag, dose, current band, done — so a composed sibling trait can repaint its own shell. A harmless no-op when the atom runs standalone."
428
430
  @synonyms "drip toggle, running state, auto-dose"
429
431
  @tier "domain"
432
+ LEARN_ROUND_COMPLETE -> external {
433
+ score : number
434
+ total : number
435
+ }
436
+ @description "Fired once when the dose crosses into a terminal band and latches done — the standard learning completion broadcast a celebration/progression sibling (fx cues, badges) rides."
437
+ @synonyms "round complete, finished, done, completion, terminal band"
438
+ @tier "domain"
430
439
  }
431
440
 
432
441
  config {
@@ -206,6 +206,7 @@ orbital EstimateOrbital {
206
206
  (emit ESTIMATE_JUDGED { band: @entity.band, estimate: @entity.estimate, answer: @entity.answer, score: @entity.score })
207
207
 
208
208
  NEXT -> idle when (and (== @entity.active true) (and (== @entity.locked true) (== @entity.done false)))
209
+ (if (>= (+ @entity.promptIndex 1) (array/len @config.prompts)) (emit LEARN_ROUND_COMPLETE { score: @entity.score, total: (array/len @config.prompts) }) null)
209
210
  (let (
210
211
  (nextIndex (+ @entity.promptIndex 1))
211
212
  (finished (>= @nextIndex (array/len @config.prompts)))
@@ -355,6 +356,13 @@ orbital EstimateOrbital {
355
356
  }
356
357
  @description "The learner restarted the round from scratch."
357
358
  @tier "domain"
359
+ LEARN_ROUND_COMPLETE -> external {
360
+ score : number
361
+ total : number
362
+ }
363
+ @description "Fired once when the round finishes (advancing past the final prompt) — the standard learning completion broadcast a celebration/progression sibling (fx cues, badges) rides."
364
+ @synonyms "round complete, finished, done, completion"
365
+ @tier "domain"
358
366
  }
359
367
 
360
368
  config {
@@ -0,0 +1,147 @@
1
+ app std-learn-fx-cues "1.0.0"
2
+ "Headless pedagogy→fx translator: rides the bare-event cascade of the graded learning instruments (ANSWER_JUDGED, TAP_JUDGED, ESTIMATE_JUDGED, CLASSIFY_UPDATED, ARRANGEMENT_CHECKED, GOAL_CHECKED, LEARN_ROUND_COMPLETE, TRACE_DONE) and emits BURST cues for std-fx-particles to hold and a composed fx-overlay to play — correct/wrong beats, streak glow at a threshold, and a celebration on completion. Cues are typed FxCue config values carrying their own style (position, ttl, screen effect, color), so a lab opts in with two `uses` lines plus an overlay node and restyles or disables any cue purely through config; the instruments never learn about visuals, and this atom never learns about subjects. A new judged event elsewhere in the substrate means a new arm here — this listens list is the closed extension point. Renders nothing."
3
+ orbital LearnFxCuesOrbital {
4
+ type Fx = {
5
+ id : string!
6
+ type : string!
7
+ x : number!
8
+ z : number
9
+ y : number
10
+ message : string
11
+ ttl : number!
12
+ space : string
13
+ effect : string
14
+ color : string
15
+ size : number
16
+ particleCount : number
17
+ }
18
+ type FxCue = {
19
+ type : string!
20
+ x : number!
21
+ z : number
22
+ ttl : number!
23
+ space : string
24
+ effect : string
25
+ color : string
26
+ size : number
27
+ particleCount : number
28
+ message : string
29
+ }
30
+ type FxBandCue = {
31
+ band : string!
32
+ cue : FxCue!
33
+ }
34
+
35
+ entity LearnFxCuesScene [runtime, shared] {
36
+ id : string!
37
+ cueSeq : number = 0
38
+ }
39
+
40
+ trait LearnFxCues -> @rebindable LearnFxCuesScene [lifecycle] {
41
+ initial: active
42
+
43
+ state active {
44
+ INIT -> active
45
+ (set @entity.cueSeq 0)
46
+ ANSWER_JUDGED -> active when @config.running
47
+ (emit BURST { particles: (array/concat
48
+ [ (object/merge (if ?correct @config.correctCue @config.wrongCue) { id: (str/concat "cue-" (+ @entity.cueSeq 1)) }) ]
49
+ (if (and ?correct (>= ?streak @config.streakThreshold))
50
+ [ (object/merge @config.streakCue { id: (str/concat "streak-" (+ @entity.cueSeq 1)) }) ]
51
+ [])) })
52
+ (set @entity.cueSeq (+ @entity.cueSeq 1))
53
+ TAP_JUDGED -> active when @config.running
54
+ (emit BURST { particles: (array/map [ (if ?correct @config.correctCue @config.wrongCue) ] (fn c { id: (str/concat "cue-" (+ @entity.cueSeq 1)), type: (object/get @c type), x: (object/get @c x), z: (object/get @c z), ttl: (object/get @c ttl), space: (object/get @c space), effect: (object/get @c effect), color: (object/get @c color), size: (object/get @c size), particleCount: (object/get @c particleCount), message: (object/get @c message) })) })
55
+ (set @entity.cueSeq (+ @entity.cueSeq 1))
56
+ ESTIMATE_JUDGED -> active when @config.running
57
+ (let ((matched (array/filter @config.bandCues (fn b (== (object/get @b band) ?band)))))
58
+ (if (> (array/len @matched) 0)
59
+ (emit BURST { particles: [ (object/merge (object/get (array/first @matched) cue) { id: (str/concat "cue-" (+ @entity.cueSeq 1)) }) ] })
60
+ null))
61
+ (set @entity.cueSeq (+ @entity.cueSeq 1))
62
+ CLASSIFY_UPDATED -> active when @config.running
63
+ (let ((matched (array/filter @config.verdictCues (fn v (== (object/get @v band) ?verdict)))))
64
+ (if (> (array/len @matched) 0)
65
+ (emit BURST { particles: [ (object/merge (object/get (array/first @matched) cue) { id: (str/concat "cue-" (+ @entity.cueSeq 1)) }) ] })
66
+ null))
67
+ (set @entity.cueSeq (+ @entity.cueSeq 1))
68
+ ARRANGEMENT_CHECKED -> active when @config.running
69
+ (if (< ?correctCount ?total)
70
+ (emit BURST { particles: [ (object/merge @config.progressCue { id: (str/concat "cue-" (+ @entity.cueSeq 1)) }) ] })
71
+ null)
72
+ (set @entity.cueSeq (+ @entity.cueSeq 1))
73
+ GOAL_CHECKED -> active when @config.running
74
+ (if (not ?matched)
75
+ (emit BURST { particles: [ (object/merge @config.wrongCue { id: (str/concat "cue-" (+ @entity.cueSeq 1)) }) ] })
76
+ null)
77
+ (set @entity.cueSeq (+ @entity.cueSeq 1))
78
+ LEARN_ROUND_COMPLETE -> active when @config.running
79
+ (emit BURST { particles: (array/map [ @config.celebrationCue ] (fn c { id: (str/concat "cue-" (+ @entity.cueSeq 1)), type: (object/get @c type), x: (object/get @c x), z: (object/get @c z), ttl: (object/get @c ttl), space: (object/get @c space), effect: (object/get @c effect), color: (object/get @c color), size: (object/get @c size), particleCount: (object/get @c particleCount), message: (object/get @c message) })) })
80
+ (set @entity.cueSeq (+ @entity.cueSeq 1))
81
+ TRACE_DONE -> active when @config.running
82
+ (emit BURST { particles: (array/map [ @config.celebrationCue ] (fn c { id: (str/concat "cue-" (+ @entity.cueSeq 1)), type: (object/get @c type), x: (object/get @c x), z: (object/get @c z), ttl: (object/get @c ttl), space: (object/get @c space), effect: (object/get @c effect), color: (object/get @c color), size: (object/get @c size), particleCount: (object/get @c particleCount), message: (object/get @c message) })) })
83
+ (set @entity.cueSeq (+ @entity.cueSeq 1))
84
+ }
85
+
86
+ listens {
87
+ ANSWER_JUDGED { correct : boolean, timeLeftTenths : number, score : number, streak : number }
88
+ TAP_JUDGED { correct : boolean, promptIndex : number, tries : number }
89
+ ESTIMATE_JUDGED { band : string, estimate : number, answer : number, score : number }
90
+ CLASSIFY_UPDATED { itemIndex : number, correctCount : number, attemptedCount : number, selectedCategory : string, verdict : string, roundComplete : boolean }
91
+ ARRANGEMENT_CHECKED { correctCount : number, total : number }
92
+ GOAL_CHECKED { matched : boolean, distance : number, attempts : number }
93
+ LEARN_ROUND_COMPLETE { score : number, total : number }
94
+ TRACE_DONE { totalFrames : number }
95
+ }
96
+
97
+ emits {
98
+ BURST -> external {
99
+ particles : [Fx]
100
+ }
101
+ @description "Spawned fx cues for a composed std-fx-particles sink (and the fx-overlay reading its entity list) — the same BURST channel the game mechanics use."
102
+ @synonyms "fx, cue, celebration, feedback burst, confetti, shake"
103
+ @tier "domain"
104
+ }
105
+
106
+ config {
107
+ running : boolean = true
108
+ @label "Running"
109
+ @description "When false every cue arm is inert — visual feedback fully disabled without unmounting."
110
+ @tier "domain"
111
+ streakThreshold : number = 3
112
+ @label "Streak Threshold"
113
+ @description "Minimum ANSWER_JUDGED streak that adds the streak cue on top of the correct cue."
114
+ @tier "domain"
115
+ correctCue : FxCue = { type: "correct", x: 0.5, z: 0.35, ttl: 1.5, space: "screen", effect: "sparkle", color: "#4ade80", particleCount: 10 }
116
+ @label "Correct Cue"
117
+ @description "Cue spawned on a correct judged answer."
118
+ @tier "presentation"
119
+ wrongCue : FxCue = { type: "wrong", x: 0.5, z: 0.35, ttl: 1, space: "screen", effect: "shake", color: "#fda4af" }
120
+ @label "Wrong Cue"
121
+ @description "Cue spawned on a wrong judged answer (and an unmatched goal check)."
122
+ @tier "presentation"
123
+ streakCue : FxCue = { type: "streak", x: 0.5, z: 0.2, ttl: 2.5, space: "screen", effect: "streak-glow", color: "#fbbf24" }
124
+ @label "Streak Cue"
125
+ @description "Extra cue layered on the correct cue once the streak reaches the threshold."
126
+ @tier "presentation"
127
+ celebrationCue : FxCue = { type: "celebration", x: 0.5, z: 0.45, ttl: 5, space: "screen", effect: "confetti", color: "#facc15", particleCount: 40 }
128
+ @label "Celebration Cue"
129
+ @description "Cue spawned when a round completes (LEARN_ROUND_COMPLETE / TRACE_DONE)."
130
+ @tier "presentation"
131
+ progressCue : FxCue = { type: "progress", x: 0.5, z: 0.35, ttl: 1.5, space: "screen", effect: "sparkle", color: "#a5b4fc", particleCount: 4 }
132
+ @label "Progress Cue"
133
+ @description "Cue spawned on a partial arrangement check (some but not all slots correct)."
134
+ @tier "presentation"
135
+ bandCues : [FxBandCue] = []
136
+ @label "Band Cues"
137
+ @description "Per-band cues for ESTIMATE_JUDGED — one row per error band (exact/close/off); bands with no row spawn nothing."
138
+ @tier "presentation"
139
+ verdictCues : [FxBandCue] = []
140
+ @label "Verdict Cues"
141
+ @description "Per-verdict cues for CLASSIFY_UPDATED — one row per verdict string; verdicts with no row spawn nothing."
142
+ @tier "presentation"
143
+ }
144
+ }
145
+
146
+ page "/learn-fx-cues" as LearnFxCuesPage -> LearnFxCues
147
+ }
@@ -362,6 +362,7 @@ orbital GoalSeekOrbital {
362
362
  ]
363
363
  })
364
364
  (emit GOAL_CHECKED { matched: @entity.solved, distance: @entity.distance, attempts: @entity.attempts })
365
+ (if @entity.solved (emit LEARN_ROUND_COMPLETE { score: 1, total: 1 }) null)
365
366
 
366
367
  RESET -> idle when (== @entity.active true)
367
368
  (let (
@@ -478,6 +479,13 @@ orbital GoalSeekOrbital {
478
479
  }
479
480
  @description "The learner reset the instrument back to the default parameters."
480
481
  @tier "domain"
482
+ LEARN_ROUND_COMPLETE -> external {
483
+ score : number
484
+ total : number
485
+ }
486
+ @description "Fired once when a Check lands within tolerance (the goal is matched) — the standard learning completion broadcast a celebration/progression sibling (fx cues, badges) rides."
487
+ @synonyms "round complete, finished, done, completion, solved"
488
+ @tier "domain"
481
489
  }
482
490
 
483
491
  config {
@@ -207,6 +207,7 @@ orbital HotspotOrbital {
207
207
  (emit TAP_JUDGED { correct: @entity.found, promptIndex: @entity.promptIndex, tries: @entity.tries })
208
208
 
209
209
  NEXT -> idle when (and (== @entity.active true) (and (== @entity.found true) (== @entity.done false)))
210
+ (if (>= (+ @entity.promptIndex 1) (array/len @config.prompts)) (emit LEARN_ROUND_COMPLETE { score: @entity.score, total: (array/len @config.prompts) }) null)
210
211
  (let (
211
212
  (nextIndex (+ @entity.promptIndex 1))
212
213
  (finished (>= @nextIndex (array/len @config.prompts)))
@@ -343,6 +344,13 @@ orbital HotspotOrbital {
343
344
  }
344
345
  @description "The learner restarted the round from scratch."
345
346
  @tier "domain"
347
+ LEARN_ROUND_COMPLETE -> external {
348
+ score : number
349
+ total : number
350
+ }
351
+ @description "Fired once when the round finishes (advancing past the final prompt) — the standard learning completion broadcast a celebration/progression sibling (fx cues, badges) rides."
352
+ @synonyms "round complete, finished, done, completion"
353
+ @tier "domain"
346
354
  }
347
355
 
348
356
  config {
@@ -174,6 +174,7 @@ orbital PredictOrbital {
174
174
  (emit PREDICTION_COMMITTED { scenarioIndex: @entity.scenarioIndex, chosenIndex: @entity.chosenIndex })
175
175
 
176
176
  NEXT -> idle when (and (== @entity.active true) (and (== @entity.phase "judged") (== @entity.done false)))
177
+ (if (>= (+ @entity.scenarioIndex 1) (array/len @config.scenarios)) (emit LEARN_ROUND_COMPLETE { score: @entity.score, total: (array/len @config.scenarios) }) null)
177
178
  (let (
178
179
  (nextIndex (+ @entity.scenarioIndex 1))
179
180
  (finished (>= @nextIndex (array/len @config.scenarios)))
@@ -341,6 +342,13 @@ orbital PredictOrbital {
341
342
  }
342
343
  @description "The learner restarted the round from scratch."
343
344
  @tier "domain"
345
+ LEARN_ROUND_COMPLETE -> external {
346
+ score : number
347
+ total : number
348
+ }
349
+ @description "Fired once when the round finishes (advancing past the final scenario) — the standard learning completion broadcast a celebration/progression sibling (fx cues, badges) rides."
350
+ @synonyms "round complete, finished, done, completion"
351
+ @tier "domain"
344
352
  }
345
353
 
346
354
  config {
@@ -211,6 +211,7 @@ orbital LearnScatterFitOrbital {
211
211
  (set @entity.curves (array/concat @baseCurves @residualCurves))
212
212
  (set @entity.done @nowDone)
213
213
  (set @entity.narration (str/concat @config.narrationAddedTemplate " " (str/concat "" @n) " — r = " (str/concat "" (math/round @newR 3)) ", R² = " (str/concat "" (math/round (* @newR @newR) 3)) (if @nowDone @config.narrationMaxReachedSuffix "")))
214
+ (if @nowDone (emit LEARN_ROUND_COMPLETE { score: @n, total: @config.maxPoints }) null)
214
215
  )
215
216
  )
216
217
  (render-ui main {
@@ -540,6 +541,7 @@ orbital LearnScatterFitOrbital {
540
541
  (set @entity.curves (array/concat @baseCurves @residualCurves))
541
542
  (set @entity.done @nowDone)
542
543
  (set @entity.narration (str/concat @config.narrationAddedTemplate " " (str/concat "" @n) " — r = " (str/concat "" (math/round @newR 3)) ", R² = " (str/concat "" (math/round (* @newR @newR) 3)) (if @nowDone @config.narrationMaxReachedSuffix "")))
544
+ (if @nowDone (emit LEARN_ROUND_COMPLETE { score: @n, total: @config.maxPoints }) null)
543
545
  )
544
546
  )
545
547
  (render-ui main {
@@ -600,6 +602,13 @@ orbital LearnScatterFitOrbital {
600
602
  @description "The least-squares fit and narration after each learner action, auto-add tick, or reset — a composed listener (readout, grader) can subscribe; a harmless no-op when the atom runs standalone."
601
603
  @synonyms "fit update, line of best fit, slope, intercept, correlation, r squared"
602
604
  @tier "domain"
605
+ LEARN_ROUND_COMPLETE -> external {
606
+ score : number
607
+ total : number
608
+ }
609
+ @description "Fired once when the point that reaches maxPoints lands and the round latches done — the standard learning completion broadcast a celebration/progression sibling (fx cues, badges) rides."
610
+ @synonyms "round complete, finished, done, completion, max points"
611
+ @tier "domain"
603
612
  }
604
613
 
605
614
  config {
@@ -144,6 +144,7 @@ orbital SpeedDrillOrbital {
144
144
  (emit ANSWER_JUDGED { correct: (== @entity.verdict "correct"), timeLeftTenths: @entity.remaining, score: @entity.score, streak: @entity.streak })
145
145
 
146
146
  NEXT -> idle when (and (== @entity.active true) (and (== @entity.running false) (and (!= @entity.verdict "") (== @entity.done false))))
147
+ (if (>= (+ @entity.itemIndex 1) (array/len @config.items)) (emit LEARN_ROUND_COMPLETE { score: @entity.score, total: (array/len @config.items) }) null)
147
148
  (let (
148
149
  (nextIndex (+ @entity.itemIndex 1))
149
150
  (finished (>= @nextIndex (array/len @config.items)))
@@ -292,6 +293,13 @@ orbital SpeedDrillOrbital {
292
293
  }
293
294
  @description "The learner restarted the round from scratch."
294
295
  @tier "domain"
296
+ LEARN_ROUND_COMPLETE -> external {
297
+ score : number
298
+ total : number
299
+ }
300
+ @description "Fired once when the round finishes (advancing past the final prompt) — the standard learning completion broadcast a celebration/progression sibling (fx cues, badges) rides."
301
+ @synonyms "round complete, finished, done, completion"
302
+ @tier "domain"
295
303
  }
296
304
 
297
305
  config {
@@ -153,6 +153,7 @@ orbital LearnTour3DOrbital {
153
153
  (emit TOUR_3D_UPDATED { id: @entity.selectedId, name: (object/get (array/nth @entity.stations 0) name), caption: (object/get (array/nth @entity.stations 0) caption), visited: (array/len @entity.visitedIds), total: (array/len @entity.stations), tourComplete: false })
154
154
 
155
155
  TOUR -> idle when (== @entity.active true)
156
+ (if (and (not @entity.tourComplete) (== (if (array/includes @entity.visitedIds (object/get (array/nth @entity.stations (math/mod (+ @entity.selectedIndex 1) (array/len @entity.stations))) id)) (array/len @entity.visitedIds) (+ (array/len @entity.visitedIds) 1)) (array/len @entity.stations))) (emit LEARN_ROUND_COMPLETE { score: (array/len @entity.stations), total: (array/len @entity.stations) }) null)
156
157
  (set @entity.selectedIndex (math/mod (+ @entity.selectedIndex 1) (array/len @entity.stations)))
157
158
  (set @entity.selectedId (object/get (array/nth @entity.stations @entity.selectedIndex) id))
158
159
  (set @entity.visitedIds (if (array/includes @entity.visitedIds @entity.selectedId) @entity.visitedIds (array/append @entity.visitedIds @entity.selectedId)))
@@ -193,6 +194,7 @@ orbital LearnTour3DOrbital {
193
194
  (idx (if (>= @found 0) @found @entity.selectedIndex))
194
195
  )
195
196
  (do
197
+ (if (and (not @entity.tourComplete) (== (if (array/includes @entity.visitedIds (object/get (array/nth @entity.stations @idx) id)) (array/len @entity.visitedIds) (+ (array/len @entity.visitedIds) 1)) (array/len @entity.stations))) (emit LEARN_ROUND_COMPLETE { score: (array/len @entity.stations), total: (array/len @entity.stations) }) null)
196
198
  (set @entity.selectedIndex @idx)
197
199
  (set @entity.selectedId (object/get (array/nth @entity.stations @idx) id))
198
200
  (set @entity.visitedIds (if (array/includes @entity.visitedIds @entity.selectedId) @entity.visitedIds (array/append @entity.visitedIds @entity.selectedId)))
@@ -332,6 +334,13 @@ orbital LearnTour3DOrbital {
332
334
  @description "Fires on INIT/MODE_SELECTED/Tour/click/Reset with the currently selected station's id, name, and caption, plus visited/total progress and whether every station has now been visited — a composed listener (readout, quiz) can subscribe; a harmless no-op when the atom runs standalone."
333
335
  @synonyms "select, choose, station, explore, tour, flythrough, pick"
334
336
  @tier "domain"
337
+ LEARN_ROUND_COMPLETE -> external {
338
+ score : number
339
+ total : number
340
+ }
341
+ @description "Fired once when the visit that completes the tour lands (every station explored) — the standard learning completion broadcast a celebration/progression sibling (fx cues, badges) rides."
342
+ @synonyms "round complete, finished, done, completion, tour complete"
343
+ @tier "domain"
335
344
  }
336
345
 
337
346
  config {