@almadar/std 16.152.0 → 16.154.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 (194) hide show
  1. package/behaviors/lolo/infra/atoms/std-data-erasure.lolo +5 -1
  2. package/behaviors/lolo/ui/core/atoms/std-browse.lolo +1 -0
  3. package/behaviors/lolo/ui/core/molecules/ui-emoji-picker.lolo +56 -0
  4. package/behaviors/lolo/ui/core/molecules/ui-popover.lolo +39 -0
  5. package/behaviors/lolo/ui/core/molecules/ui-table-view.lolo +11 -0
  6. package/behaviors/lolo/ui/game/atoms/ui-draw-sprite.lolo +2 -2
  7. package/behaviors/lolo/ui/game/molecules/std-crew-board-2d.lolo +4 -4
  8. package/behaviors/lolo/ui/game/molecules/std-dungeon-board-2d.lolo +6 -6
  9. package/behaviors/lolo/ui/game/molecules/std-tactics-board-2d.lolo +41 -9
  10. package/behaviors/lolo/ui/game/molecules/std-worldmap-board-2d.lolo +2 -2
  11. package/behaviors/lolo/ui/game/molecules/ui-canvas-2d.lolo +11 -1
  12. package/behaviors/lolo/ui/game/molecules/ui-canvas.lolo +10 -0
  13. package/behaviors/lolo/ui/game/organisms/ui-arena-board.lolo +2 -2
  14. package/behaviors/lolo/ui/game/organisms/ui-battle-board.lolo +110 -7
  15. package/behaviors/lolo/ui/game/organisms/ui-builder-board.lolo +5 -20
  16. package/behaviors/lolo/ui/game/organisms/ui-card-battler-board.lolo +2 -0
  17. package/behaviors/lolo/ui/game/organisms/ui-castle-board.lolo +3 -3
  18. package/behaviors/lolo/ui/game/organisms/ui-city-builder-board.lolo +9 -7
  19. package/behaviors/lolo/ui/game/organisms/ui-fishing-board.lolo +8 -8
  20. package/behaviors/lolo/ui/game/organisms/ui-grid-tactics-demo-board.lolo +2 -2
  21. package/behaviors/lolo/ui/game/organisms/ui-hex-strategy-board.lolo +5 -1
  22. package/behaviors/lolo/ui/game/organisms/ui-holiday-runner-board.lolo +1 -1
  23. package/behaviors/lolo/ui/game/organisms/ui-minigolf-board.lolo +22 -22
  24. package/behaviors/lolo/ui/game/organisms/ui-physics-sandbox-board.lolo +5 -5
  25. package/behaviors/lolo/ui/game/organisms/ui-pinball-board.lolo +7 -7
  26. package/behaviors/lolo/ui/game/organisms/ui-pirate-board.lolo +14 -14
  27. package/behaviors/lolo/ui/game/organisms/ui-platformer-board.lolo +4 -0
  28. package/behaviors/lolo/ui/game/organisms/ui-roguelike-board.lolo +5 -5
  29. package/behaviors/lolo/ui/game/organisms/ui-sokoban-board.lolo +5 -5
  30. package/behaviors/lolo/ui/game/organisms/ui-sports-board.lolo +10 -7
  31. package/behaviors/lolo/ui/game/organisms/ui-state-architect-board.lolo +4 -4
  32. package/behaviors/lolo/ui/game/organisms/ui-tactics-demo-2d.lolo +7 -3
  33. package/behaviors/lolo/ui/game/organisms/ui-tanks-board.lolo +5 -1
  34. package/behaviors/lolo/ui/game/organisms/ui-topdown-rts-board.lolo +5 -1
  35. package/behaviors/lolo/ui/game/organisms/ui-tower-defense-board.lolo +8 -8
  36. package/behaviors/lolo/ui/game/organisms/ui-uncontrolled-battle-board.lolo +5 -5
  37. package/behaviors/lolo/ui/game/organisms/ui-winter-ski-board.lolo +7 -7
  38. package/behaviors/lolo/ui/game/organisms/ui-world-map-board.lolo +3 -3
  39. package/behaviors/registry/factory-signatures.json +6211 -2181
  40. package/behaviors/registry/infra/atoms/std-approval-gate.orb +16 -8
  41. package/behaviors/registry/infra/atoms/std-cache-aside.orb +62 -31
  42. package/behaviors/registry/infra/atoms/std-circuit-breaker.orb +32 -16
  43. package/behaviors/registry/infra/atoms/std-data-erasure.orb +17 -9
  44. package/behaviors/registry/infra/atoms/std-mod-queue.orb +16 -8
  45. package/behaviors/registry/infra/atoms/std-rate-limiter.orb +28 -14
  46. package/behaviors/registry/ml/molecules/std-escalating-decision.orb +10 -5
  47. package/behaviors/registry/ml/molecules/std-knowledge-tracing.orb +4 -2
  48. package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +2 -1
  49. package/behaviors/registry/ui/core/atoms/std-browse.orb +4 -0
  50. package/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +2 -1
  51. package/behaviors/registry/ui/core/atoms/std-esign-flow.orb +4 -2
  52. package/behaviors/registry/ui/core/atoms/std-esign-request.orb +6 -3
  53. package/behaviors/registry/ui/core/atoms/std-gallery.orb +18 -9
  54. package/behaviors/registry/ui/core/atoms/std-import.orb +4 -2
  55. package/behaviors/registry/ui/core/molecules/ui-emoji-picker.orb +214 -0
  56. package/behaviors/registry/ui/core/molecules/ui-popover.orb +112 -1
  57. package/behaviors/registry/ui/core/molecules/ui-table-view.orb +44 -0
  58. package/behaviors/registry/ui/game/atoms/std-arcade-flow.orb +1 -0
  59. package/behaviors/registry/ui/game/atoms/std-arcade-play.orb +1 -0
  60. package/behaviors/registry/ui/game/atoms/std-chase-ai.orb +1 -0
  61. package/behaviors/registry/ui/game/atoms/std-dungeon-combat.orb +1 -0
  62. package/behaviors/registry/ui/game/atoms/std-fx-particles.orb +1 -0
  63. package/behaviors/registry/ui/game/atoms/std-gather.orb +1 -0
  64. package/behaviors/registry/ui/game/atoms/std-gold.orb +1 -0
  65. package/behaviors/registry/ui/game/atoms/std-grid-tactics.orb +1 -0
  66. package/behaviors/registry/ui/game/atoms/std-hero-nav.orb +1 -0
  67. package/behaviors/registry/ui/game/atoms/std-market-flow.orb +1 -0
  68. package/behaviors/registry/ui/game/atoms/std-objective-flow.orb +1 -0
  69. package/behaviors/registry/ui/game/atoms/std-objective-reach.orb +1 -0
  70. package/behaviors/registry/ui/game/atoms/std-patience.orb +1 -0
  71. package/behaviors/registry/ui/game/atoms/std-production.orb +1 -0
  72. package/behaviors/registry/ui/game/atoms/std-round-flow.orb +1 -0
  73. package/behaviors/registry/ui/game/atoms/std-score.orb +1 -0
  74. package/behaviors/registry/ui/game/atoms/std-survival-flow.orb +1 -0
  75. package/behaviors/registry/ui/game/atoms/std-xp.orb +1 -0
  76. package/behaviors/registry/ui/game/atoms/ui-draw-sprite.orb +2 -2
  77. package/behaviors/registry/ui/game/molecules/std-crew-board-2d.orb +10 -12
  78. package/behaviors/registry/ui/game/molecules/std-dungeon-board-2d.orb +15 -18
  79. package/behaviors/registry/ui/game/molecules/std-tactics-board-2d.orb +860 -138
  80. package/behaviors/registry/ui/game/molecules/std-worldmap-board-2d.orb +4 -54
  81. package/behaviors/registry/ui/game/molecules/ui-canvas-2d.orb +16 -1
  82. package/behaviors/registry/ui/game/molecules/ui-canvas.orb +14 -0
  83. package/behaviors/registry/ui/game/organisms/ui-arena-board.orb +5 -6
  84. package/behaviors/registry/ui/game/organisms/ui-battle-board.orb +86 -99
  85. package/behaviors/registry/ui/game/organisms/ui-builder-board.orb +21 -226
  86. package/behaviors/registry/ui/game/organisms/ui-card-battler-board.orb +42 -0
  87. package/behaviors/registry/ui/game/organisms/ui-castle-board.orb +30 -9
  88. package/behaviors/registry/ui/game/organisms/ui-city-builder-board.orb +49 -0
  89. package/behaviors/registry/ui/game/organisms/ui-fishing-board.orb +16 -16
  90. package/behaviors/registry/ui/game/organisms/ui-grid-tactics-demo-board.orb +5 -6
  91. package/behaviors/registry/ui/game/organisms/ui-hex-strategy-board.orb +30 -10
  92. package/behaviors/registry/ui/game/organisms/ui-holiday-runner-board.orb +2 -3
  93. package/behaviors/registry/ui/game/organisms/ui-minigolf-board.orb +55 -11
  94. package/behaviors/registry/ui/game/organisms/ui-physics-sandbox-board.orb +10 -10
  95. package/behaviors/registry/ui/game/organisms/ui-pinball-board.orb +8 -8
  96. package/behaviors/registry/ui/game/organisms/ui-pirate-board.orb +14 -0
  97. package/behaviors/registry/ui/game/organisms/ui-platformer-board.orb +72 -0
  98. package/behaviors/registry/ui/game/organisms/ui-roguelike-board.orb +10 -10
  99. package/behaviors/registry/ui/game/organisms/ui-sokoban-board.orb +10 -10
  100. package/behaviors/registry/ui/game/organisms/ui-sports-board.orb +60 -0
  101. package/behaviors/registry/ui/game/organisms/ui-state-architect-board.orb +8 -8
  102. package/behaviors/registry/ui/game/organisms/ui-tactics-demo-2d.orb +48 -28
  103. package/behaviors/registry/ui/game/organisms/ui-tanks-board.orb +30 -10
  104. package/behaviors/registry/ui/game/organisms/ui-topdown-rts-board.orb +31 -12
  105. package/behaviors/registry/ui/game/organisms/ui-tower-defense-board.orb +8 -0
  106. package/behaviors/registry/ui/game/organisms/ui-uncontrolled-battle-board.orb +5 -0
  107. package/behaviors/registry/ui/game/organisms/ui-winter-ski-board.orb +14 -14
  108. package/behaviors/registry/ui/game/organisms/ui-world-map-board.orb +8 -12
  109. package/dist/behaviors/behaviors-embeddings.json +384 -380
  110. package/dist/behaviors/behaviors-registry.json +594 -57
  111. package/dist/behaviors/exports-reader.js +3409 -3101
  112. package/dist/behaviors/functions/index.d.ts +596 -5
  113. package/dist/behaviors/functions/index.js +3389 -3102
  114. package/dist/behaviors/index.d.ts +1 -1
  115. package/dist/behaviors/index.js +3410 -3102
  116. package/dist/behaviors/knob-embeddings.json +1 -1
  117. package/dist/behaviors/query.js +3409 -3101
  118. package/dist/behaviors/registry/factory-signatures.json +6211 -2181
  119. package/dist/behaviors/registry/infra/atoms/std-approval-gate.orb +16 -8
  120. package/dist/behaviors/registry/infra/atoms/std-cache-aside.orb +62 -31
  121. package/dist/behaviors/registry/infra/atoms/std-circuit-breaker.orb +32 -16
  122. package/dist/behaviors/registry/infra/atoms/std-data-erasure.orb +17 -9
  123. package/dist/behaviors/registry/infra/atoms/std-mod-queue.orb +16 -8
  124. package/dist/behaviors/registry/infra/atoms/std-rate-limiter.orb +28 -14
  125. package/dist/behaviors/registry/ml/molecules/std-escalating-decision.orb +10 -5
  126. package/dist/behaviors/registry/ml/molecules/std-knowledge-tracing.orb +4 -2
  127. package/dist/behaviors/registry/ui/core/atoms/std-billable-hour.orb +2 -1
  128. package/dist/behaviors/registry/ui/core/atoms/std-browse.orb +4 -0
  129. package/dist/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +2 -1
  130. package/dist/behaviors/registry/ui/core/atoms/std-esign-flow.orb +4 -2
  131. package/dist/behaviors/registry/ui/core/atoms/std-esign-request.orb +6 -3
  132. package/dist/behaviors/registry/ui/core/atoms/std-gallery.orb +18 -9
  133. package/dist/behaviors/registry/ui/core/atoms/std-import.orb +4 -2
  134. package/dist/behaviors/registry/ui/core/molecules/ui-emoji-picker.orb +214 -0
  135. package/dist/behaviors/registry/ui/core/molecules/ui-popover.orb +112 -1
  136. package/dist/behaviors/registry/ui/core/molecules/ui-table-view.orb +44 -0
  137. package/dist/behaviors/registry/ui/game/atoms/std-arcade-flow.orb +1 -0
  138. package/dist/behaviors/registry/ui/game/atoms/std-arcade-play.orb +1 -0
  139. package/dist/behaviors/registry/ui/game/atoms/std-chase-ai.orb +1 -0
  140. package/dist/behaviors/registry/ui/game/atoms/std-dungeon-combat.orb +1 -0
  141. package/dist/behaviors/registry/ui/game/atoms/std-fx-particles.orb +1 -0
  142. package/dist/behaviors/registry/ui/game/atoms/std-gather.orb +1 -0
  143. package/dist/behaviors/registry/ui/game/atoms/std-gold.orb +1 -0
  144. package/dist/behaviors/registry/ui/game/atoms/std-grid-tactics.orb +1 -0
  145. package/dist/behaviors/registry/ui/game/atoms/std-hero-nav.orb +1 -0
  146. package/dist/behaviors/registry/ui/game/atoms/std-market-flow.orb +1 -0
  147. package/dist/behaviors/registry/ui/game/atoms/std-objective-flow.orb +1 -0
  148. package/dist/behaviors/registry/ui/game/atoms/std-objective-reach.orb +1 -0
  149. package/dist/behaviors/registry/ui/game/atoms/std-patience.orb +1 -0
  150. package/dist/behaviors/registry/ui/game/atoms/std-production.orb +1 -0
  151. package/dist/behaviors/registry/ui/game/atoms/std-round-flow.orb +1 -0
  152. package/dist/behaviors/registry/ui/game/atoms/std-score.orb +1 -0
  153. package/dist/behaviors/registry/ui/game/atoms/std-survival-flow.orb +1 -0
  154. package/dist/behaviors/registry/ui/game/atoms/std-xp.orb +1 -0
  155. package/dist/behaviors/registry/ui/game/atoms/ui-draw-sprite.orb +2 -2
  156. package/dist/behaviors/registry/ui/game/molecules/std-crew-board-2d.orb +10 -12
  157. package/dist/behaviors/registry/ui/game/molecules/std-dungeon-board-2d.orb +15 -18
  158. package/dist/behaviors/registry/ui/game/molecules/std-tactics-board-2d.orb +860 -138
  159. package/dist/behaviors/registry/ui/game/molecules/std-worldmap-board-2d.orb +4 -54
  160. package/dist/behaviors/registry/ui/game/molecules/ui-canvas-2d.orb +16 -1
  161. package/dist/behaviors/registry/ui/game/molecules/ui-canvas.orb +14 -0
  162. package/dist/behaviors/registry/ui/game/organisms/ui-arena-board.orb +5 -6
  163. package/dist/behaviors/registry/ui/game/organisms/ui-battle-board.orb +86 -99
  164. package/dist/behaviors/registry/ui/game/organisms/ui-builder-board.orb +21 -226
  165. package/dist/behaviors/registry/ui/game/organisms/ui-card-battler-board.orb +42 -0
  166. package/dist/behaviors/registry/ui/game/organisms/ui-castle-board.orb +30 -9
  167. package/dist/behaviors/registry/ui/game/organisms/ui-city-builder-board.orb +49 -0
  168. package/dist/behaviors/registry/ui/game/organisms/ui-fishing-board.orb +16 -16
  169. package/dist/behaviors/registry/ui/game/organisms/ui-grid-tactics-demo-board.orb +5 -6
  170. package/dist/behaviors/registry/ui/game/organisms/ui-hex-strategy-board.orb +30 -10
  171. package/dist/behaviors/registry/ui/game/organisms/ui-holiday-runner-board.orb +2 -3
  172. package/dist/behaviors/registry/ui/game/organisms/ui-minigolf-board.orb +55 -11
  173. package/dist/behaviors/registry/ui/game/organisms/ui-physics-sandbox-board.orb +10 -10
  174. package/dist/behaviors/registry/ui/game/organisms/ui-pinball-board.orb +8 -8
  175. package/dist/behaviors/registry/ui/game/organisms/ui-pirate-board.orb +14 -0
  176. package/dist/behaviors/registry/ui/game/organisms/ui-platformer-board.orb +72 -0
  177. package/dist/behaviors/registry/ui/game/organisms/ui-roguelike-board.orb +10 -10
  178. package/dist/behaviors/registry/ui/game/organisms/ui-sokoban-board.orb +10 -10
  179. package/dist/behaviors/registry/ui/game/organisms/ui-sports-board.orb +60 -0
  180. package/dist/behaviors/registry/ui/game/organisms/ui-state-architect-board.orb +8 -8
  181. package/dist/behaviors/registry/ui/game/organisms/ui-tactics-demo-2d.orb +48 -28
  182. package/dist/behaviors/registry/ui/game/organisms/ui-tanks-board.orb +30 -10
  183. package/dist/behaviors/registry/ui/game/organisms/ui-topdown-rts-board.orb +31 -12
  184. package/dist/behaviors/registry/ui/game/organisms/ui-tower-defense-board.orb +8 -0
  185. package/dist/behaviors/registry/ui/game/organisms/ui-uncontrolled-battle-board.orb +5 -0
  186. package/dist/behaviors/registry/ui/game/organisms/ui-winter-ski-board.orb +14 -14
  187. package/dist/behaviors/registry/ui/game/organisms/ui-world-map-board.orb +8 -12
  188. package/dist/behaviors-embeddings.json +384 -380
  189. package/dist/behaviors-registry.json +594 -57
  190. package/dist/index.d.ts +1 -1
  191. package/dist/index.js +3410 -3102
  192. package/dist/knob-embeddings.json +1 -1
  193. package/dist/registry/factory-signatures.json +6211 -2181
  194. package/package.json +2 -2
