@netforge/template-tower 0.0.0-stage → 0.2.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.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Display names for the template's content ids. Labels only; the rules are
3
+ * in src/game.ts. A Net Forge game generated on this template ships its own
4
+ * table (the model writes src/names.json; the build turns it into this
5
+ * module), so the client imports only these names from here. Towers are
6
+ * the "weapons" table, creeps the "enemies" table.
7
+ */
8
+ export const TITLE = "Cogspire";
9
+
10
+ export const WEAPON_NAMES: Readonly<Record<string, { name: string; line: string }>> = {
11
+ bolt_post: { name: "Bolt Post", line: "Fast single shots" },
12
+ tar_mortar: { name: "Tar Mortar", line: "Slow, hits a crowd" },
13
+ frost_vane: { name: "Frost Vane", line: "Slows what it hits" },
14
+ arc_coil: { name: "Arc Coil", line: "Jumps between creeps" },
15
+ };
16
+
17
+ export const UPGRADE_NAMES: Readonly<Record<string, { name: string; line: string }>> = {
18
+ overclock: { name: "Overclock", line: "Your towers fire faster" },
19
+ tempered_brass: { name: "Tempered Brass", line: "Your towers hit harder" },
20
+ spyglass: { name: "Spyglass", line: "Your towers reach further" },
21
+ spare_bell: { name: "Spare Bell", line: "+5 bells for the team" },
22
+ };
23
+
24
+ export const ENEMY_NAMES: Readonly<Record<string, string>> = {
25
+ rustling: "Rustling",
26
+ gearbeetle: "Gearbeetle",
27
+ cinder_moth: "Cinder Moth",
28
+ boilerback: "Boilerback",
29
+ };
30
+
31
+ export const RULE_NAMES: Readonly<Record<string, string>> = {
32
+ first_gear: "First Gear",
33
+ long_shift: "Long Shift",
34
+ boiler_breaker: "Boiler Breaker",
35
+ scrap_heap: "Scrap Heap",
36
+ };
37
+
38
+ export const SKU_NAMES: Readonly<Record<string, string>> = {
39
+ overclock: "Overclock",
40
+ tempered_brass: "Tempered Brass",
41
+ spyglass: "Spyglass",
42
+ spare_bell: "Spare Bell",
43
+ skin_verdigris: "Verdigris finish",
44
+ skin_gilt: "Gilt finish",
45
+ brass_gear: "Brass gear",
46
+ };
47
+
48
+ /** Player colours by slot, and a highlight palette (the generated names module carries both). */
49
+ export const KEEPER_COLORS = [0xffb347, 0x5ec8ff, 0x9dff7a, 0xff7ab8, 0xc9a7ff, 0xffe066, 0x7affd4, 0xff9a5c];
50
+ export const AURORA = { body: 0x3cf2c4, glow: 0xa77bff };
package/llms-full.txt ADDED
@@ -0,0 +1,292 @@
1
+ # Net Forge tower defense template (COGSPIRE)
2
+
3
+ > A co-op tower defense for up to 4 players on one shared board. Creeps
4
+ > walk a fixed road in waves toward the spire; players build and upgrade
5
+ > towers beside the road with their own gold. Every kill pays its tower's
6
+ > builder a bounty and the rest of the team a share; the team shares the
7
+ > spire's bells (lives). Clear every wave to win the run.
8
+ > You change the game by editing src/game.ts only. The template already has
9
+ > the board, the road, waves, targeting, splash, slow and chain shots,
10
+ > gold, upgrades, selling, calling a wave early, co-op, netcode, rendering,
11
+ > touch controls, earn and the store. Do not write renderers, netcode or
12
+ > bot AI.
13
+
14
+ ## Files
15
+
16
+ - src/game.ts: THE GAME. Declarations against @netforge/engine:
17
+ game(), towerBoard(), tower(), prefab() (the creeps), waves(), upgrade(),
18
+ earn.rule(), earn.stake(), store.sku().
19
+ - src/names.json: the title and every label players read. Towers go in
20
+ "weapons" ({ name, line }), creeps in "enemies", plus upgrades, earn
21
+ rules and store items.
22
+ - Everything else is template machinery.
23
+
24
+ ## Rules the build enforces
25
+
26
+ - game({ template: "tower-defense", style, places, players: { prefab, max } }).
27
+ players.max is at most 4 (one board, four builders).
28
+ - Exactly one towerBoard(...) and one waves(...); 1 to 8 tower(...)s.
29
+ - The road: towerBoard path is tile waypoints; each step changes x or y,
30
+ never both; it starts and ends on the board's edge. blocked tiles are
31
+ rocks nobody builds on.
32
+ - Every waves group names a declared prefab (a creep, not a pickup).
33
+ - Tower kinds are "bolt", "splash", "frost" or "chain".
34
+ - Upgrades read four stats only: might (tower damage), haste (fire rate),
35
+ area (tower range) and maxHealth (bells for the team).
36
+ - No weapon(...): a tower defense game declares tower(...).
37
+ - Asset ids look like "style.lowpoly.turret", never URLs or paths, and must
38
+ be in the asset library. game({ style }) is the art style the creator
39
+ picked (lowpoly, toon, ps1, pixel, voxel or handdrawn); keep it.
40
+ - 4 playtest players must kill creeps, clear wave 3 and fire an earn rule
41
+ in 4 minutes, and must not lose the spire before wave 4.
42
+
43
+ ## Units
44
+
45
+ A tile is 2 m. Board size, road, range and radius are in tiles; creep
46
+ speed is metres a second; rate is shots a second; gold is the run's own
47
+ money (never coins). Tick rate 30 Hz.
48
+
49
+ ## The loop
50
+
51
+ Before wave 1 everyone gets towerBoard.prep seconds to build. A wave
52
+ spawns its groups along the road; it is cleared when the last creep dies
53
+ or leaks. Then there is a breather of towerBoard.breather seconds, which
54
+ everyone pressing Ready (Space) skips for a little gold. A leak costs the
55
+ creep's damage in bells; at zero bells the run is lost. Clearing the last
56
+ wave wins. Towers aim at the creep furthest along the road in range.
57
+ More builders make tougher creeps (+30% health a player past the first).
58
+
59
+ ## Common changes
60
+
61
+ ### A different road
62
+ towerBoard({ size: [20, 12], path: [[0, 5], [6, 5], [6, 1], [13, 1], [13, 10], [19, 10]], lives: 20, gold: 100 });
63
+ Longer roads with more turns give towers more time; a straight road is hard.
64
+
65
+ ### Tune a tower
66
+ tower("bolt_post", { model: "style.lowpoly.turret", kind: "bolt", cost: 40, damage: 8, rate: 1.5, range: 3,
67
+ levels: [{ cost: 45, damage: 14 }, { cost: 80, damage: 24, range: 3.5 }] });
68
+ Each level is one upgrade: its gold cost and the stats it changes.
69
+
70
+ ### Add a tower
71
+ tower("sky_harpoon", { model: "style.lowpoly.rocket", kind: "bolt", cost: 120, damage: 60, rate: 0.4, range: 6 });
72
+ splash: radius (tiles) around the target; frost: slow (0..0.9) for
73
+ slowSeconds; chain: chains (jumps), 30% weaker each jump.
74
+ Add its label to names.json weapons. The bar shows towers in order (keys 1-8).
75
+
76
+ ### Add a creep
77
+ prefab("ironclad", { model: "style.lowpoly.robot_walker", health: 160, speed: 0.9, value: 14, damage: 3 });
78
+ value is the gold bounty, damage the bells a leak costs. Then bring it in:
79
+ waves({ ..., groups: [..., { prefab: "ironclad", from: 8, count: 2, grow: 0.5, gap: 2 }] });
80
+
81
+ ### A boss
82
+ prefab("boilerback", { model: "style.lowpoly.robot_large", health: 1800, speed: 0.7, value: 80, damage: 5, boss: true });
83
+ { prefab: "boilerback", from: 10, every: 10, count: 1, delay: 8 } (a boss every tenth wave).
84
+ Bosses get a health bar at the top of the screen.
85
+
86
+ ### Harder or easier
87
+ Raise waves health (growth a wave), counts and grow, or lower gold and
88
+ lives. "More waves": waves total. "Faster": shorter prep and breather.
89
+
90
+ ### Add an earn rule
91
+ earn.rule("clean_sweep", { when: "wave.cleared", n: 15, pay: 30, oncePer: "run" });
92
+ Events: enemy.killed (credits the tower's builder), boss.killed and
93
+ wave.cleared and run.won (everyone on the board), tower.built and
94
+ tower.upgraded (credits the builder). Each rule pays once per run per
95
+ player, in coins; the room server is the referee. A player who joins a run
96
+ in progress is never paid for waves cleared before they joined.
97
+
98
+ ### The buy-in run (required)
99
+ earn.stake({ min: 20, max: 200, multiple: 2 });
100
+ One top-level line; a game without it does not build. A staked run pays
101
+ its normal earn plus a share of stake x multiple, by how much of the run's
102
+ most earn the player made. "stake" is reserved.
103
+
104
+ ### Coins: earn and the store
105
+ Every game has coins: 1 coin = 1 of the game's own token, shown under the
106
+ token's own name, symbol and logo.
107
+ store.sku("overclock", { coins: 40 }); // one level of the "overclock" upgrade for the run
108
+ store.sku("skin_gilt", { coins: 500, grants: { cosmetic: 2 } }); // a finish for your tinker, owned for good
109
+ store.sku("brass_gear", { coins: 150, grants: { gear: "tempered_brass", loseBelow: 0.25 } }); // losable gear
110
+ A store item named after a declared upgrade is a run upgrade on the
111
+ buyer's own towers. Keep the SKUs the client is wired to (overclock,
112
+ tempered_brass, spyglass, spare_bell, skin_verdigris, skin_gilt,
113
+ brass_gear) at their prices. Gear brings one level of its upgrade into
114
+ every staked run and is lost when that run makes less than loseBelow of
115
+ its most earn. Price against what a run pays (the four rules here pay up
116
+ to 80).
117
+
118
+ ### Change the look
119
+ players.prefab is your tinker (a real CC0 character). Tower and creep
120
+ models are library ids: turret, turret_double, satellite_dish, generator,
121
+ rocket, crystal_rock for towers; slime_*, crab, bee, frog, bat,
122
+ robot_walker, robot_large, skeleton_grunt for creeps.
123
+
124
+ ## Test
125
+
126
+ engine.test.run({ ticks: 30 * 240, bots: 4 }) plays the board with 4
127
+ builders and reports kills, wave reached, runs won and lost, leaks (as
128
+ deaths), earned by rule, softlock, ms per tick, and the genre's own
129
+ checks under rules.checks.
130
+
131
+ ## Worked example: src/game.ts
132
+
133
+ The whole game as the template ships it. Every change starts from this file.
134
+
135
+ ```ts
136
+ /**
137
+ * COGSPIRE: the tower defense template (play style: tower defense).
138
+ *
139
+ * A brass clockwork spire stands at the end of an old rail road across the
140
+ * moor. Rust swarms crawl the road toward it in waves. Up to four tinkers
141
+ * share the board: each builds and upgrades brass towers with their own
142
+ * gold, every kill pays its builder and a share to the rest, and the team
143
+ * shares the spire's bells (lives). Hold twenty waves to keep the spire.
144
+ *
145
+ * This file is the whole game as the model writes it: declarations only,
146
+ * against the typed API in @netforge/engine. The board, the waves,
147
+ * targeting, co-op, netcode, renderer and referee are the template's
148
+ * machinery.
149
+ */
150
+ import { earn, game, prefab, store, tower, towerBoard, upgrade, waves } from "@netforge/engine";
151
+
152
+ export default game({
153
+ template: "tower-defense",
154
+ style: "lowpoly",
155
+ places: { moor: { chunks: "places/moor", spawn: [0, 0, 0] } },
156
+ players: { prefab: "style.lowpoly.worker", max: 4 },
157
+ });
158
+
159
+ // The board: 18 x 12 tiles of 2 m. The road enters at the west edge and winds to the spire on the east edge.
160
+ towerBoard({
161
+ size: [18, 12],
162
+ path: [
163
+ [0, 2],
164
+ [5, 2],
165
+ [5, 8],
166
+ [10, 8],
167
+ [10, 3],
168
+ [15, 3],
169
+ [15, 9],
170
+ [17, 9],
171
+ ],
172
+ blocked: [
173
+ [2, 6],
174
+ [3, 9],
175
+ [8, 1],
176
+ [12, 10],
177
+ [13, 6],
178
+ ],
179
+ lives: 20,
180
+ gold: 100,
181
+ prep: 15,
182
+ breather: 10,
183
+ share: 0.25,
184
+ });
185
+
186
+ // Creeps. Health in hit points, speed in metres a second (a tile is 2 m), value is the gold bounty, damage the bells a leak costs.
187
+ prefab("rustling", { model: "style.lowpoly.slime_green", health: 26, speed: 1.6, value: 4 });
188
+ prefab("gearbeetle", { model: "style.lowpoly.crab", health: 90, speed: 1, value: 9, damage: 2 });
189
+ prefab("cinder_moth", { model: "style.lowpoly.bee", health: 20, speed: 3, value: 4 });
190
+ prefab("boilerback", { model: "style.lowpoly.robot_large", health: 1800, speed: 0.7, value: 80, damage: 5, boss: true });
191
+
192
+ // Towers. Damage a hit, rate in shots a second, range and radius in tiles. Each level is an upgrade: its cost and what it changes.
193
+ tower("bolt_post", {
194
+ model: "style.lowpoly.turret",
195
+ kind: "bolt",
196
+ cost: 40,
197
+ damage: 8,
198
+ rate: 1.5,
199
+ range: 3,
200
+ levels: [
201
+ { cost: 45, damage: 14 },
202
+ { cost: 80, damage: 24, range: 3.5 },
203
+ { cost: 140, damage: 40, rate: 2 },
204
+ ],
205
+ });
206
+ tower("tar_mortar", {
207
+ model: "style.lowpoly.turret_double",
208
+ kind: "splash",
209
+ cost: 70,
210
+ damage: 12,
211
+ rate: 0.6,
212
+ range: 3.5,
213
+ radius: 1.1,
214
+ levels: [
215
+ { cost: 70, damage: 20 },
216
+ { cost: 120, damage: 34, radius: 1.4 },
217
+ ],
218
+ });
219
+ tower("frost_vane", {
220
+ model: "style.lowpoly.satellite_dish",
221
+ kind: "frost",
222
+ cost: 55,
223
+ damage: 3,
224
+ rate: 1,
225
+ range: 2.6,
226
+ slow: 0.45,
227
+ slowSeconds: 1.6,
228
+ levels: [
229
+ { cost: 60, slow: 0.55, range: 3 },
230
+ { cost: 100, damage: 8, slow: 0.65 },
231
+ ],
232
+ });
233
+ tower("arc_coil", {
234
+ model: "style.lowpoly.generator",
235
+ kind: "chain",
236
+ cost: 90,
237
+ damage: 14,
238
+ rate: 0.8,
239
+ range: 3,
240
+ chains: 3,
241
+ levels: [
242
+ { cost: 90, damage: 22, chains: 4 },
243
+ { cost: 150, damage: 36, chains: 5 },
244
+ ],
245
+ });
246
+
247
+ // Twenty waves. Each group joins at wave `from`, starts with `count` and adds `grow` a wave; a boss every tenth wave.
248
+ waves({
249
+ total: 20,
250
+ health: 0.3,
251
+ groups: [
252
+ { prefab: "rustling", count: 6, grow: 1.6, gap: 0.8 },
253
+ { prefab: "gearbeetle", from: 3, count: 2, grow: 0.8, gap: 1.4, delay: 3 },
254
+ { prefab: "cinder_moth", from: 5, count: 4, grow: 1.2, gap: 0.45, delay: 6 },
255
+ { prefab: "boilerback", from: 10, every: 10, count: 1, delay: 8 },
256
+ ],
257
+ });
258
+
259
+ // Store upgrades: one level each, for the rest of the run, on the buyer's own towers (spare_bell adds bells for the team).
260
+ upgrade("overclock", { stat: "haste", step: 0.15, maxLevel: 1 });
261
+ upgrade("tempered_brass", { stat: "might", step: 0.15, maxLevel: 1 });
262
+ upgrade("spyglass", { stat: "area", step: 0.12, maxLevel: 1 });
263
+ upgrade("spare_bell", { stat: "maxHealth", step: 5, maxLevel: 1 });
264
+
265
+ // Earn (mandatory). Pays the game's coins (1 coin = 1 token), once per run each. Values are PLACEHOLDER.
266
+ earn.rule("first_gear", { when: "wave.cleared", n: 3, pay: 5, oncePer: "run" });
267
+ earn.rule("long_shift", { when: "wave.cleared", n: 10, pay: 20, oncePer: "run" });
268
+ earn.rule("boiler_breaker", { when: "boss.killed", n: 1, pay: 50, oncePer: "run" });
269
+ earn.rule("scrap_heap", { when: "enemy.killed", n: 60, pay: 5, oncePer: "run" });
270
+ // Buy-in run (the mandatory sink loop): stake coins at run start to earn above free play.
271
+ // A staked run also pays up to stake x multiple, by the share of this run's earn the player makes. Values are PLACEHOLDER.
272
+ earn.stake({ min: 20, max: 200, multiple: 2 });
273
+
274
+ // Store (the Kit), priced in coins: four run upgrades and two cosmetic finishes owned for good (kept in the profile).
275
+ store.sku("overclock", { coins: 40 });
276
+ store.sku("tempered_brass", { coins: 40 });
277
+ store.sku("spyglass", { coins: 60 });
278
+ store.sku("spare_bell", { coins: 50 });
279
+ store.sku("skin_verdigris", { coins: 300, grants: { cosmetic: 1 } });
280
+ store.sku("skin_gilt", { coins: 500, grants: { cosmetic: 2 } });
281
+ // Losable gear: owned for good until lost. It brings one level of its upgrade into every staked run,
282
+ // and is lost when that run makes less than loseBelow of its most earn. Values are PLACEHOLDER.
283
+ store.sku("brass_gear", { coins: 150, grants: { gear: "tempered_brass", loseBelow: 0.25 } });
284
+ ```
285
+
286
+ ## Validate and playtest
287
+
288
+ - Validate: the MCP tool `netforge_validate_game` builds `src/game.ts` and returns one actionable line per problem (an unknown asset id, a missing earn.stake, a bad id). The same checks throw `GameDefinitionError` when the module is imported.
289
+ - Playtest: the MCP tool `netforge_run_playtest`, or in code `engine.test.run({ ticks: 30 * 240, bots: 4 })`. It reports kills, wave reached, runs won and lost, leaks (as deaths), earned by rule, softlock, ms per tick, and the genre's own checks under rules.checks.
290
+ - Template tests: test/harness.test.ts, test/store.test.ts, run with `npx vitest run` in this package.
291
+ - Play locally: put `src/game.ts` and a `netnet.json` of `{"template":"tower-defense"}` in a folder, install `@netforge/kit-play` and `@netforge/engine`, and run `npx kit-play` (or the MCP tool `netforge_play_locally`). It opens http://127.0.0.1:4663 with play money.
292
+ - Docs: https://forge.netnet.capital/docs/templates/tower
package/llms.txt ADDED
@@ -0,0 +1,129 @@
1
+ # Net Forge tower defense template (COGSPIRE)
2
+
3
+ > A co-op tower defense for up to 4 players on one shared board. Creeps
4
+ > walk a fixed road in waves toward the spire; players build and upgrade
5
+ > towers beside the road with their own gold. Every kill pays its tower's
6
+ > builder a bounty and the rest of the team a share; the team shares the
7
+ > spire's bells (lives). Clear every wave to win the run.
8
+ > You change the game by editing src/game.ts only. The template already has
9
+ > the board, the road, waves, targeting, splash, slow and chain shots,
10
+ > gold, upgrades, selling, calling a wave early, co-op, netcode, rendering,
11
+ > touch controls, earn and the store. Do not write renderers, netcode or
12
+ > bot AI.
13
+
14
+ ## Files
15
+
16
+ - src/game.ts: THE GAME. Declarations against @netforge/engine:
17
+ game(), towerBoard(), tower(), prefab() (the creeps), waves(), upgrade(),
18
+ earn.rule(), earn.stake(), store.sku().
19
+ - src/names.json: the title and every label players read. Towers go in
20
+ "weapons" ({ name, line }), creeps in "enemies", plus upgrades, earn
21
+ rules and store items.
22
+ - Everything else is template machinery.
23
+
24
+ ## Rules the build enforces
25
+
26
+ - game({ template: "tower-defense", style, places, players: { prefab, max } }).
27
+ players.max is at most 4 (one board, four builders).
28
+ - Exactly one towerBoard(...) and one waves(...); 1 to 8 tower(...)s.
29
+ - The road: towerBoard path is tile waypoints; each step changes x or y,
30
+ never both; it starts and ends on the board's edge. blocked tiles are
31
+ rocks nobody builds on.
32
+ - Every waves group names a declared prefab (a creep, not a pickup).
33
+ - Tower kinds are "bolt", "splash", "frost" or "chain".
34
+ - Upgrades read four stats only: might (tower damage), haste (fire rate),
35
+ area (tower range) and maxHealth (bells for the team).
36
+ - No weapon(...): a tower defense game declares tower(...).
37
+ - Asset ids look like "style.lowpoly.turret", never URLs or paths, and must
38
+ be in the asset library. game({ style }) is the art style the creator
39
+ picked (lowpoly, toon, ps1, pixel, voxel or handdrawn); keep it.
40
+ - 4 playtest players must kill creeps, clear wave 3 and fire an earn rule
41
+ in 4 minutes, and must not lose the spire before wave 4.
42
+
43
+ ## Units
44
+
45
+ A tile is 2 m. Board size, road, range and radius are in tiles; creep
46
+ speed is metres a second; rate is shots a second; gold is the run's own
47
+ money (never coins). Tick rate 30 Hz.
48
+
49
+ ## The loop
50
+
51
+ Before wave 1 everyone gets towerBoard.prep seconds to build. A wave
52
+ spawns its groups along the road; it is cleared when the last creep dies
53
+ or leaks. Then there is a breather of towerBoard.breather seconds, which
54
+ everyone pressing Ready (Space) skips for a little gold. A leak costs the
55
+ creep's damage in bells; at zero bells the run is lost. Clearing the last
56
+ wave wins. Towers aim at the creep furthest along the road in range.
57
+ More builders make tougher creeps (+30% health a player past the first).
58
+
59
+ ## Common changes
60
+
61
+ ### A different road
62
+ towerBoard({ size: [20, 12], path: [[0, 5], [6, 5], [6, 1], [13, 1], [13, 10], [19, 10]], lives: 20, gold: 100 });
63
+ Longer roads with more turns give towers more time; a straight road is hard.
64
+
65
+ ### Tune a tower
66
+ tower("bolt_post", { model: "style.lowpoly.turret", kind: "bolt", cost: 40, damage: 8, rate: 1.5, range: 3,
67
+ levels: [{ cost: 45, damage: 14 }, { cost: 80, damage: 24, range: 3.5 }] });
68
+ Each level is one upgrade: its gold cost and the stats it changes.
69
+
70
+ ### Add a tower
71
+ tower("sky_harpoon", { model: "style.lowpoly.rocket", kind: "bolt", cost: 120, damage: 60, rate: 0.4, range: 6 });
72
+ splash: radius (tiles) around the target; frost: slow (0..0.9) for
73
+ slowSeconds; chain: chains (jumps), 30% weaker each jump.
74
+ Add its label to names.json weapons. The bar shows towers in order (keys 1-8).
75
+
76
+ ### Add a creep
77
+ prefab("ironclad", { model: "style.lowpoly.robot_walker", health: 160, speed: 0.9, value: 14, damage: 3 });
78
+ value is the gold bounty, damage the bells a leak costs. Then bring it in:
79
+ waves({ ..., groups: [..., { prefab: "ironclad", from: 8, count: 2, grow: 0.5, gap: 2 }] });
80
+
81
+ ### A boss
82
+ prefab("boilerback", { model: "style.lowpoly.robot_large", health: 1800, speed: 0.7, value: 80, damage: 5, boss: true });
83
+ { prefab: "boilerback", from: 10, every: 10, count: 1, delay: 8 } (a boss every tenth wave).
84
+ Bosses get a health bar at the top of the screen.
85
+
86
+ ### Harder or easier
87
+ Raise waves health (growth a wave), counts and grow, or lower gold and
88
+ lives. "More waves": waves total. "Faster": shorter prep and breather.
89
+
90
+ ### Add an earn rule
91
+ earn.rule("clean_sweep", { when: "wave.cleared", n: 15, pay: 30, oncePer: "run" });
92
+ Events: enemy.killed (credits the tower's builder), boss.killed and
93
+ wave.cleared and run.won (everyone on the board), tower.built and
94
+ tower.upgraded (credits the builder). Each rule pays once per run per
95
+ player, in coins; the room server is the referee. A player who joins a run
96
+ in progress is never paid for waves cleared before they joined.
97
+
98
+ ### The buy-in run (required)
99
+ earn.stake({ min: 20, max: 200, multiple: 2 });
100
+ One top-level line; a game without it does not build. A staked run pays
101
+ its normal earn plus a share of stake x multiple, by how much of the run's
102
+ most earn the player made. "stake" is reserved.
103
+
104
+ ### Coins: earn and the store
105
+ Every game has coins: 1 coin = 1 of the game's own token, shown under the
106
+ token's own name, symbol and logo.
107
+ store.sku("overclock", { coins: 40 }); // one level of the "overclock" upgrade for the run
108
+ store.sku("skin_gilt", { coins: 500, grants: { cosmetic: 2 } }); // a finish for your tinker, owned for good
109
+ store.sku("brass_gear", { coins: 150, grants: { gear: "tempered_brass", loseBelow: 0.25 } }); // losable gear
110
+ A store item named after a declared upgrade is a run upgrade on the
111
+ buyer's own towers. Keep the SKUs the client is wired to (overclock,
112
+ tempered_brass, spyglass, spare_bell, skin_verdigris, skin_gilt,
113
+ brass_gear) at their prices. Gear brings one level of its upgrade into
114
+ every staked run and is lost when that run makes less than loseBelow of
115
+ its most earn. Price against what a run pays (the four rules here pay up
116
+ to 80).
117
+
118
+ ### Change the look
119
+ players.prefab is your tinker (a real CC0 character). Tower and creep
120
+ models are library ids: turret, turret_double, satellite_dish, generator,
121
+ rocket, crystal_rock for towers; slime_*, crab, bee, frog, bat,
122
+ robot_walker, robot_large, skeleton_grunt for creeps.
123
+
124
+ ## Test
125
+
126
+ engine.test.run({ ticks: 30 * 240, bots: 4 }) plays the board with 4
127
+ builders and reports kills, wave reached, runs won and lost, leaks (as
128
+ deaths), earned by rule, softlock, ms per tick, and the genre's own
129
+ checks under rules.checks.
package/package.json CHANGED
@@ -1,6 +1,82 @@
1
1
  {
2
2
  "name": "@netforge/template-tower",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Co-op tower defense game template for Net Forge: up to 4 players, waves, building and upgrades, shared lives (COGSPIRE).",
5
+ "keywords": [
6
+ "game-template",
7
+ "multiplayer",
8
+ "browser-game",
9
+ "html5-game",
10
+ "threejs",
11
+ "game-engine",
12
+ "typescript",
13
+ "gamedev",
14
+ "ai-agents",
15
+ "mcp",
16
+ "net-forge",
17
+ "tower-defense"
18
+ ],
19
+ "license": "MIT",
20
+ "type": "module",
21
+ "exports": {
22
+ ".": "./src/manifest.ts",
23
+ "./store": "./src/store.ts",
24
+ "./package.json": "./package.json",
25
+ "./llms.txt": "./llms.txt",
26
+ "./llms-full.txt": "./llms-full.txt"
27
+ },
28
+ "files": [
29
+ "src",
30
+ "client",
31
+ "!client/vite.config.ts",
32
+ "llms.txt",
33
+ "README.md",
34
+ "NOTICE",
35
+ "LICENSE",
36
+ "llms-full.txt",
37
+ "AGENTS.md",
38
+ "CLAUDE.md"
39
+ ],
40
+ "sideEffects": [
41
+ "./src/game.ts",
42
+ "./src/manifest.ts",
43
+ "./client/**"
44
+ ],
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "homepage": "https://forge.netnet.capital/docs/templates/tower",
49
+ "bugs": {
50
+ "url": "https://github.com/netnetdev/net-forge/issues"
51
+ },
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/netnetdev/net-forge.git",
55
+ "directory": "packages/template-tower"
56
+ },
57
+ "engines": {
58
+ "node": ">=20"
59
+ },
60
+ "dependencies": {
61
+ "three": "0.186.1",
62
+ "@netforge/asset-library": "0.2.0",
63
+ "@netforge/engine": "0.2.0",
64
+ "@netforge/engine-sim": "0.2.0",
65
+ "@netforge/tabletop": "0.2.0"
66
+ },
67
+ "devDependencies": {
68
+ "@types/node": "22.20.1",
69
+ "@types/three": "0.186.0",
70
+ "tsx": "4.23.0",
71
+ "typescript": "5.8.3",
72
+ "vite": "6.4.3",
73
+ "vitest": "3.2.7"
74
+ },
75
+ "scripts": {
76
+ "build": "node ../engine/scripts/fresh-dist.mjs && vite build --config client/vite.config.ts",
77
+ "typecheck": "tsc -p tsconfig.json",
78
+ "test": "vitest run",
79
+ "dev": "vite --config client/vite.config.ts",
80
+ "preview": "vite preview --config client/vite.config.ts"
81
+ }
6
82
  }