@netforge/template-tower 0.1.0 → 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.
- package/AGENTS.md +66 -0
- package/CLAUDE.md +11 -0
- package/README.md +68 -41
- package/llms-full.txt +292 -0
- package/llms.txt +1 -1
- package/package.json +37 -12
package/AGENTS.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Extending COGSPIRE
|
|
2
|
+
|
|
3
|
+
COGSPIRE is the tower defense template of the Net Forge game engine. This file tells a coding agent how to change the game safely. `llms.txt` in this package has the full recipe book for this genre, and `llms-full.txt` adds the whole `src/game.ts` as a worked example.
|
|
4
|
+
|
|
5
|
+
## The one file to edit
|
|
6
|
+
|
|
7
|
+
Change the game by editing `src/game.ts` only. It holds declarations, not engine code. src/names.json holds the title and every label players read. Towers go in "weapons" ({ name, line }), creeps in "enemies", plus upgrades, earn rules and store items. Everything else (the client, the manifest, the store hooks, the simulation, the netcode) is template machinery.
|
|
8
|
+
|
|
9
|
+
## Import only @netforge/engine
|
|
10
|
+
|
|
11
|
+
`src/game.ts` imports from `@netforge/engine` and nothing else. The template's game imports:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { earn, game, prefab, store, tower, towerBoard, upgrade, waves } from "@netforge/engine";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Also available for this genre: `economy`. `earn` has `earn.rule(...)` and `earn.stake(...)`; `store` has `store.sku(...)`. `system(id, ctx => ...)` gets the systems API described in `llms.txt`. The renderer, input, netcode and physics live elsewhere and are not reachable from game code.
|
|
18
|
+
|
|
19
|
+
## Rules the build enforces
|
|
20
|
+
|
|
21
|
+
- Ids are lower_snake_case.
|
|
22
|
+
- Every game declares at least one `earn.rule(...)` (mandatory earn) and one `earn.stake(...)` (the buy-in run), on one top-level line. A game without either does not build.
|
|
23
|
+
- Asset ids are library ids such as `style.lowpoly.turret`, never URLs or file paths, and must exist in the asset library.
|
|
24
|
+
- `game({ template: "tower-defense", style, places, players: { prefab, max } })` with `players.max` at most 4.
|
|
25
|
+
- Exactly one `towerBoard(...)` and one `waves(...)`; 1 to 8 `tower(...)`s.
|
|
26
|
+
- The road is tile waypoints in `towerBoard({ path })`: each step changes x or y, never both, and it starts and ends on the board's edge. `blocked` tiles are rocks nobody builds on.
|
|
27
|
+
- Every waves group names a declared creep prefab, not a pickup.
|
|
28
|
+
- Tower kinds are "bolt", "splash", "frost" or "chain". Upgrades read four stats only: might, haste, area and maxHealth.
|
|
29
|
+
- No `weapon(...)`: a tower defense game declares `tower(...)`.
|
|
30
|
+
- `game({ style })` is the art style the creator picked (lowpoly, toon, ps1, pixel, voxel or handdrawn). Keep it.
|
|
31
|
+
- Keep the store SKUs the client is wired to, at their prices: overclock, tempered_brass, spyglass, spare_bell, skin_verdigris, skin_gilt and the brass_gear gear. "stake" is reserved and is never a SKU id.
|
|
32
|
+
|
|
33
|
+
## Validate
|
|
34
|
+
|
|
35
|
+
- With the MCP server (`npx -y @netforge/mcp`, bin `netforge-mcp`): call `netforge_validate_game` on the game. It returns one line per problem.
|
|
36
|
+
- In this package: `npx vitest run` runs `test/harness.test.ts`, `test/store.test.ts`. A bad declaration throws `GameDefinitionError` with a line you can act on.
|
|
37
|
+
|
|
38
|
+
## Playtest
|
|
39
|
+
|
|
40
|
+
- With the MCP server: call `netforge_run_playtest`.
|
|
41
|
+
- In code: `import { engine } from "@netforge/engine"` and run `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.
|
|
42
|
+
|
|
43
|
+
## Play locally
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
mkdir -p my-game/src && cd my-game
|
|
47
|
+
npm init -y && npm install @netforge/kit-play @netforge/engine @netforge/template-tower
|
|
48
|
+
cp node_modules/@netforge/template-tower/src/game.ts src/
|
|
49
|
+
echo '{"template":"tower-defense"}' > netnet.json
|
|
50
|
+
npx kit-play # opens http://127.0.0.1:4663
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The MCP tool `netforge_play_locally` does the same. Coins are play money; nothing pays out and no chain or wallet is involved. Edit `src/game.ts` and run it again to try a change.
|
|
54
|
+
|
|
55
|
+
## What not to do
|
|
56
|
+
|
|
57
|
+
- Do not write renderers, netcode, physics or bot AI. The template already has them.
|
|
58
|
+
- Do not use asset URLs or file paths. Use library ids.
|
|
59
|
+
- Do not import anything other than `@netforge/engine` in `src/game.ts`.
|
|
60
|
+
- Do not edit `client/`, `src/manifest.ts` or `src/store.ts`.
|
|
61
|
+
- Do not rename or reprice the store SKUs the client is wired to.
|
|
62
|
+
- Do not write targeting or pathing code; towers and creeps are declared and the rules module runs them.
|
|
63
|
+
|
|
64
|
+
## Docs
|
|
65
|
+
|
|
66
|
+
https://forge.netnet.capital/docs/templates/tower
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Read AGENTS.md before changing this game.
|
|
4
|
+
|
|
5
|
+
The five hard rules:
|
|
6
|
+
|
|
7
|
+
1. Edit only `src/game.ts`.
|
|
8
|
+
2. Import only `@netforge/engine`.
|
|
9
|
+
3. Ids are lower_snake_case.
|
|
10
|
+
4. Declare at least one `earn.rule(...)` and exactly one `earn.stake(...)`.
|
|
11
|
+
5. No renderers, netcode, physics or asset URLs.
|
package/README.md
CHANGED
|
@@ -1,54 +1,81 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @netforge/template-tower
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
moor. Rust swarms crawl the road toward it in waves. Up to four tinkers
|
|
5
|
-
share the board: each builds and upgrades brass towers with their own
|
|
6
|
-
gold, every kill pays its builder and a share to the rest, and the team
|
|
7
|
-
shares the spire's bells. Hold twenty waves to keep the spire.
|
|
3
|
+
A co-op tower defense game template for Net Forge: up to 4 players on one shared board (COGSPIRE).
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
Rust swarms crawl an old rail road toward a brass clockwork spire in waves. Each tinker builds and upgrades towers with their own gold, every kill pays its builder a bounty and a share to the rest, and the team shares the spire's bells. Hold twenty waves to keep the spire.
|
|
6
|
+
|
|
7
|
+
It is a board genre: a deterministic rules module runs on the room host at 30 Hz behind the same adapter seam as the action templates. Your tinker (the cursor) is predicted; builds, upgrades, sells and the ready vote are actions the room validates (gold, tile, ownership). The client kit is `@netforge/tabletop`.
|
|
8
|
+
|
|
9
|
+
Towers aim at the creep furthest along the road in range. Splash hits a radius, frost slows, chain jumps. A leak costs the creep's damage in bells, and zero bells loses the run.
|
|
10
|
+
|
|
11
|
+
Earn: three waves, ten waves, the first boss and sixty kills. A player who joins mid-run is not paid for waves cleared before their run started.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install @netforge/template-tower
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
You normally do not import this package yourself. `kit play` runs it on your own computer, and the Net Forge MCP server and studio copy its `src/game.ts` as the starting point for a new game.
|
|
20
|
+
|
|
21
|
+
## Play it locally
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
mkdir -p my-game/src && cd my-game
|
|
25
|
+
npm init -y && npm install @netforge/kit-play @netforge/engine @netforge/template-tower
|
|
26
|
+
cp node_modules/@netforge/template-tower/src/game.ts src/
|
|
27
|
+
echo '{"template":"tower-defense"}' > netnet.json
|
|
28
|
+
npx kit-play # opens http://127.0.0.1:4663
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Coins are play money and nothing pays out. Real-token earning, stakes and claims run only on the hosted Net Forge server.
|
|
32
|
+
|
|
33
|
+
## Change the game
|
|
34
|
+
|
|
35
|
+
The bolt post is the cheap first tower. Make it cheaper and give it more reach:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
tower("bolt_post", {
|
|
39
|
+
model: "style.lowpoly.turret",
|
|
40
|
+
kind: "bolt",
|
|
41
|
+
cost: 35,
|
|
42
|
+
damage: 8,
|
|
43
|
+
rate: 1.5,
|
|
44
|
+
range: 3.5,
|
|
45
|
+
// levels stay as they are
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Save `src/game.ts` and run `npx kit-play` again.
|
|
50
|
+
|
|
51
|
+
## Files
|
|
13
52
|
|
|
14
53
|
| Path | What |
|
|
15
54
|
|---|---|
|
|
16
|
-
| `src/game.ts` | The game,
|
|
17
|
-
| `src/manifest.ts` | Loads
|
|
18
|
-
| `src/store.ts` |
|
|
19
|
-
| `client/` | The browser client: the board, towers, creeps, tracers, the build bar and tower panel. |
|
|
55
|
+
| `src/game.ts` | The game, declarations only: the board and road, towers, creeps, waves, upgrades, earn, store. |
|
|
56
|
+
| `src/manifest.ts` | Loads the game; `towerAdapter()` and `towerManifest()` for the room host and the client. |
|
|
57
|
+
| `src/store.ts` | The coin prices the client is wired to. |
|
|
58
|
+
| `client/` | The browser client: the board, towers, creeps, tracers, the build bar and the tower panel. |
|
|
20
59
|
| `test/` | Playtest bots through `engine.test.run`, and the store. |
|
|
21
|
-
| `llms.txt` | How to change the game
|
|
60
|
+
| `llms.txt` | How to change the game, for agents. |
|
|
22
61
|
|
|
23
|
-
##
|
|
62
|
+
## Controls
|
|
24
63
|
|
|
25
|
-
|
|
26
|
-
pointing never waits for the server; builds, upgrades, sells and the
|
|
27
|
-
ready vote are actions the room validates (gold, tile, ownership).
|
|
28
|
-
- Towers aim at the creep furthest along the road in range. Splash hits a
|
|
29
|
-
radius, frost slows, chain jumps. Kills pay the builder a bounty and the
|
|
30
|
-
rest of the team a share; a cleared wave pays everyone.
|
|
31
|
-
- Shared bells: a leak costs the creep's damage; zero bells loses the run.
|
|
32
|
-
The end screen shows what the run paid, then a fresh run starts.
|
|
33
|
-
- Earn (the room is the referee): three waves, ten waves, the first boss,
|
|
34
|
-
sixty kills. A player who joins mid-run is not paid for waves cleared
|
|
35
|
-
before their run started.
|
|
64
|
+
Click a tile to build the tower picked in the bar (keys 1 to 4), click a tower to upgrade or sell it, Space calls the next wave. Touch: tap a tile to aim, tap again to build. The backquote key shows stats.
|
|
36
65
|
|
|
37
|
-
##
|
|
66
|
+
## For AI agents
|
|
38
67
|
|
|
39
|
-
|
|
40
|
-
`kit play` (`@netforge/kit-play`). From the repository root:
|
|
68
|
+
Read `AGENTS.md` and `llms.txt` in this package before changing the game. `llms-full.txt` adds the whole `src/game.ts` as a worked example.
|
|
41
69
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
70
|
+
Agents can also drive this template through the Net Forge MCP server: run `npx -y @netforge/mcp` (package `@netforge/mcp`, bin `netforge-mcp`). Its tools are `netforge_list_templates`, `netforge_create_game`, `netforge_explain_api`, `netforge_search_docs`, `netforge_validate_game`, `netforge_run_playtest` and `netforge_play_locally`.
|
|
71
|
+
|
|
72
|
+
To set up a coding agent in a game project, run `npx netforge setup agent`. It writes AGENTS.md, CLAUDE.md, Cursor rules and `.mcp.json` into the project. In Claude Code you can also add the server directly with `claude mcp add netforge -- npx -y @netforge/mcp`.
|
|
73
|
+
|
|
74
|
+
## Docs
|
|
75
|
+
|
|
76
|
+
- https://forge.netnet.capital/docs/templates/tower
|
|
77
|
+
- `llms.txt` and `llms-full.txt` in this package (also exported as `@netforge/template-tower/llms.txt` and `@netforge/template-tower/llms-full.txt`)
|
|
48
78
|
|
|
49
|
-
|
|
50
|
-
earning, stakes and claims run only on the hosted Net Forge server.
|
|
79
|
+
## License
|
|
51
80
|
|
|
52
|
-
|
|
53
|
-
upgrade or sell it, Space calls the next wave. Touch: tap a tile to aim,
|
|
54
|
-
tap again to build. ` shows stats.
|
|
81
|
+
MIT. See `LICENSE` and `NOTICE`.
|
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
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Net Forge tower defense template (COGSPIRE)
|
|
2
2
|
|
|
3
3
|
> A co-op tower defense for up to 4 players on one shared board. Creeps
|
|
4
4
|
> walk a fixed road in waves toward the spire; players build and upgrade
|
package/package.json
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netforge/template-tower",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
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
|
+
],
|
|
5
19
|
"license": "MIT",
|
|
6
20
|
"type": "module",
|
|
7
21
|
"exports": {
|
|
8
22
|
".": "./src/manifest.ts",
|
|
9
|
-
"./store": "./src/store.ts"
|
|
23
|
+
"./store": "./src/store.ts",
|
|
24
|
+
"./package.json": "./package.json",
|
|
25
|
+
"./llms.txt": "./llms.txt",
|
|
26
|
+
"./llms-full.txt": "./llms-full.txt"
|
|
10
27
|
},
|
|
11
28
|
"files": [
|
|
12
29
|
"src",
|
|
@@ -15,26 +32,37 @@
|
|
|
15
32
|
"llms.txt",
|
|
16
33
|
"README.md",
|
|
17
34
|
"NOTICE",
|
|
18
|
-
"LICENSE"
|
|
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/**"
|
|
19
44
|
],
|
|
20
45
|
"publishConfig": {
|
|
21
46
|
"access": "public"
|
|
22
47
|
},
|
|
48
|
+
"homepage": "https://forge.netnet.capital/docs/templates/tower",
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/netnetdev/net-forge/issues"
|
|
51
|
+
},
|
|
23
52
|
"repository": {
|
|
24
53
|
"type": "git",
|
|
25
54
|
"url": "git+https://github.com/netnetdev/net-forge.git",
|
|
26
55
|
"directory": "packages/template-tower"
|
|
27
56
|
},
|
|
28
|
-
"homepage": "https://github.com/netnetdev/net-forge/tree/main/packages/template-tower#readme",
|
|
29
57
|
"engines": {
|
|
30
58
|
"node": ">=20"
|
|
31
59
|
},
|
|
32
60
|
"dependencies": {
|
|
33
61
|
"three": "0.186.1",
|
|
34
|
-
"@netforge/asset-library": "0.
|
|
35
|
-
"@netforge/engine
|
|
36
|
-
"@netforge/engine": "0.
|
|
37
|
-
"@netforge/tabletop": "0.
|
|
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"
|
|
38
66
|
},
|
|
39
67
|
"devDependencies": {
|
|
40
68
|
"@types/node": "22.20.1",
|
|
@@ -44,9 +72,6 @@
|
|
|
44
72
|
"vite": "6.4.3",
|
|
45
73
|
"vitest": "3.2.7"
|
|
46
74
|
},
|
|
47
|
-
"bugs": {
|
|
48
|
-
"url": "https://github.com/netnetdev/net-forge/issues"
|
|
49
|
-
},
|
|
50
75
|
"scripts": {
|
|
51
76
|
"build": "node ../engine/scripts/fresh-dist.mjs && vite build --config client/vite.config.ts",
|
|
52
77
|
"typecheck": "tsc -p tsconfig.json",
|