@@ -211,7 +211,11 @@ orbital DataErasureOrbital {
211
211
  }
212
212
 
213
213
  state browsing {
214
- CANCEL_ERASURE -> loading
214
+ ;; Stays in `browsing`: this persist's result arms (ErasureSaved /
215
+ ;; ErasureSaveFailed) are declared here. Leaving for `loading` landed the
216
+ ;; save on a deaf state and wedged the panel on its spinner — the
217
+ ;; ErasureSaved arm below is what moves to `loading` to re-fetch.
218
+ CANCEL_ERASURE -> browsing
215
219
  (set @entity.id ?id)
216
220
  (set @entity.status cancelled)
217
221
  (persist update ErasureRequest @entity {
@@ -43,6 +43,7 @@ orbital BrowseItemOrbital {
43
43
  columns: @config.columns
44
44
  emptyMessage: "No records"
45
45
  itemActions: @config.itemActions
46
+ itemClickEvent: VIEW
46
47
  look: dense
47
48
  maxInlineActions: @config.maxInlineActions
48
49
  pageSize: @config.displayPageSize
@@ -0,0 +1,56 @@
1
+ app ui-emoji-picker "1.0.0"
2
+ "EmojiPicker — UI factory (1:1 with the @almadar/ui emoji-picker pattern)."
3
+ orbital EmojiPickerOrbital {
4
+ entity EmojiPickerItem [runtime] {
5
+ id : string!
6
+ }
7
+
8
+ trait EmojiPickerRender -> @rebindable EmojiPickerItem [interaction, instance] {
9
+ initial: idle
10
+
11
+ state idle {
12
+ INIT -> idle
13
+ (render-ui main {
14
+ pickEvent: @config.pickEvent
15
+ position: @config.position
16
+ triggerIcon: @config.triggerIcon
17
+ triggerLabel: @config.triggerLabel
18
+ className: @config.className
19
+ type: emoji-picker
20
+ })
21
+ }
22
+
23
+ emits {
24
+ @config.pickEvent -> external {
25
+ id : string
26
+ }
27
+ @description "Declarative event name — picking an emoji emits UI:{pickEvent} with { emoji } via eventBus"
28
+ @tier "essential"
29
+ }
30
+
31
+ config {
32
+ pickEvent : string = "PICK"
33
+ @label "Pick Event"
34
+ @description "Declarative event name — picking an emoji emits UI:{pickEvent} with { emoji } via eventBus"
35
+ @tier "presentation"
36
+ position : "top" | "bottom" = top
37
+ @label "Position"
38
+ @description "Which side of the trigger the panel opens on"
39
+ @tier "presentation"
40
+ triggerIcon : icon
41
+ @label "Trigger Icon"
42
+ @description "Icon shown on the trigger button"
43
+ @tier "presentation"
44
+ triggerLabel : string = "Add emoji"
45
+ @label "Trigger Label"
46
+ @description "Accessible label for the trigger button"
47
+ @tier "presentation"
48
+ className : string
49
+ @label "Class Name"
50
+ @description "Additional CSS classes applied to the trigger button"
51
+ @tier "presentation"
52
+ }
53
+ }
54
+
55
+ page "/emoji-picker" as EmojiPickerPage -> EmojiPickerRender
56
+ }
@@ -3,6 +3,7 @@ app ui-popover "1.0.0"
3
3
  orbital PopoverOrbital {
4
4
  entity PopoverItem [runtime] {
5
5
  id : string!
6
+ open : boolean
6
7
  }
7
8
 
8
9
  trait PopoverRender -> @rebindable PopoverItem [interaction, instance] {
@@ -10,15 +11,45 @@ orbital PopoverOrbital {
10
11
 
11
12
  state idle {
12
13
  INIT -> idle
14
+ (set @entity.open @config.open)
13
15
  (render-ui main {
14
16
  content: @config.content
15
17
  children: @config.children
16
18
  position: @config.position
17
19
  trigger: @config.trigger
18
20
  showArrow: @config.showArrow
21
+ open: @entity.open
22
+ onOpenChange: @config.onOpenChange
19
23
  className: @config.className
20
24
  type: popover
21
25
  })
26
+ OPEN_CHANGE -> idle
27
+ (set @entity.open ?open)
28
+ (render-ui main {
29
+ content: @config.content
30
+ children: @config.children
31
+ position: @config.position
32
+ trigger: @config.trigger
33
+ showArrow: @config.showArrow
34
+ open: @entity.open
35
+ onOpenChange: @config.onOpenChange
36
+ className: @config.className
37
+ type: popover
38
+ })
39
+ }
40
+
41
+ emits {
42
+ @config.onOpenChange -> external {
43
+ open : boolean
44
+ }
45
+ @description "Fired when the popover wants to change visibility (trigger click, outside click)"
46
+ @tier "essential"
47
+ }
48
+
49
+ listens {
50
+ OPEN_CHANGE {
51
+ open : boolean
52
+ }
22
53
  }
23
54
 
24
55
  config {
@@ -42,6 +73,14 @@ orbital PopoverOrbital {
42
73
  @label "Show Arrow"
43
74
  @description "Show arrow"
44
75
  @tier "presentation"
76
+ open : boolean
77
+ @label "Open"
78
+ @description "Controlled open state. When set, the host owns visibility and the popover reports intent through onOpenChange instead of toggling itself."
79
+ @tier "presentation"
80
+ onOpenChange : string = "OPEN_CHANGE"
81
+ @label "On Open Change"
82
+ @description "Fired when the popover wants to change visibility (trigger click, outside click)"
83
+ @tier "presentation"
45
84
  className : string
46
85
  @label "Class Name"
47
86
  @description "Additional CSS classes"
@@ -66,6 +66,7 @@ orbital TableViewOrbital {
66
66
  fields: @config.fields
67
67
  itemActions: @config.itemActions
68
68
  maxInlineActions: @config.maxInlineActions
69
+ itemClickEvent: @config.itemClickEvent
69
70
  selectable: @config.selectable
70
71
  selectEvent: @config.selectEvent
71
72
  selectedIds: @config.selectedIds
@@ -96,6 +97,7 @@ orbital TableViewOrbital {
96
97
  fields: @config.fields
97
98
  itemActions: @config.itemActions
98
99
  maxInlineActions: @config.maxInlineActions
100
+ itemClickEvent: @config.itemClickEvent
99
101
  selectable: @config.selectable
100
102
  selectEvent: @config.selectEvent
101
103
  selectedIds: @config.selectedIds
@@ -114,6 +116,11 @@ orbital TableViewOrbital {
114
116
  }
115
117
 
116
118
  emits {
119
+ @config.itemClickEvent -> external {
120
+ id : string
121
+ }
122
+ @description "When set, the whole row is clickable and emits UI:{itemClickEvent} with { id, row } (action-button clicks stopPropagation so they still win). Mirrors DataList's contract."
123
+ @tier "essential"
117
124
  @config.selectEvent -> external {
118
125
  id : string
119
126
  }
@@ -184,6 +191,10 @@ orbital TableViewOrbital {
184
191
  @label "Max Inline Actions"
185
192
  @description "Max inline action buttons before the rest collapse into a '⋯' overflow menu. Omit = all inline."
186
193
  @tier "presentation"
194
+ itemClickEvent : string = "ITEM_CLICK"
195
+ @label "Item Click Event"
196
+ @description "When set, the whole row is clickable and emits UI:{itemClickEvent} with { id, row } (action-button clicks stopPropagation so they still win). Mirrors DataList's contract."
197
+ @tier "presentation"
187
198
  selectable : boolean = false
188
199
  @label "Selectable"
189
200
  @description "Render a leading checkbox column. Selection changes emit `selectEvent`."
@@ -61,11 +61,11 @@ orbital DrawSpriteOrbital {
61
61
  @tier "presentation"
62
62
  width : number
63
63
  @label "Width"
64
- @description "Draw width in world units (fractions of `projector.tileWidth`); omittedresolved source width in px."
64
+ @description "Draw width in world units (fractions of `projector.tileWidth`). Omittedone cell on tile grids (`flat`/`iso`/`hex`); native source px on `free`/`side`."
65
65
  @tier "presentation"
66
66
  height : number
67
67
  @label "Height"
68
- @description "Draw height in world units (fractions of `projector.tileWidth`); omittedresolved source height in px."
68
+ @description "Draw height in world units (fractions of `projector.tileWidth`). Omittedone cell on tile grids (`flat`/`iso`/`hex`); native source px on `free`/`side`."
69
69
  @tier "presentation"
70
70
  frame : json = {}
71
71
  @label "Frame"
@@ -123,11 +123,11 @@ orbital CrewBoard2DOrbital {
123
123
  hud: { type: stack, direction: horizontal, gap: md, justify: between, children: [ { type: game-hud, stats: [ { label: "Crew", value: @entity.crewCount, iconUrl: @config.crewIcon }, { label: "Oxygen", value: @entity.oxygen, iconUrl: @config.oxygenIcon } ] }, { type: stack, direction: horizontal, gap: xs, align: center, children: [ { type: typography, variant: caption, content: "Status" }, { type: badge, label: @entity.phase, variant: (if (== @entity.phase "movement") "info" "neutral") } ] }, { type: timer-display, value: @entity.oxygen, label: "O2" }, { type: button, action: CANCEL, label: "Cancel", iconAsset: @config.cancelIcon, variant: secondary } ] }
124
124
  children: [ { type: canvas, camera: { zoom: (math/min 1 (/ 5 (+ 1 (math/max (array/max @entity.tiles "x") (array/max @entity.tiles "y"))))), pos: { x: (/ (+ (array/min @entity.tiles "x") (array/max @entity.tiles "x")) 2), y: (/ (+ (array/min @entity.tiles "y") (array/max @entity.tiles "y")) 2) } }, projection: @config.projection, showMinimap: @config.showMinimap, tileClickEvent: TILE_CLICK, unitClickEvent: UNIT_CLICK, children: [
125
125
  { type: draw-shape-layer, items: (array/map @entity.tiles (fn t { type: draw-shape, shape: "cell", position: { x: (object/get @t x), y: (object/get @t y) }, fill: "#5c94fc", stroke: "rgba(255,255,255,0.35)", strokeWidth: 1 })) }
126
- { type: draw-sprite-layer, items: (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 0.5, height: 0.5 })) }
126
+ { type: draw-sprite-layer, items: (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 1, height: 1 })) }
127
127
  { type: draw-sprite-layer, items: (array/map @config.features (fn f { type: draw-sprite, position: { x: (object/get @f x), y: (object/get @f y) }, asset: (object/get @f assetUrl) })) }
128
128
  { type: draw-shape-layer, items: (array/map @entity.validMoves (fn m { type: draw-shape, shape: "cell", position: { x: (object/get @m x), y: (object/get @m z) }, fill: "rgba(74,222,128,0.4)" })) }
129
129
  { type: draw-shape-layer, items: (array/map (array/filter @entity.units (fn u (== (object/get @u id) @entity.selectedUnitId))) (fn u { type: draw-shape, shape: "ellipse", position: (object/get @u position), radiusX: 0.5, radiusY: 0.2, stroke: "rgba(0,200,255,0.8)", strokeWidth: 3 })) }
130
- { type: draw-sprite-layer, items: (array/map @entity.units (fn u { type: draw-sprite, position: (object/get @u position), asset: (if (object/get (object/get @u modelUrl) url) (object/get @u modelUrl) (object/get (object/get @config.assetManifest units) (object/get @u unitType))), id: (object/get @u id), anchor: "ground", width: 0.5, height: 0.5 })) }
130
+ { type: draw-sprite-layer, items: (array/map @entity.units (fn u { type: draw-sprite, position: (object/get @u position), asset: (if (object/get (object/get @u modelUrl) url) (object/get @u modelUrl) (object/get (object/get @config.assetManifest units) (object/get @u unitType))), id: (object/get @u id), anchor: "ground", width: 1, height: 1 })) }
131
131
  { type: draw-shape-layer, items: (array/map @entity.units (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)" })) }
132
132
  { type: draw-shape-layer, items: (array/map @entity.units (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" })) }
133
133
  { 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" })) }
@@ -166,11 +166,11 @@ orbital CrewBoard2DOrbital {
166
166
  hud: { type: stack, direction: horizontal, gap: md, justify: between, children: [ { type: game-hud, stats: [ { label: "Crew", value: @entity.crewCount, iconUrl: @config.crewIcon }, { label: "Oxygen", value: @entity.oxygen, iconUrl: @config.oxygenIcon } ] }, { type: stack, direction: horizontal, gap: xs, align: center, children: [ { type: typography, variant: caption, content: "Status" }, { type: badge, label: @entity.phase, variant: (if (== @entity.phase "movement") "info" "neutral") } ] }, { type: timer-display, value: @entity.oxygen, label: "O2" }, { type: button, action: CANCEL, label: "Cancel", iconAsset: @config.cancelIcon, variant: secondary } ] }
167
167
  children: [ { type: canvas, camera: { zoom: (math/min 1 (/ 5 (+ 1 (math/max (array/max @entity.tiles "x") (array/max @entity.tiles "y"))))), pos: { x: (/ (+ (array/min @entity.tiles "x") (array/max @entity.tiles "x")) 2), y: (/ (+ (array/min @entity.tiles "y") (array/max @entity.tiles "y")) 2) } }, projection: @config.projection, showMinimap: @config.showMinimap, tileClickEvent: TILE_CLICK, unitClickEvent: UNIT_CLICK, children: [
168
168
  { type: draw-shape-layer, items: (array/map @entity.tiles (fn t { type: draw-shape, shape: "cell", position: { x: (object/get @t x), y: (object/get @t y) }, fill: "#5c94fc", stroke: "rgba(255,255,255,0.35)", strokeWidth: 1 })) }
169
- { type: draw-sprite-layer, items: (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 0.5, height: 0.5 })) }
169
+ { type: draw-sprite-layer, items: (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 1, height: 1 })) }
170
170
  { type: draw-sprite-layer, items: (array/map @config.features (fn f { type: draw-sprite, position: { x: (object/get @f x), y: (object/get @f y) }, asset: (object/get @f assetUrl) })) }
171
171
  { type: draw-shape-layer, items: (array/map @entity.validMoves (fn m { type: draw-shape, shape: "cell", position: { x: (object/get @m x), y: (object/get @m z) }, fill: "rgba(74,222,128,0.4)" })) }
172
172
  { type: draw-shape-layer, items: (array/map (array/filter @entity.units (fn u (== (object/get @u id) @entity.selectedUnitId))) (fn u { type: draw-shape, shape: "ellipse", position: (object/get @u position), radiusX: 0.5, radiusY: 0.2, stroke: "rgba(0,200,255,0.8)", strokeWidth: 3 })) }
173
- { type: draw-sprite-layer, items: (array/map @entity.units (fn u { type: draw-sprite, position: (object/get @u position), asset: (if (object/get (object/get @u modelUrl) url) (object/get @u modelUrl) (object/get (object/get @config.assetManifest units) (object/get @u unitType))), id: (object/get @u id), anchor: "ground", width: 0.5, height: 0.5 })) }
173
+ { type: draw-sprite-layer, items: (array/map @entity.units (fn u { type: draw-sprite, position: (object/get @u position), asset: (if (object/get (object/get @u modelUrl) url) (object/get @u modelUrl) (object/get (object/get @config.assetManifest units) (object/get @u unitType))), id: (object/get @u id), anchor: "ground", width: 1, height: 1 })) }
174
174
  { type: draw-shape-layer, items: (array/map @entity.units (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)" })) }
175
175
  { type: draw-shape-layer, items: (array/map @entity.units (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" })) }
176
176
  { 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" })) }
@@ -195,11 +195,11 @@ orbital DungeonBoard2DOrbital {
195
195
  hud: { type: game-hud, stats: [ { label: "HP", value: @entity.hp, max: @entity.maxHp, format: "hearts" }, { label: "Score", value: (+ @entity.score @entity.combatScore) } ] }
196
196
  controls: { type: control-grid, kind: "dpad", directionEvents: { up: "MOVE_UP", down: "MOVE_DOWN", left: "MOVE_LEFT", right: "MOVE_RIGHT" }, directionAssets: @config.directionAssets }
197
197
  overlay: (if (!= @entity.result "none") { type: game-menu, title: "VICTORY", subtitle: "You found the chest!", options: [ { id: "again", label: "Play Again", event: "PLAY_AGAIN", variant: "primary" } ] } (if (!= @entity.combatResult "none") { type: game-menu, title: "DEFEAT", subtitle: "The dungeon claims another hero", options: [ { id: "again", label: "Play Again", event: "PLAY_AGAIN", variant: "primary" } ] } null))
198
- children: [ { type: canvas, camera: { zoom: @config.scale }, projection: "flat", showMinimap: false, keyMap: { ArrowUp: "MOVE_UP", KeyW: "MOVE_UP", ArrowDown: "MOVE_DOWN", KeyS: "MOVE_DOWN", ArrowLeft: "MOVE_LEFT", KeyA: "MOVE_LEFT", ArrowRight: "MOVE_RIGHT", KeyD: "MOVE_RIGHT" }, children: [
198
+ children: [ { type: canvas, fit: true, camera: { zoom: @config.scale }, projection: "flat", showMinimap: false, keyMap: { ArrowUp: "MOVE_UP", KeyW: "MOVE_UP", ArrowDown: "MOVE_DOWN", KeyS: "MOVE_DOWN", ArrowLeft: "MOVE_LEFT", KeyA: "MOVE_LEFT", ArrowRight: "MOVE_RIGHT", KeyD: "MOVE_RIGHT" }, children: [
199
199
  { type: draw-shape-layer, items: (array/map @entity.tiles (fn t { type: draw-shape, shape: "cell", position: { x: (object/get @t x), y: (object/get @t y) }, fill: "#5c94fc" })) }
200
- { type: draw-sprite-layer, items: (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 0.5, height: 0.5 })) }
200
+ { type: draw-sprite-layer, items: (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 1, height: 1 })) }
201
201
  { type: draw-sprite-layer, items: (array/map @entity.features (fn f { type: draw-sprite, position: { x: (object/get @f x), y: (object/get @f y) }, asset: (object/get @f sprite) })) }
202
- { type: draw-sprite-layer, items: (array/map (array/concat @entity.units @entity.enemies) (fn u { type: draw-sprite, position: (object/get @u position), asset: (if (object/get (object/get @u sprite) url) (object/get @u sprite) (object/get (object/get @config.assetManifest units) (object/get @u unitType))), anchor: "ground", width: 0.5, height: 0.5 })) }
202
+ { type: draw-sprite-layer, items: (array/map (array/concat @entity.units @entity.enemies) (fn u { type: draw-sprite, position: (object/get @u position), asset: (if (object/get (object/get @u sprite) url) (object/get @u sprite) (object/get (object/get @config.assetManifest units) (object/get @u unitType))), anchor: "ground", width: 1, height: 1 })) }
203
203
  { 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
204
  { 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
205
  { 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" })) }
@@ -261,11 +261,11 @@ orbital DungeonBoard2DOrbital {
261
261
  hud: { type: game-hud, stats: [ { label: "HP", value: @entity.hp, max: @entity.maxHp, format: "hearts" }, { label: "Score", value: (+ @entity.score @entity.combatScore) } ] }
262
262
  controls: { type: control-grid, kind: "dpad", directionEvents: { up: "MOVE_UP", down: "MOVE_DOWN", left: "MOVE_LEFT", right: "MOVE_RIGHT" }, directionAssets: @config.directionAssets }
263
263
  overlay: (if (!= @entity.result "none") { type: game-menu, title: "VICTORY", subtitle: "You found the chest!", options: [ { id: "again", label: "Play Again", event: "PLAY_AGAIN", variant: "primary" } ] } (if (!= @entity.combatResult "none") { type: game-menu, title: "DEFEAT", subtitle: "The dungeon claims another hero", options: [ { id: "again", label: "Play Again", event: "PLAY_AGAIN", variant: "primary" } ] } null))
264
- children: [ { type: canvas, camera: { zoom: @config.scale }, projection: "flat", showMinimap: false, keyMap: { ArrowUp: "MOVE_UP", KeyW: "MOVE_UP", ArrowDown: "MOVE_DOWN", KeyS: "MOVE_DOWN", ArrowLeft: "MOVE_LEFT", KeyA: "MOVE_LEFT", ArrowRight: "MOVE_RIGHT", KeyD: "MOVE_RIGHT" }, children: [
264
+ children: [ { type: canvas, fit: true, camera: { zoom: @config.scale }, projection: "flat", showMinimap: false, keyMap: { ArrowUp: "MOVE_UP", KeyW: "MOVE_UP", ArrowDown: "MOVE_DOWN", KeyS: "MOVE_DOWN", ArrowLeft: "MOVE_LEFT", KeyA: "MOVE_LEFT", ArrowRight: "MOVE_RIGHT", KeyD: "MOVE_RIGHT" }, children: [
265
265
  { type: draw-shape-layer, items: (array/map @entity.tiles (fn t { type: draw-shape, shape: "cell", position: { x: (object/get @t x), y: (object/get @t y) }, fill: "#5c94fc" })) }
266
- { type: draw-sprite-layer, items: (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 0.5, height: 0.5 })) }
266
+ { type: draw-sprite-layer, items: (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 1, height: 1 })) }
267
267
  { type: draw-sprite-layer, items: (array/map @entity.features (fn f { type: draw-sprite, position: { x: (object/get @f x), y: (object/get @f y) }, asset: (object/get @f sprite) })) }
268
- { type: draw-sprite-layer, items: (array/map (array/concat @entity.units @entity.enemies) (fn u { type: draw-sprite, position: (object/get @u position), asset: (if (object/get (object/get @u sprite) url) (object/get @u sprite) (object/get (object/get @config.assetManifest units) (object/get @u unitType))), anchor: "ground", width: 0.5, height: 0.5 })) }
268
+ { type: draw-sprite-layer, items: (array/map (array/concat @entity.units @entity.enemies) (fn u { type: draw-sprite, position: (object/get @u position), asset: (if (object/get (object/get @u sprite) url) (object/get @u sprite) (object/get (object/get @config.assetManifest units) (object/get @u unitType))), anchor: "ground", width: 1, height: 1 })) }
269
269
  { 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
270
  { 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
271
  { 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" })) }
@@ -5,6 +5,7 @@ orbital TacticsBoard2DOrbital {
5
5
  uses Particles from "std/behaviors/std-fx-particles"
6
6
  uses Round from "std/behaviors/std-round-flow"
7
7
  uses Score from "std/behaviors/std-score"
8
+ uses AnimTick from "std/behaviors/std-anim-tick"
8
9
 
9
10
  type Asset = {
10
11
  url : asset
@@ -100,6 +101,13 @@ orbital TacticsBoard2DOrbital {
100
101
  turn : number = 0
101
102
  score : number = 0
102
103
  scoreBaseline : number = 0
104
+ animCounter : number = 0
105
+ frameName : string = "0"
106
+ animUnitId : string = ""
107
+ animState : string = "idle"
108
+ animTicksLeft : number = 0
109
+ animAttackerId : string = ""
110
+ animAtkTicksLeft : number = 0
103
111
  }
104
112
 
105
113
  trait TacticsAuthority = Tactics.traits.GridTactics -> GameState {
@@ -133,6 +141,10 @@ orbital TacticsBoard2DOrbital {
133
141
  config { running: true, pointsPerKill: 10, startScore: 0, initialEnemies: 0 }
134
142
  }
135
143
 
144
+ trait AnimClock = AnimTick.traits.AnimTick -> GameState {
145
+ config { frames: [] }
146
+ }
147
+
136
148
  trait PlayerIntent -> GameState [interaction, instance] {
137
149
  initial: playing
138
150
 
@@ -149,23 +161,27 @@ orbital TacticsBoard2DOrbital {
149
161
  hudBackgroundAsset: @config.hudBackgroundAsset
150
162
  hud: { type: game-hud, stats: (array/concat [ { label: "Result", value: @entity.result }, { label: "Phase", value: @entity.uiPhase }, { label: "Turn", value: @entity.turn }, { label: "Score", value: @entity.score } ] @config.extraStats) }
151
163
  addons: { type: stack, direction: vertical, gap: sm, children: (array/concat [ { type: button, action: END_TURN, label: "End Turn", variant: primary }, { type: button, action: CANCEL, label: "Cancel", variant: secondary } ] @config.extraAddons) }
152
- children: [ { type: canvas, camera: { zoom: (math/min 1 (/ 5 (+ 1 (math/max (array/max @entity.tiles "x") (array/max @entity.tiles "y"))))), pos: { x: (/ (+ (array/min @entity.tiles "x") (array/max @entity.tiles "x")) 2), y: (/ (+ (array/min @entity.tiles "y") (array/max @entity.tiles "y")) 2) } }, projection: @config.projection, showMinimap: @config.showMinimap, tileClickEvent: TILE_CLICK, unitClickEvent: UNIT_CLICK, children: [
164
+ children: [ { type: canvas, fit: true, camera: { pos: { x: (/ (+ (array/min @entity.tiles "x") (array/max @entity.tiles "x")) 2), y: (/ (+ (array/min @entity.tiles "y") (array/max @entity.tiles "y")) 2) } }, projection: @config.projection, showMinimap: @config.showMinimap, tileClickEvent: TILE_CLICK, unitClickEvent: UNIT_CLICK, children: [
153
165
  { type: draw-shape-layer, items: (array/map @entity.tiles (fn t { type: draw-shape, shape: "cell", position: { x: (object/get @t x), y: (object/get @t y) }, fill: "#5c94fc", stroke: "rgba(255,255,255,0.35)", strokeWidth: 1 })) }
154
- { type: draw-sprite-layer, items: (array/filter (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 0.5, height: 0.5 })) (fn s (object/get (object/get @s asset) url))) }
166
+ { type: draw-sprite-layer, items: (array/filter (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 1, height: 1 })) (fn s (object/get (object/get @s asset) url))) }
155
167
  { type: draw-sprite-layer, items: (array/filter (array/map @config.features (fn f { type: draw-sprite, position: { x: (object/get @f x), y: (object/get @f y) }, asset: (object/get @f assetUrl) })) (fn s (object/get (object/get @s asset) url))) }
156
168
  { type: draw-shape-layer, items: (array/map @entity.validMoves (fn m { type: draw-shape, shape: "cell", position: { x: (object/get @m x), y: (object/get @m z) }, fill: "rgba(74,222,128,0.4)" })) }
157
169
  { type: draw-shape-layer, items: (array/map @entity.attackTargets (fn a { type: draw-shape, shape: "cell", position: { x: (object/get @a x), y: (object/get @a z) }, fill: "rgba(239,68,68,0.4)" })) }
158
170
  { type: draw-shape-layer, items: (array/map (array/filter @entity.units (fn u (== (object/get @u id) @entity.selectedUnitId))) (fn u { type: draw-shape, shape: "ellipse", position: (object/get @u position), radiusX: 0.5, radiusY: 0.2, stroke: "rgba(0,200,255,0.8)", strokeWidth: 3 })) }
159
- { type: draw-sprite-layer, items: (array/filter (array/map @entity.units (fn u { type: draw-sprite, position: (object/get @u position), asset: (if (object/get (object/get @u modelUrl) url) (object/get @u modelUrl) (object/get (object/get @config.assetManifest units) (object/get @u unitType))), id: (object/get @u id), anchor: "ground", width: 0.5, height: 0.5 })) (fn s (object/get (object/get @s asset) url))) }
171
+ { type: draw-sprite-layer, items: (array/filter (array/map @entity.units (fn u (let ((unitAsset (if (object/get (object/get @u modelUrl) url) (object/get @u modelUrl) (object/get (object/get @config.assetManifest units) (object/get @u unitType)))) (unitUrl (object/get @unitAsset url)) (unitAnim (if (<= (object/get @u health) 0) "death" (if (and (== (object/get @u id) @entity.animAttackerId) (> @entity.animAtkTicksLeft 0)) "attack" (if (== (object/get @u id) @entity.animUnitId) @entity.animState "idle"))))) { type: draw-sprite, position: (object/get @u position), asset: (if (and @unitUrl (str/includes @unitUrl ".svg")) (object/merge @unitAsset { url: (str/concat (array/nth (str/split @unitUrl ".svg") 0) (str/concat "." (str/concat @unitAnim (str/concat "-" (str/concat (if (== @unitAnim "walk") (% @entity.animCounter 2) (if (== @unitAnim "idle") (% (math/floor (/ @entity.animCounter 3)) 4) 0))) ".svg")))) }) @unitAsset), id: (object/get @u id), anchor: "ground", width: 0.5, height: 0.5 }))) (fn s (object/get (object/get @s asset) url))) }
160
172
  { type: draw-shape-layer, items: (array/map @entity.units (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)" })) }
161
173
  { type: draw-shape-layer, items: (array/map @entity.units (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" })) }
162
174
  { 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" })) }
163
- { 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" })) }
175
+ { 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" })) }
176
+ { type: draw-sprite-layer, items: (array/filter (array/map @entity.fx (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))) }
177
+ { 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 })) }
164
178
  ] } ]
165
179
  })
166
180
 
167
181
  UNIT_CLICK -> playing when (and (== @entity.uiPhase "action") (and (== (object/get (array/find @entity.units (fn u (== @u.id ?unitId))) team) "enemy") (<= (grid/manhattan-distance (object/get (array/find @entity.units (fn u (== @u.id @entity.selectedUnitId))) position) (object/get (array/find @entity.units (fn u (== @u.id ?unitId))) position)) 1)))
168
182
  (emit ATTACK { targetId: ?unitId })
183
+ (set @entity.animAttackerId @entity.selectedUnitId)
184
+ (set @entity.animAtkTicksLeft 4)
169
185
  (set @entity.selectedUnitId "")
170
186
  (set @entity.validMoves [])
171
187
  (set @entity.attackTargets [])
@@ -198,28 +214,44 @@ orbital TacticsBoard2DOrbital {
198
214
  END_TURN -> playing when (== @entity.result "none")
199
215
  (set @entity.turn (+ @entity.turn 1))
200
216
  (emit AI_TURN { turn: @entity.turn })
217
+
218
+ MOVE -> playing
219
+ (set @entity.animUnitId ?unitId)
220
+ (set @entity.animState "walk")
221
+ (set @entity.animTicksLeft 6)
222
+
223
+ ATTACK -> playing
224
+ (set @entity.animUnitId ?targetId)
225
+ (set @entity.animState "hit")
226
+ (set @entity.animTicksLeft 4)
201
227
  }
202
228
 
203
229
  ticks {
204
230
  renderTick every 100ms
231
+ (set @entity.animTicksLeft (if (> @entity.animTicksLeft 0) (- @entity.animTicksLeft 1) 0))
232
+ (set @entity.animAtkTicksLeft (if (> @entity.animAtkTicksLeft 0) (- @entity.animAtkTicksLeft 1) 0))
233
+ (set @entity.animState (if (== @entity.animTicksLeft 0) "idle" @entity.animState))
234
+ (set @entity.animUnitId (if (== @entity.animTicksLeft 0) "" @entity.animUnitId))
235
+ (set @entity.animAttackerId (if (== @entity.animAtkTicksLeft 0) "" @entity.animAttackerId))
205
236
  (render-ui main {
206
237
  type: game-shell
207
238
  backgroundAsset: @config.backgroundAsset
208
239
  hudBackgroundAsset: @config.hudBackgroundAsset
209
240
  hud: { type: game-hud, stats: (array/concat [ { label: "Result", value: @entity.result }, { label: "Phase", value: @entity.uiPhase }, { label: "Turn", value: @entity.turn }, { label: "Score", value: @entity.score } ] @config.extraStats) }
210
241
  addons: { type: stack, direction: vertical, gap: sm, children: (array/concat [ { type: button, action: END_TURN, label: "End Turn", variant: primary }, { type: button, action: CANCEL, label: "Cancel", variant: secondary } ] @config.extraAddons) }
211
- children: [ { type: canvas, camera: { zoom: (math/min 1 (/ 5 (+ 1 (math/max (array/max @entity.tiles "x") (array/max @entity.tiles "y"))))), pos: { x: (/ (+ (array/min @entity.tiles "x") (array/max @entity.tiles "x")) 2), y: (/ (+ (array/min @entity.tiles "y") (array/max @entity.tiles "y")) 2) } }, projection: @config.projection, showMinimap: @config.showMinimap, tileClickEvent: TILE_CLICK, unitClickEvent: UNIT_CLICK, children: [
242
+ children: [ { type: canvas, fit: true, camera: { pos: { x: (/ (+ (array/min @entity.tiles "x") (array/max @entity.tiles "x")) 2), y: (/ (+ (array/min @entity.tiles "y") (array/max @entity.tiles "y")) 2) } }, projection: @config.projection, showMinimap: @config.showMinimap, tileClickEvent: TILE_CLICK, unitClickEvent: UNIT_CLICK, children: [
212
243
  { type: draw-shape-layer, items: (array/map @entity.tiles (fn t { type: draw-shape, shape: "cell", position: { x: (object/get @t x), y: (object/get @t y) }, fill: "#5c94fc", stroke: "rgba(255,255,255,0.35)", strokeWidth: 1 })) }
213
- { type: draw-sprite-layer, items: (array/filter (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 0.5, height: 0.5 })) (fn s (object/get (object/get @s asset) url))) }
244
+ { type: draw-sprite-layer, items: (array/filter (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 1, height: 1 })) (fn s (object/get (object/get @s asset) url))) }
214
245
  { type: draw-sprite-layer, items: (array/filter (array/map @config.features (fn f { type: draw-sprite, position: { x: (object/get @f x), y: (object/get @f y) }, asset: (object/get @f assetUrl) })) (fn s (object/get (object/get @s asset) url))) }
215
246
  { type: draw-shape-layer, items: (array/map @entity.validMoves (fn m { type: draw-shape, shape: "cell", position: { x: (object/get @m x), y: (object/get @m z) }, fill: "rgba(74,222,128,0.4)" })) }
216
247
  { type: draw-shape-layer, items: (array/map @entity.attackTargets (fn a { type: draw-shape, shape: "cell", position: { x: (object/get @a x), y: (object/get @a z) }, fill: "rgba(239,68,68,0.4)" })) }
217
248
  { type: draw-shape-layer, items: (array/map (array/filter @entity.units (fn u (== (object/get @u id) @entity.selectedUnitId))) (fn u { type: draw-shape, shape: "ellipse", position: (object/get @u position), radiusX: 0.5, radiusY: 0.2, stroke: "rgba(0,200,255,0.8)", strokeWidth: 3 })) }
218
- { type: draw-sprite-layer, items: (array/filter (array/map @entity.units (fn u { type: draw-sprite, position: (object/get @u position), asset: (if (object/get (object/get @u modelUrl) url) (object/get @u modelUrl) (object/get (object/get @config.assetManifest units) (object/get @u unitType))), id: (object/get @u id), anchor: "ground", width: 0.5, height: 0.5 })) (fn s (object/get (object/get @s asset) url))) }
249
+ { type: draw-sprite-layer, items: (array/filter (array/map @entity.units (fn u (let ((unitAsset (if (object/get (object/get @u modelUrl) url) (object/get @u modelUrl) (object/get (object/get @config.assetManifest units) (object/get @u unitType)))) (unitUrl (object/get @unitAsset url)) (unitAnim (if (<= (object/get @u health) 0) "death" (if (and (== (object/get @u id) @entity.animAttackerId) (> @entity.animAtkTicksLeft 0)) "attack" (if (== (object/get @u id) @entity.animUnitId) @entity.animState "idle"))))) { type: draw-sprite, position: (object/get @u position), asset: (if (and @unitUrl (str/includes @unitUrl ".svg")) (object/merge @unitAsset { url: (str/concat (array/nth (str/split @unitUrl ".svg") 0) (str/concat "." (str/concat @unitAnim (str/concat "-" (str/concat (if (== @unitAnim "walk") (% @entity.animCounter 2) (if (== @unitAnim "idle") (% (math/floor (/ @entity.animCounter 3)) 4) 0))) ".svg")))) }) @unitAsset), id: (object/get @u id), anchor: "ground", width: 0.5, height: 0.5 }))) (fn s (object/get (object/get @s asset) url))) }
219
250
  { type: draw-shape-layer, items: (array/map @entity.units (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)" })) }
220
251
  { type: draw-shape-layer, items: (array/map @entity.units (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" })) }
221
252
  { 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" })) }
222
- { 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" })) }
253
+ { 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" })) }
254
+ { type: draw-sprite-layer, items: (array/filter (array/map @entity.fx (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))) }
223
255
  ] } ]
224
256
  })
225
257
  }
@@ -321,5 +353,5 @@ orbital TacticsBoard2DOrbital {
321
353
  }
322
354
  }
323
355
 
324
- page "/tactics-board-2d" as TacticsBoard2DPage -> TacticsAuthority, FxDecay, RoundLogic, ScoreKeeper, PlayerIntent
356
+ page "/tactics-board-2d" as TacticsBoard2DPage -> TacticsAuthority, FxDecay, RoundLogic, ScoreKeeper, PlayerIntent, AnimClock
325
357
  }
@@ -147,7 +147,7 @@ orbital WorldMapBoard2DOrbital {
147
147
  hud: { type: stack, direction: horizontal, gap: md, justify: between, children: [ { type: game-hud, stats: (array/concat [ { label: "Region", value: @config.region, iconUrl: @config.regionIconAsset }, { label: "Turn", value: @entity.turn, iconUrl: @config.turnIconAsset }, { label: "Gold", value: @entity.gold, iconUrl: @config.goldIconAsset } ] @config.extraStats) }, { type: stack, direction: horizontal, gap: xs, align: center, children: [ { type: icon, name: map-pin }, { type: typography, variant: caption, content: "Waypoints" } ] } ] }
148
148
  addons: { type: stack, direction: vertical, gap: sm, children: (array/concat [ { type: button, action: ADVANCE_TURN, label: "Pass Turn", iconAsset: @config.travelIconAsset, variant: primary } ] @config.extraAddons) }
149
149
  overlay: (if (!= @entity.result "none") { type: game-menu, title: (if (== @entity.result "victory") "VICTORY" "DEFEAT"), subtitle: (if (== @entity.result "victory") "The realm is secured" "Your heroes have fallen"), options: [] } null)
150
- children: [ { type: canvas, camera: { mode: "isometric", zoom: (math/min 1 (/ 5 (+ 1 (math/max (array/max @entity.tiles "x") (array/max @entity.tiles "y"))))), pos: { x: (/ (+ (array/min @entity.tiles "x") (array/max @entity.tiles "x")) 2), y: (/ (+ (array/min @entity.tiles "y") (array/max @entity.tiles "y")) 2) } }, projection: "isometric", showMinimap: true, tileClickEvent: HERO_MOVE, unitClickEvent: HERO_SELECT, children: [
150
+ children: [ { type: canvas, fit: true, camera: { pos: { x: (/ (+ (array/min @entity.tiles "x") (array/max @entity.tiles "x")) 2), y: (/ (+ (array/min @entity.tiles "y") (array/max @entity.tiles "y")) 2) } }, projection: "isometric", showMinimap: true, tileClickEvent: HERO_MOVE, unitClickEvent: HERO_SELECT, children: [
151
151
  { type: draw-shape-layer, items: (array/map @entity.tiles (fn t { type: draw-shape, shape: "cell", position: { x: (object/get @t x), y: (object/get @t y) }, fill: "#5c94fc", stroke: "rgba(255,255,255,0.35)", strokeWidth: 1 })) }
152
152
  { type: draw-sprite-layer, items: (array/filter (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 0.5, height: 0.5 })) (fn s (object/get (object/get @s asset) url))) }
153
153
  { type: draw-sprite-layer, items: (array/map @config.features (fn f { type: draw-sprite, position: { x: (object/get @f x), y: (object/get @f y) }, asset: (object/get @f sprite) })) }
@@ -195,7 +195,7 @@ orbital WorldMapBoard2DOrbital {
195
195
  hud: { type: stack, direction: horizontal, gap: md, justify: between, children: [ { type: game-hud, stats: (array/concat [ { label: "Region", value: @config.region, iconUrl: @config.regionIconAsset }, { label: "Turn", value: @entity.turn, iconUrl: @config.turnIconAsset }, { label: "Gold", value: @entity.gold, iconUrl: @config.goldIconAsset } ] @config.extraStats) }, { type: stack, direction: horizontal, gap: xs, align: center, children: [ { type: icon, name: map-pin }, { type: typography, variant: caption, content: "Waypoints" } ] } ] }
196
196
  addons: { type: stack, direction: vertical, gap: sm, children: (array/concat [ { type: button, action: ADVANCE_TURN, label: "Pass Turn", iconAsset: @config.travelIconAsset, variant: primary } ] @config.extraAddons) }
197
197
  overlay: (if (!= @entity.result "none") { type: game-menu, title: (if (== @entity.result "victory") "VICTORY" "DEFEAT"), subtitle: (if (== @entity.result "victory") "The realm is secured" "Your heroes have fallen"), options: [] } null)
198
- children: [ { type: canvas, camera: { mode: "isometric", zoom: (math/min 1 (/ 5 (+ 1 (math/max (array/max @entity.tiles "x") (array/max @entity.tiles "y"))))), pos: { x: (/ (+ (array/min @entity.tiles "x") (array/max @entity.tiles "x")) 2), y: (/ (+ (array/min @entity.tiles "y") (array/max @entity.tiles "y")) 2) } }, projection: "isometric", showMinimap: true, tileClickEvent: HERO_MOVE, unitClickEvent: HERO_SELECT, children: [
198
+ children: [ { type: canvas, fit: true, camera: { pos: { x: (/ (+ (array/min @entity.tiles "x") (array/max @entity.tiles "x")) 2), y: (/ (+ (array/min @entity.tiles "y") (array/max @entity.tiles "y")) 2) } }, projection: "isometric", showMinimap: true, tileClickEvent: HERO_MOVE, unitClickEvent: HERO_SELECT, children: [
199
199
  { type: draw-shape-layer, items: (array/map @entity.tiles (fn t { type: draw-shape, shape: "cell", position: { x: (object/get @t x), y: (object/get @t y) }, fill: "#5c94fc", stroke: "rgba(255,255,255,0.35)", strokeWidth: 1 })) }
200
200
  { type: draw-sprite-layer, items: (array/filter (array/map @entity.tiles (fn t { type: draw-sprite, position: { x: (object/get @t x), y: (object/get @t y) }, asset: (if (object/get (object/get @t terrainSprite) url) (object/get @t terrainSprite) (object/get (object/get @config.assetManifest terrains) (object/get @t terrain))), anchor: "center", width: 0.5, height: 0.5 })) (fn s (object/get (object/get @s asset) url))) }
201
201
  { type: draw-sprite-layer, items: (array/map @config.features (fn f { type: draw-sprite, position: { x: (object/get @f x), y: (object/get @f y) }, asset: (object/get @f sprite) })) }
@@ -42,6 +42,8 @@ orbital Canvas2dOrbital {
42
42
  keyUpMap: @config.keyUpMap
43
43
  camera: @config.camera
44
44
  scale: @config.scale
45
+ tileWidth: @config.tileWidth
46
+ fit: @config.fit
45
47
  showMinimap: @config.showMinimap
46
48
  followTarget: @config.followTarget
47
49
  cameraPos: @config.cameraPos
@@ -130,7 +132,15 @@ orbital Canvas2dOrbital {
130
132
  @tier "presentation"
131
133
  scale : number = 0.4
132
134
  @label "Scale"
133
- @description "Render scale (0.4 = 40% zoom). Ignored by `free`/`side` (world-pixel-direct)."
135
+ @description "Render scale, legacy-squared semantics: on-screen cell ≈ `256 × scale²` px (the authored contract every board tuned its value for). Converted internally to the single camera zoom against the board's native tile width, so the cell pitch follows the asset while the on-screen size stays as authored. Ignored when `fit` is on; passed through raw for `free`/`side` (world-pixel-direct)."
136
+ @tier "presentation"
137
+ tileWidth : number
138
+ @label "Tile Width"
139
+ @description "Native tile/cell width in source px for this board's asset (e.g. 16 for Kenney tiny-dungeon, ~128 for iso blocks). The grid cell pitch follows the asset, so tile textures map 1:1 (crisp, no stretch). Defaults to the detected atlas tile width, else 256."
140
+ @tier "presentation"
141
+ fit : boolean = false
142
+ @label "Fit"
143
+ @description "Auto-fit the board's grid extent to the viewport (default false — boards render at their authored `scale` and overflow → pan). Opt in for whole-board-overview boards. User wheel/pinch zoom always wins after the initial fit."
134
144
  @tier "presentation"
135
145
  showMinimap : boolean = true
136
146
  @label "Show Minimap"
@@ -19,6 +19,8 @@ orbital CanvasOrbital {
19
19
  isLoading: @config.isLoading
20
20
  unitScale: @config.unitScale
21
21
  showMinimap: @config.showMinimap
22
+ fit: @config.fit
23
+ tileWidth: @config.tileWidth
22
24
  backgroundImage: @config.backgroundImage
23
25
  backgroundColor: @config.backgroundColor
24
26
  worldWidth: @config.worldWidth
@@ -104,6 +106,14 @@ orbital CanvasOrbital {
104
106
  @label "Show Minimap"
105
107
  @description "Minimap overlay (2D)."
106
108
  @tier "presentation"
109
+ fit : boolean
110
+ @label "Fit"
111
+ @description "Auto-fit the board's grid extent to the viewport (2D grid layouts; default false — boards render at their authored `camera.zoom` scale)."
112
+ @tier "presentation"
113
+ tileWidth : number
114
+ @label "Tile Width"
115
+ @description "Native tile/cell width in source px of the board's asset (2D grid layouts; defaults to the detected atlas tile width, else 256)."
116
+ @tier "presentation"
107
117
  backgroundImage : asset
108
118
  @label "Background Image"
109
119
  @description "Backdrop image (2D iso/hex/flat/free/side)."
@@ -42,7 +42,7 @@ orbital ArenaBoardOrbital {
42
42
  (render-ui main {
43
43
  type: game-shell
44
44
  hud: { type: game-hud, stats: [ { label: "Result", value: @entity.result } ] }
45
- children: [ { type: canvas, projection: "flat", showMinimap: false, children: [
45
+ children: [ { type: canvas, fit: true, projection: "flat", showMinimap: false, children: [
46
46
  { type: draw-shape-layer, items: (array/map @config.tiles (fn t { type: draw-shape, shape: "cell", position: { x: (object/get @t x), y: (object/get @t y) }, fill: "#4ade80", stroke: "rgba(0,0,0,0.2)" })) }
47
47
  { type: draw-shape-layer, items: (array/map @entity.units (fn u { type: draw-shape, shape: "ellipse", position: (object/get @u position), radiusX: 0.4, radiusY: 0.4, fill: (if (== (object/get @u team) "player") "#3b82f6" "#ef4444") })) }
48
48
  { 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)" })) }
@@ -57,7 +57,7 @@ orbital ArenaBoardOrbital {
57
57
  (render-ui main {
58
58
  type: game-shell
59
59
  hud: { type: game-hud, stats: [ { label: "Result", value: @entity.result } ] }
60
- children: [ { type: canvas, projection: "flat", showMinimap: false, children: [
60
+ children: [ { type: canvas, fit: true, projection: "flat", showMinimap: false, children: [
61
61
  { type: draw-shape-layer, items: (array/map @config.tiles (fn t { type: draw-shape, shape: "cell", position: { x: (object/get @t x), y: (object/get @t y) }, fill: "#4ade80", stroke: "rgba(0,0,0,0.2)" })) }
62
62
  { type: draw-shape-layer, items: (array/map @entity.units (fn u { type: draw-shape, shape: "ellipse", position: (object/get @u position), radiusX: 0.4, radiusY: 0.4, fill: (if (== (object/get @u team) "player") "#3b82f6" "#ef4444") })) }
63
63
  { 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)" })) }