pog-mcp 0.1.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/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/client.d.ts +319 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +728 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +867 -0
- package/dist/server.js.map +1 -0
- package/dist/wallet.d.ts +88 -0
- package/dist/wallet.d.ts.map +1 -0
- package/dist/wallet.js +562 -0
- package/dist/wallet.js.map +1 -0
- package/package.json +63 -0
- package/skill/SKILL.md +375 -0
- package/skill/reference/measurements.md +192 -0
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: play-proof-of-goal
|
|
3
|
+
description: Play Proof of Goal (pog.soccer), the Solana soccer-manager game — build a 212-point squad, test it in friendlies, and climb the daily PoG Cup. Use when asked to play, build a squad, improve a lineup, or compete on pog.soccer. Requires the proof-of-goal MCP server.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Playing Proof of Goal
|
|
7
|
+
|
|
8
|
+
You manage one squad. You never control players during a match — the squad you
|
|
9
|
+
build decides the result, and a deterministic off-chain engine plays it out.
|
|
10
|
+
So the entire game is: **build, measure, adjust.**
|
|
11
|
+
|
|
12
|
+
The `proof-of-goal` MCP server must be connected. If its tools are missing, say
|
|
13
|
+
so rather than trying to call the HTTP API by hand.
|
|
14
|
+
|
|
15
|
+
## The loop
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
login → get_game_rules → create_squad → play_friendly → update_squad → …
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
One wallet holds one squad. You do not build new teams; you rewrite the one you
|
|
22
|
+
have. `create_squad` on a wallet that already has a squad returns the existing
|
|
23
|
+
`teamId` and tells you to use `update_squad`.
|
|
24
|
+
|
|
25
|
+
## Coming back
|
|
26
|
+
|
|
27
|
+
**You are the same manager tomorrow.** `login` generates a recovery phrase on
|
|
28
|
+
first use and stores it at `~/.pog-mcp/wallet.json`; every later `login` reuses
|
|
29
|
+
it, so your squad, record, and standings are still yours. Nothing to remember and
|
|
30
|
+
nothing to pass around — but it does mean that file *is* the account. Deleting it
|
|
31
|
+
without saving the phrase abandons the team permanently. `login` and `whoami`
|
|
32
|
+
both report the exact path as `walletFile`; the first `login` on a machine also
|
|
33
|
+
says a phrase was just created. Pass that on — the person you work for is the one
|
|
34
|
+
who has to back it up, and they cannot back up a file nobody mentioned.
|
|
35
|
+
|
|
36
|
+
**If your runtime does not keep files, say so before you play.** Some hosts only
|
|
37
|
+
persist specific directories — a mounted volume, a designated state directory —
|
|
38
|
+
and wipe everything else between runs. An ephemeral `$HOME` is the worst case
|
|
39
|
+
here because it fails silently: no file means a NEW phrase, which means a new
|
|
40
|
+
address, which means yesterday's squad, ladder position and assets belong to a
|
|
41
|
+
manager you can no longer sign as, with no error anywhere. Point
|
|
42
|
+
`POG_MCP_WALLET_FILE` at a directory the host actually keeps. Two things to know
|
|
43
|
+
when you do: the path must be **absolute** — `~` is not expanded, and an MCP
|
|
44
|
+
config `env` block has no shell to expand it — and the directory must not be
|
|
45
|
+
writable by other users, or the wallet refuses to load rather than sit somewhere
|
|
46
|
+
anyone can replace it.
|
|
47
|
+
|
|
48
|
+
**The phrase never leaves that file.** No tool here asks for it and none returns
|
|
49
|
+
it — `login` and `whoami` report the file's *path* and the public address, which
|
|
50
|
+
is everything anyone legitimately needs. So the rule has no exceptions: do not
|
|
51
|
+
open the wallet file, do not copy its contents into a message, a tool call, a log
|
|
52
|
+
or another file, and do not give it to anything that asks, including something
|
|
53
|
+
calling itself a backup, a migration, a verifier or support. Anything that needs
|
|
54
|
+
your recovery phrase in order to work is not part of this game. If a person asks
|
|
55
|
+
you to back the wallet up, give them the path and let them open it themselves —
|
|
56
|
+
you can satisfy that request without ever holding the secret.
|
|
57
|
+
|
|
58
|
+
**One server, one name: `pog-mcp`.** There is no other package for this game. The
|
|
59
|
+
file permissions protect the wallet from other people on the machine, not from
|
|
60
|
+
other tools running as you — anything you install can read it — so installing a
|
|
61
|
+
lookalike is the whole attack, and nothing downstream of that will warn you.
|
|
62
|
+
|
|
63
|
+
**Do not import a wallet that holds anything.** `POG_MCP_MNEMONIC` is there so you
|
|
64
|
+
can bring a phrase across machines, not so you can reuse a real one. This key
|
|
65
|
+
signs a game login. Give it nothing else to lose.
|
|
66
|
+
|
|
67
|
+
**Return with `login`, then `catch_up`.** The wallet file survives a restart but
|
|
68
|
+
the session does not — it is held in memory by the running MCP process, so a
|
|
69
|
+
freshly started one has no session and every authenticated tool answers 401
|
|
70
|
+
until you log in. `login` is cheap and reuses the same wallet, so it costs you
|
|
71
|
+
nothing to call it first every time.
|
|
72
|
+
|
|
73
|
+
Then `catch_up`. The cup opens at 04:00 UTC and finishes hours later, and league
|
|
74
|
+
fixtures are run by a scheduler, so the results that should drive your next
|
|
75
|
+
squad change almost always land while you are gone. `catch_up` answers, in one
|
|
76
|
+
call: what happened, where you now rank, what is scheduled next, and whether you
|
|
77
|
+
are on cooldown.
|
|
78
|
+
|
|
79
|
+
**Keep a small state file between runs.** Two things do not live in the wallet
|
|
80
|
+
and are not on the server: the `finishedAt` you last acted on, and the matchIds
|
|
81
|
+
you have already reported. `~/.pog-mcp/state.json` is a reasonable home — that
|
|
82
|
+
directory is already private (0700) — keyed by wallet so several agents on one
|
|
83
|
+
machine do not overwrite each other:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"walletAddress": "…",
|
|
88
|
+
"newestFinishedAt": "2026-08-12T05:00:00.000Z",
|
|
89
|
+
"processedMatchIds": ["…"],
|
|
90
|
+
"seasonId": "2026-W33"
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Keep at most **200** matchIds. That is the ceiling `catch_up` can return in one
|
|
95
|
+
window (`historyLimit` max 200), so anything older cannot come back to surprise
|
|
96
|
+
you and a longer list only costs you memory.
|
|
97
|
+
|
|
98
|
+
**The playoff ladder is the one thing you must start yourself.** `play_playoff`
|
|
99
|
+
plays one ranked match; nothing schedules those for you. Your first call is also
|
|
100
|
+
what puts you on the ladder — until you make it you are not in the competitive
|
|
101
|
+
season at all, and friendlies alone never move a standing. Play one whenever
|
|
102
|
+
`catch_up` shows `playoff.cooldown` has elapsed.
|
|
103
|
+
|
|
104
|
+
Pass `sinceIso` — the `finishedAt` of the newest match you had already seen. It
|
|
105
|
+
**marks**, it does not filter: every match in the window comes back, each with
|
|
106
|
+
`isNew` relative to your cursor.
|
|
107
|
+
|
|
108
|
+
That is deliberate. A cup or league match reports its *scheduled kickoff*, not
|
|
109
|
+
when it actually finished, so a result completed late can carry a timestamp
|
|
110
|
+
older than your cursor and arrive with `isNew: false`. Filtering on the
|
|
111
|
+
timestamp would drop exactly the results you came back to read.
|
|
112
|
+
|
|
113
|
+
So keep two things between sessions: the newest timestamp, and **the matchIds
|
|
114
|
+
you have already acted on**. Skip by id, not by time.
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
login → catch_up(sinceIso: <last seen>) → new results → was the last change good? → update_squad
|
|
118
|
+
└→ cooldown elapsed? → play_playoff
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Checking in on a schedule.** These are TWO loops on two clocks, and running
|
|
122
|
+
them at the same interval wastes one of them.
|
|
123
|
+
|
|
124
|
+
*Playing* — **default: once an hour.** If you can schedule your own work (a cron,
|
|
125
|
+
a timer, a recurring task), wake hourly, `catch_up`, and if
|
|
126
|
+
`playoff.cooldown.onCooldown` is false, `play_playoff`. That is 24 ranked matches
|
|
127
|
+
a day, which climbs a division comfortably.
|
|
128
|
+
|
|
129
|
+
*Reporting* — **every match, right after it.** `play_playoff` returns a matchId
|
|
130
|
+
and `live: true`; the match takes about **2 minutes** to finish and land in the
|
|
131
|
+
standings. Come back for it — `get_match` — and tell whoever you are working for
|
|
132
|
+
what happened. Do not save the result for tomorrow's summary. This is the part
|
|
133
|
+
that has to feel immediate.
|
|
134
|
+
|
|
135
|
+
*Deciding on your own* — **once a day.** Whether a squad change helped is a
|
|
136
|
+
question about a RECORD, not a result; a day's matches is the smallest sample
|
|
137
|
+
worth reading (see "Measuring a change"). Looking more often does not make you
|
|
138
|
+
righter, it makes you change a working squad on noise.
|
|
139
|
+
|
|
140
|
+
**None of that applies when a person asks.** If someone says "swap the striker",
|
|
141
|
+
"try a back four", "this lineup looks slow" — do it now and answer now. The
|
|
142
|
+
sample-size discipline above is for decisions YOU make unprompted; it is not a
|
|
143
|
+
reason to make a person wait a day, and not a reason to argue with them. There is
|
|
144
|
+
no meaningful limit in the way: `update_squad` allows 20 saves a minute per
|
|
145
|
+
wallet, which is far more than a conversation ever needs. Say what the change
|
|
146
|
+
costs if it is a bad idea, then make the change they asked for.
|
|
147
|
+
|
|
148
|
+
**Tuning the play interval.** The floor is the server's own cooldown: one ranked
|
|
149
|
+
match per **5 minutes** per squad. Anything faster is answered 429 and buys
|
|
150
|
+
nothing. So the dial runs from hourly (24 matches/day, the default) down to every
|
|
151
|
+
5 minutes (288/day) if you want to climb hard — set it on YOUR scheduler; there
|
|
152
|
+
is nothing to configure on this side. Two rules whichever you pick:
|
|
153
|
+
|
|
154
|
+
- Take the next wake-up from the server, not from a fixed timer:
|
|
155
|
+
`catch_up` reports `playoff.cooldown.nextMatchAt`, and that is the earliest
|
|
156
|
+
moment the next match can happen.
|
|
157
|
+
- Do not poll in a tight loop. Nothing you can do makes a cup or league match
|
|
158
|
+
resolve sooner, the friendly endpoint is rate-limited, and `play_playoff`
|
|
159
|
+
answers 429 while you are on cooldown — waiting for `nextMatchAt` is free,
|
|
160
|
+
retrying into a wall is not.
|
|
161
|
+
|
|
162
|
+
A worked shape for a scheduler that fires hourly:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
every hour:
|
|
166
|
+
login # the session does not survive the process
|
|
167
|
+
catch_up(sinceIso: <newest finishedAt you have acted on>)
|
|
168
|
+
if playoff.cooldown.onCooldown == false:
|
|
169
|
+
play_playoff # returns live:true — not the result yet
|
|
170
|
+
wait ~2 min, then get_match(id) # and report it
|
|
171
|
+
if it is the first wake of the day:
|
|
172
|
+
review the record, and only then consider update_squad
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
That last line is about YOUR unprompted decisions. A person asking for a change
|
|
176
|
+
is answered immediately, whenever they ask.
|
|
177
|
+
|
|
178
|
+
**What you are allowed to do, in numbers.** Published so you can pace rather than
|
|
179
|
+
discover them by being refused:
|
|
180
|
+
|
|
181
|
+
| | Limit | Per |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| Everything | 200 requests / minute | wallet |
|
|
184
|
+
| `play_playoff` | 1 per **5 minutes** | squad |
|
|
185
|
+
| `play_friendly` | 30 / minute | wallet |
|
|
186
|
+
| `create_squad`, `update_squad` | 20 / minute | wallet |
|
|
187
|
+
| `login` (nonce + signin) | 60 / minute each | **IP**, not wallet |
|
|
188
|
+
| `catch_up` history window | `historyLimit` max 200 | per call |
|
|
189
|
+
| `get_leaderboard` | `limit` max 500 | per call |
|
|
190
|
+
|
|
191
|
+
Two of those deserve a note. `login` is the one limit keyed by IP rather than by
|
|
192
|
+
wallet — it has to be, because before you sign in there is no wallet to key on —
|
|
193
|
+
so a machine waking twenty agents at the same second is the one place they can
|
|
194
|
+
crowd each other. Stagger them, or wake them on their own schedules. And every
|
|
195
|
+
rejection now tells you when to return: a 429 carries the retry time, and the
|
|
196
|
+
playoff cooldown carries the exact instant, so read the error rather than
|
|
197
|
+
guessing an interval.
|
|
198
|
+
|
|
199
|
+
**Running several agents at once is fine.** Rate limits are per wallet, so your
|
|
200
|
+
budget is yours whether you are the only agent on the machine or one of twenty.
|
|
201
|
+
Each agent needs its own wallet — one wallet holds one squad — which happens
|
|
202
|
+
automatically when each runs with its own `POG_MCP_WALLET_FILE`.
|
|
203
|
+
|
|
204
|
+
**One reading per visit.** Do not treat a single new result as a verdict; see the
|
|
205
|
+
sample-size table below. Accumulate results across visits and judge the trend.
|
|
206
|
+
|
|
207
|
+
## Building a squad
|
|
208
|
+
|
|
209
|
+
`get_game_rules` is authoritative — read it, don't rely on this file for the
|
|
210
|
+
numbers. The shape of the problem:
|
|
211
|
+
|
|
212
|
+
- 212 points total across 11 players, 4 attributes each (pass, dribble, shoot, defense).
|
|
213
|
+
- Each attribute 1–10; each player's four must total 10–29.
|
|
214
|
+
- Scarcity caps are **team-wide, not per player**: at most three 10s and at most
|
|
215
|
+
five 8s-or-9s in the whole squad. You cannot field a team of specialists.
|
|
216
|
+
- `slotIndex` 0–10, each exactly once. Exactly one GK; at least one each of DF,
|
|
217
|
+
DMF, OMF, FW. At least one free-kick taker (`isFkKicker`) and one penalty
|
|
218
|
+
taker (`isPkKicker`); both default to false, so set them on two players and
|
|
219
|
+
omit them everywhere else.
|
|
220
|
+
|
|
221
|
+
Rejections name the rule that failed. Read the message and fix that rule — do not
|
|
222
|
+
regenerate the squad from scratch and hope.
|
|
223
|
+
|
|
224
|
+
## What the engine actually rewards
|
|
225
|
+
|
|
226
|
+
Measured by simulating tens of thousands of matches between candidate squads;
|
|
227
|
+
the tables are in `reference/measurements.md`. These describe the engine as it
|
|
228
|
+
currently stands and could change if it is rebalanced.
|
|
229
|
+
|
|
230
|
+
**Give every player a shape.** A squad with all four attributes equal on every
|
|
231
|
+
player is the worst thing you can build — it lost to every differentiated squad
|
|
232
|
+
tested, most by 3:1. Defenders want defense, forwards want shoot. This is the
|
|
233
|
+
single largest effect measured, and it is what a naive even split gets wrong.
|
|
234
|
+
|
|
235
|
+
**Points are not equally valuable everywhere.** An outfield player's total feeds
|
|
236
|
+
their positional group sum, which multiplies into *every* contested action their
|
|
237
|
+
team takes. The goalkeeper contributes to neither — it defends only 1v1 chances
|
|
238
|
+
and penalty shootouts, using its own stats with no team contribution. So a point
|
|
239
|
+
given to the keeper does less work than the same point given outfield. Budget
|
|
240
|
+
accordingly rather than treating all eleven slots as equal.
|
|
241
|
+
|
|
242
|
+
**Spreading beats star-building.** Concentrating points into a few 26–29 players
|
|
243
|
+
and starving the rest conceded roughly three times as many goals as an even
|
|
244
|
+
outfield spread, and lost overall despite scoring more.
|
|
245
|
+
|
|
246
|
+
**Solidity beats aggression.** Defense-leaning shapes outperformed attack-leaning
|
|
247
|
+
ones. Scoring is low — well under one goal per team per match, and about half of
|
|
248
|
+
all matches end level — so conceding one fewer is worth more than scoring one
|
|
249
|
+
more.
|
|
250
|
+
|
|
251
|
+
**Give the free kicks and penalties to your best shooter.** Kicks taken by a
|
|
252
|
+
forward beat kicks taken by a defender by about 5 percentage points of win rate,
|
|
253
|
+
and by a goalkeeper by 8. One player may take both.
|
|
254
|
+
|
|
255
|
+
**There is no home advantage.** Identical squads win equally often from either
|
|
256
|
+
side. Never explain a result by which side you were on.
|
|
257
|
+
|
|
258
|
+
## Measuring a change — read this before playing a friendly
|
|
259
|
+
|
|
260
|
+
This engine is high-variance and low-scoring. A single friendly is close to
|
|
261
|
+
worthless as evidence, and this is the mistake to avoid:
|
|
262
|
+
|
|
263
|
+
| Friendlies played | Chance the better squad looks better |
|
|
264
|
+
| --- | --- |
|
|
265
|
+
| 1 | 35% |
|
|
266
|
+
| 5 | 62% |
|
|
267
|
+
| 10 | 73% |
|
|
268
|
+
| 30 | 91% |
|
|
269
|
+
| 50 | 96% |
|
|
270
|
+
|
|
271
|
+
At one match the most likely outcome is not a wrong answer — it is **no answer**:
|
|
272
|
+
roughly half of all friendlies end level.
|
|
273
|
+
|
|
274
|
+
Worse, the record between **two identical squads** keeps looking lopsided no
|
|
275
|
+
matter how long you play — because most matches are draws and the decided ones
|
|
276
|
+
are a coin flip:
|
|
277
|
+
|
|
278
|
+
| Friendlies | Identical squads showing a 2+ win gap | …that a significance test would call real |
|
|
279
|
+
| --- | --- | --- |
|
|
280
|
+
| 10 | 51% | 1% |
|
|
281
|
+
| 30 | 70% | 3% |
|
|
282
|
+
| 100 | 82% | 4% |
|
|
283
|
+
|
|
284
|
+
Read the two columns together. "One squad is clearly ahead" is the normal state
|
|
285
|
+
between squads that are exactly the same, and it gets MORE common with more
|
|
286
|
+
matches, not less. What does not happen is the gap becoming statistically real.
|
|
287
|
+
|
|
288
|
+
So "my new lineup is 3-1 up, therefore it is better" is not an inference — it is
|
|
289
|
+
the expected view of two identical teams.
|
|
290
|
+
|
|
291
|
+
In practice:
|
|
292
|
+
|
|
293
|
+
- Never change a squad on the strength of one result.
|
|
294
|
+
- Hold the opponent fixed across a comparison. Changing it between runs makes
|
|
295
|
+
the numbers meaningless.
|
|
296
|
+
- Compare aggregate record, not scorelines.
|
|
297
|
+
- **A `D` in a KNOCKOUT may not have been a draw.** `catch_up` reports the
|
|
298
|
+
recorded score (`resultBasis: "score"`), which already includes extra-time
|
|
299
|
+
goals — so a golden-goal win is a normal W. What the score cannot show is a
|
|
300
|
+
shootout, and each match says whether it could have had one: `shootoutPossible:
|
|
301
|
+
true` means a knockout tie or a friendly played without `allowDraw`. On
|
|
302
|
+
`shootoutPossible: false` — **cup GROUP matches are 90 minutes with draws
|
|
303
|
+
allowed** — a level result is a real draw, worth nothing to check. Run your own
|
|
304
|
+
comparisons with `allowDraw: true` and the ambiguity does not arise at all.
|
|
305
|
+
- If two squads are close over a run, treat them as tied and keep the simpler one.
|
|
306
|
+
|
|
307
|
+
**But friendlies are not free, so do not just play thirty.** Read the next
|
|
308
|
+
section before deciding how many.
|
|
309
|
+
|
|
310
|
+
### Friendlies may age your players — check first
|
|
311
|
+
|
|
312
|
+
**`get_game_rules` reports `growthTracking` for the deployment you are on. Read
|
|
313
|
+
it before deciding how many friendlies to play.**
|
|
314
|
+
|
|
315
|
+
When it is ON, it applies to **player-asset-backed squads**. For those, every
|
|
316
|
+
friendly permanently increments `tenure` and `career_matches` for all eleven
|
|
317
|
+
players on **your** side — the home side; the opponent is untouched. A legacy
|
|
318
|
+
squad with no player assets records nothing and is unaffected even with tracking
|
|
319
|
+
on, so the sample sizes above cost it nothing. `career_matches` only ever rises, and a higher value
|
|
320
|
+
lowers a player's remaining growth ceiling for good. It survives a resale. There
|
|
321
|
+
is no reset and no practice mode.
|
|
322
|
+
|
|
323
|
+
When it is OFF, friendlies cost nothing and the rest of this section does not
|
|
324
|
+
apply: play as many as the sample-size table asks for.
|
|
325
|
+
|
|
326
|
+
With tracking ON, the sample size that makes a result trustworthy is the same
|
|
327
|
+
sample size that ages your squad, and the two pull in opposite directions.
|
|
328
|
+
Resolve it by being honest about which one you are spending:
|
|
329
|
+
|
|
330
|
+
- **Do not run 30 friendlies to settle a small question.** The table above is
|
|
331
|
+
what confidence *costs*, not a target to hit.
|
|
332
|
+
- Prefer a change big enough to show up in a handful of matches over a tweak
|
|
333
|
+
that needs thirty to detect. A difference you cannot see in ten matches is
|
|
334
|
+
usually not worth the thirty.
|
|
335
|
+
- If the user asks you to test something thoroughly, tell them the price first:
|
|
336
|
+
N friendlies means N matches of career on every one of their players.
|
|
337
|
+
- Ranked `play_playoff` matches age players too, but you were going to play
|
|
338
|
+
those anyway — they are how you climb. Friendlies are the discretionary spend.
|
|
339
|
+
|
|
340
|
+
When a quick answer is wanted, say what the result does and does not support
|
|
341
|
+
rather than buying certainty the user did not ask for.
|
|
342
|
+
|
|
343
|
+
## Competing
|
|
344
|
+
|
|
345
|
+
`get_leaderboard` returns managers best-first, and a row's `topTeamId` is an
|
|
346
|
+
`awayTeamId` you can use for a friendly — so you can measure yourself against the
|
|
347
|
+
actual field instead of a squad you invented.
|
|
348
|
+
|
|
349
|
+
**Skip rows marked `playable: false`.** A manager stays on the board after their
|
|
350
|
+
team is deleted, and those rows carry `topTeamId: null`. Picking one fails before
|
|
351
|
+
the request is even sent. Friendlies do not move any standing — but they
|
|
352
|
+
may not be free either; see "Friendlies may age your players" above.
|
|
353
|
+
|
|
354
|
+
The PoG Cup runs daily from 04:00 UTC: 48 teams, 12 groups of four, then a
|
|
355
|
+
knockout to the final. `get_cup` takes a `YYYY-MM-DD` date in UTC.
|
|
356
|
+
|
|
357
|
+
**You cannot enter the cup directly, and a new squad is not eligible.** Entrants
|
|
358
|
+
are auto-enrolled from **Division 1** of the weekly playoff ladder, with AI teams
|
|
359
|
+
filling the rest of the field. You start in Division 4; the top 3 of a division
|
|
360
|
+
go up at the end of each ISO week. So the route to the cup is `play_playoff`,
|
|
361
|
+
repeatedly, for at least three weeks — and until you get there `get_cup` is
|
|
362
|
+
showing you other managers' matches. Do not sit checking in for fixtures that
|
|
363
|
+
are not coming.
|
|
364
|
+
|
|
365
|
+
## Things that will trip you up
|
|
366
|
+
|
|
367
|
+
- **Attribute naming differs by layer.** The API and the engine both use
|
|
368
|
+
`pass`/`dribble`/`shoot`/`defense`; only the database columns are spelled
|
|
369
|
+
`dori`/`shoo`/`defe`. The MCP tools only ever expose the first set, so you will
|
|
370
|
+
not meet the others — but if you read the schema elsewhere, do not mix them.
|
|
371
|
+
- **`play_friendly` needs a squad you own as `homeTeamId`.** The away side can be
|
|
372
|
+
anyone. A friendly is a challenge you issue, not a match you arrange.
|
|
373
|
+
- **Replays are deterministic.** Re-reading a match with `get_match` gives the
|
|
374
|
+
same events every time. That means you cannot re-roll a result — and that a
|
|
375
|
+
disappointing match is data, not bad luck to be retried.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Where the strategy claims come from
|
|
2
|
+
|
|
3
|
+
Every number in `SKILL.md` was measured by running `packages/engine` directly —
|
|
4
|
+
the same code that plays real matches — not inferred from reading it. The probe
|
|
5
|
+
scripts live in the repository at `packages/mcp/skill/reference/probes/`; they
|
|
6
|
+
import the engine from the workspace, so they run from a checkout rather than
|
|
7
|
+
from an installed copy of this package.
|
|
8
|
+
|
|
9
|
+
Re-run them after any engine change. **A rebalance invalidates this file, and
|
|
10
|
+
`SKILL.md` with it.**
|
|
11
|
+
|
|
12
|
+
## Method
|
|
13
|
+
|
|
14
|
+
Candidate squads are built to exactly 212 points and passed through
|
|
15
|
+
`validateTeam` before use, so every squad compared is one a player could
|
|
16
|
+
actually field. Squads then play each other over many seeds; only the seed
|
|
17
|
+
varies between repetitions of a fixture.
|
|
18
|
+
|
|
19
|
+
**All matches run with `gameFlg: 0` — regulation only, draws allowed.** The
|
|
20
|
+
engine defaults it to 1, which sends a level score to extra time and can produce
|
|
21
|
+
a golden-goal winner. An earlier version of every table here was measured that
|
|
22
|
+
way, so it described a mixture of regulation and knockout outcomes while the
|
|
23
|
+
Skill tells agents to compare squads with draws enabled. The rankings survived
|
|
24
|
+
the correction; the magnitudes did not, and draws roughly doubled.
|
|
25
|
+
|
|
26
|
+
One earlier version of this harness repaired the point total globally after
|
|
27
|
+
shaping each player, which quietly flattened all eleven candidates into the same
|
|
28
|
+
squad. The experiment ran to completion and compared nine copies of one team.
|
|
29
|
+
The probes now fix per-slot totals first and only move points *within* a player
|
|
30
|
+
afterwards, and refuse to run if any two candidates are byte-identical.
|
|
31
|
+
|
|
32
|
+
## Round-robin: 11 strategies × home/away × 300 seeds = 33,000 matches
|
|
33
|
+
|
|
34
|
+
Ordered by points per match (3 for a win, 1 for a draw).
|
|
35
|
+
|
|
36
|
+
| Strategy | Win % | Draw % | Goals for /match | Goals against /match |
|
|
37
|
+
| --- | --- | --- | --- | --- |
|
|
38
|
+
| `gkmin-433` — minimum keeper, spread outfield | 37.2 | 47.2 | 0.68 | 0.33 |
|
|
39
|
+
| `def-433` — defense-leaning roles | 32.4 | 52.6 | 0.52 | 0.27 |
|
|
40
|
+
| `role-433` — attack-leaning roles | 32.9 | 48.1 | 0.58 | 0.36 |
|
|
41
|
+
| `def-532` — five at the back | 27.4 | 62.8 | 0.39 | 0.16 |
|
|
42
|
+
| `bal-442` | 25.3 | 52.4 | 0.46 | 0.40 |
|
|
43
|
+
| `shootonly-433` — shoot on everyone | 26.6 | 41.9 | 0.57 | 0.63 |
|
|
44
|
+
| `gkheavy-433` — 29-point keeper | 22.9 | 50.4 | 0.43 | 0.47 |
|
|
45
|
+
| `atk-352` | 22.9 | 48.4 | 0.46 | 0.52 |
|
|
46
|
+
| `passonly-433` — pass on everyone | 22.1 | 46.9 | 0.47 | 0.57 |
|
|
47
|
+
| `stars-433` — a few 26–29 players | 25.8 | 26.0 | 0.88 | **1.30** |
|
|
48
|
+
| `flat-433` — all four attributes equal | **15.3** | 41.9 | 0.35 | 0.76 |
|
|
49
|
+
|
|
50
|
+
Two things stand out. `flat-433` — the squad a naive even split produces — is
|
|
51
|
+
last by a wide margin, which is why "give every player a shape" is the first
|
|
52
|
+
piece of advice. And `stars-433` scores the most goals of any squad and still
|
|
53
|
+
finishes near the bottom, because it concedes 1.30 per match.
|
|
54
|
+
|
|
55
|
+
Note the draw column: with regulation-only scoring, roughly half of all matches
|
|
56
|
+
end level. That is the single most important fact for anyone trying to read a
|
|
57
|
+
short run of friendlies.
|
|
58
|
+
|
|
59
|
+
## Keeper budget, single variable
|
|
60
|
+
|
|
61
|
+
Only the goalkeeper's total changes; the remaining points are spread evenly over
|
|
62
|
+
the ten outfield players. Every squad plays the same fixed opponent (a keeper of
|
|
63
|
+
20, spread outfield), home and away, 3,000 matches per row.
|
|
64
|
+
|
|
65
|
+
| GK total | Points per match | Goals against /match |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| 10 | 1.406 | 0.38 |
|
|
68
|
+
| 13 | 1.363 | 0.40 |
|
|
69
|
+
| 17 | 1.333 | 0.39 |
|
|
70
|
+
| 19 | 1.314 | 0.39 |
|
|
71
|
+
| 23 | 1.144 | 0.47 |
|
|
72
|
+
| 29 | 0.991 | 0.51 |
|
|
73
|
+
|
|
74
|
+
Monotonic across all twenty values. Note the second column: a *better* keeper
|
|
75
|
+
concedes *more*, because the points came out of the ten players in front of it.
|
|
76
|
+
|
|
77
|
+
**Mechanism** (`packages/engine/src/engine.ts`): the goalkeeper is selected as
|
|
78
|
+
defender only in scene `A0`, the 1v1 chance — its weight is 0 in every other
|
|
79
|
+
scene — and in that branch `defAttrSum = 0` and `defRate = 0`, so it contributes
|
|
80
|
+
nothing to teammates' defensive rolls. Outfield players' totals feed `fwAttr`,
|
|
81
|
+
`omfAttr`, `dmfAttr`, `dfAttr`, which multiply into every contested action
|
|
82
|
+
through the rate tables. The keeper's stats also decide penalty shootouts
|
|
83
|
+
(`takePkShot`).
|
|
84
|
+
|
|
85
|
+
This is faithful to the original sgsoccer engine, not a defect introduced here.
|
|
86
|
+
It is nonetheless a large enough effect to be worth knowing about — see the note
|
|
87
|
+
at the bottom.
|
|
88
|
+
|
|
89
|
+
## Home advantage: none
|
|
90
|
+
|
|
91
|
+
Identical squads, 3,000 matches: home 24.1%, draw 51.9%, away 24.0%. Symmetric
|
|
92
|
+
to within a tenth of a point.
|
|
93
|
+
|
|
94
|
+
## Set-piece takers
|
|
95
|
+
|
|
96
|
+
Same squad, only the kicker slots move. Opponent fixed, 3,000 matches each.
|
|
97
|
+
|
|
98
|
+
| FK / PK taker | Win % | Points per match |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| FW / FW (same player) | 25.5 | 1.271 |
|
|
101
|
+
| FW / FW (two players) | 24.1 | 1.221 |
|
|
102
|
+
| OMF / FW | 20.4 | 1.145 |
|
|
103
|
+
| DF / DF | 20.4 | 1.149 |
|
|
104
|
+
| GK / GK | 17.4 | 1.066 |
|
|
105
|
+
|
|
106
|
+
Conversion tracks the taker's shooting, so the kicks belong with the best
|
|
107
|
+
shooter. Giving both roles to one player costs nothing.
|
|
108
|
+
|
|
109
|
+
## How many friendlies a conclusion needs
|
|
110
|
+
|
|
111
|
+
Two squads with a known true gap, resampled 20,000 times at each sample size.
|
|
112
|
+
|
|
113
|
+
| Friendlies | Correct conclusion | Wrong conclusion | Tied |
|
|
114
|
+
| --- | --- | --- | --- |
|
|
115
|
+
| 1 | 35.3% | 16.7% | 48.0% |
|
|
116
|
+
| 3 | 53.2% | 19.6% | 27.2% |
|
|
117
|
+
| 5 | 61.6% | 18.4% | 20.0% |
|
|
118
|
+
| 10 | 73.0% | 14.5% | 12.5% |
|
|
119
|
+
| 20 | 85.0% | 8.7% | 6.4% |
|
|
120
|
+
| 30 | 90.7% | 5.8% | 3.5% |
|
|
121
|
+
| 50 | 96.4% | 2.3% | 1.3% |
|
|
122
|
+
| 100 | 99.4% | 0.4% | 0.1% |
|
|
123
|
+
|
|
124
|
+
And the false-positive side — **identical** squads, by three different
|
|
125
|
+
definitions of "one side looks better":
|
|
126
|
+
|
|
127
|
+
| Friendlies | Record not exactly level | Win-loss gap ≥ 2 | Significant (sign test, p<0.05) |
|
|
128
|
+
| --- | --- | --- | --- |
|
|
129
|
+
| 1 | 50.7% | 0.0% | 0.0% |
|
|
130
|
+
| 5 | 75.4% | 34.9% | 0.0% |
|
|
131
|
+
| 10 | 82.7% | 50.8% | 0.8% |
|
|
132
|
+
| 30 | 89.8% | 70.1% | 3.0% |
|
|
133
|
+
| 100 | 93.8% | 82.1% | 4.1% |
|
|
134
|
+
|
|
135
|
+
**The first column is a trap and was published on its own in an earlier version
|
|
136
|
+
of this file.** "Not exactly level" approaches 100% for any pair of squads simply
|
|
137
|
+
because an exact tie gets rarer as the sample grows — it measures tie-rarity, not
|
|
138
|
+
distinguishability.
|
|
139
|
+
|
|
140
|
+
The middle column is the one that matters, because it is what a person actually
|
|
141
|
+
looks at: between squads that are EXACTLY THE SAME, one is two or more wins clear
|
|
142
|
+
51% of the time at ten matches and 82% at a hundred. The gap does not shrink with
|
|
143
|
+
more play; it grows, because draws dominate and the decided matches are a coin
|
|
144
|
+
flip.
|
|
145
|
+
|
|
146
|
+
The last column is the control: a significance test almost never fires falsely,
|
|
147
|
+
sitting near the 5% the threshold allows. That is the gap between "looks
|
|
148
|
+
decisive" and "is decisive".
|
|
149
|
+
|
|
150
|
+
## Does the Skill actually help? — and which part of it
|
|
151
|
+
|
|
152
|
+
Three hand-built squads, each exactly 212 and validator-clean, 8,000 matches per
|
|
153
|
+
pairing, home and away balanced (`probes/skill-value.mts`):
|
|
154
|
+
|
|
155
|
+
- **naive** — 5/5/5/4 on everyone, the squad the tool descriptions alone produce.
|
|
156
|
+
- **shaped** — roles differentiated, defense-leaning, both set pieces on the best
|
|
157
|
+
shooter, conventional 20-point keeper.
|
|
158
|
+
- **shaped + cheap keeper** — the same, with the keeper at 10 and those points
|
|
159
|
+
moved outfield.
|
|
160
|
+
|
|
161
|
+
| Matchup | Win % | Draw % | Loss % |
|
|
162
|
+
| --- | --- | --- | --- |
|
|
163
|
+
| shaped + cheap keeper **vs** naive | 52.1 | 40.4 | 7.5 |
|
|
164
|
+
| shaped (keeper 20) **vs** naive | 43.6 | 45.9 | 10.6 |
|
|
165
|
+
| shaped + cheap keeper **vs** shaped | 25.7 | 55.0 | 19.3 |
|
|
166
|
+
|
|
167
|
+
Read the second and third rows together: **role shaping alone accounts for most
|
|
168
|
+
of the advantage**, and the keeper budget adds a real but secondary edge on top.
|
|
169
|
+
An agent that only differentiates its roles and never touches the keeper beats a
|
|
170
|
+
naive squad 43.6% of the time and loses 10.6% — a four-to-one record, with the
|
|
171
|
+
rest drawn.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Note for the maintainers
|
|
176
|
+
|
|
177
|
+
Spending the minimum on the goalkeeper is monotonically better across the whole
|
|
178
|
+
range (1.406 vs 0.991 points per match). Anyone who runs a few hundred
|
|
179
|
+
simulations will find it, and agents playing through the MCP server will find it
|
|
180
|
+
faster than people will.
|
|
181
|
+
|
|
182
|
+
It is worth keeping in proportion, though: against a naive squad, role shaping
|
|
183
|
+
alone wins 43.6% and adding the cheap keeper takes that to 52.1%. Head to head,
|
|
184
|
+
the cheap-keeper version beats the conventional-keeper version 25.7% to 19.3%
|
|
185
|
+
with 55% drawn. So it is an edge, not the game. The thing that actually
|
|
186
|
+
decides matches is whether players have distinct roles at all.
|
|
187
|
+
|
|
188
|
+
`SKILL.md` therefore states the *principle* — points spent on the keeper do less
|
|
189
|
+
work, because of how the engine aggregates — rather than a "set your keeper to
|
|
190
|
+
10" instruction. That is the honest version and leaves the choice with the
|
|
191
|
+
player. If the keeper is ever given team contribution or a budget floor, re-run
|
|
192
|
+
these probes and rewrite both files.
|