pog-mcp 0.9.14 → 0.9.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pog-mcp",
3
- "version": "0.9.14",
3
+ "version": "0.9.15",
4
4
  "type": "module",
5
5
  "description": "MCP server that lets an AI agent play Proof of Goal — wallet, sign-in, squad building, and matches as typed tools.",
6
6
  "license": "MIT",
@@ -664,10 +664,11 @@ sets it — there is no `fair:` outside the engine's own tests, and every engine
664
664
  input is built from an explicit field list — so it is a global constant in
665
665
  practice, and setting it on both squads IS the rule change.
666
666
 
667
- **Not swept: the zone-press coefficient.** Its selector is broken — every
668
- candidate weight is `0.0`, so `weightedPick` falls back to "the first non-GK
669
- slot" — and a coefficient in front of an undefined selection measures nothing.
670
- Forbidden until the selector is decided.
667
+ **Not swept: the zone-press coefficient.** At the time its selector was broken —
668
+ every candidate weight was `0.0`, so `weightedPick` fell back to "the first non-GK
669
+ slot" — and a coefficient in front of an undefined selection measures nothing. The
670
+ selector was fixed in #730 (`zp-selector.mts` below); the coefficient is sweepable
671
+ now but has not been swept.
671
672
 
672
673
  ### The statistic, and why not the obvious one
673
674
 
@@ -1810,7 +1811,9 @@ another is classified as a `swap` in `FormationPitch`, which calls `handleDrop`
1810
1811
  so the drop handler wired there never fires. One drag reaches this at team creation; an
1811
1812
  existing team cannot be reordered that way.
1812
1813
 
1813
- **The mechanism is unidentified, and both candidates are back open.**
1814
+ **The mechanism is unidentified, and both candidates are back open.** *(Superseded:
1815
+ `zp-selector.mts` below identified it — the zone-press fallback, removed by #730. The
1816
+ paragraphs that follow are kept as the record of why the first ablation missed it.)*
1814
1817
 
1815
1818
  | candidate | test | result |
1816
1819
  | --- | --- | --- |
@@ -4279,3 +4282,183 @@ literal early broke one seven times; the last was a markdown table's `` `7036372
4279
4282
  the edit script itself. No tsconfig includes those. The remedy is asserting the match count on
4280
4283
  every replacement, so a broken script dies before it touches the file — which is exactly what
4281
4284
  happened. A smoke run is a behavioural check, not the parse gate.
