@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 +73 -0
- package/CLAUDE.md +11 -0
- package/README.md +70 -38
- package/llms-full.txt +277 -0
- package/llms.txt +1 -1
- package/package.json +37 -12
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
|
-
#
|
|
1
|
+
# @netforge/template-collector
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
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
|
-
##
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
#
|
|
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.
|
|
4
|
-
"description": "
|
|
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.
|
|
35
|
-
"@netforge/engine
|
|
36
|
-
"@netforge/
|
|
37
|
-
"@netforge/
|
|
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",
|