@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
@@ -0,0 +1,391 @@
1
+ app std-platformer-board-2d "1.0.0"
2
+ "2D run/jump platformer board FRAME: composes platformer-body (run/jump physics, one-way AABB collision, hazard/goal/fall signals) + fx-particles + objective-flow (win/lose gate) onto one shared GameState with a side-view canvas render. The intent trait owns only game RULES — input forwarding (LEFT/RIGHT/STOP/JUMP → STEER/JUMP intents), goal scoring, hazard/pit lives with respawn, PLAY_AGAIN reset — physics, particles, and the win gate belong to the atoms. Games consume this organism and supply level geometry (platforms, in both BodyAuthority.platforms for collision and the render-side platforms knob), theme/assets, and physics feel via config/traitOverrides."
3
+ orbital PlatformerBoard2DOrbital {
4
+ uses Body from "std/behaviors/std-platformer-body"
5
+ uses Particles from "std/behaviors/std-fx-particles"
6
+ uses Gate from "std/behaviors/std-objective-flow"
7
+
8
+ type Asset = {
9
+ url : asset
10
+ atlas : asset
11
+ sprite : string
12
+ role : string
13
+ category : string
14
+ animations : [string]
15
+ style : string
16
+ variant : string
17
+ dimension : string
18
+ aspect : string
19
+ name : string
20
+ thumbnailUrl : string
21
+ }
22
+ type PlatformerBody = {
23
+ x : number!
24
+ y : number!
25
+ width : number!
26
+ height : number!
27
+ vx : number
28
+ vy : number
29
+ grounded : boolean
30
+ facingRight : boolean
31
+ }
32
+ type PlatformerPlatform = {
33
+ id : string
34
+ x : number!
35
+ y : number!
36
+ width : number!
37
+ height : number!
38
+ type : "ground" | "platform" | "hazard" | "goal"
39
+ }
40
+ type Fx = {
41
+ id : string!
42
+ type : string!
43
+ x : number!
44
+ z : number
45
+ y : number
46
+ message : string
47
+ ttl : number!
48
+ maxTtl : number
49
+ bornAt : number
50
+ space : string
51
+ effect : string
52
+ color : string
53
+ size : number
54
+ vx : number
55
+ vy : number
56
+ vz : number
57
+ particleCount : number
58
+ }
59
+ type FxPreset = {
60
+ type : string!
61
+ color : string
62
+ color2 : string
63
+ size : number
64
+ shape : string
65
+ count : number
66
+ glow : number
67
+ gravity : number
68
+ vx : number
69
+ vy : number
70
+ vz : number
71
+ space : string
72
+ effect : string
73
+ particleCount : number
74
+ }
75
+ type DirectionAssets = {
76
+ up : Asset
77
+ down : Asset
78
+ left : Asset
79
+ right : Asset
80
+ }
81
+
82
+ entity GameState [runtime, shared] {
83
+ id : string!
84
+ body : PlatformerBody = { x: 80, y: 320, width: 32, height: 48, vx: 0, vy: 0, grounded: false, facingRight: true }
85
+ fell : boolean = false
86
+ hazardId : string = ""
87
+ goalId : string = ""
88
+ skating : boolean = false
89
+ fx : [Fx] = []
90
+ result : string = "none"
91
+ score : number = 0
92
+ lives : number = 3
93
+ objectivesLeft : number = 1
94
+ frame : number = 0
95
+ celebrated : boolean = false
96
+ }
97
+
98
+ trait BodyAuthority = Body.traits.PlatformerBody -> GameState {
99
+ events {
100
+ STEER: PLAT_STEER
101
+ JUMP: PLAT_JUMP
102
+ PLACE: PLAT_PLACE
103
+ BODY_MOVED: PLAT_MOVED
104
+ BODY_JUMPED: PLAT_JUMPED
105
+ BODY_LANDED: PLAT_LANDED
106
+ BODY_HAZARD: PLAT_HAZARD
107
+ BODY_GOAL: PLAT_GOAL
108
+ BODY_FELL: PLAT_FELL
109
+ }
110
+ config {
111
+ running: true
112
+ spawn: { x: 80, y: 320, width: 32, height: 48, vx: 0, vy: 0, grounded: false, facingRight: true }
113
+ platforms: [
114
+ { id: "g0", x: 0, y: 368, width: 800, height: 32, type: "ground" }
115
+ { id: "p1", x: 150, y: 280, width: 160, height: 16, type: "platform" }
116
+ { id: "p2", x: 420, y: 220, width: 160, height: 16, type: "platform" }
117
+ { id: "h1", x: 580, y: 300, width: 80, height: 16, type: "hazard" }
118
+ { id: "goal", x: 700, y: 340, width: 64, height: 28, type: "goal" }
119
+ ]
120
+ gravity: 550
121
+ jumpStrength: 365
122
+ moveSpeed: 120
123
+ maxFallSpeed: 600
124
+ xMin: 0
125
+ xMax: 768
126
+ killY: 460
127
+ }
128
+ }
129
+
130
+ trait FxDecay = Particles.traits.FxParticles -> GameState {
131
+ config { running: true }
132
+ }
133
+
134
+ trait RoundGate = Gate.traits.ObjectiveFlow -> GameState {
135
+ config { running: true }
136
+ }
137
+
138
+ trait PlatformerIntent -> GameState [interaction, instance] {
139
+ initial: playing
140
+
141
+ state playing {
142
+ INIT -> playing
143
+ (set @entity.score 0)
144
+ (set @entity.lives @config.startLives)
145
+ (set @entity.objectivesLeft 1)
146
+ (set @entity.celebrated false)
147
+ (emit RESTART { id: "board" })
148
+ (render-ui main {
149
+ type: game-shell
150
+ backgroundAsset: @config.backgroundAsset
151
+ hudBackgroundAsset: @config.hudBackgroundAsset
152
+ hud: { type: stack, direction: horizontal, gap: md, justify: between, children: [ { type: game-hud, stats: [ { label: "Score", value: @entity.score }, { label: "Lives", value: @entity.lives } ] }, { type: control-grid, kind: "dpad", directionEvents: { left: "LEFT", right: "RIGHT", up: "JUMP" }, directionReleaseEvents: { left: "STOP", right: "STOP" }, directionAssets: @config.directionAssets } ] }
153
+ overlay: (if (== @entity.result "won") { type: game-menu, title: @config.clearTitle, subtitle: (str/concat "Score: " @entity.score), options: [ { id: "again", label: "Play Again", event: "PLAY_AGAIN", variant: "primary" } ] } (if (== @entity.result "lost") { type: game-menu, title: @config.lostTitle, subtitle: (str/concat "Score: " @entity.score), options: [ { id: "again", label: "Play Again", event: "PLAY_AGAIN", variant: "primary" } ] } null))
154
+ children: [ { type: canvas, camera: { mode: "follow", zoom: 1, pos: { x: (/ @config.worldWidth 2), y: (/ @config.worldHeight 2) }, target: { x: @entity.body.x, y: @entity.body.y } }, projection: "side", worldWidth: @config.worldWidth, worldHeight: @config.worldHeight, backgroundColor: @config.bgColor, backgroundImage: @config.backgroundImage, keyMap: { ArrowLeft: "LEFT", KeyA: "LEFT", ArrowRight: "RIGHT", KeyD: "RIGHT", ArrowUp: "JUMP", KeyW: "JUMP", Space: "JUMP" }, keyUpMap: { ArrowLeft: "STOP", KeyA: "STOP", ArrowRight: "STOP", KeyD: "STOP" }, drawables: [
155
+ { type: draw-sprite-layer, items: (array/map @config.platforms (fn p { type: draw-sprite, position: { x: (object/get @p x), y: (object/get @p y) }, width: (object/get @p width), height: (object/get @p height), asset: (object/get @config.tileSprites (object/get @p type)) })) }
156
+ { type: draw-sprite-layer, items: [ (object/merge { type: "draw-sprite", position: { x: @entity.body.x, y: @entity.body.y }, width: @entity.body.width, height: @entity.body.height, frame: @entity.frame, flipX: (not @entity.body.facingRight) } { asset: (object/merge @config.playerSprite { sprite: (if @entity.body.grounded (if (!= @entity.body.vx 0) (if (== (% @entity.frame 2) 0) "adventurer_walk1.png" "adventurer_walk2.png") "adventurer_idle.png") "adventurer_jump.png") }) }) ] }
157
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets }
158
+ ] } ]
159
+ })
160
+
161
+ LEFT -> playing when (== @entity.result "none")
162
+ (emit PLAT_STEER { dir: -1 })
163
+
164
+ RIGHT -> playing when (== @entity.result "none")
165
+ (emit PLAT_STEER { dir: 1 })
166
+
167
+ STOP -> playing when (== @entity.result "none")
168
+ (emit PLAT_STEER { dir: 0 })
169
+
170
+ JUMP -> playing when (== @entity.result "none")
171
+ (emit PLAT_JUMP {})
172
+
173
+ PLAT_GOAL -> playing when (== @entity.result "none")
174
+ (set @entity.score (+ @entity.score @config.goalScore))
175
+ (set @entity.objectivesLeft 0)
176
+ (emit BURST { particles: (array/map (array/filter @config.platforms (fn p (== (object/get @p "id" "") @payload.id))) (fn p { id: (str/concat "fx-goal-" @payload.id), type: "score", x: @payload.x, z: @payload.y, message: (str/concat "+" @config.goalScore), ttl: 3 })) })
177
+ (emit PROGRESS { objectivesLeft: 0, movesLeft: @entity.lives })
178
+
179
+ PLAT_HAZARD -> playing when (== @entity.result "none")
180
+ (if (<= (- @entity.lives 1) 0)
181
+ (do
182
+ (set @entity.lives 0)
183
+ (emit PROGRESS { objectivesLeft: 1, movesLeft: 0 }))
184
+ (do
185
+ (set @entity.lives (- @entity.lives 1))
186
+ (emit BURST { particles: (array/map (array/filter @config.platforms (fn p (== (object/get @p "id" "") @payload.id))) (fn p { id: (str/concat "fx-hazard-" @payload.id), type: "damage", x: @payload.x, z: @payload.y, message: "-1", ttl: 3 })) })
187
+ (emit PLAT_PLACE { x: @config.respawnX, y: @config.respawnY })))
188
+
189
+ PLAT_FELL -> playing when (== @entity.result "none")
190
+ (if (<= (- @entity.lives 1) 0)
191
+ (do
192
+ (set @entity.lives 0)
193
+ (emit PROGRESS { objectivesLeft: 1, movesLeft: 0 }))
194
+ (do
195
+ (set @entity.lives (- @entity.lives 1))
196
+ (emit BURST { particles: (array/map [ "fx-fell" ] (fn fid { id: @fid, type: "damage", x: @payload.x, z: @config.worldHeight, message: "-1", ttl: 3 })) })
197
+ (emit PLAT_PLACE { x: @config.respawnX, y: @config.respawnY })))
198
+
199
+ GAME_END -> playing when (== @entity.celebrated false)
200
+ (set @entity.celebrated true)
201
+
202
+ PLAY_AGAIN -> playing
203
+ (set @entity.score 0)
204
+ (set @entity.lives @config.startLives)
205
+ (set @entity.objectivesLeft 1)
206
+ (set @entity.celebrated false)
207
+ (emit RESTART { id: "board" })
208
+ }
209
+
210
+ ticks {
211
+ renderTick every 100ms
212
+ (set @entity.frame (% (+ @entity.frame 1) 8))
213
+ (render-ui main {
214
+ type: game-shell
215
+ backgroundAsset: @config.backgroundAsset
216
+ hudBackgroundAsset: @config.hudBackgroundAsset
217
+ hud: { type: stack, direction: horizontal, gap: md, justify: between, children: [ { type: game-hud, stats: [ { label: "Score", value: @entity.score }, { label: "Lives", value: @entity.lives } ] }, { type: control-grid, kind: "dpad", directionEvents: { left: "LEFT", right: "RIGHT", up: "JUMP" }, directionReleaseEvents: { left: "STOP", right: "STOP" }, directionAssets: @config.directionAssets } ] }
218
+ overlay: (if (== @entity.result "won") { type: game-menu, title: @config.clearTitle, subtitle: (str/concat "Score: " @entity.score), options: [ { id: "again", label: "Play Again", event: "PLAY_AGAIN", variant: "primary" } ] } (if (== @entity.result "lost") { type: game-menu, title: @config.lostTitle, subtitle: (str/concat "Score: " @entity.score), options: [ { id: "again", label: "Play Again", event: "PLAY_AGAIN", variant: "primary" } ] } null))
219
+ children: [ { type: canvas, camera: { mode: "follow", zoom: 1, pos: { x: (/ @config.worldWidth 2), y: (/ @config.worldHeight 2) }, target: { x: @entity.body.x, y: @entity.body.y } }, projection: "side", worldWidth: @config.worldWidth, worldHeight: @config.worldHeight, backgroundColor: @config.bgColor, backgroundImage: @config.backgroundImage, keyMap: { ArrowLeft: "LEFT", KeyA: "LEFT", ArrowRight: "RIGHT", KeyD: "RIGHT", ArrowUp: "JUMP", KeyW: "JUMP", Space: "JUMP" }, keyUpMap: { ArrowLeft: "STOP", KeyA: "STOP", ArrowRight: "STOP", KeyD: "STOP" }, drawables: [
220
+ { type: draw-sprite-layer, items: (array/map @config.platforms (fn p { type: draw-sprite, position: { x: (object/get @p x), y: (object/get @p y) }, width: (object/get @p width), height: (object/get @p height), asset: (object/get @config.tileSprites (object/get @p type)) })) }
221
+ { type: draw-sprite-layer, items: [ (object/merge { type: "draw-sprite", position: { x: @entity.body.x, y: @entity.body.y }, width: @entity.body.width, height: @entity.body.height, frame: @entity.frame, flipX: (not @entity.body.facingRight) } { asset: (object/merge @config.playerSprite { sprite: (if @entity.body.grounded (if (!= @entity.body.vx 0) (if (== (% @entity.frame 2) 0) "adventurer_walk1.png" "adventurer_walk2.png") "adventurer_idle.png") "adventurer_jump.png") }) }) ] }
222
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets }
223
+ ] } ]
224
+ })
225
+ }
226
+
227
+ emits {
228
+ LEFT -> external {
229
+ id : string
230
+ }
231
+ @description "Move-left intent from ArrowLeft/KeyA keydown or d-pad (forwarded to the body authority as STEER dir −1)"
232
+ @tier "domain"
233
+ RIGHT -> external {
234
+ id : string
235
+ }
236
+ @description "Move-right intent from ArrowRight/KeyD keydown or d-pad (forwarded to the body authority as STEER dir +1)"
237
+ @tier "domain"
238
+ STOP -> external {
239
+ id : string
240
+ }
241
+ @description "Horizontal key release (keyUpMap or d-pad directionReleaseEvents); forwarded as STEER dir 0 so the body stops"
242
+ @tier "domain"
243
+ JUMP -> external {
244
+ id : string
245
+ }
246
+ @description "Jump intent from Space/ArrowUp/KeyW or d-pad up; forwarded to the body authority, which accepts it only while grounded"
247
+ @tier "domain"
248
+ PLAT_STEER -> external {
249
+ dir : number!
250
+ }
251
+ @description "Renamed STEER consumed by the platformer-body authority: −1/0/+1 × moveSpeed horizontal velocity"
252
+ @tier "domain"
253
+ PLAT_JUMP -> external {
254
+ id : string
255
+ }
256
+ @description "Renamed JUMP consumed by the platformer-body authority (grounded-gated vertical kick)"
257
+ @tier "domain"
258
+ PLAT_PLACE -> external {
259
+ x : number!
260
+ y : number!
261
+ }
262
+ @description "Renamed PLACE consumed by the platformer-body authority; hazard/pit respawn teleport"
263
+ @tier "domain"
264
+ PLAT_GOAL -> external {
265
+ id : string!
266
+ x : number!
267
+ y : number!
268
+ }
269
+ @description "Goal-tile contact signal from the platformer-body authority (renamed BODY_GOAL); scores and trips the win gate"
270
+ @tier "domain"
271
+ PLAT_HAZARD -> external {
272
+ id : string!
273
+ x : number!
274
+ y : number!
275
+ }
276
+ @description "Hazard-tile contact signal from the platformer-body authority (renamed BODY_HAZARD); costs a life and respawns"
277
+ @tier "domain"
278
+ PLAT_FELL -> external {
279
+ x : number!
280
+ y : number!
281
+ }
282
+ @description "Pit-fall signal from the platformer-body authority (renamed BODY_FELL); costs a life and respawns"
283
+ @tier "domain"
284
+ BURST -> external {
285
+ particles : [Fx]
286
+ }
287
+ @description "Floating-text particle batch spawned on score/damage; fx-particles fades it"
288
+ @tier "domain"
289
+ PROGRESS -> external {
290
+ objectivesLeft : number!
291
+ movesLeft : number!
292
+ }
293
+ @description "Sparse win/lose signal consumed by the objective-flow gate: objectivesLeft 0 = won, movesLeft 0 = lost"
294
+ @tier "domain"
295
+ GAME_END -> external {
296
+ result : string!
297
+ }
298
+ @description "Win/lose resolution from the objective-flow gate; guarded by celebrated so it lands once"
299
+ @tier "domain"
300
+ RESTART -> external {
301
+ id : string
302
+ }
303
+ @description "Broadcast reset consumed by the platformer-body and objective-flow authorities on INIT/PLAY_AGAIN"
304
+ @tier "domain"
305
+ PLAY_AGAIN -> external {
306
+ id : string
307
+ }
308
+ @description "End-overlay restart button; reseeds score/lives/objectives and broadcasts RESTART"
309
+ @tier "domain"
310
+ }
311
+
312
+ config {
313
+ platforms : [PlatformerPlatform] = [
314
+ { id: "g0", x: 0, y: 368, width: 800, height: 32, type: "ground" }
315
+ { id: "p1", x: 150, y: 280, width: 160, height: 16, type: "platform" }
316
+ { id: "p2", x: 420, y: 220, width: 160, height: 16, type: "platform" }
317
+ { id: "h1", x: 580, y: 300, width: 80, height: 16, type: "hazard" }
318
+ { id: "goal", x: 700, y: 340, width: 64, height: 28, type: "goal" }
319
+ ]
320
+ @label "Platforms"
321
+ @description "Level geometry rendered by the canvas (must match BodyAuthority.platforms, which owns collision — override both together)"
322
+ @tier "domain"
323
+ respawnX : number = 80
324
+ @label "Respawn X"
325
+ @description "X the body teleports to after hazard/pit damage (matches BodyAuthority spawn.x)"
326
+ @tier "domain"
327
+ respawnY : number = 320
328
+ @label "Respawn Y"
329
+ @description "Y the body teleports to after hazard/pit damage (matches BodyAuthority spawn.y)"
330
+ @tier "domain"
331
+ goalScore : number = 100
332
+ @label "Goal Score"
333
+ @description "Points awarded when the body touches the goal tile"
334
+ @tier "domain"
335
+ startLives : number = 3
336
+ @label "Start Lives"
337
+ @description "Lives seeded on INIT/PLAY_AGAIN; hazard or pit contact costs one, zero loses the round"
338
+ @tier "domain"
339
+ worldWidth : number = 800
340
+ @label "World Width"
341
+ @description "World pixel width (camera + canvas bounds)"
342
+ @tier "presentation"
343
+ fxPresets : [FxPreset] = [ { type: "score", color: "#ffe066", color2: "#fff4cc", shape: "spark", size: 22, count: 6, glow: 0.15, vy: -35 }, { type: "damage", color: "#ef4444", color2: "#7f1d1d", shape: "spark", size: 20, count: 5, glow: 0.12, gravity: 40 } ]
344
+ @label "Fx Presets"
345
+ @description "This board's effect vocabulary — one look/recipe row per fx type its intent trait emits (goal score, hazard/fell damage)."
346
+ @tier "presentation"
347
+ worldHeight : number = 400
348
+ @label "World Height"
349
+ @description "World pixel height (camera + canvas bounds)"
350
+ @tier "presentation"
351
+ bgColor : string = "#5c94fc"
352
+ @label "Bg Color"
353
+ @description "Canvas background color"
354
+ @tier "presentation"
355
+ clearTitle : string = "YOU WIN!"
356
+ @label "Clear Title"
357
+ @description "Overlay title when the goal tile is reached"
358
+ @tier "presentation"
359
+ lostTitle : string = "GAME OVER"
360
+ @label "Lost Title"
361
+ @description "Overlay title when lives run out"
362
+ @tier "presentation"
363
+ playerSprite : Asset = {}
364
+ @label "Player Sprite"
365
+ @description "Player sprite Asset; its atlas must expose adventurer_idle/walk1/walk2/jump frames (or override the sprite-selection expression)"
366
+ @tier "presentation"
367
+ tileSprites : Map string Asset = {}
368
+ @label "Tile Sprites"
369
+ @description "Map of platform type (ground/platform/hazard/goal) to tile sprite Asset"
370
+ @tier "presentation"
371
+ backgroundImage : Asset = {}
372
+ @label "Background Image"
373
+ @description "Canvas background image Asset"
374
+ @tier "presentation"
375
+ backgroundAsset : Asset = {}
376
+ @label "Background Asset"
377
+ @description "Game-shell background panel sprite (theme chrome)"
378
+ @tier "presentation"
379
+ hudBackgroundAsset : Asset = {}
380
+ @label "HUD Background Asset"
381
+ @description "HUD panel background sprite (theme chrome)"
382
+ @tier "presentation"
383
+ directionAssets : DirectionAssets = {}
384
+ @label "Direction Assets"
385
+ @description "D-pad direction icon sprites"
386
+ @tier "presentation"
387
+ }
388
+ }
389
+
390
+ page "/platformer-board-2d" as PlatformerBoard2DPage -> BodyAuthority, FxDecay, RoundGate, PlatformerIntent
391
+ }
@@ -50,6 +50,32 @@ orbital SurvivalBoard3DOrbital {
50
50
  y : number
51
51
  message : string
52
52
  ttl : number!
53
+ maxTtl : number
54
+ bornAt : number
55
+ space : string
56
+ effect : string
57
+ color : string
58
+ size : number
59
+ vx : number
60
+ vy : number
61
+ vz : number
62
+ particleCount : number
63
+ }
64
+ type FxPreset = {
65
+ type : string!
66
+ color : string
67
+ color2 : string
68
+ size : number
69
+ shape : string
70
+ count : number
71
+ glow : number
72
+ gravity : number
73
+ vx : number
74
+ vy : number
75
+ vz : number
76
+ space : string
77
+ effect : string
78
+ particleCount : number
53
79
  }
