@almadar/std 16.201.0 → 16.202.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 (45) hide show
  1. package/behaviors/lolo/ui/core/molecules/ui-command-palette.lolo +116 -0
  2. package/behaviors/lolo/ui/core/molecules/ui-floating-toolbar.lolo +2 -1
  3. package/behaviors/lolo/ui/game/atoms/std-audio-cue.lolo +138 -0
  4. package/behaviors/lolo/ui/game/atoms/std-camera-rig.lolo +158 -0
  5. package/behaviors/lolo/ui/game/atoms/ui-canvas-2d.lolo +42 -0
  6. package/behaviors/lolo/ui/game/atoms/ui-canvas.lolo +42 -0
  7. package/behaviors/lolo/ui/game/atoms/ui-game-audio-cue.lolo +68 -0
  8. package/behaviors/lolo/ui/game/atoms/ui-game-audio-toggle.lolo +13 -0
  9. package/behaviors/lolo/ui/learning/atoms/ui-algo-graph-canvas.lolo +1 -0
  10. package/behaviors/lolo/ui/learning/atoms/ui-algorithm-canvas.lolo +1 -0
  11. package/behaviors/lolo/ui/learning/atoms/ui-biology-canvas.lolo +1 -0
  12. package/behaviors/lolo/ui/learning/atoms/ui-chemistry-canvas.lolo +1 -0
  13. package/behaviors/lolo/ui/learning/atoms/ui-learning-canvas.lolo +7 -0
  14. package/behaviors/lolo/ui/learning/atoms/ui-math-canvas.lolo +7 -0
  15. package/behaviors/lolo/ui/learning/atoms/ui-physics-canvas.lolo +1 -0
  16. package/behaviors/registry/ui/core/molecules/ui-command-palette.orb +589 -0
  17. package/behaviors/registry/ui/core/molecules/ui-floating-toolbar.orb +13 -2
  18. package/behaviors/registry/ui/game/atoms/std-audio-cue.orb +653 -0
  19. package/behaviors/registry/ui/game/atoms/std-camera-rig.orb +813 -0
  20. package/behaviors/registry/ui/game/atoms/ui-canvas-2d.orb +135 -0
  21. package/behaviors/registry/ui/game/atoms/ui-canvas.orb +134 -0
  22. package/behaviors/registry/ui/game/atoms/ui-game-audio-cue.orb +258 -0
  23. package/behaviors/registry/ui/game/atoms/ui-game-audio-toggle.orb +48 -0
  24. package/behaviors/registry/ui/learning/atoms/ui-algo-graph-canvas.orb +9 -0
  25. package/behaviors/registry/ui/learning/atoms/ui-algorithm-canvas.orb +13 -0
  26. package/behaviors/registry/ui/learning/atoms/ui-biology-canvas.orb +13 -0
  27. package/behaviors/registry/ui/learning/atoms/ui-chemistry-canvas.orb +13 -0
  28. package/behaviors/registry/ui/learning/atoms/ui-learning-canvas.orb +21 -0
  29. package/behaviors/registry/ui/learning/atoms/ui-math-canvas.orb +21 -0
  30. package/behaviors/registry/ui/learning/atoms/ui-physics-canvas.orb +13 -0
  31. package/dist/behaviors/exports-reader.js +3067 -2891
  32. package/dist/behaviors/functions/index.d.ts +407 -5
  33. package/dist/behaviors/functions/index.js +3048 -2884
  34. package/dist/behaviors/index.d.ts +1 -1
  35. package/dist/behaviors/index.js +3060 -2884
  36. package/dist/behaviors/query.js +3067 -2891
  37. package/dist/behaviors-embeddings.hash.json +3 -3
  38. package/dist/behaviors-embeddings.json +23 -7
  39. package/dist/behaviors-registry.json +2780 -391
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.js +3060 -2884
  42. package/dist/knob-embeddings.hash.json +3 -3
  43. package/dist/knob-embeddings.json +1 -1
  44. package/dist/registry/factory-signatures.json +1 -1
  45. package/package.json +2 -2
