@almadar/std 16.178.0 → 16.180.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 (80) hide show
  1. package/behaviors/lolo/ui/core/atoms/std-detail-layout.lolo +82 -0
  2. package/behaviors/lolo/ui/core/molecules/ui-breadcrumb.lolo +20 -2
  3. package/behaviors/lolo/ui/core/molecules/ui-fx-overlay.lolo +63 -0
  4. package/behaviors/lolo/ui/core/molecules/ui-page-header.lolo +1 -1
  5. package/behaviors/lolo/ui/core/organisms/ui-detail-panel.lolo +6 -0
  6. package/behaviors/lolo/ui/core/organisms/ui-form-section.lolo +2 -2
  7. package/behaviors/lolo/ui/core/organisms/ui-form.lolo +2 -2
  8. package/behaviors/lolo/ui/core/templates/ui-dashboard-layout.lolo +2 -2
  9. package/behaviors/lolo/ui/game/atoms/std-fx-particles.lolo +23 -4
  10. package/behaviors/lolo/ui/game/atoms/std-platformer-body.lolo +251 -0
  11. package/behaviors/lolo/ui/game/atoms/ui-draw-fx-layer.lolo +109 -0
  12. package/behaviors/lolo/ui/game/organisms/std-arcade-board-3d.lolo +32 -2
  13. package/behaviors/lolo/ui/game/organisms/std-crew-board-2d.lolo +32 -2
  14. package/behaviors/lolo/ui/game/organisms/std-dungeon-board-2d.lolo +32 -2
  15. package/behaviors/lolo/ui/game/organisms/std-market-board-3d.lolo +32 -2
  16. package/behaviors/lolo/ui/game/organisms/std-objective-board-3d.lolo +32 -2
  17. package/behaviors/lolo/ui/game/organisms/std-platformer-board-2d.lolo +391 -0
  18. package/behaviors/lolo/ui/game/organisms/std-survival-board-3d.lolo +32 -2
  19. package/behaviors/lolo/ui/game/organisms/std-tactics-board-2d.lolo +34 -6
  20. package/behaviors/lolo/ui/game/organisms/std-tactics-board-3d.lolo +34 -4
  21. package/behaviors/lolo/ui/learning/atoms/std-learn-arrangement.lolo +8 -0
  22. package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +8 -0
  23. package/behaviors/lolo/ui/learning/atoms/std-learn-derivation.lolo +8 -0
  24. package/behaviors/lolo/ui/learning/atoms/std-learn-doser.lolo +9 -0
  25. package/behaviors/lolo/ui/learning/atoms/std-learn-estimate.lolo +8 -0
  26. package/behaviors/lolo/ui/learning/atoms/std-learn-fx-cues.lolo +147 -0
  27. package/behaviors/lolo/ui/learning/atoms/std-learn-goal-seek.lolo +8 -0
  28. package/behaviors/lolo/ui/learning/atoms/std-learn-hotspot.lolo +8 -0
  29. package/behaviors/lolo/ui/learning/atoms/std-learn-predict.lolo +8 -0
  30. package/behaviors/lolo/ui/learning/atoms/std-learn-scatter-fit.lolo +9 -0
  31. package/behaviors/lolo/ui/learning/atoms/std-learn-speed-drill.lolo +8 -0
  32. package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +9 -0
  33. package/behaviors/registry/ui/core/atoms/std-detail-layout.orb +265 -0
  34. package/behaviors/registry/ui/core/molecules/ui-breadcrumb.orb +55 -17
  35. package/behaviors/registry/ui/core/molecules/ui-fx-overlay.orb +281 -0
  36. package/behaviors/registry/ui/core/molecules/ui-page-header.orb +1 -1
  37. package/behaviors/registry/ui/core/organisms/ui-detail-panel.orb +8 -0
  38. package/behaviors/registry/ui/core/organisms/ui-form-section.orb +6 -0
  39. package/behaviors/registry/ui/core/organisms/ui-form.orb +6 -0
  40. package/behaviors/registry/ui/core/templates/ui-dashboard-layout.orb +2 -2
  41. package/behaviors/registry/ui/game/atoms/std-fx-particles.orb +265 -4
  42. package/behaviors/registry/ui/game/atoms/std-platformer-body.orb +2136 -0
  43. package/behaviors/registry/ui/game/atoms/ui-draw-fx-layer.orb +523 -0
  44. package/behaviors/registry/ui/game/organisms/std-arcade-board-3d.orb +159 -70
  45. package/behaviors/registry/ui/game/organisms/std-crew-board-2d.orb +148 -58
  46. package/behaviors/registry/ui/game/organisms/std-dungeon-board-2d.orb +169 -58
  47. package/behaviors/registry/ui/game/organisms/std-market-board-3d.orb +160 -70
  48. package/behaviors/registry/ui/game/organisms/std-objective-board-3d.orb +151 -60
  49. package/behaviors/registry/ui/game/organisms/std-platformer-board-2d.orb +2981 -0
  50. package/behaviors/registry/ui/game/organisms/std-survival-board-3d.orb +150 -60
  51. package/behaviors/registry/ui/game/organisms/std-tactics-board-2d.orb +163 -316
  52. package/behaviors/registry/ui/game/organisms/std-tactics-board-3d.orb +156 -184
  53. package/behaviors/registry/ui/learning/atoms/std-learn-arrangement.orb +62 -0
  54. package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +73 -0
  55. package/behaviors/registry/ui/learning/atoms/std-learn-derivation.orb +66 -0
  56. package/behaviors/registry/ui/learning/atoms/std-learn-doser.orb +71 -0
  57. package/behaviors/registry/ui/learning/atoms/std-learn-estimate.orb +72 -0
  58. package/behaviors/registry/ui/learning/atoms/std-learn-fx-cues.orb +1546 -0
  59. package/behaviors/registry/ui/learning/atoms/std-learn-goal-seek.orb +58 -0
  60. package/behaviors/registry/ui/learning/atoms/std-learn-hotspot.orb +72 -0
  61. package/behaviors/registry/ui/learning/atoms/std-learn-predict.orb +72 -0
  62. package/behaviors/registry/ui/learning/atoms/std-learn-scatter-fit.orb +71 -0
  63. package/behaviors/registry/ui/learning/atoms/std-learn-speed-drill.orb +72 -0
  64. package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +176 -0
  65. package/canonical-operators.json +14 -2
  66. package/dist/behaviors/exports-reader.js +8655 -5239
  67. package/dist/behaviors/functions/index.d.ts +686 -16
  68. package/dist/behaviors/functions/index.js +8637 -5240
  69. package/dist/behaviors/index.d.ts +1 -1
  70. package/dist/behaviors/index.js +8656 -5240
  71. package/dist/behaviors/query.js +8655 -5239
  72. package/dist/behaviors-embeddings.hash.json +3 -3
  73. package/dist/behaviors-embeddings.json +381 -353
  74. package/dist/behaviors-registry.json +2415 -443
  75. package/dist/index.d.ts +1 -1
  76. package/dist/index.js +8656 -5240
  77. package/dist/knob-embeddings.hash.json +3 -3
  78. package/dist/knob-embeddings.json +1 -1
  79. package/dist/registry/factory-signatures.json +1 -1
  80. package/package.json +2 -2