4285
+
4286
+ ## Is the slot-order lever the zone-press fallback? (`zp-selector.mts`)
4287
+
4288
+ Yes — on the one core where a lever exists, and a two-line engine change removes
4289
+ it. This settles the question the depth-cap note above left open ("the mechanism
4290
+ is unidentified, and both candidates are back open"): the candidate it withdrew
4291
+ for the wrong reason was the right one.
4292
+
4293
+ ### What was broken, exactly
4294
+
4295
+ `tryZonePress` drew the presser with the DEFENSIVE appearance table, whose
4296
+ `SCENE.ZP` column is all-zero for every position (`D_*_APPR[ZP] = 0/0/0/0`), so
4297
+ `weightedPick` had no candidate and fell through to `players[first non-GK]` on
4298
+ every zone press. `getPoint` then drew the possessing side's responder on the
4299
+ same scene with the same table — same fallback, same slot. Both sides of every
4300
+ zone press were therefore the first non-GK slot of their team, a slot the manager
4301
+ chooses freely at team creation. The offence column `O_*_APPR[ZP]` (FW 1.0, OMF
4302
+ 0.9, DMF 0.8, DF 0.7) was populated and unreachable.
4303
+
4304
+ The fix draws both with the offence table — the side `getPoint` already scores
4305
+ the presser as (`O_*_RATE[ZP]`). It invents no table; it revives one.
4306
+
4307
+ It also had to define one case the old code left undefined. `getPoint`'s DMF
4308
+ branch carries a ported defect: on the home side it subtracts the ball carrier
4309
+ `this.kiten` rather than the acting player. For a zone press `this.kiten` indexes
4310
+ the POSSESSING team while the calculation is the PRESSING team's, so it names an
4311
+ arbitrary same-numbered player — the keeper included — and makes the press depend
4312
+ on the OPPONENT's slot order. The fallback hid this: the presser was always the
4313
+ first non-GK slot, so a DMF pressed only when a manager happened to put one
4314
+ there. Drawing by weight makes DMF pressers ~19% of presses, so `SCENE.ZP` now
4315
+ takes the acting player, like every other position in that switch. The defect is
4316
+ untouched everywhere it was measured. That this path really was unreachable is
4317
+ now asserted, not assumed: reverting the two selector lines in a source copy
4318
+ reproduces the pre-#725 corpus digest exactly.
4319
+
4320
+ ### Method
4321
+
4322
+ Four cores (`flat`, `shaped`, `gkheavy`, `gkmin`), each against a basket of the
4323
+ other three, both modes exposure-weighted (knockout 8.1%). Units are pp of WIN
4324
+ RATE on that mix. Two engines run the same matches from the same seeds: the
4325
+ shipped engine and a copy with the two selector lines patched in a `mkdtemp`
4326
+ directory (the `appr-role.mts` pattern; nothing is written inside the
4327
+ repository). Three readings per core:
4328
+
4329
+ 1. **Sweep**: swap each slot j into the first non-GK slot, paired against the
4330
+ base order, on both engines, plus the paired **shipped − fixed contrast** on
4331
+ the same matches. If the lever is the fallback, the shipped column moves and
4332
+ the fixed column does not.
4333
+ 2. **Best permutation, selected TWICE — once per engine**: 120 permutations
4334
+ scored at 200 seeds, the winner published at 1500 fresh seeds on both
4335
+ engines (selection column and publication column are different seeds, so the
4336
+ selection optimism is not in the published number). The search runs once
4337
+ against the shipped engine and once, independently, against the fixed one.
4338
+ Optimising only against the shipped engine can show the old exploit is gone;
4339
+ it cannot show the fixed engine has no lever of its own, because a
4340
+ permutation the fixed engine would favour is never tried.
4341
+ 3. **Base shift**: fixed − shipped for the base order itself, descriptive only
4342
+ and outside the family, because it answers a different question (does the fix
4343
+ move the level, not the lever).
4344
+
4345
+ Pre-registered family m=132, Bonferroni at α=0.05 → |t| ≥ 3.56 (df 1499). The
4346
+ family is every comparison the run prints with a significance mark — each swap
4347
+ on both engines (72), every swap's own engine contrast (36, not a preselected
4348
+ "widest": nothing picks one and the readings below use several), and the two
4349
+ selected permutations on both engines with their contrasts (16 + 4). Every
4350
+ interval is a paired per-seed t interval.
4351
+
4352
+ Controls, every one of which aborts the run if it fails: the import-rewritten
4353
+ engine with no selector change reproduces 1000 stock matches byte-for-byte
4354
+ (whole `MatchResult`); a fallback census over 400 matches counts 1056 zero-weight
4355
+ fallbacks on the shipped engine (one per zone press) and 0 on the fixed one;
4356
+ mirror cells (true z = 0) read z = 0.3/0.2 shipped and 1.2/1.3 fixed; and the
4357
+ base order replayed reproduces itself exactly on both engines.
4358
+
4359
+ ### Result
4360
+
4361
+ | core | shipped-selected perm, on shipped | the same perm, on fixed | shipped − fixed (paired) | **fixed-selected perm, on fixed** | sweep swaps significant, shipped / fixed |
4362
+ | --- | --- | --- | --- | --- | --- |
4363
+ | `shaped` | **+5.20pp** [+0.45, +9.95], t 3.9 | +0.76pp [−3.97, +5.50], t 0.6 | **+4.44pp** [+0.13, +8.74], t 3.7 | **+0.02pp** [−4.60, +4.65], t 0.0 | **4** / 0 |
4364
+ | `flat` | −1.57pp [−4.44, +1.30], t −1.9 | −0.79pp [−3.71, +2.13] | −0.78pp [−3.46, +1.90], t −1.0 | −0.21pp [−3.19, +2.76], t −0.3 | 1 / 0 |
4365
+ | `gkheavy` | +1.10pp [−2.20, +4.41], t 1.2 | +1.18pp [−2.16, +4.52] | −0.08pp [−3.00, +2.84], t −0.1 | +0.82pp [−2.50, +4.13], t 0.9 | 0 / 0 |
4366
+ | `gkmin` | −0.45pp [−3.80, +2.89], t −0.5 | −1.65pp [−5.12, +1.82] | +1.19pp [−1.87, +4.25], t 1.4 | +0.39pp [−3.06, +3.85], t 0.4 | 0 / 0 |
4367
+
4368
+ `shaped` is the only core with a lever on the shipped engine, and the fix is what
4369
+ takes it away — with one honest limit on how far that can be pushed.
4370
+
4371
+ What is RESOLVED: the paired engine contrast. The best permutation's +5.20pp,
4372
+ which put a FW (t28) in the slot a DF (t12) held, reads +0.75pp on the fixed
4373
+ engine, and the shipped−fixed difference over the same permutation and the same
4374
+ matches is **+4.44pp [+0.13, +8.74], t 3.7** — a resolved, corrected difference,
4375
+ so the drop is the fix and not noise. The sweep says the same thing with nine
4376
+ paired swaps instead of one permutation: the four significant ones (slots 7–10,
4377
+ +5.19 to +7.02pp, an OMF or FW into the first slot) read +0.46 to −0.31pp fixed,
4378
+ every one of their contrasts significant (t 4.3 to 6.5), and the range of the
4379
+ whole sweep collapses from 7.02pp to 1.71pp — 0 of 9 significant.
4380
+
4381
+ What that one column does NOT resolve: the residual on the SHIPPED-selected
4382
+ permutation. +0.76pp carries [−3.97, +5.50], whose upper bound still reaches
4383
+ past the 5pp gate, so that reading alone cannot certify the leftover as *below*
4384
+ it — non-significance is not equivalence, and the probe says `UNDECIDED vs the
4385
+ gate` there rather than `REMOVED`.
4386
+
4387
+ What answers the question it was actually asked: give the FIXED engine its own
4388
+ search. A permutation chosen against the shipped engine is not what a manager
4389
+ optimising against the fixed one would ever pick, so the shipped-selected winner
4390
+ cannot speak to whether a lever remains. Under an independent 120-permutation
4391
+ search on the fixed engine, **every core comes in below the gate**: `shaped`
4392
+ +0.02pp [−4.60, +4.65], `flat` −0.21pp, `gkheavy` +0.82pp, `gkmin` +0.39pp, none
4393
+ significant (|t| ≤ 0.9). That is an equivalence result at the gate, on all four
4394
+ cores, for the engine that ships after this fix. The sweep says the same with
4395
+ nine paired swaps: 0 of 9 significant on every core.
4396
+
4397
+ `flat` shows the lever's other face: moving its OMF into slot 1 is a LOSS of
4398
+ 3.48pp (t −4.5) on the shipped engine and +0.30pp on the fixed one, contrast
4399
+ −3.78pp (t −4.9). The fallback was hurting managers who put the wrong player
4400
+ first as much as it rewarded the right one.
4401
+
4402
+ The fixed engine does not create a lever on any core: 0 of 36 sweep swaps are
4403
+ significant across the four cores, no fixed-engine gain clears the gate in
4404
+ either direction, and its own independent search finds nothing (above).
4405
+ One isolated contrast stays marked: `gkheavy` slot 10, −2.77pp (t −3.6), with
4406
+ neither engine's own swap significant (−1.49pp t −1.8 shipped, +1.28pp t 1.7
4407
+ fixed) — a resolved difference between two unresolved readings, reported and not
4408
+ read as a lever. `gkmin` slot 5 (−2.27pp, t −3.5) cleared the old m=88 bar and
4409
+ does not clear the m=132 one; it is no longer marked.
4410
+
4411
+ Who presses under the fix, from the revived weights, on a 4-3-3: FW 36%, DMF
4412
+ 19%, OMF 11%, DF 34% (on `gkmin`'s 4-4-2: 24/20/22/34). Under the shipped
4413
+ engine: the first slot, a DF in all four cores, 100% of the time.
4414
+
4415
+ Base shift for the record: `shaped` +2.75pp [−0.32, +5.81], `flat` −1.20, `gkheavy`
4416
+ −0.57, `gkmin` −2.64pp [−4.94, −0.34]. The fix moves levels between cores by a
4417
+ few points, as any change to who presses must.
4418
+
4419
+ ### What it means
4420
+
4421
+ - **The +5.7pp of the depth-cap note was the fallback.** Its clone ablation
4422
+ withdrew the candidate because it held POSITION fixed and varied only
4423
+ attributes — but what the fallback delivers into the first slot is a player
4424
+ whose `getPoint` branch depends on position, so a same-position swap could
4425
+ not see it. Disabling the path and re-running the same permutations, which
4426
+ that note said was needed, is what this probe did.
4427
+ - **The lever is gone once the fix ships**, so the Skill records nothing about
4428
+ slot order: a lever that no longer exists must not be documented as one.
4429
+ - **Determinism** (P5): completed matches are persisted as events and do not
4430
+ change; the live stream and the replay both read those persisted events, so
4431
+ nothing about a match is observable before it is persisted. Every future
4432
+ match changes — a zone press happens about once a match (1.07 on the test
4433
+ teams, up to 7), and the real draw consumes RNG the fallback did not, so every
4434
+ later draw in that match shifts. The scheduler is one Vercel Cron invocation
4435
+ a minute, not a fleet of replicas: a deploy lands between ticks, and the only
4436
+ match that can straddle it is one whose invocation died mid-tick and whose
4437
+ 5-minute lease is then reclaimed by a tick running the new build. That match
4438
+ is simulated once, by the new build, from the stored snapshot and seed — the
4439
+ old build never produced a result for it. Determinism is per build; every
4440
+ engine change (#737, #786, #794 before this one) has made such a boundary, and
4441
+ an engine-replay verification whose window spans a deploy sees it as an
4442
+ ENGINE boundary.
4443
+ - **Workload tally** (F-1/F-2): predicate D — actor plus responder minus
4444
+ zero-weight fallbacks — is unchanged in meaning, and on the shipped engine
4445
+ every fallback was a zone-press engagement, so D excluded zone presses
4446
+ entirely. Now it counts them: on the test teams that is **+0.10 engagements
4447
+ per player-match, +6.1%** of the tally (5.75% of the fixed engine's D). The
4448
+ F-2 curve is `cond = 5 + a − b·L/(L+L₀)` with `b = 0.4914`, `L₀ = 59.03`; a
4449
+ 6.1% inflation of the load L moves condition by at most **0.0075** points
4450
+ (at L = L₀; 0.0006 at the neutral load 1.146, 0.0047 at the p95 14.76) — an
4451
+ order of magnitude below the 0.5 band step and far below anything
4452
+ `cond-effect.mts` could detect. `PLAYER_WORKLOAD_VERSION` is therefore NOT
4453
+ bumped: the version is a hard filter in the form store, so a bump erases
4454
+ every accumulated load league-wide — a reset three orders of magnitude larger
4455
+ than the drift it would prevent. The boundary is recorded here instead, and
4456
+ `L_neutral`/`L₀` are re-measured by `engagement-load.mts` on the first
4457
+ production window after the deploy (C-2 follow-up).
4458
+ - **Open sub-decision, recorded**: the responder. The original appears to have
4459
+ skipped the defensive role at ZP entirely (port comment at `game.pl:1924`);
4460
+ drawing it from the same offence column is the smallest change that invents
4461
+ nothing, and "no responder, team term only" was not adopted because the exact
4462
+ original expression cannot be recovered.
4463
+
4464
+ 5,648,400 matches in about a minute. Whole run: `zp-selector.mts`.