@netforge/template-collector 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 ADDED
@@ -0,0 +1,73 @@
1
+ # Extending MOSSBOUND
2
+
3
+ MOSSBOUND is the monster collector template of the Net Forge game engine. The template already has walking and collision, encounters, turn order, damage, the element cycle, snaring, team swaps, XP and levels, the camp, the warden fight, dusk, netcode, rendering, touch controls, earn and the store. You change the game by changing declarations. Read `llms.txt` for every field, and `llms-full.txt` for the complete worked example.
4
+
5
+ ## The one file to edit
6
+
7
+ - `src/game.ts` is the whole game. Change declarations in place.
8
+ - `src/names.json`, next to it, holds the title and every label players read. Creatures go in `"weapons"` (`{ name, line }`), the warden in `"enemies"`, plus upgrades, earn rules and store items. Moves are shown from their ids (`leaf_lash` reads "Leaf Lash").
9
+ - Everything else (`src/manifest.ts`, `src/store.ts`, `client/`, `test/`, `scripts/`) is template machinery. Do not edit it.
10
+
11
+ ## Imports
12
+
13
+ `src/game.ts` imports only from `@netforge/engine`. The shipped game uses:
14
+
15
+ ```ts
16
+ import { creature, earn, game, marsh, move, store, upgrade } from "@netforge/engine";
17
+ ```
18
+
19
+ ## Declarations
20
+
21
+ - `game({ template: "collector", style, places, players: { prefab, max } })`.
22
+ - `marsh({ size, camp, starters, starterLevel, water, blocked, grass, warden, dusk, snares })`, exactly one. Rects are `[x, y, width, height]` in 2 m tiles.
23
+ - `creature(id, { model, element, hp, atk, def, spd, moves, catchRate?, boss?, size? })`, 1 to 24 of them. Base stats are at level 1 and grow 12% a level.
24
+ - `move(id, { element, power, accuracy?, effect?, amount? })`, at most 48. Effects are `"drain"`, `"heal"` and `"guard"`.
25
+ - `upgrade(id, { stat, step, maxLevel })` with stat `might`, `maxHealth`, `armor`, `amount` or `regen` only.
26
+ - `earn.rule(id, { when, n, pay, oncePer: "run" })`. Events: `creature.caught`, `creature.defeated`, `creature.levelup`, `level.five`, `level.ten`, `warden.defeated` and `run.won`.
27
+ - `earn.stake({ min, max, multiple })`, exactly one, on one top-level line.
28
+ - `store.sku(id, { coins, grants? })`.
29
+
30
+ ## Rules the build enforces
31
+
32
+ - Ids are lower_snake_case.
33
+ - Every game declares at least one `earn.rule` and exactly one `earn.stake`. `"stake"` is reserved.
34
+ - Every creature's moves are declared moves, 1 to 4 each.
35
+ - Starters and grass pools name declared creatures that are not bosses. The warden names a creature with `boss: true`.
36
+ - The camp and the warden stand inside the marsh on tiles that are not blocked. Grass and blocked rects stay inside the marsh. Leave a walkable way from camp to every grass zone and to the warden.
37
+ - Elements are `root`, `tide`, `spark` and `stone`, a cycle where each beats the next.
38
+ - No `weapon(...)` in a collector game.
39
+ - Asset ids are library ids such as `"style.lowpoly.frog"`, never URLs or paths, and must exist in the asset library. Keep `game({ style })` as the creator picked it.
40
+ - Four playtest keepers must win battles, catch creatures and fire an earn rule in 4 minutes, and must not lose their whole team more than twice each.
41
+ - Keep the SKUs the client is wired to at their prices: `thornbark`, `mossmail`, `spare_snares`, `fen_tonic`, `skin_lichen`, `skin_amber` and `lucky_reed`.
42
+
43
+ ## Validate
44
+
45
+ - With the MCP server (`npx -y @netforge/mcp`), call `netforge_validate_game` on `src/game.ts`. It returns one actionable line per problem.
46
+ - In this package, run the tests with vitest: `test/harness.test.ts` and `test/store.test.ts`.
47
+
48
+ ## Playtest
49
+
50
+ - With the MCP server, call `netforge_run_playtest`.
51
+ - In code, import the game module and run the headless harness:
52
+
53
+ ```ts
54
+ import { engine } from "@netforge/engine";
55
+ import "./src/game.js";
56
+
57
+ const report = await engine.test.run({ ticks: 30 * 240, bots: 4, seed: 1 });
58
+ console.log(report.kills, report.deaths, report.earned, report.rules?.checks);
59
+ ```
60
+
61
+ The report has battles won plus catches (kills), team wipes (deaths), earn by rule, softlock, ms per tick and the genre's checks under `rules.checks`.
62
+
63
+ ## Play locally
64
+
65
+ In a folder with `netnet.json` (`{"template":"collector"}`) and `src/game.ts`, with `@netforge/kit-play` and `@netforge/engine` installed, run `npx kit-play`. It opens http://127.0.0.1:4663 with play money. The MCP tool `netforge_play_locally` gives the same steps.
66
+
67
+ ## Do not
68
+
69
+ - Do not write renderers, netcode, physics or battle AI. The template has them.
70
+ - Do not reference assets by URL or file path.
71
+ - Do not import anything other than `@netforge/engine` in `src/game.ts`.
72
+ - Do not edit `client/`, `src/manifest.ts` or `src/store.ts`.
73
+ - Do not rename or reprice the SKUs the client is wired to.
package/CLAUDE.md ADDED
@@ -0,0 +1,11 @@
1
+ # CLAUDE.md
2
+
3
+ This package is MOSSBOUND, a Net Forge game template. Read AGENTS.md before changing this game. It lists the declarations this genre has, the rules the build enforces, and how to validate, playtest and play the game locally.
4
+
5
+ The five hard rules:
6
+
7
+ 1. Edit only src/game.ts (and src/names.json for the labels players read).
8
+ 2. Import only from @netforge/engine.
9
+ 3. Ids are lower_snake_case.
10
+ 4. Declare at least one earn.rule and exactly one earn.stake.
11
+ 5. Never write renderers, netcode, physics or bot AI, and never reference assets by URL or file path (use library ids such as "style.lowpoly.frog").
package/README.md CHANGED
@@ -1,50 +1,82 @@
1
- # MOSSBOUND (monster collector template)
1
+ # @netforge/template-collector
2
2
 
