@almadar/std 16.154.0 → 16.155.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.
- package/behaviors/lolo/ui/game/molecules/std-tactics-board-2d.lolo +35 -21
- package/behaviors/lolo/ui/game/molecules/ui-canvas.lolo +5 -0
- package/behaviors/lolo/ui/game/organisms/ui-battle-board.lolo +122 -74
- package/behaviors/lolo/ui/game/organisms/ui-hex-strategy-board.lolo +153 -9
- package/behaviors/lolo/ui/game/organisms/ui-tactics-demo-2d.lolo +130 -8
- package/behaviors/lolo/ui/game/organisms/ui-tanks-board.lolo +147 -7
- package/behaviors/lolo/ui/game/organisms/ui-topdown-rts-board.lolo +291 -19
- package/behaviors/registry/factory-signatures.json +1893 -2294
- package/behaviors/registry/ui/game/molecules/std-tactics-board-2d.orb +798 -189
- package/behaviors/registry/ui/game/molecules/ui-canvas.orb +7 -0
- package/behaviors/registry/ui/game/organisms/ui-battle-board.orb +17 -1
- package/behaviors/registry/ui/game/organisms/ui-hex-strategy-board.orb +68 -1180
- package/behaviors/registry/ui/game/organisms/ui-tactics-demo-2d.orb +192 -322
- package/behaviors/registry/ui/game/organisms/ui-tanks-board.orb +97 -85
- package/behaviors/registry/ui/game/organisms/ui-topdown-rts-board.orb +1477 -259
- package/dist/behaviors/behaviors-embeddings.json +244 -236
- package/dist/behaviors/behaviors-registry.json +143 -772
- package/dist/behaviors/exports-reader.js +1099 -2090
- package/dist/behaviors/functions/index.d.ts +33 -26
- package/dist/behaviors/functions/index.js +1099 -2091
- package/dist/behaviors/index.d.ts +1 -1
- package/dist/behaviors/index.js +1100 -2091
- package/dist/behaviors/knob-embeddings.json +1 -1
- package/dist/behaviors/query.js +1099 -2090
- package/dist/behaviors/registry/factory-signatures.json +1893 -2294
- package/dist/behaviors/registry/ui/game/molecules/std-tactics-board-2d.orb +798 -189
- package/dist/behaviors/registry/ui/game/molecules/ui-canvas.orb +7 -0
- package/dist/behaviors/registry/ui/game/organisms/ui-battle-board.orb +17 -1
- package/dist/behaviors/registry/ui/game/organisms/ui-hex-strategy-board.orb +68 -1180
- package/dist/behaviors/registry/ui/game/organisms/ui-tactics-demo-2d.orb +192 -322
- package/dist/behaviors/registry/ui/game/organisms/ui-tanks-board.orb +97 -85
- package/dist/behaviors/registry/ui/game/organisms/ui-topdown-rts-board.orb +1477 -259
- package/dist/behaviors-embeddings.json +244 -236
- package/dist/behaviors-registry.json +143 -772
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1100 -2091
- package/dist/knob-embeddings.json +1 -1
- package/dist/registry/factory-signatures.json +1893 -2294
- package/package.json +2 -2
|
@@ -1,9 +1,130 @@
|
|
|
1
1
|
app ui-topdown-rts-board "1.0.0"
|
|
2
|
-
"
|
|
2
|
+
"Real-time RTS base skirmish: thin organism composing the std-tactics-board-2d frame + std-score, plus three organism-level traits (RtsClock / RtsEconomy / RtsWaves) that turn the turn-based frame into a real-time RTS — a 2s clock auto-advances turns, workers harvest gold at the mine, gold musters tanks at the town hall, and raider waves stream out of a fortified den. Win by destroying the den; lose when your whole base (town hall included) is wiped."
|
|
3
|
+
;; @capabilities "real-time strategy, RTS economy, wave defense, base assault"
|
|
4
|
+
|
|
5
|
+
;; ============================================================================
|
|
6
|
+
;; PERSONA + USER STORIES
|
|
7
|
+
;;
|
|
8
|
+
;; Read these before changing anything here, and walk them end to end after.
|
|
9
|
+
;; One persona: the PLAYER, running a base on an 8x6 flat top-down grid:
|
|
10
|
+
;; workers mine gold, gold musters tanks, raider waves stream from the den.
|
|
11
|
+
;; Statuses verified 2026-07-30 by a scripted full playthrough driving the
|
|
12
|
+
;; real game on the runtime-verify catalog through window.__orbitalVerification
|
|
13
|
+
;; (events in, canvas drawables + HUD DOM + bus event log out) plus
|
|
14
|
+
;; play_transition guard probes on the almadar-tools daemon.
|
|
15
|
+
;;
|
|
16
|
+
;; THE GENRE (what a real-time RTS player expects): play is CONTINUOUS — there
|
|
17
|
+
;; is no manual end turn. A 2-second clock advances the round on its own: your
|
|
18
|
+
;; spent units refresh and the enemy AI acts, whether or not you touched
|
|
19
|
+
;; anything. The economy is the engine: workers standing at the gold mine
|
|
20
|
+
;; harvest every cycle, you stockpile, and you spend 5 gold to muster a tank
|
|
21
|
+
;; at the town hall. Pressure escalates: a raider wave spawns at the den every
|
|
22
|
+
;; ~16 seconds, each one tougher than the last, and they walk straight at your
|
|
23
|
+
;; base. You win by carrying the fight EAST and destroying the fortified den
|
|
24
|
+
;; (and every raider it spawned); you lose when your whole base — workers,
|
|
25
|
+
;; tanks, AND the town hall — is wiped. A fight looks like: select a tank ->
|
|
26
|
+
;; green reach cells -> move -> the tank STAYS selected and auto-arms (red
|
|
27
|
+
;; cells = enemies in artillery range 2) -> strike for 3 -> the tank is SPENT
|
|
28
|
+
;; until the next clock tick. The hardcoded "End Turn" button stays as a
|
|
29
|
+
;; manual round-hurry for impatient players; the clock makes it optional.
|
|
30
|
+
;; Both bases are fortified with impassable palisade walls: the raider den is
|
|
31
|
+
;; FULLY boxed — all 5 in-bounds neighbor cells are walls, INCLUDING the
|
|
32
|
+
;; diagonals, because the enemy AI steps diagonally (x and y both advance one
|
|
33
|
+
;; cell per tick) and cuts orthogonal corners — so it holds position as a
|
|
34
|
+
;; spawn fortress, and your tanks shell it over the wall from artillery
|
|
35
|
+
;; range 2. Your town hall is walled on three sides with a one-cell muster
|
|
36
|
+
;; yard in front — that yard is the chokepoint raiders funnel into to batter
|
|
37
|
+
;; the hall.
|
|
38
|
+
;; ============================================================================
|
|
39
|
+
;;
|
|
40
|
+
;; PLAYER — "I run my base economy and burn the raider den before the waves burn mine."
|
|
41
|
+
;;
|
|
42
|
+
;; 1. I open the board and see the whole battlefield framed on screen —
|
|
43
|
+
;; terrain under every cell, my walled base at the west (gold mine,
|
|
44
|
+
;; town hall, muster yard), my two workers at the mine and a garrison
|
|
45
|
+
;; tank in the yard, the walled raider den at the east, and the HUD
|
|
46
|
+
;; showing Result/Phase/Turn/Score/Gold/Wave.
|
|
47
|
+
;; UNVERIFIED.
|
|
48
|
+
;; 2. Every unit on the board is a visible sprite with a name label and a
|
|
49
|
+
;; health bar I can read at a glance, idling in place — worker, tank,
|
|
50
|
+
;; town hall and den sprites all visually distinct.
|
|
51
|
+
;; UNVERIFIED.
|
|
52
|
+
;; 3. Play is continuous: the Turn counter advances about every 2 seconds
|
|
53
|
+
;; WITHOUT me clicking End Turn, and a unit I spent last cycle is fresh
|
|
54
|
+
;; again on the next. The End Turn button still works as a manual hurry.
|
|
55
|
+
;; UNVERIFIED.
|
|
56
|
+
;; 4. My workers harvest: while a living worker stands on or next to the
|
|
57
|
+
;; gold mine (manhattan <= 1), Gold rises every clock cycle; with no
|
|
58
|
+
;; worker at the mine it does not.
|
|
59
|
+
;; UNVERIFIED.
|
|
60
|
+
;; 5. With 5+ gold I hit "Tank (5g)" and a fresh tank rolls out on the
|
|
61
|
+
;; muster yard beside the hall; my gold drops by 5. Under 5 gold — or
|
|
62
|
+
;; with the yard occupied — the button is a dead click (guard).
|
|
63
|
+
;; UNVERIFIED.
|
|
64
|
+
;; 6. Raider waves: about every 16 seconds a raider spawns at the den's
|
|
65
|
+
;; muster point with scaling health (6 + wave number) and walks toward
|
|
66
|
+
;; my base on the next clock ticks. If the muster point is occupied the
|
|
67
|
+
;; wave waits for it to clear.
|
|
68
|
+
;; UNVERIFIED.
|
|
69
|
+
;; 7. Combat: I select my tank (green reach), move (walk animation, one
|
|
70
|
+
;; move per cycle), it auto-arms (red = enemies within artillery range
|
|
71
|
+
;; 2), and I strike for 3 — the tank is then spent until the next tick.
|
|
72
|
+
;; UNVERIFIED.
|
|
73
|
+
;; 8. The den is FULLY boxed by palisade walls: the AI can never step it
|
|
74
|
+
;; (all 5 in-bounds neighbor cells impassable, diagonals included — the
|
|
75
|
+
;; AI steps diagonally), so it holds position as a spawn fortress — and
|
|
76
|
+
;; my tanks destroy it by shelling over the wall from range 2. (Its
|
|
77
|
+
;; retaliatory strike only reaches orthogonally-adjacent attackers, and
|
|
78
|
+
;; no attacker can stand adjacent to it.)
|
|
79
|
+
;; UNVERIFIED.
|
|
80
|
+
;; 9. My town hall is a building: walled on three sides, its only lit
|
|
81
|
+
;; move cell is the corridor mouth in front of its own muster yard —
|
|
82
|
+
;; there is never a reason to move it, and the garrison tank starts on
|
|
83
|
+
;; the yard. (Honest note: the frame treats the hall as a unit, so it
|
|
84
|
+
;; CAN technically shuffle into its own yard/corridor; zero valid
|
|
85
|
+
;; moves is impossible while raiders must reach the hall for the
|
|
86
|
+
;; defeat path.)
|
|
87
|
+
;; UNVERIFIED.
|
|
88
|
+
;; 10. When the den and every raider are dead, I win — Result flips to
|
|
89
|
+
;; victory and the board freezes. When my whole base including the
|
|
90
|
+
;; town hall is dead, Result is defeat. Kills tick Score +10.
|
|
91
|
+
;; UNVERIFIED.
|
|
92
|
+
;;
|
|
93
|
+
;; MULTIPLAYER NOTE: GameState is [runtime, shared], so every connected
|
|
94
|
+
;; client watches the same skirmish on the same frame; intents serialize
|
|
95
|
+
;; through the server authority. There is no per-seat ownership — any
|
|
96
|
+
;; client can drive any player unit.
|
|
97
|
+
;;
|
|
98
|
+
;; ASSET INVENTORY (hand-authored svg, almadar-kflow-assets.web.app
|
|
99
|
+
;; /shared/ui-topdown-rts-board/svg/flat/, generated by tools/asset-workflow
|
|
100
|
+
;; svg-generator from the sibling .md descriptions)
|
|
101
|
+
;;
|
|
102
|
+
;; Terrain terrain/ground.svg full-cell flat tiles; terrain/sand.svg
|
|
103
|
+
;; marks the south-east patch (4 tiles).
|
|
104
|
+
;; Units units/{worker,tank,town_hall,raider,raider_den}.svg +
|
|
105
|
+
;; animation frames .{idle-0..3, walk-0..1, attack-0, hit-0,
|
|
106
|
+
;; death-0}.svg; the frame molecule url-rewrites name.svg ->
|
|
107
|
+
;; name.<anim>-<i>.svg. Spawned tanks/raiders (SPAWN carries no
|
|
108
|
+
;; modelUrl) render via the assetManifest.units[unitType]
|
|
109
|
+
;; fallback with an empty name label (known atom gap).
|
|
110
|
+
;; player: w1 Pick worker 5hp (0,0) on the mine
|
|
111
|
+
;; w2 Shovel worker 5hp (1,0) at the mine
|
|
112
|
+
;; t1 Alpha tank 10hp (1,3) on the muster yard
|
|
113
|
+
;; hall Bastion town_hall 30hp (0,3), walled
|
|
114
|
+
;; enemy: den Den raider_den 30hp (7,2), fully walled
|
|
115
|
+
;; Features features/gold_mine.svg at (0,0) — the harvest anchor;
|
|
116
|
+
;; features/wall.svg x11 palisade segments at (0,1),(0,2),(0,4),
|
|
117
|
+
;; (0,5),(1,2),(1,4) boxing the town hall and (6,1),(6,2),(6,3),
|
|
118
|
+
;; (7,1),(7,3) boxing the den — diagonals included, the AI steps
|
|
119
|
+
;; diagonally (all matching tiles passable: false).
|
|
120
|
+
;; Effects effects/hit.svg (BURST "hit" fx), effects/explosion.svg
|
|
121
|
+
;; (manifest-only).
|
|
122
|
+
;; UI chrome ui/panel-frame.svg = game-shell background + HUD panel.
|
|
123
|
+
;; ============================================================================
|
|
124
|
+
|
|
3
125
|
orbital TopdownRtsBoardOrbital {
|
|
4
126
|
uses Frame from "std/behaviors/std-tactics-board-2d"
|
|
5
127
|
uses Score from "std/behaviors/std-score"
|
|
6
|
-
uses Production from "std/behaviors/std-production"
|
|
7
128
|
|
|
8
129
|
type Asset = {
|
|
9
130
|
url : asset
|
|
@@ -69,19 +190,23 @@ orbital TopdownRtsBoardOrbital {
|
|
|
69
190
|
|
|
70
191
|
entity GameState [runtime, shared] {
|
|
71
192
|
id : string!
|
|
72
|
-
units : [Unit] = [ { id: "
|
|
193
|
+
units : [Unit] = [ { id: "w1", position: { x: 0, y: 0 }, x: 0, y: 0, z: 0, team: "player", health: 5, maxHealth: 5, unitType: "worker", name: "Pick", modelUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/worker.svg", role: "npc", category: "worker", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "worker", thumbnailUrl: "" } }, { id: "w2", position: { x: 1, y: 0 }, x: 1, y: 0, z: 0, team: "player", health: 5, maxHealth: 5, unitType: "worker", name: "Shovel", modelUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/worker.svg", role: "npc", category: "worker", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "worker", thumbnailUrl: "" } }, { id: "t1", position: { x: 1, y: 3 }, x: 1, y: 3, z: 0, team: "player", health: 10, maxHealth: 10, unitType: "tank", name: "Alpha", modelUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/tank.svg", role: "npc", category: "tank", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "tank", thumbnailUrl: "" } }, { id: "hall", position: { x: 0, y: 3 }, x: 0, y: 3, z: 0, team: "player", health: 30, maxHealth: 30, unitType: "town_hall", name: "Bastion", modelUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/town_hall.svg", role: "npc", category: "town_hall", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "town_hall", thumbnailUrl: "" } }, { id: "den", position: { x: 7, y: 2 }, x: 7, y: 2, z: 0, team: "enemy", health: 30, maxHealth: 30, unitType: "raider_den", name: "Den", modelUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/raider_den.svg", role: "npc", category: "raider_den", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "raider_den", thumbnailUrl: "" } } ]
|
|
73
194
|
fx : [Fx] = []
|
|
74
|
-
tiles : [Tile] = [ { id: "t-0-0", x: 0, y: 0, terrain: "ground", passable: true }, { id: "t-1-0", x: 1, y: 0, terrain: "ground", passable: true }, { id: "t-2-0", x: 2, y: 0, terrain: "ground", passable: true }, { id: "t-3-0", x: 3, y: 0, terrain: "ground", passable: true }, { id: "t-4-0", x: 4, y: 0, terrain: "ground", passable: true }, { id: "t-5-0", x: 5, y: 0, terrain: "ground", passable: true }, { id: "t-6-0", x: 6, y: 0, terrain: "ground", passable: true }, { id: "t-7-0", x: 7, y: 0, terrain: "ground", passable: true }, { id: "t-0-1", x: 0, y: 1, terrain: "ground", passable:
|
|
195
|
+
tiles : [Tile] = [ { id: "t-0-0", x: 0, y: 0, terrain: "ground", passable: true }, { id: "t-1-0", x: 1, y: 0, terrain: "ground", passable: true }, { id: "t-2-0", x: 2, y: 0, terrain: "ground", passable: true }, { id: "t-3-0", x: 3, y: 0, terrain: "ground", passable: true }, { id: "t-4-0", x: 4, y: 0, terrain: "ground", passable: true }, { id: "t-5-0", x: 5, y: 0, terrain: "ground", passable: true }, { id: "t-6-0", x: 6, y: 0, terrain: "ground", passable: true }, { id: "t-7-0", x: 7, y: 0, terrain: "ground", passable: true }, { id: "t-0-1", x: 0, y: 1, terrain: "ground", passable: false }, { id: "t-1-1", x: 1, y: 1, terrain: "ground", passable: true }, { id: "t-2-1", x: 2, y: 1, terrain: "ground", passable: true }, { id: "t-3-1", x: 3, y: 1, terrain: "ground", passable: true }, { id: "t-4-1", x: 4, y: 1, terrain: "ground", passable: true }, { id: "t-5-1", x: 5, y: 1, terrain: "ground", passable: true }, { id: "t-6-1", x: 6, y: 1, terrain: "ground", passable: false }, { id: "t-7-1", x: 7, y: 1, terrain: "ground", passable: false }, { id: "t-0-2", x: 0, y: 2, terrain: "ground", passable: false }, { id: "t-1-2", x: 1, y: 2, terrain: "ground", passable: false }, { id: "t-2-2", x: 2, y: 2, terrain: "ground", passable: true }, { id: "t-3-2", x: 3, y: 2, terrain: "ground", passable: true }, { id: "t-4-2", x: 4, y: 2, terrain: "ground", passable: true }, { id: "t-5-2", x: 5, y: 2, terrain: "ground", passable: true }, { id: "t-6-2", x: 6, y: 2, terrain: "ground", passable: false }, { id: "t-7-2", x: 7, y: 2, terrain: "ground", passable: true }, { id: "t-0-3", x: 0, y: 3, terrain: "ground", passable: true }, { id: "t-1-3", x: 1, y: 3, terrain: "ground", passable: true }, { id: "t-2-3", x: 2, y: 3, terrain: "ground", passable: true }, { id: "t-3-3", x: 3, y: 3, terrain: "ground", passable: true }, { id: "t-4-3", x: 4, y: 3, terrain: "ground", passable: true }, { id: "t-5-3", x: 5, y: 3, terrain: "ground", passable: true }, { id: "t-6-3", x: 6, y: 3, terrain: "ground", passable: false }, { id: "t-7-3", x: 7, y: 3, terrain: "ground", passable: false }, { id: "t-0-4", x: 0, y: 4, terrain: "ground", passable: false }, { id: "t-1-4", x: 1, y: 4, terrain: "ground", passable: false }, { id: "t-2-4", x: 2, y: 4, terrain: "ground", passable: true }, { id: "t-3-4", x: 3, y: 4, terrain: "ground", passable: true }, { id: "t-4-4", x: 4, y: 4, terrain: "ground", passable: true }, { id: "t-5-4", x: 5, y: 4, terrain: "sand", passable: true }, { id: "t-6-4", x: 6, y: 4, terrain: "sand", passable: true }, { id: "t-7-4", x: 7, y: 4, terrain: "ground", passable: true }, { id: "t-0-5", x: 0, y: 5, terrain: "ground", passable: false }, { id: "t-1-5", x: 1, y: 5, terrain: "ground", passable: true }, { id: "t-2-5", x: 2, y: 5, terrain: "ground", passable: true }, { id: "t-3-5", x: 3, y: 5, terrain: "ground", passable: true }, { id: "t-4-5", x: 4, y: 5, terrain: "ground", passable: true }, { id: "t-5-5", x: 5, y: 5, terrain: "sand", passable: true }, { id: "t-6-5", x: 6, y: 5, terrain: "sand", passable: true }, { id: "t-7-5", x: 7, y: 5, terrain: "ground", passable: true } ]
|
|
75
196
|
result : string = "none"
|
|
76
197
|
selectedUnitId : string = ""
|
|
77
198
|
validMoves : [ValidMove] = []
|
|
78
199
|
attackTargets : [ValidMove] = []
|
|
200
|
+
movedIds : [string] = []
|
|
201
|
+
actedIds : [string] = []
|
|
79
202
|
uiPhase : string = "observation"
|
|
80
203
|
turn : number = 0
|
|
81
204
|
score : number = 0
|
|
82
205
|
scoreBaseline : number = 0
|
|
83
|
-
|
|
84
|
-
|
|
206
|
+
gold : number = 0
|
|
207
|
+
ecoSpawnCount : number = 0
|
|
208
|
+
waveCount : number = 0
|
|
209
|
+
clockTicks : number = 0
|
|
85
210
|
}
|
|
86
211
|
|
|
87
212
|
trait Authority = Frame.traits.TacticsAuthority -> GameState {
|
|
@@ -90,8 +215,8 @@ orbital TopdownRtsBoardOrbital {
|
|
|
90
215
|
gridWidth: 8
|
|
91
216
|
gridHeight: 6
|
|
92
217
|
attackDamage: 3
|
|
93
|
-
units: [ { id: "
|
|
94
|
-
tiles: [ { id: "t-0-0", x: 0, y: 0, terrain: "ground", passable: true }, { id: "t-1-0", x: 1, y: 0, terrain: "ground", passable: true }, { id: "t-2-0", x: 2, y: 0, terrain: "ground", passable: true }, { id: "t-3-0", x: 3, y: 0, terrain: "ground", passable: true }, { id: "t-4-0", x: 4, y: 0, terrain: "ground", passable: true }, { id: "t-5-0", x: 5, y: 0, terrain: "ground", passable: true }, { id: "t-6-0", x: 6, y: 0, terrain: "ground", passable: true }, { id: "t-7-0", x: 7, y: 0, terrain: "ground", passable: true }, { id: "t-0-1", x: 0, y: 1, terrain: "ground", passable:
|
|
218
|
+
units: [ { id: "w1", position: { x: 0, y: 0 }, x: 0, y: 0, z: 0, team: "player", health: 5, maxHealth: 5, unitType: "worker", name: "Pick", modelUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/worker.svg", role: "npc", category: "worker", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "worker", thumbnailUrl: "" } }, { id: "w2", position: { x: 1, y: 0 }, x: 1, y: 0, z: 0, team: "player", health: 5, maxHealth: 5, unitType: "worker", name: "Shovel", modelUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/worker.svg", role: "npc", category: "worker", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "worker", thumbnailUrl: "" } }, { id: "t1", position: { x: 1, y: 3 }, x: 1, y: 3, z: 0, team: "player", health: 10, maxHealth: 10, unitType: "tank", name: "Alpha", modelUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/tank.svg", role: "npc", category: "tank", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "tank", thumbnailUrl: "" } }, { id: "hall", position: { x: 0, y: 3 }, x: 0, y: 3, z: 0, team: "player", health: 30, maxHealth: 30, unitType: "town_hall", name: "Bastion", modelUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/town_hall.svg", role: "npc", category: "town_hall", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "town_hall", thumbnailUrl: "" } }, { id: "den", position: { x: 7, y: 2 }, x: 7, y: 2, z: 0, team: "enemy", health: 30, maxHealth: 30, unitType: "raider_den", name: "Den", modelUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/raider_den.svg", role: "npc", category: "raider_den", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "raider_den", thumbnailUrl: "" } } ]
|
|
219
|
+
tiles: [ { id: "t-0-0", x: 0, y: 0, terrain: "ground", passable: true }, { id: "t-1-0", x: 1, y: 0, terrain: "ground", passable: true }, { id: "t-2-0", x: 2, y: 0, terrain: "ground", passable: true }, { id: "t-3-0", x: 3, y: 0, terrain: "ground", passable: true }, { id: "t-4-0", x: 4, y: 0, terrain: "ground", passable: true }, { id: "t-5-0", x: 5, y: 0, terrain: "ground", passable: true }, { id: "t-6-0", x: 6, y: 0, terrain: "ground", passable: true }, { id: "t-7-0", x: 7, y: 0, terrain: "ground", passable: true }, { id: "t-0-1", x: 0, y: 1, terrain: "ground", passable: false }, { id: "t-1-1", x: 1, y: 1, terrain: "ground", passable: true }, { id: "t-2-1", x: 2, y: 1, terrain: "ground", passable: true }, { id: "t-3-1", x: 3, y: 1, terrain: "ground", passable: true }, { id: "t-4-1", x: 4, y: 1, terrain: "ground", passable: true }, { id: "t-5-1", x: 5, y: 1, terrain: "ground", passable: true }, { id: "t-6-1", x: 6, y: 1, terrain: "ground", passable: false }, { id: "t-7-1", x: 7, y: 1, terrain: "ground", passable: false }, { id: "t-0-2", x: 0, y: 2, terrain: "ground", passable: false }, { id: "t-1-2", x: 1, y: 2, terrain: "ground", passable: false }, { id: "t-2-2", x: 2, y: 2, terrain: "ground", passable: true }, { id: "t-3-2", x: 3, y: 2, terrain: "ground", passable: true }, { id: "t-4-2", x: 4, y: 2, terrain: "ground", passable: true }, { id: "t-5-2", x: 5, y: 2, terrain: "ground", passable: true }, { id: "t-6-2", x: 6, y: 2, terrain: "ground", passable: false }, { id: "t-7-2", x: 7, y: 2, terrain: "ground", passable: true }, { id: "t-0-3", x: 0, y: 3, terrain: "ground", passable: true }, { id: "t-1-3", x: 1, y: 3, terrain: "ground", passable: true }, { id: "t-2-3", x: 2, y: 3, terrain: "ground", passable: true }, { id: "t-3-3", x: 3, y: 3, terrain: "ground", passable: true }, { id: "t-4-3", x: 4, y: 3, terrain: "ground", passable: true }, { id: "t-5-3", x: 5, y: 3, terrain: "ground", passable: true }, { id: "t-6-3", x: 6, y: 3, terrain: "ground", passable: false }, { id: "t-7-3", x: 7, y: 3, terrain: "ground", passable: false }, { id: "t-0-4", x: 0, y: 4, terrain: "ground", passable: false }, { id: "t-1-4", x: 1, y: 4, terrain: "ground", passable: false }, { id: "t-2-4", x: 2, y: 4, terrain: "ground", passable: true }, { id: "t-3-4", x: 3, y: 4, terrain: "ground", passable: true }, { id: "t-4-4", x: 4, y: 4, terrain: "ground", passable: true }, { id: "t-5-4", x: 5, y: 4, terrain: "sand", passable: true }, { id: "t-6-4", x: 6, y: 4, terrain: "sand", passable: true }, { id: "t-7-4", x: 7, y: 4, terrain: "ground", passable: true }, { id: "t-0-5", x: 0, y: 5, terrain: "ground", passable: false }, { id: "t-1-5", x: 1, y: 5, terrain: "ground", passable: true }, { id: "t-2-5", x: 2, y: 5, terrain: "ground", passable: true }, { id: "t-3-5", x: 3, y: 5, terrain: "ground", passable: true }, { id: "t-4-5", x: 4, y: 5, terrain: "ground", passable: true }, { id: "t-5-5", x: 5, y: 5, terrain: "sand", passable: true }, { id: "t-6-5", x: 6, y: 5, terrain: "sand", passable: true }, { id: "t-7-5", x: 7, y: 5, terrain: "ground", passable: true } ]
|
|
95
220
|
}
|
|
96
221
|
}
|
|
97
222
|
|
|
@@ -104,32 +229,179 @@ orbital TopdownRtsBoardOrbital {
|
|
|
104
229
|
}
|
|
105
230
|
|
|
106
231
|
trait ScoreKeeper = Score.traits.ScoreKeeper -> GameState {
|
|
107
|
-
config { running: true, pointsPerKill: 10, startScore: 0, initialEnemies:
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
trait Producer = Production.traits.Producer -> GameState {
|
|
111
|
-
config { running: true, startPoints: 3, pointsPerTurn: 2, spawnCost: 3, spawnX: 0, spawnY: 0, spawnTeam: "player", spawnUnitType: "tank", spawnHealth: 10 }
|
|
232
|
+
config { running: true, pointsPerKill: 10, startScore: 0, initialEnemies: 1 }
|
|
112
233
|
}
|
|
113
234
|
|
|
114
235
|
trait AnimClock = Frame.traits.AnimClock -> GameState {
|
|
115
236
|
config { frames: [] }
|
|
116
237
|
}
|
|
117
238
|
|
|
239
|
+
trait RtsClock -> @rebindable GameState [lifecycle] {
|
|
240
|
+
initial: running
|
|
241
|
+
|
|
242
|
+
state running {
|
|
243
|
+
INIT -> running
|
|
244
|
+
(set @entity.clockTicks 0)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
ticks {
|
|
248
|
+
turnTick every 2000ms when (== @entity.result "none")
|
|
249
|
+
(set @entity.clockTicks (+ @entity.clockTicks 1))
|
|
250
|
+
(emit END_TURN { id: "clock" })
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
emits {
|
|
254
|
+
END_TURN -> external {
|
|
255
|
+
id : string
|
|
256
|
+
}
|
|
257
|
+
@description "Real-time round advance; the frame's PlayerIntent treats it exactly like the End Turn button (turn bump, activation reset, AI_TURN)"
|
|
258
|
+
@tier "domain"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
trait RtsEconomy -> @rebindable GameState [lifecycle] {
|
|
263
|
+
initial: active
|
|
264
|
+
|
|
265
|
+
state active {
|
|
266
|
+
INIT -> active
|
|
267
|
+
(set @entity.gold @config.startGold)
|
|
268
|
+
(set @entity.ecoSpawnCount 0)
|
|
269
|
+
|
|
270
|
+
BUILD_TANK -> active when (and @config.running (and (== @entity.result "none") (and (>= @entity.gold @config.tankCost) (== (array/len (array/filter @entity.units (fn u (and (> @u.health 0) (and (== (object/get (object/get @u position) x) @config.tankSpawnX) (== (object/get (object/get @u position) y) @config.tankSpawnY)))))) 0))))
|
|
271
|
+
(set @entity.gold (- @entity.gold @config.tankCost))
|
|
272
|
+
(emit SPAWN { id: (str/concat "rts-t-" @entity.ecoSpawnCount), x: @config.tankSpawnX, y: @config.tankSpawnY, team: "player", health: @config.tankHealth, maxHealth: @config.tankHealth, unitType: "tank" })
|
|
273
|
+
(set @entity.ecoSpawnCount (+ @entity.ecoSpawnCount 1))
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
ticks {
|
|
277
|
+
harvestTick every 2000ms when (and @config.running (== @entity.result "none"))
|
|
278
|
+
(set @entity.gold (+ @entity.gold (* (array/len (array/filter @entity.units (fn u (and (== @u.team "player") (and (> @u.health 0) (and (== @u.unitType "worker") (<= (grid/manhattan-distance (object/get @u position) { x: @config.mineX, y: @config.mineY }) 1))))))) @config.goldPerWorkerTick)))
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
emits {
|
|
282
|
+
SPAWN -> external {
|
|
283
|
+
id : string!
|
|
284
|
+
x : number!
|
|
285
|
+
y : number!
|
|
286
|
+
team : string!
|
|
287
|
+
health : number!
|
|
288
|
+
maxHealth : number!
|
|
289
|
+
unitType : string!
|
|
290
|
+
}
|
|
291
|
+
@description "Mustered-tank intent applied by the grid-tactics authority (the sole units writer)"
|
|
292
|
+
@tier "domain"
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
listens {
|
|
296
|
+
BUILD_TANK { id : string }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
config {
|
|
300
|
+
running : boolean = true
|
|
301
|
+
@label "Running"
|
|
302
|
+
@description "When true, workers harvest and BUILD_TANK musters; toggle to freeze the economy."
|
|
303
|
+
@tier "domain"
|
|
304
|
+
startGold : number = 3
|
|
305
|
+
@label "Start Gold"
|
|
306
|
+
@description "Gold stockpile set on INIT."
|
|
307
|
+
@tier "domain"
|
|
308
|
+
goldPerWorkerTick : number = 1
|
|
309
|
+
@label "Gold Per Worker Tick"
|
|
310
|
+
@description "Gold each living worker at the mine harvests every 2s cycle."
|
|
311
|
+
@tier "domain"
|
|
312
|
+
tankCost : number = 5
|
|
313
|
+
@label "Tank Cost"
|
|
314
|
+
@description "Gold spent per mustered tank; BUILD_TANK only fires when affordable and the yard is clear."
|
|
315
|
+
@tier "domain"
|
|
316
|
+
tankHealth : number = 10
|
|
317
|
+
@label "Tank Health"
|
|
318
|
+
@description "Health/maxHealth of mustered tanks."
|
|
319
|
+
@tier "domain"
|
|
320
|
+
mineX : number = 0
|
|
321
|
+
@label "Mine X"
|
|
322
|
+
@description "Grid column of the gold-mine harvest anchor."
|
|
323
|
+
@tier "domain"
|
|
324
|
+
mineY : number = 0
|
|
325
|
+
@label "Mine Y"
|
|
326
|
+
@description "Grid row of the gold-mine harvest anchor."
|
|
327
|
+
@tier "domain"
|
|
328
|
+
tankSpawnX : number = 1
|
|
329
|
+
@label "Tank Spawn X"
|
|
330
|
+
@description "Grid column of the muster yard where new tanks roll out."
|
|
331
|
+
@tier "domain"
|
|
332
|
+
tankSpawnY : number = 3
|
|
333
|
+
@label "Tank Spawn Y"
|
|
334
|
+
@description "Grid row of the muster yard where new tanks roll out."
|
|
335
|
+
@tier "domain"
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
trait RtsWaves -> @rebindable GameState [lifecycle] {
|
|
340
|
+
initial: active
|
|
341
|
+
|
|
342
|
+
state active {
|
|
343
|
+
INIT -> active
|
|
344
|
+
(set @entity.waveCount 0)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
ticks {
|
|
348
|
+
waveTick every 16000ms when (and @config.running (and (== @entity.result "none") (== (array/len (array/filter @entity.units (fn u (and (> @u.health 0) (and (== (object/get (object/get @u position) x) @config.waveSpawnX) (== (object/get (object/get @u position) y) @config.waveSpawnY)))))) 0)))
|
|
349
|
+
(emit SPAWN { id: (str/concat "rts-w-" @entity.waveCount), x: @config.waveSpawnX, y: @config.waveSpawnY, team: "enemy", health: (+ @config.waveBaseHealth @entity.waveCount), maxHealth: (+ @config.waveBaseHealth @entity.waveCount), unitType: "raider" })
|
|
350
|
+
(set @entity.waveCount (+ @entity.waveCount 1))
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
emits {
|
|
354
|
+
SPAWN -> external {
|
|
355
|
+
id : string!
|
|
356
|
+
x : number!
|
|
357
|
+
y : number!
|
|
358
|
+
team : string!
|
|
359
|
+
health : number!
|
|
360
|
+
maxHealth : number!
|
|
361
|
+
unitType : string!
|
|
362
|
+
}
|
|
363
|
+
@description "Raider-wave intent applied by the grid-tactics authority (the sole units writer)"
|
|
364
|
+
@tier "domain"
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
config {
|
|
368
|
+
running : boolean = true
|
|
369
|
+
@label "Running"
|
|
370
|
+
@description "When true, raider waves spawn every 16s; toggle to freeze the waves."
|
|
371
|
+
@tier "domain"
|
|
372
|
+
waveSpawnX : number = 7
|
|
373
|
+
@label "Wave Spawn X"
|
|
374
|
+
@description "Grid column of the den muster point where raiders stream in."
|
|
375
|
+
@tier "domain"
|
|
376
|
+
waveSpawnY : number = 5
|
|
377
|
+
@label "Wave Spawn Y"
|
|
378
|
+
@description "Grid row of the den muster point where raiders stream in."
|
|
379
|
+
@tier "domain"
|
|
380
|
+
waveBaseHealth : number = 6
|
|
381
|
+
@label "Wave Base Health"
|
|
382
|
+
@description "Health of the first raider wave; each later wave adds +1."
|
|
383
|
+
@tier "domain"
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
118
387
|
trait Player = Frame.traits.PlayerIntent -> GameState {
|
|
119
388
|
config {
|
|
120
389
|
gridWidth: 8
|
|
121
390
|
gridHeight: 6
|
|
122
391
|
movementRange: 2
|
|
392
|
+
attackRange: 2
|
|
123
393
|
projection: "flat"
|
|
124
394
|
showMinimap: false
|
|
125
395
|
scale: 0.45
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
396
|
+
backgroundAsset: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/ui/panel-frame.svg", role: "ui", category: "ui", animations: [], style: "", dimension: "2d", aspect: "1:1", name: "panel-frame", thumbnailUrl: "" }
|
|
397
|
+
hudBackgroundAsset: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/ui/panel-frame.svg", role: "ui", category: "ui", animations: [], style: "", dimension: "2d", aspect: "1:1", name: "panel-frame", thumbnailUrl: "" }
|
|
398
|
+
extraStats: [ { label: "Gold", value: @entity.gold }, { label: "Wave", value: @entity.waveCount } ]
|
|
399
|
+
extraAddonEvent: "BUILD_TANK"
|
|
400
|
+
extraAddons: [ { type: "button", action: BUILD_TANK, label: "Tank (5g)", variant: "secondary" } ]
|
|
401
|
+
features: [ { id: "mine1", x: 0, y: 0, type: "gold_mine", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/gold_mine.svg", role: "decoration", category: "gold_mine", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "gold_mine", thumbnailUrl: "" } }, { id: "wall1", x: 0, y: 1, type: "wall", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } }, { id: "wall2", x: 0, y: 2, type: "wall", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } }, { id: "wall3", x: 0, y: 4, type: "wall", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } }, { id: "wall4", x: 0, y: 5, type: "wall", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } }, { id: "wall5", x: 1, y: 2, type: "wall", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } }, { id: "wall6", x: 1, y: 4, type: "wall", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } }, { id: "wall7", x: 6, y: 1, type: "wall", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } }, { id: "wall8", x: 6, y: 2, type: "wall", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } }, { id: "wall9", x: 6, y: 3, type: "wall", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } }, { id: "wall10", x: 7, y: 1, type: "wall", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } }, { id: "wall11", x: 7, y: 3, type: "wall", assetUrl: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } } ]
|
|
402
|
+
assetManifest: { terrains: { ground: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/terrain/ground.svg", role: "tile", category: "ground", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "ground", thumbnailUrl: "" }, sand: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/terrain/sand.svg", role: "tile", category: "sand", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "sand", thumbnailUrl: "" } }, units: { tank: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/tank.svg", role: "npc", category: "tank", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "tank", thumbnailUrl: "" }, raider: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/raider.svg", role: "npc", category: "raider", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "raider", thumbnailUrl: "" }, worker: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/worker.svg", role: "npc", category: "worker", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "worker", thumbnailUrl: "" }, town_hall: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/town_hall.svg", role: "npc", category: "town_hall", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "town_hall", thumbnailUrl: "" }, raider_den: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/units/raider_den.svg", role: "npc", category: "raider_den", animations: ["idle","walk","attack","hit","death"], style: "", dimension: "2d", aspect: "1:1", name: "raider_den", thumbnailUrl: "" } }, features: { gold_mine: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/gold_mine.svg", role: "decoration", category: "gold_mine", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "gold_mine", thumbnailUrl: "" }, wall: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/features/wall.svg", role: "decoration", category: "wall", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "wall", thumbnailUrl: "" } }, effects: { hit: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/effects/hit.svg", role: "effect", category: "hit", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "hit", thumbnailUrl: "" }, explosion: { url: "https://almadar-kflow-assets.web.app/shared/ui-topdown-rts-board/svg/flat/effects/explosion.svg", role: "effect", category: "explosion", animations: ["static"], style: "", dimension: "2d", aspect: "1:1", name: "explosion", thumbnailUrl: "" } } }
|
|
131
403
|
}
|
|
132
404
|
}
|
|
133
405
|
|
|
134
|
-
page "/topdown-rts-board" as TopdownRtsBoardPage -> Authority, FxDecay, RoundLogic, ScoreKeeper,
|
|
406
|
+
page "/topdown-rts-board" as TopdownRtsBoardPage -> Authority, FxDecay, RoundLogic, ScoreKeeper, Player, AnimClock, RtsClock, RtsEconomy, RtsWaves
|
|
135
407
|
}
|