54
80
  type ValidMove = { x : number! z : number! }
55
81
  type Feature = {
@@ -149,7 +175,7 @@ orbital SurvivalBoard3DOrbital {
149
175
  { type: draw-shape-layer, items: (array/map @entity.units (fn u { type: draw-shape, shape: "rect", position: (object/get @u position), offsetY: -0.6, width: 0.8, height: 0.12, fill: "rgba(0,0,0,0.6)" })) }
150
176
  { type: draw-shape-layer, items: (array/map @entity.units (fn u { type: draw-shape, shape: "rect", position: (object/get @u position), offsetY: -0.6, width: (* 0.8 (/ (object/get @u health) (object/get @u maxHealth))), height: 0.12, fill: "#22c55e" })) }
151
177
  { type: draw-text-layer, items: (array/map @entity.units (fn u { type: draw-text, text: (object/get @u name), position: (object/get @u position), offsetY: -0.85, color: "#ffffff" })) }
152
- { 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) }, color: "#ffe066" })) }
178
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets }
153
179
  ] } ]
154
180
  })
155
181
 
@@ -195,7 +221,7 @@ orbital SurvivalBoard3DOrbital {
195
221
  { type: draw-shape-layer, items: (array/map @entity.units (fn u { type: draw-shape, shape: "rect", position: (object/get @u position), offsetY: -0.6, width: 0.8, height: 0.12, fill: "rgba(0,0,0,0.6)" })) }
196
222
  { type: draw-shape-layer, items: (array/map @entity.units (fn u { type: draw-shape, shape: "rect", position: (object/get @u position), offsetY: -0.6, width: (* 0.8 (/ (object/get @u health) (object/get @u maxHealth))), height: 0.12, fill: "#22c55e" })) }
197
223
  { type: draw-text-layer, items: (array/map @entity.units (fn u { type: draw-text, text: (object/get @u name), position: (object/get @u position), offsetY: -0.85, color: "#ffffff" })) }
198
- { 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) }, color: "#ffe066" })) }
224
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets }
199
225
  ] } ]
200
226
  })
201
227
 
@@ -288,6 +314,10 @@ orbital SurvivalBoard3DOrbital {
288
314
  @label "Bg Color"
289
315
  @description "Canvas background color."
290
316
  @tier "presentation"
317
+ fxPresets : [FxPreset] = [ { type: "pickup", color: "#84a98c", color2: "#6b4226", size: 0.45, shape: "spark", count: 5, vy: -0.2 } ]
318
+ @label "Fx Presets"
319
+ @description "This board's effect vocabulary — one look/recipe row per fx type its mechanics emit; unknown types fall back to a neutral spark."
320
+ @tier "presentation"
291
321
  assetManifest : AssetManifest = { terrains: {}, units: {}, features: {}, effects: {} }
292
322
  @label "Asset Manifest"
293
323
  @description "Per-role model manifest resolving tile/unit/feature models by terrain/category key."
@@ -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 {