3
- Fen spirits live in the tall grass of a flooded marsh. Each keeper picks a
4
- starter at camp, walks the reeds, meets wild spirits in the grass and
5
- battles them turn by turn with a team of three, snaring the ones worth
6
- keeping in a reed lantern. At the far end of the marsh the Fenwarden waits:
7
- every keeper in the marsh wears down the same warden, and it must fall
8
- before dusk.
3
+ A monster collector game template for Net Forge: a shared marsh for up to 8 keepers who catch creatures, battle turn by turn and bring down one warden together (MOSSBOUND).
9
4
 
10
- A board genre (`packages/engine/src/rules/collector.ts`) on the room
11
- server at 30 Hz behind the same SimAdapter seam as every template. The
12
- client kit is `@netforge/tabletop`.
5
+ Fen spirits live in the tall grass of a flooded marsh. Each keeper picks a starter at camp, walks the reeds, meets wild spirits in the grass and battles them with a team of three, snaring the ones worth keeping in a reed lantern. At the far end of the marsh the Fenwarden waits. Every keeper wears down the same warden, and it must fall before dusk.
13
6
 
14
- | Path | What |
15
- |---|---|
16
- | `src/game.ts` | The game, as the model writes it (declarations only). |
17
- | `src/manifest.ts` | Loads it; `collectorAdapter()` for the room server and client. Room template `collector`. |
18
- | `src/store.ts` | Coin prices the client is wired to, the template's own coin (MOSS). |
19
- | `client/` | The browser client: the marsh, keepers, companions, the battle panel, the starter pick. |
20
- | `test/` | Playtest bots through `engine.test.run`, and the store. |
21
- | `llms.txt` | How to change the game (for agents). |
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install @netforge/template-collector
11
+ ```
12
+
13
+ You rarely import this package directly. kit play (`@netforge/kit-play`) and the Net Forge MCP server use it to build and run a game whose `src/game.ts` you write.
14
+
15
+ ## Play it locally
16
+
17
+ ```sh
18
+ mkdir -p my-game/src && cd my-game
19
+ npm init -y && npm install @netforge/kit-play @netforge/engine @netforge/template-collector
20
+ cp node_modules/@netforge/template-collector/src/game.ts src/
21
+ echo '{"template":"collector"}' > netnet.json
22
+ npx kit-play # opens http://127.0.0.1:4663
23
+ ```
24
+
25
+ Open the link in more tabs for more keepers. Coins are play money. Real-token earning, stakes and claims run only on the hosted Net Forge server.
26
+
27
+ ## Change the game
28
+
29
+ Edit `src/game.ts` and run `npx kit-play` again. For example, make the warden tougher and give keepers more time and snares in `marsh({ ... })`:
30
+
31
+ ```ts
32
+ warden: { creature: "fenwarden", at: [27, 10], level: 10, hp: 900 },
33
+ dusk: 720,
34
+ snares: 8,
35
+ ```
36
+
37
+ Or add a creature and a move, then put the creature in a grass pool and its label in `src/names.json` under `weapons`:
38
+
39
+ ```ts
40
+ creature("thistlehog", { model: "style.lowpoly.fox", element: "root", hp: 50, atk: 52, def: 48, spd: 46, moves: ["leaf_lash", "thorn_curl"], catchRate: 0.7 });
41
+ move("thorn_curl", { element: "root", power: 0, effect: "guard" });
42
+ ```
22
43
 
23
44
  ## How it plays on the server
24
45
 
25
- - Your keeper walks with a stick (WASD, arrows, or the left thumb) and is
26
- predicted with the same tile collision the room runs, so walking never
27
- waits. Every roll (encounters, accuracy, damage spread, snares, the foe's
28
- move) is the room's PRNG, so a run replays exactly from its inputs.
29
- - Battles are private: your team, your foe and the battle log reach only
30
- you (SimAdapter.infoFor). Others see the creature beside you.
31
- - The warden's health is one pool for the whole marsh; when it falls,
32
- everyone who hurt it is paid. Dusk ends the run.
33
- - Earn (the room is the referee): the first catch, three catches, the
34
- warden, a creature at level 5. All are a player's own acts except the
35
- warden, which pays only those who hurt it.
46
+ - A board genre on the engine's rules runtime (`@netforge/engine/rules`) at 30 Hz, drawn with `@netforge/tabletop`.
47
+ - Your keeper walks with a stick (WASD, arrows or the left thumb) and is predicted with the same tile collision the room runs. Every roll (encounters, accuracy, damage spread, snares, the foe's move) comes from the room's PRNG, so a run replays exactly from its inputs.
48
+ - Battles are private: your team, your foe and the battle log reach only you. Others see the creature beside you.
49
+ - The warden's health is one pool for the whole marsh. When it falls, everyone who hurt it is paid. Dusk ends the run.
50
+ - Earn: the first catch, three catches, the warden and a creature at level 5. The room server is the referee.
36
51
 
37
- ## Run it
52
+ ## Files
53
+
54
+ | Path | What |
55
+ |---|---|
56
+ | `src/game.ts` | The game, as declarations only. This is the file you edit. |
57
+ | `src/manifest.ts` | Loads the game; `collectorAdapter()` for the room host and the client. |
58
+ | `src/store.ts` | Coin prices the client is wired to. |
59
+ | `client/` | The marsh, keepers, companions, the battle panel and the starter pick. |
60
+ | `test/` | Playtest bots through `engine.test.run`, and the store test. |
61
+ | `llms.txt`, `llms-full.txt` | How to change the game, for agents. |
38
62
 
39
- This template runs on your own computer with play money through
40
- `kit play` (`@netforge/kit-play`). From the repository root:
63
+ ## For AI agents
64
+
65
+ Read `AGENTS.md` and `llms.txt` before editing. The Net Forge MCP server gives a coding agent the templates, the API and a playtest loop:
41
66
 
42
67
  ```sh