@@ -0,0 +1,116 @@
1
+ app ui-command-palette "1.0.0"
2
+ "CommandPalette — UI factory (1:1 with the @almadar/ui command-palette pattern)."
3
+ orbital CommandPaletteOrbital {
4
+ type CommandPaletteCommandsItem = {
5
+ id : string!
6
+ label : string!
7
+ icon : icon
8
+ shortcut : string
9
+ keywords : [string]
10
+ group : string
11
+ disabled : boolean
12
+ event : string
13
+ action : string
14
+ actionPayload : Map string scalar
15
+ }
16
+ type CommandPaletteSELECTCommand = {
17
+ id : string!
18
+ label : string!
19
+ icon : icon
20
+ shortcut : string
21
+ keywords : [string]
22
+ group : string
23
+ disabled : boolean
24
+ event : string
25
+ action : string
26
+ actionPayload : Map string scalar
27
+ }
28
+
29
+ entity CommandPaletteItem [runtime] {
30
+ id : string!
31
+ open : boolean = false
32
+ }
33
+
34
+ trait CommandPaletteRender -> @rebindable CommandPaletteItem [interaction, instance] {
35
+ initial: idle
36
+
37
+ state idle {
38
+ INIT -> idle
39
+ (set @entity.open @config.open)
40
+ (render-ui main {
41
+ open: @entity.open
42
+ onOpenChange: @config.onOpenChange
43
+ commands: @config.commands
44
+ onSelect: @config.onSelect
45
+ placeholder: @config.placeholder
46
+ emptyLabel: @config.emptyLabel
47
+ className: @config.className
48
+ type: command-palette
49
+ })
50
+ OPEN_CHANGE -> idle
51
+ (set @entity.open ?open)
52
+ (render-ui main {
53
+ open: @entity.open
54
+ onOpenChange: @config.onOpenChange
55
+ commands: @config.commands
56
+ onSelect: @config.onSelect
57
+ placeholder: @config.placeholder
58
+ emptyLabel: @config.emptyLabel
59
+ className: @config.className
60
+ type: command-palette
61
+ })
62
+ }
63
+
64
+ emits {
65
+ @config.onOpenChange -> external {
66
+ open : boolean
67
+ }
68
+ @description "Called to open/close the overlay (Escape, overlay click, or a selection)"
69
+ @tier "essential"
70
+ @config.onSelect -> external {
71
+ command : CommandPaletteSELECTCommand
72
+ }
73
+ @description "Called with the selected command (click, or Enter on the highlighted row)"
74
+ @tier "essential"
75
+ }
76
+
77
+ listens {
78
+ OPEN_CHANGE {
79
+ open : boolean
80
+ }
81
+ }
82
+
83
+ config {
84
+ open : boolean = false
85
+ @label "Open"
86
+ @description "Whether the palette overlay is open"
87
+ @tier "presentation"
88
+ onOpenChange : event = "OPEN_CHANGE"
89
+ @label "On Open Change"
90
+ @description "Called to open/close the overlay (Escape, overlay click, or a selection)"
91
+ @tier "presentation"
92
+ commands : [CommandPaletteCommandsItem] = [{ id: "Id", label: "Label", icon: "circle", shortcut: "Shortcut", keywords: ["Item", "Item 2"], group: "Group", disabled: false, action: "Action" }, { id: "Id 2", label: "Label 2", icon: "circle", shortcut: "Shortcut 2", keywords: ["Item", "Item 2"], group: "Group 2", disabled: true, action: "Action 2" }]
93
+ @label "Commands"
94
+ @description "Commands listed in the palette, filtered as the user types"
95
+ @tier "presentation"
96
+ onSelect : event = "SELECT"
97
+ @label "On Select"
98
+ @description "Called with the selected command (click, or Enter on the highlighted row)"
99
+ @tier "presentation"
100
+ placeholder : string = "Type a command..."
101
+ @label "Placeholder"
102
+ @description "Search field placeholder. Consumers own i18n — plain string prop, no package-internal translation (matches FloatingToolbar's convention)."
103
+ @tier "presentation"
104
+ emptyLabel : string = "No matching commands"
105
+ @label "Empty Label"
106
+ @description "Label shown when no command matches the query"
107
+ @tier "presentation"
108
+ className : string
109
+ @label "Class Name"
110
+ @description "Additional CSS classes on the overlay's dialog"
111
+ @tier "presentation"
112
+ }
113
+ }
114
+
115
+ page "/command-palette" as CommandPalettePage -> CommandPaletteRender
116
+ }
@@ -10,6 +10,7 @@ orbital FloatingToolbarOrbital {
10
10
  actionPayload : Map string scalar
11
11
  active : boolean
12
12
  disabled : boolean
13
+ testId : string
13
14
  }
