@almadar/std 16.177.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 +413 -393
  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
@@ -60,6 +60,32 @@ orbital DungeonBoard2DOrbital {
60
60
  y : number
61
61
  message : string
62
62
  ttl : number!
63
+ maxTtl : number
64
+ bornAt : number
65
+ space : string
66
+ effect : string
67
+ color : string
68
+ size : number
69
+ vx : number
70
+ vy : number
71
+ vz : number
72
+ particleCount : number
73
+ }
74
+ type FxPreset = {
75
+ type : string!
76
+ color : string
77
+ color2 : string
78
+ size : number
79
+ shape : string
80
+ count : number
81
+ glow : number
82
+ gravity : number
83
+ vx : number
84
+ vy : number
85
+ vz : number
86
+ space : string
87
+ effect : string
88
+ particleCount : number
63
89
  }
64
90
  type DirectionAssets = {
65
91
  up : Asset
@@ -203,7 +229,7 @@ orbital DungeonBoard2DOrbital {
203
229
  { type: draw-shape-layer, items: (array/map (array/concat @entity.units @entity.enemies) (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)" })) }
204
230
  { type: draw-shape-layer, items: (array/map (array/concat @entity.units @entity.enemies) (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" })) }
205
231
  { type: draw-text-layer, items: (array/map (array/concat @entity.units @entity.enemies) (fn u { type: draw-text, text: (object/get @u name), position: (object/get @u position), offsetY: -0.85, color: "#ffffff" })) }
206
- { 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" })) }
232
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets, sprites: (object/get @config.assetManifest effects) }
207
233
  ] } ]
208
234
  })
209
235
 
@@ -269,7 +295,7 @@ orbital DungeonBoard2DOrbital {
269
295
  { type: draw-shape-layer, items: (array/map (array/concat @entity.units @entity.enemies) (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)" })) }
270
296
  { type: draw-shape-layer, items: (array/map (array/concat @entity.units @entity.enemies) (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" })) }
271
297
  { type: draw-text-layer, items: (array/map (array/concat @entity.units @entity.enemies) (fn u { type: draw-text, text: (object/get @u name), position: (object/get @u position), offsetY: -0.85, color: "#ffffff" })) }
272
- { 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" })) }
298
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets, sprites: (object/get @config.assetManifest effects) }
273
299
  ] } ]
274
300
  })
275
301
 
@@ -345,6 +371,10 @@ orbital DungeonBoard2DOrbital {
345
371
  @label "Font Family"
346
372
  @description "game-shell font family (e.g. pixel)."
347
373
  @tier "presentation"
374
+ fxPresets : [FxPreset] = [ { type: "explosion", color: "#fb923c", color2: "#7c2d12", shape: "ring", size: 0.85, count: 8, glow: 0.2, gravity: 0.4 }, { type: "pickup", color: "#ffe066", color2: "#fff4cc", shape: "spark", size: 0.4, count: 5, glow: 0.1, vy: -0.5 } ]
375
+ @label "Fx Presets"
376
+ @description "This board's effect vocabulary — one look/recipe row per fx type its mechanics emit; unknown types fall back to a neutral spark."
377
+ @tier "presentation"
348
378
  assetManifest : AssetManifest = { terrains: {}, units: {}, features: {}, effects: {} }
349
379
  @label "Asset Manifest"
350
380
  @description "Terrain/unit/feature/effect sprite manifest."
@@ -59,6 +59,32 @@ orbital MarketBoard3DOrbital {
59
59
  y : number
60
60
  message : string
61
61
  ttl : number!
62
+ maxTtl : number
63
+ bornAt : number
64
+ space : string
65
+ effect : string
66
+ color : string
67
+ size : number
68
+ vx : number
69
+ vy : number
70
+ vz : number
71
+ particleCount : number
72
+ }
73
+ type FxPreset = {
74
+ type : string!
75
+ color : string
76
+ color2 : string
77
+ size : number
78
+ shape : string
79
+ count : number
80
+ glow : number
81
+ gravity : number
82
+ vx : number
83
+ vy : number
84
+ vz : number
85
+ space : string
86
+ effect : string
87
+ particleCount : number
62
88
  }
63
89
  type ValidMove = { x : number! z : number! }
64
90
  type Feature = {
@@ -182,7 +208,7 @@ orbital MarketBoard3DOrbital {
182
208
  { type: draw-shape-layer, items: (array/concat (array/map @entity.units (fn u { type: draw-shape, shape: "rect", position: { x: (object/get (object/get @u position) x), y: (object/get (object/get @u position) y), z: (object/get @u z) }, offsetY: -0.6, width: 0.8, height: 0.12, fill: "rgba(0,0,0,0.6)" })) (array/map @entity.customers (fn c { type: draw-shape, shape: "rect", position: (object/get @c position), offsetY: -0.6, width: 0.8, height: 0.12, fill: "rgba(0,0,0,0.6)" }))) }
183
209
  { type: draw-shape-layer, items: (array/concat (array/map @entity.units (fn u { type: draw-shape, shape: "rect", position: { x: (object/get (object/get @u position) x), y: (object/get (object/get @u position) 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" })) (array/map @entity.customers (fn c { type: draw-shape, shape: "rect", position: (object/get @c position), offsetY: -0.6, width: 0.8, height: 0.12, fill: "#22c55e" }))) }
184
210
  { type: draw-text-layer, items: (array/concat (array/map @entity.units (fn u { type: draw-text, text: (object/get @u name), position: { x: (object/get (object/get @u position) x), y: (object/get (object/get @u position) y), z: (object/get @u z) }, offsetY: -0.85, color: "#ffffff" })) (array/map @entity.customers (fn c { type: draw-text, text: (object/get @c name), position: (object/get @c position), offsetY: -0.85, color: "#ffffff" }))) }
185
- { 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" })) }
211
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets }
186
212
  ] } ]
187
213
  })
188
214
 
@@ -225,7 +251,7 @@ orbital MarketBoard3DOrbital {
225
251
  { type: draw-shape-layer, items: (array/concat (array/map @entity.units (fn u { type: draw-shape, shape: "rect", position: { x: (object/get (object/get @u position) x), y: (object/get (object/get @u position) y), z: (object/get @u z) }, offsetY: -0.6, width: 0.8, height: 0.12, fill: "rgba(0,0,0,0.6)" })) (array/map @entity.customers (fn c { type: draw-shape, shape: "rect", position: (object/get @c position), offsetY: -0.6, width: 0.8, height: 0.12, fill: "rgba(0,0,0,0.6)" }))) }
226
252
  { type: draw-shape-layer, items: (array/concat (array/map @entity.units (fn u { type: draw-shape, shape: "rect", position: { x: (object/get (object/get @u position) x), y: (object/get (object/get @u position) 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" })) (array/map @entity.customers (fn c { type: draw-shape, shape: "rect", position: (object/get @c position), offsetY: -0.6, width: 0.8, height: 0.12, fill: "#22c55e" }))) }
227
253
  { type: draw-text-layer, items: (array/concat (array/map @entity.units (fn u { type: draw-text, text: (object/get @u name), position: { x: (object/get (object/get @u position) x), y: (object/get (object/get @u position) y), z: (object/get @u z) }, offsetY: -0.85, color: "#ffffff" })) (array/map @entity.customers (fn c { type: draw-text, text: (object/get @c name), position: (object/get @c position), offsetY: -0.85, color: "#ffffff" }))) }
228
- { 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" })) }
254
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets }
229
255
  ] } ]
230
256
  })
231
257
  }
@@ -317,6 +343,10 @@ orbital MarketBoard3DOrbital {
317
343
  @label "Features"
318
344
  @description "Static decorative props (shelves/freezers/checkout/etc); never mutated by any mechanic."
319
345
  @tier "presentation"
346
+ fxPresets : [FxPreset] = [ { type: "score", color: "#fbbf24", color2: "#fde68a", size: 0.45, shape: "spark", count: 6, glow: 0.1, vy: -0.3 }, { type: "damage", color: "#ef4444", color2: "#450a0a", size: 0.5, shape: "puff", count: 4, gravity: 0.3 } ]
347
+ @label "Fx Presets"
348
+ @description "This board's effect vocabulary — one look/recipe row per fx type its mechanics emit; unknown types fall back to a neutral spark."
349
+ @tier "presentation"
320
350
  assetManifest : AssetManifest = { terrains: {}, units: {}, features: {}, effects: {} }
321
351
  @label "Asset Manifest"
322
352
  @description "Per-role model manifest resolving tile models by terrain key."
@@ -50,6 +50,32 @@ orbital ObjectiveBoard3DOrbital {
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 = {
@@ -174,7 +200,7 @@ orbital ObjectiveBoard3DOrbital {
174
200
  { 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)" })) }
175
201
  { 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" })) }
176
202
  { 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" })) }
177
- { 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" })) }
203
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets }
178
204
  ] } ]
179
205
  })
180
206
 
@@ -217,7 +243,7 @@ orbital ObjectiveBoard3DOrbital {
217
243
  { 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)" })) }
218
244
  { 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" })) }
219
245
  { 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" })) }
220
- { 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" })) }
246
+ { type: draw-fx-layer, items: @entity.fx, tickMs: 500, presets: @config.fxPresets }
221
247
  ] } ]
222
248
  })
223
249
 
@@ -305,6 +331,10 @@ orbital ObjectiveBoard3DOrbital {
305
331
  @label "Bg Color"
306
332
  @description "Canvas background color."
307
333
  @tier "presentation"
334
+ fxPresets : [FxPreset] = [ { type: "pickup", color: "#ffe066", color2: "#ffffff", size: 0.5, shape: "spark", count: 6, glow: 0.14, vy: -0.3 } ]
335
+ @label "Fx Presets"
336
+ @description "This board's effect vocabulary — one look/recipe row per fx type its mechanics emit; unknown types fall back to a neutral spark."
337
+ @tier "presentation"
308
338
  assetManifest : AssetManifest = { terrains: {}, units: {}, features: {}, effects: {} }
309
339
  @label "Asset Manifest"
310
340
  @description "Per-role model manifest resolving tile/unit/feature models by terrain/category key."
@@ -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."