43
- pnpm install --frozen-lockfile && pnpm build
44
- mkdir -p play-collector/src && cp packages/template-collector/src/game.ts play-collector/src/
45
- echo '{"template":"collector"}' > play-collector/netnet.json
46
- node packages/kit-play/dist/cli.js play-collector # opens http://127.0.0.1:4663
68
+ npx -y @netforge/mcp
47
69
  ```
48
70
 
49
- Edit `play-collector/src/game.ts` and run it again to try a change. Real-token
50
- earning, stakes and claims run only on the hosted Net Forge server.
71
+ The package is `@netforge/mcp` and its bin is `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`.
72
+
73
+ 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`.
74
+
75
+ ## Docs
76
+
77
+ - https://forge.netnet.capital/docs/templates/collector
78
+ - `llms.txt` and `llms-full.txt` in this package. The full file includes the complete `src/game.ts` and the validate and playtest steps.
79
+
80
+ ## License
81
+
82
+ MIT. See LICENSE and NOTICE.
package/llms-full.txt ADDED
@@ -0,0 +1,277 @@
1
+ # Net Forge monster collector template (MOSSBOUND)
2
+
3
+ > A shared marsh for up to 8 keepers. Each picks one of the starters at
4
+ > camp, walks the marsh, meets wild creatures in tall grass and battles them
5
+ > turn by turn with a team of three, snaring the ones worth keeping. Every
6
+ > keeper in the marsh wears down the same warden; it must fall before dusk.
7
+ > You change the game by editing src/game.ts only. The template already has
8
+ > walking and collision, encounters, turn order, damage, the element cycle,
9
+ > snaring, team swaps, XP and levels, the camp, the warden fight, dusk,
10
+ > netcode, rendering, touch controls, earn and the store. Do not write
11
+ > renderers, netcode or battle AI.
12
+
13
+ ## Files
14
+
15
+ - src/game.ts: THE GAME. Declarations against @netforge/engine:
16
+ game(), marsh(), creature(), move(), upgrade(), earn.rule(), earn.stake(), store.sku().
17
+ - src/names.json: the title and every label players read. Creatures go in
18
+ "weapons" ({ name, line }), the warden in "enemies", plus upgrades, earn
19
+ rules and store items. Moves are shown from their ids ("leaf_lash" reads
20
+ "Leaf Lash"), so name moves the way players should read them.
21
+ - Everything else is template machinery.
22
+
23
+ ## Rules the build enforces
24
+
25
+ - game({ template: "collector", style, places, players: { prefab, max } }).
26
+ - Exactly one marsh(...); 1 to 24 creature(...)s; at most 48 move(...)s.
27
+ - Every creature's moves are declared moves (1 to 4 each).
28
+ - marsh starters and grass pools name declared creatures that are not
29
+ bosses; the warden names a creature with boss: true.
30
+ - The camp and the warden stand inside the marsh on tiles that are not
31
+ blocked; grass and blocked rects stay inside the marsh.
32
+ - Elements are "root", "tide", "spark", "stone": a cycle where each beats
33
+ the next (root soaks tide, tide shorts spark, spark cracks stone, stone
34
+ crushes root). Strong hits deal 1.5x, weak ones 0.67x.
35
+ - Upgrades read five stats only: might (team attack), maxHealth (team
36
+ health, a fraction: 0.15 is +15%), armor (team defence), amount (extra
37
+ snares) and regen (share of max health healed each second out of battle).
38
+ - No weapon(...): a collector game declares creature(...) and move(...).
39
+ - Asset ids look like "style.lowpoly.frog", never URLs or paths, and must be
40
+ in the asset library. game({ style }) is the art style the creator picked
41
+ (lowpoly, toon, ps1, pixel, voxel or handdrawn); keep it.
42
+ - 4 playtest keepers must win battles, catch creatures and fire an earn rule
43
+ in 4 minutes, and must not lose their whole team more than twice each.
44
+
45
+ ## Units
46
+
47
+ A tile is 2 m; marsh rects are [x, y, width, height] in tiles. Stats are
48
+ base values at level 1 and grow 12% a level. power 40 is a plain hit.
49
+ Walking speed is metres a second. Tick rate 30 Hz.
50
+
51
+ ## The loop
52
+
53
+ A keeper picks a starter at camp (starterLevel). Every step into a new tile
54
+ of tall grass rolls the zone's rate; a hit starts a battle with a creature
55
+ from the zone's pool at a level in its range. A turn: you pick a move, a
56
+ swap, a snare or flee; the faster creature acts first; the foe answers
57
+ with its best move. A beaten foe gives XP (the active creature full, the
58
+ rest half). A snare's chance is catchRate x (0.25 + 0.75 x the foe's
59
+ missing health); a caught creature joins the team (three at most; more go
60
+ into the lantern). Wild creatures hit at 80%. If your whole team faints you
61
+ are sent to camp with half health; standing at camp heals. Near the
62
+ warden, Challenge starts the warden fight: its health is one pool for the
63
+ whole marsh, it hits at full strength, and when it falls everyone who hurt
64
+ it shares the win. At dusk the run is lost.
65
+
66
+ ## Common changes
67
+
68
+ ### A new creature
69
+ creature("thistlehog", { model: "style.lowpoly.fox", element: "root", hp: 50, atk: 52, def: 48, spd: 46, moves: ["leaf_lash", "thorn_curl"], catchRate: 0.7 });
70
+ move("thorn_curl", { element: "root", power: 0, effect: "guard" });
71
+ Then put it in a grass pool, and its label in names.json weapons.
72
+
73
+ ### Moves
74
+ move("tide_crash", { element: "tide", power: 70, accuracy: 0.8 });
75
+ effect "drain" heals half the damage dealt; "heal" (power 0) restores
76
+ amount of max health; "guard" halves the next hit taken.
77
+
78
+ ### The map
79
+ marsh({ size: [30, 20], camp: [2, 10], water: [[11, 0, 2, 6]], blocked: [[19, 0, 1, 8]],
80
+ grass: [{ at: [4, 2, 6, 5], rate: 0.22, pool: [{ creature: "sporecap", weight: 3, level: [2, 3] }] }], ... });
81
+ Put weak creatures near camp and stronger ones toward the warden. Leave a
82
+ walkable way from camp to every grass zone and to the warden.
83
+
84
+ ### Harder or easier
85
+ Raise pool levels, the warden's level or hp, or lower snares and grass
86
+ rates. "Longer": a later dusk. "More catching": higher catchRate and snares.
87
+
88
+ ### Add an earn rule
89
+ earn.rule("ten_wins", { when: "creature.defeated", n: 10, pay: 15, oncePer: "run" });
90
+ Events: creature.caught, creature.defeated, creature.levelup, level.five
91
+ and level.ten (each credits the keeper), warden.defeated (everyone who hurt
92
+ it), run.won (everyone in the marsh). Each rule pays once per run per player,
93
+ in coins; the room server is the referee.
94
+
95
+ ### The buy-in run (required)
96
+ earn.stake({ min: 20, max: 200, multiple: 2 });
97
+ One top-level line; a game without it does not build. "stake" is reserved.
98
+
99
+ ### Coins: earn and the store
100
+ Every game has coins: 1 coin = 1 of the game's own token, shown under the
101
+ token's own name, symbol and logo.
102
+ store.sku("thornbark", { coins: 40 }); // one level of the "thornbark" upgrade for the run
103
+ store.sku("skin_amber", { coins: 500, grants: { cosmetic: 2 } }); // a keeper's cloak, owned for good
104
+ store.sku("lucky_reed", { coins: 150, grants: { gear: "thornbark", loseBelow: 0.25 } }); // losable gear
105
+ Keep the SKUs the client is wired to (thornbark, mossmail, spare_snares,
106
+ fen_tonic, skin_lichen, skin_amber, lucky_reed) at their prices.
107
+
108
+ ## Test
109
+
110
+ engine.test.run({ ticks: 30 * 240, bots: 4 }) plays the marsh with 4
111
+ keepers and reports battles won plus catches (kills), 1 + catches (wave),
112
+ the highest level, team wipes (deaths), earned by rule, softlock, ms per
113
+ tick, and the genre's own checks under rules.checks.
114
+
115
+ ## Worked example: src/game.ts
116
+
117
+ This is the template's complete src/game.ts as it ships. A new game on this
118
+ template starts from this file and changes declarations in place.
119
+
120
+ ```ts
121
+ /**
122
+ * MOSSBOUND: the monster collector template (play style: monster-collector RPG).
123
+ *
124
+ * Fen spirits live in the tall grass of a flooded marsh. Each keeper picks
125
+ * one of three starters at camp, walks the reeds, meets wild spirits in the
126
+ * grass and battles them turn by turn with a team of three, snaring the
127
+ * ones worth keeping in a reed lantern. At the far end of the marsh the
128
+ * Fenwarden waits: every keeper in the marsh wears down the same warden,
129
+ * and it must fall before dusk.
130
+ *
131
+ * This file is the whole game as the model writes it: declarations only,
132
+ * against the typed API in @netforge/engine. Walking, encounters,
133
+ * battles, catching, levels, netcode, renderer and referee are the
134
+ * template's machinery.
135
+ */
136
+ import { creature, earn, game, marsh, move, store, upgrade } from "@netforge/engine";
137
+
138
+ export default game({
139
+ template: "collector",
140
+ style: "lowpoly",
141
+ places: { fen: { chunks: "places/fen", spawn: [0, 0, 0] } },
142
+ players: { prefab: "style.lowpoly.hooded_adventurer", max: 8 },
143
+ });
144
+
145
+ // The marsh: 30 x 20 tiles of 2 m. Camp in the west, the warden's mound in the east, water and rocks between.
146
+ marsh({
147
+ size: [30, 20],
148
+ camp: [2, 10],
149
+ starters: ["mossling", "cindertail", "puddlewump"],
150
+ starterLevel: 4,
151
+ water: [
152
+ [11, 0, 2, 6],
153
+ [11, 14, 2, 6],
154
+ [24, 0, 3, 3],
155
+ ],
156
+ blocked: [
157
+ [19, 0, 1, 8],
158
+ [19, 12, 1, 8],
159
+ [8, 9, 1, 2],
160
+ [15, 2, 1, 1],
161
+ [16, 16, 1, 1],
162
+ ],
163
+ grass: [
164
+ { at: [4, 2, 6, 5], rate: 0.22, pool: [{ creature: "sporecap", weight: 3, level: [2, 3] }, { creature: "voltmoth", weight: 2, level: [2, 3] }, { creature: "reedcrab", weight: 1, level: [2, 4] }] },
165
+ { at: [4, 13, 6, 5], rate: 0.22, pool: [{ creature: "reedcrab", weight: 2, level: [2, 4] }, { creature: "sporecap", weight: 1, level: [3, 4] }, { creature: "pebblewing", weight: 1, level: [3, 4] }] },
166
+ { at: [14, 7, 4, 6], rate: 0.2, pool: [{ creature: "pebblewing", weight: 2, level: [4, 6] }, { creature: "voltmoth", weight: 2, level: [4, 6] }, { creature: "bogling", weight: 1, level: [5, 6] }] },
167
+ { at: [21, 4, 4, 12], rate: 0.18, pool: [{ creature: "bogling", weight: 2, level: [6, 8] }, { creature: "pebblewing", weight: 1, level: [6, 8] }, { creature: "reedcrab", weight: 1, level: [6, 8] }] },
168
+ ],
169
+ warden: { creature: "fenwarden", at: [27, 10], level: 9, hp: 700 },
170
+ dusk: 600,
171
+ snares: 6,
172
+ });
173
+
174
+ // Creatures. Base stats at level 1 (each grows 12% a level); catchRate scales every snare.
175
+ creature("mossling", { model: "style.lowpoly.frog", element: "root", hp: 46, atk: 48, def: 50, spd: 44, moves: ["leaf_lash", "sap_drink"] });
176
+ creature("cindertail", { model: "style.lowpoly.fox", element: "spark", hp: 40, atk: 54, def: 42, spd: 58, moves: ["ember_nip", "static_dash"] });
177
+ creature("puddlewump", { model: "style.lowpoly.slime_drop", element: "tide", hp: 52, atk: 44, def: 52, spd: 40, moves: ["splash_jab", "bubble_ward"] });
178
+ creature("sporecap", { model: "style.lowpoly.mushroom_imp", element: "root", hp: 42, atk: 46, def: 46, spd: 38, moves: ["spore_puff", "leaf_lash"], catchRate: 0.9 });
179
+ creature("voltmoth", { model: "style.lowpoly.bee", element: "spark", hp: 36, atk: 50, def: 38, spd: 64, moves: ["static_dash", "ember_nip"], catchRate: 0.85 });
180
+ creature("reedcrab", { model: "style.lowpoly.crab", element: "tide", hp: 48, atk: 50, def: 60, spd: 36, moves: ["splash_jab", "shell_up"], catchRate: 0.8 });
181
+ creature("pebblewing", { model: "style.lowpoly.pebble_flyer", element: "stone", hp: 44, atk: 48, def: 56, spd: 50, moves: ["gravel_toss", "shell_up"], catchRate: 0.7 });
182
+ creature("bogling", { model: "style.lowpoly.blob_orc", element: "stone", hp: 60, atk: 52, def: 54, spd: 30, moves: ["gravel_toss", "mire_slam"], catchRate: 0.6, size: 1.3 });
183
+ creature("fenwarden", { model: "style.lowpoly.mushroom_king", element: "stone", hp: 120, atk: 66, def: 64, spd: 40, moves: ["mire_slam", "gravel_toss", "sap_drink"], boss: true, size: 3.4 });
184
+
185
+ // Moves. power 40 is a plain hit; drain heals half the damage, heal restores a share of max health, guard halves the next hit.
186
+ move("leaf_lash", { element: "root", power: 40 });
187
+ move("sap_drink", { element: "root", power: 30, effect: "drain" });
188
+ move("spore_puff", { element: "root", power: 45, accuracy: 0.9 });
189
+ move("ember_nip", { element: "spark", power: 40 });
190
+ move("static_dash", { element: "spark", power: 55, accuracy: 0.85 });
191
+ move("splash_jab", { element: "tide", power: 40 });
192
+ move("bubble_ward", { element: "tide", power: 0, effect: "heal", amount: 0.3 });
193
+ move("gravel_toss", { element: "stone", power: 40 });
194
+ move("shell_up", { element: "stone", power: 0, effect: "guard" });
195
+ move("mire_slam", { element: "stone", power: 70, accuracy: 0.8 });
196
+
197
+ // Store upgrades: one level each, for the rest of the run, on the buyer's own team.
198
+ upgrade("thornbark", { stat: "might", step: 0.15, maxLevel: 1 });
199
+ upgrade("mossmail", { stat: "armor", step: 0.15, maxLevel: 1 });
200
+ upgrade("spare_snares", { stat: "amount", step: 3, maxLevel: 1 });
201
+ upgrade("fen_tonic", { stat: "regen", step: 0.03, maxLevel: 1 });
202
+
203
+ // Earn (mandatory). Pays the game's coins (1 coin = 1 token), once per run each. Values are PLACEHOLDER.
204
+ earn.rule("first_snare", { when: "creature.caught", n: 1, pay: 5, oncePer: "run" });
205
+ earn.rule("full_lantern", { when: "creature.caught", n: 3, pay: 20, oncePer: "run" });
206
+ earn.rule("warden_breaker", { when: "warden.defeated", n: 1, pay: 50, oncePer: "run" });
207
+ earn.rule("seasoned", { when: "level.five", n: 1, pay: 5, oncePer: "run" });
208
+ // Buy-in run (the mandatory sink loop): stake coins at run start to earn above free play.
209
+ // A staked run also pays up to stake x multiple, by the share of this run's earn the player makes. Values are PLACEHOLDER.
210
+ earn.stake({ min: 20, max: 200, multiple: 2 });
211
+
212
+ // Store (the Kit), priced in coins: four run upgrades and two keeper finishes owned for good (kept in the profile).
213
+ store.sku("thornbark", { coins: 40 });
214
+ store.sku("mossmail", { coins: 40 });
215
+ store.sku("spare_snares", { coins: 60 });
216
+ store.sku("fen_tonic", { coins: 50 });
217
+ store.sku("skin_lichen", { coins: 300, grants: { cosmetic: 1 } });
218
+ store.sku("skin_amber", { coins: 500, grants: { cosmetic: 2 } });
219
+ // Losable gear: owned for good until lost. It brings one level of its upgrade into every staked run,
220
+ // and is lost when that run makes less than loseBelow of its most earn. Values are PLACEHOLDER.
221
+ store.sku("lucky_reed", { coins: 150, grants: { gear: "thornbark", loseBelow: 0.25 } });
222
+ ```
223
+
224
+ ## Validate and playtest
225
+
226
+ ### With the MCP server
227
+
228
+ Run `npx -y @netforge/mcp` (package @netforge/mcp, bin netforge-mcp) and
229
+ connect it to your coding agent. Its tools are netforge_list_templates, netforge_create_game,
230
+ netforge_explain_api, netforge_search_docs, netforge_validate_game, netforge_run_playtest and netforge_play_locally.
231
+
232
+ - netforge_validate_game checks src/game.ts against the engine's schemas and this
233
+ template's rules, and returns one actionable line per problem.
234
+ - netforge_run_playtest plays the game headless with bots and returns the report.
235
+ - netforge_play_locally gives the steps to open the game in a browser with kit play.
236
+
237
+ ### In code
238
+
239
+ Import the game module first so its declarations register, then run the
240
+ headless harness. This plays 4 keepers for 4 minutes at 30 Hz.
241
+
242
+ ```ts
243
+ import { engine } from "@netforge/engine";
244
+ import "./src/game.js";
245
+
246
+ const report = await engine.test.run({
247
+ ticks: 30 * 240,
248
+ bots: 4,
249
+ seed: 1,
250
+ assert: [
251
+ { name: "earn fired", check: (r) => r.earnEmitted > 0 },
252
+ { name: "no softlock", check: (r) => !r.softlock },
253
+ ],
254
+ });
255
+ if (report.failed.length > 0) throw new Error(report.failed.join("\n"));
256
+ ```
257
+
258
+ The report has battles won plus catches (kills), team wipes (deaths), earned by rule, softlock, ms per tick and the genre's checks under rules.checks.
259
+
260
+ The template's own tests (vitest) are the reference checks:
261
+
262
+ - test/harness.test.ts
263
+ - test/store.test.ts
264
+
265
+ ### Play it locally
266
+
267
+ In a folder with netnet.json and src/game.ts:
268
+
269
+ ```sh
270
+ npm install @netforge/kit-play @netforge/engine @netforge/template-collector
271
+ echo '{"template":"collector"}' > netnet.json
272
+ npx kit-play # opens http://127.0.0.1:4663
273
+ ```
274
+
275
+ kit play builds the template client with your src/game.ts and runs a local
276
+ room host with play money. No wallet, no chain and no hosted servers are
277
+ involved. Open the link in more tabs for more players.
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # MOSSBOUND: the monster collector template (NetNet game runtime, board genre)
1
+ # Net Forge monster collector template (MOSSBOUND)
2
2
 