14
15
 
15
16
  entity FloatingToolbarItem [runtime] {
@@ -31,7 +32,7 @@ orbital FloatingToolbarOrbital {
31
32
  }
32
33
 
33
34
  config {
34
- items : [FloatingToolbarItemsItem] = [{ id: "Id", icon: "circle", label: "Label", action: "Action", active: false, disabled: false }, { id: "Id 2", icon: "circle", label: "Label 2", action: "Action 2", active: true, disabled: true }]
35
+ items : [FloatingToolbarItemsItem] = [{ id: "Id", icon: "circle", label: "Label", action: "Action", active: false, disabled: false, testId: "Test Id" }, { id: "Id 2", icon: "circle", label: "Label 2", action: "Action 2", active: true, disabled: true, testId: "Test Id 2" }]
35
36
  @label "Items"
36
37
  @description "Tool items rendered as icon buttons"
37
38
  @tier "presentation"
@@ -0,0 +1,138 @@
1
+ app std-audio-cue "1.0.0"
2
+ "Headless-ish sound driver: the SOLE writer of cue/cueSeq/music/muted/volume, rendered through the game-audio-cue atom which mounts @almadar/ui's useGameAudio hook. Intents: CUE plays a one-shot SFX by manifest key (cueSeq increments so the same key can re-fire), MUSIC crossfades to a looping track by key, STOP_MUSIC fades it out, MUTE/UNMUTE/TOGGLE_MUTE flip playback and emit AUDIO_MUTED, SET_VOLUME sets master volume, RESTART reseeds from config. The manifest and baseUrl are config, not runtime state — swap them per game by rebinding this atom's config, never by hand-authoring a second audio layer."
3
+ orbital AudioCueOrbital {
4
+ type SoundEntry = {
5
+ path : string
6
+ volume : number
7
+ loop : boolean
8
+ poolSize : number
9
+ autostart : boolean
10
+ crossfade : boolean
11
+ crossfadeDurationMs : number
12
+ }
13
+
14
+ entity AudioState [runtime, shared] {
15
+ id : string!
16
+ cue : string = ""
17
+ cueSeq : number = 0
18
+ music : string = ""
19
+ muted : boolean = false
20
+ volume : number = 1
21
+ }
22
+
23
+ trait AudioCue -> @rebindable AudioState [lifecycle] {
24
+ initial: active
25
+
26
+ state active {
27
+ INIT -> active
28
+ (set @entity.cue "")
29
+ (set @entity.cueSeq 0)
30
+ (set @entity.music @config.initialMusic)
31
+ (set @entity.muted @config.initialMuted)
32
+ (set @entity.volume @config.initialVolume)
33
+ (render-ui main {
34
+ cue: @entity.cue
35
+ cueSeq: @entity.cueSeq
36
+ music: @entity.music
37
+ muted: @entity.muted
38
+ volume: @entity.volume
39
+ manifest: @config.manifest
40
+ baseUrl: @config.baseUrl
41
+ type: game-audio-cue
42
+ })
43
+
44
+ CUE -> active
45
+ (set @entity.cue ?key)
46
+ (set @entity.cueSeq (+ @entity.cueSeq 1))
47
+
48
+ MUSIC -> active
49
+ (set @entity.music ?key)
50
+
51
+ STOP_MUSIC -> active
52
+ (set @entity.music "")
53
+
54
+ MUTE -> active
55
+ (set @entity.muted true)
56
+ (emit AUDIO_MUTED { muted: true })
57
+
58
+ UNMUTE -> active
59
+ (set @entity.muted false)
60
+ (emit AUDIO_MUTED { muted: false })
61
+
62
+ TOGGLE_MUTE -> active
63
+ (set @entity.muted (not @entity.muted))
64
+ (emit AUDIO_MUTED { muted: @entity.muted })
65
+
66
+ SET_VOLUME -> active
67
+ (set @entity.volume ?volume)
68
+
69
+ RESTART -> active
70
+ (set @entity.cue "")
71
+ (set @entity.cueSeq 0)
72
+ (set @entity.music @config.initialMusic)
73
+ (set @entity.muted @config.initialMuted)
74
+ (set @entity.volume @config.initialVolume)
75
+ }
76
+
77
+ emits {
78
+ AUDIO_MUTED -> external {
79
+ muted : boolean!
80
+ }
81
+ @description "Mute state changed via MUTE/UNMUTE/TOGGLE_MUTE."
82
+ @synonyms "mute-changed, sound-toggled"
83
+ @tier "domain"
84
+ }
85
+
86
+ listens {
87
+ CUE { key : string! }
88
+ @description "Play a one-shot SFX by manifest key; re-fires even when key repeats."
89
+ @tier "domain"
90
+ MUSIC { key : string! }
91
+ @description "Crossfade to a looping background track by manifest key."
92
+ @tier "domain"
93
+ STOP_MUSIC
94
+ @description "Fade out and stop the current music."
95
+ @tier "domain"
96
+ MUTE
97
+ @description "Mute all playback."
98
+ @tier "domain"
99
+ UNMUTE
100
+ @description "Unmute playback."
101
+ @tier "domain"
102
+ TOGGLE_MUTE
103
+ @description "Flip the current mute state."
104
+ @tier "domain"
105
+ SET_VOLUME { volume : number! }
106
+ @description "Set master volume 0-1."
107
+ @tier "domain"
108
+ RESTART
109
+ @description "Reseed cue/music/muted/volume from config."
110
+ @tier "domain"
111
+ }
112
+
113
+ config {
114
+ manifest : Map string SoundEntry = {}
115
+ @label "Manifest"
116
+ @description "Sound definitions keyed by logical name — path(s), volume, loop, pooling, autostart, crossfade."
117
+ @tier "domain"
118
+ baseUrl : string = ""
119
+ @label "Base Url"
120
+ @description "Prefix prepended to every manifest path."
121
+ @tier "infra"
122
+ initialMuted : boolean = false
123
+ @label "Initial Muted"
124
+ @description "Muted state seeded on INIT/RESTART."
125
+ @tier "presentation"
126
+ initialVolume : number = 1
127
+ @label "Initial Volume"
128
+ @description "Master volume 0-1 seeded on INIT/RESTART."
129
+ @tier "presentation"
130
+ initialMusic : string = ""
131
+ @label "Initial Music"
132
+ @description "Manifest key auto-played as looping music on INIT/RESTART; empty plays none."
133
+ @tier "presentation"
134
+ }
135
+ }
136
+
137
+ page "/audio-cue" as AudioCuePage -> AudioCue
138
+ }
@@ -0,0 +1,158 @@
1
+ app std-camera-rig "1.0.0"
2
+ "Headless camera authority: the SOLE writer of camX/camY/camZoom on side-view and top-down boards. Every 33ms camera tick it eases camX/camY toward (camTargetX + camLead, camTargetY) — a dead-zone camera: inside deadzoneX/deadzoneY the axis holds, outside it lerps by stiffness — clamps the result to bounds, eases camZoom toward camTargetZoom the same way, and decays camShake toward 0 by shakeDecay. Intents: FOLLOW retargets from a moving body's x/y/vx (vx feeds camLead, the horizontal look-ahead), CUT snaps position+target instantly (no ease, for teleports/cutscenes), SET_ZOOM retargets zoom (clamped to minZoom/maxZoom), SHAKE sets camShake for a consumer to jitter with at bind time — this atom never writes shake into camX/camY itself, RESTART reseeds from config. Emits CAMERA_MOVED every tick. Renders nothing — a board binds its own canvas camera prop straight to camX/camY/camZoom."
3
+ orbital CameraRigOrbital {
4
+ type CameraPos = {
5
+ x : number!
6
+ y : number!
7
+ }
8
+ type CameraBounds = {
9
+ xMin : number!
10
+ xMax : number!
11
+ yMin : number!
12
+ yMax : number!
13
+ }
14
+
15
+ entity CameraState [runtime, shared] {
16
+ id : string!
17
+ camX : number = 0
18
+ camY : number = 0
19
+ camZoom : number = 1
20
+ camTargetX : number = 0
21
+ camTargetY : number = 0
22
+ camTargetZoom : number = 1
23
+ camLead : number = 0
24
+ camShake : number = 0
25
+ }
26
+
27
+ trait CameraRig -> @rebindable CameraState [lifecycle] {
28
+ initial: active
29
+
30
+ state active {
31
+ INIT -> active
32
+ (set @entity.camX @config.start.x)
33
+ (set @entity.camY @config.start.y)
34
+ (set @entity.camZoom @config.zoom)
35
+ (set @entity.camTargetX @config.start.x)
36
+ (set @entity.camTargetY @config.start.y)
37
+ (set @entity.camTargetZoom @config.zoom)
38
+ (set @entity.camLead 0)
39
+ (set @entity.camShake 0)
40
+
41
+ FOLLOW -> active
42
+ (set @entity.camTargetX ?x)
43
+ (set @entity.camTargetY ?y)
44
+ (set @entity.camLead (* ?vx @config.lead))
45
+
46
+ CUT -> active
47
+ (set @entity.camX ?x)
48
+ (set @entity.camY ?y)
49
+ (set @entity.camTargetX ?x)
50
+ (set @entity.camTargetY ?y)
51
+ (set @entity.camLead 0)
52
+
53
+ SET_ZOOM -> active
54
+ (set @entity.camTargetZoom (math/clamp ?zoom @config.minZoom @config.maxZoom))
55
+
56
+ SHAKE -> active
57
+ (set @entity.camShake ?magnitude)
58
+
59
+ RESTART -> active
60
+ (set @entity.camX @config.start.x)
61
+ (set @entity.camY @config.start.y)
62
+ (set @entity.camZoom @config.zoom)
63
+ (set @entity.camTargetX @config.start.x)
64
+ (set @entity.camTargetY @config.start.y)
65
+ (set @entity.camTargetZoom @config.zoom)
66
+ (set @entity.camLead 0)
67
+ (set @entity.camShake 0)
68
+ }
69
+
70
+ ticks {
71
+ camTick every 33ms
72
+ when @config.running
73
+ (do
74
+ (set @entity.camX (math/clamp (if (> (math/abs (- (+ @entity.camTargetX @entity.camLead) @entity.camX)) @config.deadzoneX) (math/lerp @entity.camX (+ @entity.camTargetX @entity.camLead) @config.stiffness) @entity.camX) @config.bounds.xMin @config.bounds.xMax))
75
+ (set @entity.camY (math/clamp (if (> (math/abs (- @entity.camTargetY @entity.camY)) @config.deadzoneY) (math/lerp @entity.camY @entity.camTargetY @config.stiffness) @entity.camY) @config.bounds.yMin @config.bounds.yMax))
76
+ (set @entity.camZoom (math/clamp (math/lerp @entity.camZoom @entity.camTargetZoom @config.stiffness) @config.minZoom @config.maxZoom))
77
+ (set @entity.camShake (math/max 0 (- @entity.camShake @config.shakeDecay))))
78
+ (emit CAMERA_MOVED { x: @entity.camX, y: @entity.camY, zoom: @entity.camZoom })
79
+ }
80
+
81
+ emits {
82
+ CAMERA_MOVED -> external {
83
+ x : number!
84
+ y : number!
85
+ zoom : number!
86
+ }
87
+ @description "Eased camera position/zoom after this tick."
88
+ @tier "domain"
89
+ }
90
+
91
+ listens {
92
+ FOLLOW { x : number!, y : number!, vx : number! }
93
+ @description "Retarget from a moving body's position and horizontal velocity (vx drives camLead)."
94
+ @tier "domain"
95
+ CUT { x : number!, y : number! }
96
+ @description "Snap position and target instantly, no ease — teleports and cutscenes."
97
+ @tier "domain"
98
+ SET_ZOOM { zoom : number! }
99
+ @description "Retarget zoom, clamped to minZoom/maxZoom."
100
+ @tier "domain"
101
+ SHAKE { magnitude : number! }
102
+ @description "Set camShake; a consumer jitters camX/camY with it at bind time — this atom never writes shake into position."
103
+ @tier "domain"
104
+ RESTART
105
+ @description "Reseed position/zoom/lead/shake from config."
106
+ @tier "domain"
107
+ }
108
+
109
+ config {
110
+ running : boolean = true
111
+ @label "Running"
112
+ @description "When true the 33ms camera tick eases toward target; toggle to freeze the camera"
113
+ @tier "domain"
114
+ start : CameraPos = { x: 0, y: 0 }
115
+ @label "Start Position"
116
+ @description "Initial and RESTART camera position/target"
117
+ @tier "domain"
118
+ zoom : number = 1
119
+ @label "Zoom"
120
+ @description "Initial and RESTART zoom level"
121
+ @tier "domain"
122
+ minZoom : number = 0.5
123
+ @label "Min Zoom"
124
+ @description "Lower clamp for SET_ZOOM and the eased zoom"
125
+ @tier "presentation"
126
+ maxZoom : number = 3
127
+ @label "Max Zoom"
128
+ @description "Upper clamp for SET_ZOOM and the eased zoom"
129
+ @tier "presentation"
130
+ stiffness : number = 0.1
131
+ @label "Stiffness"
132
+ @description "Per-tick lerp factor easing position/zoom toward target (0-1, higher = snappier)"
133
+ @tier "presentation"
134
+ deadzoneX : number = 0
135
+ @label "Dead Zone X"
136
+ @description "Horizontal distance from target the camera tolerates without easing"
137
+ @tier "presentation"
138
+ deadzoneY : number = 0
139
+ @label "Dead Zone Y"
140
+ @description "Vertical distance from target the camera tolerates without easing"
141
+ @tier "presentation"
142
+ lead : number = 0
143
+ @label "Lead"
144
+ @description "Horizontal look-ahead multiplier applied to FOLLOW's vx"
145
+ @tier "presentation"
146
+ bounds : CameraBounds = { xMin: -1000000, xMax: 1000000, yMin: -1000000, yMax: 1000000 }
147
+ @label "Bounds"
148
+ @description "World-space clamp applied to the eased camera position every tick"
149
+ @tier "presentation"
150
+ shakeDecay : number = 0.1
151
+ @label "Shake Decay"
152
+ @description "Amount camShake reduces toward 0 every tick"
153
+ @tier "presentation"
154
+ }
155
+ }
156
+
157
+ page "/camera-rig" as CameraRigPage -> CameraRig
158
+ }
@@ -40,6 +40,12 @@ orbital Canvas2dOrbital {
40
40
  tileLeaveEvent: @config.tileLeaveEvent
41
41
  keyMap: @config.keyMap
42
42
  keyUpMap: @config.keyUpMap
43
+ editable: @config.editable
44
+ selectedId: @config.selectedId
45
+ onSelect: @config.onSelect
46
+ onMove: @config.onMove
47
+ selectEvent: @config.selectEvent
48
+ moveEvent: @config.moveEvent
43
49
  camera: @config.camera
44
50
  scale: @config.scale
45
51
  tileWidth: @config.tileWidth
@@ -74,6 +80,18 @@ orbital Canvas2dOrbital {
74
80
  @config.tileLeaveEvent -> external { }
75
81
  @description "Emits UI:{tileLeaveEvent} with {} on pointer leave."
76
82
  @tier "essential"
83
+ @config.onSelect -> external {
84
+ id : string
85
+ }
86
+ @description "Fired on a click that changes selection — before `selectEvent`."
87
+ @tier "essential"
88
+ @config.onMove -> external {
89
+ id : string
90
+ x : number
91
+ y : number
92
+ }
93
+ @description "Fired once per drag gesture, on drop — before `moveEvent`."
94
+ @tier "essential"
77
95
  }
78
96
 
79
97
  config {
@@ -125,6 +143,30 @@ orbital Canvas2dOrbital {
125
143
  @label "Key Up Map"
126
144
  @description "Maps a keyup `e.code` → the board's SEMANTIC event."
127
145
  @tier "presentation"
146
+ editable : boolean = false
147
+ @label "Editable"
148
+ @description "--- Scene-edit mode (a game studio selecting/dragging drawables). Purely"
149
+ @tier "presentation"
150
+ selectedId : string
151
+ @label "Selected Id"
152
+ @description "The currently-selected drawable id (controlled); `null`/undefined = none."
153
+ @tier "presentation"
154
+ onSelect : event = "SELECT"
155
+ @label "On Select"
156
+ @description "Fired on a click that changes selection — before `selectEvent`."
157
+ @tier "presentation"
158
+ onMove : event = "MOVE"
159
+ @label "On Move"
160
+ @description "Fired once per drag gesture, on drop — before `moveEvent`."
161
+ @tier "presentation"
162
+ selectEvent : event = "SELECT"
163
+ @label "Select Event"
164
+ @description "Declarative selection event, `UI:{selectEvent} { id }`."
165
+ @tier "presentation"
166
+ moveEvent : event = "MOVE"
167
+ @label "Move Event"
168
+ @description "Declarative move event, `UI:{moveEvent} { id, x, y }` — fired once on drop."
169
+ @tier "presentation"
128
170
  camera : "pan-zoom" | "follow" | "fixed" = "pan-zoom"
129
171
  @label "Camera"
130
172
  @description "--- View config (pure render) ---"
@@ -103,6 +103,12 @@ orbital CanvasOrbital {
103
103
  featureClickEvent: @config.featureClickEvent
104
104
  keyMap: @config.keyMap
105
105
  keyUpMap: @config.keyUpMap
106
+ editable: @config.editable
107
+ selectedId: @config.selectedId
108
+ onSelect: @config.onSelect
109
+ onMove: @config.onMove
110
+ selectEvent: @config.selectEvent
111
+ moveEvent: @config.moveEvent
106
112
  children: @config.children
107
113
  type: canvas
108
114
  })
@@ -143,6 +149,18 @@ orbital CanvasOrbital {
143
149
  }
144
150
  @description "Feature-click event (3D only)."
145
151
  @tier "essential"
152
+ @config.onSelect -> external {
153
+ id : string
154
+ }
155
+ @description "Fired on a click that changes selection — before `selectEvent`."
156
+ @tier "essential"
157
+ @config.onMove -> external {
158
+ id : string
159
+ x : number
160
+ y : number
161
+ }
162
+ @description "Fired once per drag gesture, on drop — before `moveEvent`."
163
+ @tier "essential"
146
164
  }
147
165
 
148
166
  config {
@@ -262,6 +280,30 @@ orbital CanvasOrbital {
262
280
  @label "Key Up Map"
263
281
  @description "keyUpMap prop"
264
282
  @tier "presentation"
283
+ editable : boolean
284
+ @label "Editable"
285
+ @description "--- Scene-edit mode (a game studio selecting/dragging drawables). Purely"
286
+ @tier "presentation"
287
+ selectedId : string
288
+ @label "Selected Id"
289
+ @description "The currently-selected drawable id (controlled); `null`/undefined = none."
290
+ @tier "presentation"
291
+ onSelect : event = "SELECT"
292
+ @label "On Select"
293
+ @description "Fired on a click that changes selection — before `selectEvent`."
294
+ @tier "presentation"
295
+ onMove : event = "MOVE"
296
+ @label "On Move"
297
+ @description "Fired once per drag gesture, on drop — before `moveEvent`."
298
+ @tier "presentation"
299
+ selectEvent : event = "SELECT"
300
+ @label "Select Event"
301
+ @description "Declarative selection event, `UI:{selectEvent} { id }`."
302
+ @tier "presentation"
303
+ moveEvent : event = "MOVE"
304
+ @label "Move Event"
305
+ @description "Declarative move event, `UI:{moveEvent} { id, x, y }` — fired once on drop."
306
+ @tier "presentation"
265
307
  children : node
266
308
  @label "Children"
267
309
  @description "Declarative JSX drawable children (`<DrawShape .../>` composed in paint order). Forwarded to the 2D host, which collects them via drawable registry context."
@@ -0,0 +1,68 @@
1
+ app ui-game-audio-cue "1.0.0"
2
+ "GameAudioCue — UI factory (1:1 with the @almadar/ui game-audio-cue pattern)."
3
+ orbital GameAudioCueOrbital {
4
+ type GameAudioCueManifestValue = {
5
+ path : string!
6
+ volume : number
7
+ loop : boolean
8
+ poolSize : number
9
+ autostart : boolean
10
+ crossfade : boolean
11
+ crossfadeDurationMs : number
12
+ }
13
+
14
+ entity GameAudioCueItem [runtime] {
15
+ id : string!
16
+ }
17
+
18
+ trait GameAudioCueRender -> @rebindable GameAudioCueItem [interaction, instance] {
19
+ initial: idle
20
+
21
+ state idle {
22
+ INIT -> idle
23
+ (render-ui main {
24
+ cue: @config.cue
25
+ cueSeq: @config.cueSeq
26
+ music: @config.music
27
+ muted: @config.muted
28
+ volume: @config.volume
29
+ manifest: @config.manifest
30
+ baseUrl: @config.baseUrl
31
+ type: game-audio-cue
32
+ })
33
+ }
34
+
35
+ config {
36
+ cue : string
37
+ @label "Cue"
38
+ @description "Manifest key played as a one-shot SFX each time `cueSeq` advances."
39
+ @tier "presentation"
40
+ cueSeq : number
41
+ @label "Cue Seq"
42
+ @description "Monotonically increasing counter — bumping it re-triggers `cue`."
43
+ @tier "presentation"
44
+ music : string
45
+ @label "Music"
46
+ @description "Manifest key to crossfade to as looping background music; unset/empty stops music."
47
+ @tier "presentation"
48
+ muted : boolean
49
+ @label "Muted"
50
+ @description "Muted state, owned externally and mirrored into the hook."
51
+ @tier "presentation"
52
+ volume : number
53
+ @label "Volume"
54
+ @description "Master volume 0-1, owned externally and mirrored into the hook."
55
+ @tier "presentation"
56
+ manifest : Map string GameAudioCueManifestValue = {}
57
+ @label "Manifest"
58
+ @description "Sound manifest — keys mapped to sound definitions (path, volume, loop, ...)."
59
+ @tier "presentation"
60
+ baseUrl : string
61
+ @label "Base Url"
62
+ @description "Base URL prepended to every manifest path."
63
+ @tier "presentation"
64
+ }
65
+ }
66
+
67
+ page "/game-audio-cue" as GameAudioCuePage -> GameAudioCueRender
68
+ }
@@ -52,10 +52,19 @@ orbital GameAudioToggleOrbital {
52
52
  error: @config.error
53
53
  onAsset: @config.onAsset
54
54
  offAsset: @config.offAsset
55
+ toggleEvent: @config.toggleEvent
55
56
  type: game-audio-toggle
56
57
  })
57
58
  }
58
59
 
60
+ emits {
61
+ @config.toggleEvent -> external {
62
+ muted : boolean!
63
+ }
64
+ @description "Event dispatched via event bus on toggle, carrying the new muted state."
65
+ @tier "essential"
66
+ }
67
+
59
68
  config {
60
69
  size : "sm" | "md" | "lg" = sm
61
70
  @label "Size"
@@ -81,6 +90,10 @@ orbital GameAudioToggleOrbital {
81
90
  @label "Off Asset"
82
91
  @description "Asset rendered when sound is OFF (muted). Falls back to 🔇 emoji."
83
92
  @tier "presentation"
93
+ toggleEvent : event = "TOGGLE"
94
+ @label "Toggle Event"
95
+ @description "Event dispatched via event bus on toggle, carrying the new muted state."
96
+ @tier "presentation"
84
97
  }
85
98
  }
86
99
 
@@ -41,6 +41,7 @@ orbital AlgoGraphCanvasOrbital {
41
41
  text : string
42
42
  label : string
43
43
  fontSize : number
44
+ fontFamily : string
44
45
  align : "left" | "center" | "right"
45
46
  axis : "x" | "y"
46
47
  min : number
@@ -84,6 +84,7 @@ orbital AlgorithmCanvasOrbital {
84
84
  text : string
85
85
  label : string
86
86
  fontSize : number
87
+ fontFamily : string
87
88
  align : "left" | "center" | "right"
88
89
  axis : "x" | "y"
89
90
  min : number
@@ -159,6 +159,7 @@ orbital BiologyCanvasOrbital {
159
159
  text : string
160
160
  label : string
161
161
  fontSize : number
162
+ fontFamily : string
162
163
  align : "left" | "center" | "right"
163
164
  axis : "x" | "y"
164
165
  min : number