@@ -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 {
@@ -0,0 +1,265 @@
1
+ {
2
+ "description": "std-detail-layout — detail-page chrome band (back button + navigation-stack breadcrumb trail) above a single contentTrait slot. Use as the contentTrait of a detail page's AppShell binding: the band renders `← Back | Contracts › Contract details` from the orbital-scoped client navigation stack (session history, seeded from declared page-path ancestry on cold loads — the trail is never empty), and the consumer's detail trait renders below it. Back pops the stack; crumb clicks navigate to their level; a list router's `(navigate path payload { crumb: ?row.title })` labels the detail crumb with the real record title. Bind once per detail page — `XDetailLayout = DetailShell.traits.DetailLayout` with `contentTrait: @trait.XDetail`.",
3
+ "ledger": {
4
+ "entries": {
5
+ "ent_01M0J07QZHFKDT9JQZ42HBVNQQ": {
6
+ "bakedName": "DetailLayoutData",
7
+ "curName": "DetailLayoutData",
8
+ "id": "ent_01M0J07QZHFKDT9JQZ42HBVNQQ",
9
+ "kind": "entity",
10
+ "owner": "workspace",
11
+ "renames": []
12
+ },
13
+ "evt_01M0J07QZHF7RM3JBVCVWSV7B4": {
14
+ "bakedName": "INIT",
15
+ "curName": "INIT",
16
+ "id": "evt_01M0J07QZHF7RM3JBVCVWSV7B4",
17
+ "kind": "event",
18
+ "owner": "workspace",
19
+ "parent": "trt_01M0J07QZH6N0G9CKFXKBJ3AC6",
20
+ "renames": []
21
+ },
22
+ "evt_01M0J07QZKCG5P5AGK0VHN2N9G": {
23
+ "bakedName": "BACK",
24
+ "curName": "BACK",
25
+ "id": "evt_01M0J07QZKCG5P5AGK0VHN2N9G",
26
+ "kind": "event",
27
+ "owner": "workspace",
28
+ "parent": "trt_01M0J07QZH6N0G9CKFXKBJ3AC6",
29
+ "renames": []
30
+ },
31
+ "evt_01M0J07QZKF2YJ9MMQ69KZ3CVB": {
32
+ "bakedName": "CRUMB_NAV",
33
+ "curName": "CRUMB_NAV",
34
+ "id": "evt_01M0J07QZKF2YJ9MMQ69KZ3CVB",
35
+ "kind": "event",
36
+ "owner": "workspace",
37
+ "parent": "trt_01M0J07QZH6N0G9CKFXKBJ3AC6",
38
+ "renames": []
39
+ },
40
+ "orb_01M0J07QZHNJF5BJSSEKF13PVV": {
41
+ "bakedName": "DetailLayoutOrbital",
42
+ "curName": "DetailLayoutOrbital",
43
+ "id": "orb_01M0J07QZHNJF5BJSSEKF13PVV",
44
+ "kind": "orbital",
45
+ "owner": "workspace",
46
+ "renames": []
47
+ },
48
+ "pag_01M0J07QZKPKTYGMZR1DDDDFC1": {
49
+ "bakedName": "DetailLayoutPage",
50
+ "curName": "DetailLayoutPage",
51
+ "id": "pag_01M0J07QZKPKTYGMZR1DDDDFC1",
52
+ "kind": "page",
53
+ "owner": "workspace",
54
+ "renames": []
55
+ },
56
+ "trt_01M0J07QZH6N0G9CKFXKBJ3AC6": {
57
+ "bakedName": "DetailLayout",
58
+ "curName": "DetailLayout",
59
+ "id": "trt_01M0J07QZH6N0G9CKFXKBJ3AC6",
60
+ "kind": "trait",
61
+ "owner": "workspace",
62
+ "renames": []
63
+ }
64
+ },
65
+ "schemaVersion": 1
66
+ },
67
+ "name": "std-detail-layout",
68
+ "orbitals": [
69
+ {
70
+ "entity": {
71
+ "fields": [
72
+ {
73
+ "name": "id",
74
+ "required": true,
75
+ "type": "string"
76
+ }
77
+ ],
78
+ "id": "ent_01M0J07QZHFKDT9JQZ42HBVNQQ",
79
+ "name": "DetailLayoutData",
80
+ "persistence": "runtime"
81
+ },
82
+ "id": "orb_01M0J07QZHNJF5BJSSEKF13PVV",
83
+ "name": "DetailLayoutOrbital",
84
+ "pages": [
85
+ {
86
+ "id": "pag_01M0J07QZKPKTYGMZR1DDDDFC1",
87
+ "name": "DetailLayoutPage",
88
+ "path": "/detail-layout",
89
+ "traits": [
90
+ {
91
+ "ref": "DetailLayout",
92
+ "refId": "trt_01M0J07QZH6N0G9CKFXKBJ3AC6"
93
+ }
94
+ ]
95
+ }
96
+ ],
97
+ "traits": [
98
+ {
99
+ "category": "interaction",
100
+ "config": {
101
+ "backLabel": {
102
+ "default": "Back",
103
+ "description": "Label on the band's back button, e.g. \"Back to contracts\".",
104
+ "label": "Back button label",
105
+ "synonyms": "back text, return label",
106
+ "tier": "presentation",
107
+ "type": "string"
108
+ },
109
+ "contentTrait": {
110
+ "description": "The detail trait rendered below the navigation band (the DetailPanel + sections view).",
111
+ "label": "Content trait",
112
+ "tier": "presentation",
113
+ "type": "trait"
114
+ }
115
+ },
116
+ "emits": [
117
+ {
118
+ "description": "The shell's back affordance was pressed; the layout pops the navigation stack.",
119
+ "event": "BACK",
120
+ "eventId": "evt_01M0J07QZKCG5P5AGK0VHN2N9G",
121
+ "synonyms": "go back, return, previous page, up one level",
122
+ "tier": "internal"
123
+ },
124
+ {
125
+ "description": "A breadcrumb level was clicked; the layout navigates to that level's href.",
126
+ "event": "CRUMB_NAV",
127
+ "eventId": "evt_01M0J07QZKF2YJ9MMQ69KZ3CVB",
128
+ "synonyms": "breadcrumb click, jump to level, trail navigation",
129
+ "tier": "internal"
130
+ }
131
+ ],
132
+ "entityContract": {
133
+ "provides": [],
134
+ "requires": []
135
+ },
136
+ "entityRebindable": true,
137
+ "entityRefIds": {
138
+ "DetailLayoutData": "ent_01M0J07QZHFKDT9JQZ42HBVNQQ"
139
+ },
140
+ "id": "trt_01M0J07QZH6N0G9CKFXKBJ3AC6",
141
+ "linkedEntity": "DetailLayoutData",
142
+ "linkedEntityId": "ent_01M0J07QZHFKDT9JQZ42HBVNQQ",
143
+ "name": "DetailLayout",
144
+ "scope": "instance",
145
+ "stateMachine": {
146
+ "events": [
147
+ {
148
+ "id": "evt_01M0J07QZHF7RM3JBVCVWSV7B4",
149
+ "key": "INIT",
150
+ "name": "Initialize"
151
+ },
152
+ {
153
+ "description": "The shell's back affordance was pressed; the layout pops the navigation stack.",
154
+ "id": "evt_01M0J07QZKCG5P5AGK0VHN2N9G",
155
+ "key": "BACK",
156
+ "name": "Back",
157
+ "synonyms": "go back, return, previous page, up one level",
158
+ "tier": "internal"
159
+ },
160
+ {
161
+ "description": "A breadcrumb level was clicked; the layout navigates to that level's href.",
162
+ "id": "evt_01M0J07QZKF2YJ9MMQ69KZ3CVB",
163
+ "key": "CRUMB_NAV",
164
+ "name": "Crumb Nav",
165
+ "payloadSchema": [
166
+ {
167
+ "name": "label",
168
+ "type": "string"
169
+ },
170
+ {
171
+ "name": "href",
172
+ "required": true,
173
+ "type": "string"
174
+ },
175
+ {
176
+ "name": "index",
177
+ "type": "number"
178
+ }
179
+ ],
180
+ "synonyms": "breadcrumb click, jump to level, trail navigation",
181
+ "tier": "internal"
182
+ }
183
+ ],
184
+ "states": [
185
+ {
186
+ "isInitial": true,
187
+ "name": "composing"
188
+ }
189
+ ],
190
+ "transitions": [
191
+ {
192
+ "effects": [
193
+ [
194
+ "render-ui",
195
+ "main",
196
+ {
197
+ "children": [
198
+ {
199
+ "align": "center",
200
+ "children": [
201
+ {
202
+ "action": "BACK",
203
+ "icon": "arrow-left",
204
+ "label": "@config.backLabel",
205
+ "size": "sm",
206
+ "type": "button",
207
+ "variant": "ghost"
208
+ },
209
+ {
210
+ "fromNavStack": true,
211
+ "itemEvent": "CRUMB_NAV",
212
+ "type": "breadcrumb"
213
+ }
214
+ ],
215
+ "className": "w-full border-b border-border pb-3",
216
+ "direction": "horizontal",
217
+ "gap": "sm",
218
+ "type": "stack"
219
+ },
220
+ "@config.contentTrait"
221
+ ],
222
+ "className": "w-full",
223
+ "direction": "vertical",
224
+ "gap": "lg",
225
+ "type": "stack"
226
+ }
227
+ ]
228
+ ],
229
+ "event": "INIT",
230
+ "eventId": "evt_01M0J07QZHF7RM3JBVCVWSV7B4",
231
+ "from": "composing",
232
+ "to": "composing"
233
+ },
234
+ {
235
+ "effects": [
236
+ [
237
+ "navigate-back"
238
+ ]
239
+ ],
240
+ "event": "BACK",
241
+ "eventId": "evt_01M0J07QZKCG5P5AGK0VHN2N9G",
242
+ "from": "composing",
243
+ "to": "composing"
244
+ },
245
+ {
246
+ "effects": [
247
+ [
248
+ "navigate",
249
+ "@payload.href"
250
+ ]
251
+ ],
252
+ "event": "CRUMB_NAV",
253
+ "eventId": "evt_01M0J07QZKF2YJ9MMQ69KZ3CVB",
254
+ "from": "composing",
255
+ "to": "composing"
256
+ }
257
+ ]
258
+ }
259
+ }
260
+ ]
261
+ }
262
+ ],
263
+ "schemaVersion": 1,
264
+ "version": "1.0.0"
265
+ }