3
3
  > A shared marsh for up to 8 keepers. Each picks one of the starters at
4
4
  > camp, walks the marsh, meets wild creatures in tall grass and battles them
package/package.json CHANGED
@@ -1,12 +1,34 @@
1
1
  {
2
2
  "name": "@netforge/template-collector",
3
- "version": "0.1.0",
4
- "description": "MOSSBOUND: the monster collector template of the NetNet game runtime, a shared marsh where keepers catch fen spirits, battle turn by turn with a team of three and bring down the warden before dusk. The game module, its browser client and its playtest bots.",
3
+ "version": "0.2.0",
4
+ "description": "Monster collector game template for Net Forge: a shared marsh where players catch creatures and battle turn by turn with a team of three (MOSSBOUND).",
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
+ "monster-collector"
18
+ ],
5
19
  "license": "MIT",
6
20
  "type": "module",
21
+ "sideEffects": [
22
+ "./src/game.ts",
23
+ "./src/manifest.ts",
24
+ "./client/**"
25
+ ],
7
26
  "exports": {
8
27
  ".": "./src/manifest.ts",
9
- "./store": "./src/store.ts"
28
+ "./store": "./src/store.ts",
29
+ "./package.json": "./package.json",
30
+ "./llms.txt": "./llms.txt",
31
+ "./llms-full.txt": "./llms-full.txt"
10
32
  },
11
33
  "files": [
12
34
  "src",
@@ -15,26 +37,32 @@
15
37
  "llms.txt",
16
38
  "README.md",
17
39
  "NOTICE",
18
- "LICENSE"
40
+ "LICENSE",
41
+ "llms-full.txt",
42
+ "AGENTS.md",
43
+ "CLAUDE.md"
19
44
  ],
20
45
  "publishConfig": {
21
46
  "access": "public"
22
47
  },
48
+ "homepage": "https://forge.netnet.capital/docs/templates/collector",
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-collector"
27
56
  },
28
- "homepage": "https://github.com/netnetdev/net-forge/tree/main/packages/template-collector#readme",
29
57
  "engines": {
30
58
  "node": ">=20"
31
59
  },
32
60
  "dependencies": {
33
61
  "three": "0.186.1",
34
- "@netforge/asset-library": "0.1.0",
35
- "@netforge/engine-sim": "0.1.0",
36
- "@netforge/tabletop": "0.1.0",
37
- "@netforge/engine": "0.1.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",