poke-crucible 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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +192 -0
  3. package/dist/battle/pairing.d.ts +25 -0
  4. package/dist/battle/pairing.js +59 -0
  5. package/dist/battle/pairing.js.map +1 -0
  6. package/dist/battle/pool.d.ts +25 -0
  7. package/dist/battle/pool.js +97 -0
  8. package/dist/battle/pool.js.map +1 -0
  9. package/dist/battle/run-battle.d.ts +13 -0
  10. package/dist/battle/run-battle.js +49 -0
  11. package/dist/battle/run-battle.js.map +1 -0
  12. package/dist/battle/worker.d.ts +1 -0
  13. package/dist/battle/worker.js +21 -0
  14. package/dist/battle/worker.js.map +1 -0
  15. package/dist/cli.d.ts +2 -0
  16. package/dist/cli.js +326 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/critic/agent-sdk-model.d.ts +27 -0
  19. package/dist/critic/agent-sdk-model.js +21 -0
  20. package/dist/critic/agent-sdk-model.js.map +1 -0
  21. package/dist/critic/critique.d.ts +21 -0
  22. package/dist/critic/critique.js +51 -0
  23. package/dist/critic/critique.js.map +1 -0
  24. package/dist/critic/evidence.d.ts +24 -0
  25. package/dist/critic/evidence.js +17 -0
  26. package/dist/critic/evidence.js.map +1 -0
  27. package/dist/critic/llm.d.ts +34 -0
  28. package/dist/critic/llm.js +23 -0
  29. package/dist/critic/llm.js.map +1 -0
  30. package/dist/critic/parse.d.ts +5 -0
  31. package/dist/critic/parse.js +84 -0
  32. package/dist/critic/parse.js.map +1 -0
  33. package/dist/critic/prompt.d.ts +14 -0
  34. package/dist/critic/prompt.js +56 -0
  35. package/dist/critic/prompt.js.map +1 -0
  36. package/dist/critic/types.d.ts +52 -0
  37. package/dist/critic/types.js +2 -0
  38. package/dist/critic/types.js.map +1 -0
  39. package/dist/designer/agent-sdk-model.d.ts +30 -0
  40. package/dist/designer/agent-sdk-model.js +62 -0
  41. package/dist/designer/agent-sdk-model.js.map +1 -0
  42. package/dist/designer/design.d.ts +12 -0
  43. package/dist/designer/design.js +218 -0
  44. package/dist/designer/design.js.map +1 -0
  45. package/dist/designer/fitness.d.ts +25 -0
  46. package/dist/designer/fitness.js +24 -0
  47. package/dist/designer/fitness.js.map +1 -0
  48. package/dist/designer/model.d.ts +14 -0
  49. package/dist/designer/model.js +27 -0
  50. package/dist/designer/model.js.map +1 -0
  51. package/dist/designer/mutate.d.ts +31 -0
  52. package/dist/designer/mutate.js +209 -0
  53. package/dist/designer/mutate.js.map +1 -0
  54. package/dist/designer/rng.d.ts +17 -0
  55. package/dist/designer/rng.js +40 -0
  56. package/dist/designer/rng.js.map +1 -0
  57. package/dist/designer/types.d.ts +103 -0
  58. package/dist/designer/types.js +2 -0
  59. package/dist/designer/types.js.map +1 -0
  60. package/dist/evaluate.d.ts +11 -0
  61. package/dist/evaluate.js +99 -0
  62. package/dist/evaluate.js.map +1 -0
  63. package/dist/gauntlet/gauntlet.d.ts +18 -0
  64. package/dist/gauntlet/gauntlet.js +41 -0
  65. package/dist/gauntlet/gauntlet.js.map +1 -0
  66. package/dist/gauntlet/teams-g1.d.ts +23 -0
  67. package/dist/gauntlet/teams-g1.js +476 -0
  68. package/dist/gauntlet/teams-g1.js.map +1 -0
  69. package/dist/gauntlet/teams-g2.d.ts +14 -0
  70. package/dist/gauntlet/teams-g2.js +725 -0
  71. package/dist/gauntlet/teams-g2.js.map +1 -0
  72. package/dist/index.d.ts +25 -0
  73. package/dist/index.js +24 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/llm/agent-query.d.ts +21 -0
  76. package/dist/llm/agent-query.js +70 -0
  77. package/dist/llm/agent-query.js.map +1 -0
  78. package/dist/pilot/damage.d.ts +34 -0
  79. package/dist/pilot/damage.js +75 -0
  80. package/dist/pilot/damage.js.map +1 -0
  81. package/dist/pilot/eval-pilot.d.ts +39 -0
  82. package/dist/pilot/eval-pilot.js +161 -0
  83. package/dist/pilot/eval-pilot.js.map +1 -0
  84. package/dist/pilot/pilot.d.ts +71 -0
  85. package/dist/pilot/pilot.js +290 -0
  86. package/dist/pilot/pilot.js.map +1 -0
  87. package/dist/ranking.d.ts +25 -0
  88. package/dist/ranking.js +40 -0
  89. package/dist/ranking.js.map +1 -0
  90. package/dist/seed.d.ts +13 -0
  91. package/dist/seed.js +42 -0
  92. package/dist/seed.js.map +1 -0
  93. package/dist/team.d.ts +29 -0
  94. package/dist/team.js +100 -0
  95. package/dist/team.js.map +1 -0
  96. package/dist/types.d.ts +77 -0
  97. package/dist/types.js +8 -0
  98. package/dist/types.js.map +1 -0
  99. package/package.json +57 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 johnjohto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # poke-crucible
2
+
3
+ Run masses of headless **Gen 3 OU** battles to rank Teams by how often they win.
4
+ Both sides of every Battle are driven by one fixed, competent heuristic **Pilot**,
5
+ so a result measures the **Team**, not the player. Three capabilities, each a
6
+ single seam built on the last:
7
+
8
+ - **`evaluate`** (Slice 1) — rank Teams by **Strength** against a fixed Gauntlet, or round-robin.
9
+ - **`critique`** (Slice 2) — an LLM **Critic** explains one Team's weaknesses and matchup holes.
10
+ - **`design`** (Slice 3) — the **Designer** evolves stronger Teams, with the LLM proposing mutations and naming the Archetypes it discovers.
11
+
12
+ Vocabulary lives in [`CONTEXT.md`](CONTEXT.md); the specs are the
13
+ [PRDs under `docs/`](docs); decisions are the [ADRs](docs/adr).
14
+
15
+ ## Install & build
16
+
17
+ ```bash
18
+ npm install
19
+ npm run build # compiles TypeScript to dist/
20
+ ```
21
+
22
+ Requires Node ≥ 20. The stack is in-process TypeScript on the `@pkmn/*` toolkit
23
+ (`@pkmn/sim`, `@pkmn/sets`, `@pkmn/data`) — no server, no network, no Python
24
+ (ADR 0001).
25
+
26
+ ## CLI
27
+
28
+ ```bash
29
+ # Absolute Strength vs the fixed Gauntlet
30
+ node dist/cli.js eval --mode gauntlet team.txt
31
+
32
+ # Relative Strength: rank several of your own Teams round-robin
33
+ node dist/cli.js eval --mode round-robin teamA.txt teamB.txt teamC.txt
34
+
35
+ # Pipe a single Team paste in on stdin
36
+ cat team.txt | node dist/cli.js eval
37
+ ```
38
+
39
+ Options: `--mode <gauntlet|round-robin>` (default `gauntlet`), `--battles <N>`
40
+ (default 100), `--seed <string>`, `--gauntlet-version <v>`, `--out <file>`
41
+ (default `poke-crucible-results.json`). Each team file is standard Showdown
42
+ export/paste text; multiple Teams in one file are separated by
43
+ `=== [gen3ou] Name ===` header lines.
44
+
45
+ The CLI prints a ranked Strength table (with a 95% confidence interval and a
46
+ per-matchup breakdown) and writes the full structured result to `--out`.
47
+
48
+ ## Programmatic API
49
+
50
+ The base seam is `evaluate` — the Critic and Designer build on it, not on the CLI:
51
+
52
+ ```ts
53
+ import { evaluate } from 'poke-crucible';
54
+
55
+ const result = await evaluate({
56
+ teams: showdownPasteText, // string or array of pastes/{name,paste}
57
+ opposition: { mode: 'gauntlet' }, // or { mode: 'round-robin' }
58
+ battlesPerPairing: 100,
59
+ seed: 'my-seed',
60
+ });
61
+ // result: ranked Strength rows (per-matchup win-rates tagged with Archetype),
62
+ // the seed, the Gauntlet version, and any illegal-Team validation errors.
63
+ ```
64
+
65
+ Illegal Teams never throw past the seam — they come back as `result.errors`,
66
+ named, so a bad Team can't silently poison a run.
67
+
68
+ ## How it works
69
+
70
+ - **Pilot** (ADR 0002): one shared Gen 3 heuristic, identical on both sides,
71
+ built in competence layers — secure a KO → pivot out of a losing matchup →
72
+ recover → lay Spikes / spin → spread Toxic/burn/paralysis → set up → max
73
+ damage. Every Strength number is relative to this Pilot.
74
+ - **Determinism**: a master seed threads through `@pkmn/sim`'s PRNG; each
75
+ pairing's Battles derive sub-seeds deterministically and sides are swapped
76
+ 50/50, so a fixed seed reproduces identical results.
77
+ - **Gauntlet** (ADR 0003): a fixed, **versioned** roster of Archetype-labeled
78
+ Gen 3 OU Teams. Every result records the version, so `62% Strength` means the
79
+ same thing next week. Two versions ship: **`g1`** (default; hand-authored) and
80
+ **`g2`** (real [Smogon ADV OU sample teams](https://www.smogon.com/forums/threads/adv-ou-sample-teams.3687813/),
81
+ fetched verbatim, `--gauntlet-version g2`). `g1` stays the default baseline;
82
+ swapping it is a deliberate rebaseline.
83
+ - **Parallelism**: `--concurrency <N>` (or `POKE_CONCURRENCY`) runs Battles across
84
+ `N` worker threads. All Battles of a run are dispatched together so the pool
85
+ stays saturated; results are **byte-identical** to serial (each Battle keeps
86
+ its own seed). Default is 1 (inline). It pays off on big, expensive runs — the
87
+ Designer, large evaluations — where it gives roughly a 2× speedup on 4 workers;
88
+ for small/fast runs the thread overhead can make it a wash, hence the default.
89
+
90
+ ### Gauntlet provenance (a deliberate deviation)
91
+
92
+ The design envisioned sourcing the Gauntlet from Smogon's sample teams + usage
93
+ stats via fetch-and-parse. That is deferred: the `g1` roster is **hand-authored**
94
+ from canonical Smogon Gen 3 OU sets (see
95
+ [`src/gauntlet/teams-g1.ts`](src/gauntlet/teams-g1.ts)). Because the Gauntlet is
96
+ versioned and every result records its version, re-sourcing later is a `g2`
97
+ bump, not a rewrite — nothing silently drifts.
98
+
99
+ ## Critic (LLM analysis — Slice 2)
100
+
101
+ Turn an evaluation into a readable, grounded critique of one Team:
102
+
103
+ ```bash
104
+ node dist/cli.js critique team.txt
105
+ node dist/cli.js critique --model claude-sonnet-5 --effort low --battles 100 team.txt
106
+ ```
107
+
108
+ The Critic runs the evaluation, then asks Claude (via the **Claude Agent SDK**)
109
+ to explain the Team's weaknesses, the Archetypes that exploit them, the
110
+ mechanism, and concrete legal fixes — writing a structured `Critique` JSON. It
111
+ is told the Pilot under-rates stall (ADR 0004), so it caveats defense-dependent
112
+ verdicts rather than presenting them as ground truth.
113
+
114
+ **Auth (individual use):** the Critic runs on your **Claude subscription**, not
115
+ a pay-per-token key. Log in with Claude Code (or set `CLAUDE_CODE_OAUTH_TOKEN`),
116
+ and make sure **`ANTHROPIC_API_KEY` is unset** — it shadows the subscription
117
+ OAuth token and reverts to per-token billing. The model is called with all tools
118
+ disabled and low reasoning effort; nothing runs on your filesystem.
119
+
120
+ Programmatically the seam is `critique(input, model)` / `critiqueTeam(paste,
121
+ model, opts)`; the model is any `CritiqueModel` (use `StubCritiqueModel` in tests,
122
+ `AgentSdkCritiqueModel` live). See [`docs/prd-slice-2-critic.md`](docs/prd-slice-2-critic.md).
123
+
124
+ ## Designer (discovery — Slice 3)
125
+
126
+ Evolve stronger Teams automatically. A population of legal Teams is scored each
127
+ generation by a **composite fitness** (Gauntlet Strength + peer round-robin + a
128
+ worst-matchup robustness term), bred by mutation and crossover, with the LLM
129
+ proposing variants — driven by the Critic's findings — and naming the Archetypes
130
+ it discovers.
131
+
132
+ ```bash
133
+ # Evolve from a starting Team (omit the file to search from scratch)
134
+ node dist/cli.js design --population 8 --generations 4 team.txt
135
+
136
+ # Random operators only — no LLM, no subscription call
137
+ node dist/cli.js design --no-llm --population 8 --generations 4
138
+ ```
139
+
140
+ Options: `--population`, `--generations`, `--battles` (per fitness pairing),
141
+ `--model` / `--effort` (Designer LLM), `--no-llm`, `--seed`, `--gauntlet-version`,
142
+ `--out`. Every candidate is validated legal Gen 3 OU; the run is deterministic
143
+ under its seed and reports each Team's lineage, Archetype name, fitness
144
+ components, plus the budget used and total Battles run. To bound compute it
145
+ caches unchanged Teams, screens cheaply before spending full battles on
146
+ contenders, and honours a hard battle cap.
147
+
148
+ Programmatically the seam is `design(input, model, options)`; the model is any
149
+ `DesignerModel` (`StubDesignerModel` for tests, `AgentSdkDesignerModel` live),
150
+ and an optional `critic` lets the Critic drive the mutation proposals. Auth is
151
+ the same subscription path as the Critic. See
152
+ [`docs/prd-slice-3-designer.md`](docs/prd-slice-3-designer.md).
153
+
154
+ **Fitness is deliberately not raw win-rate (ADR 0004)** — it rewards well-rounded
155
+ Teams over glass cannons and dampens the Pilot's offense lean (see the limitation
156
+ below for how far that goes).
157
+
158
+ ## Testing (the trust gate)
159
+
160
+ ```bash
161
+ npm run typecheck
162
+ npm test
163
+ ```
164
+
165
+ The suite asserts external behavior only, through the `evaluate()` seam (never
166
+ the Pilot's individual move choices): determinism, sanity ordering (a strong
167
+ Team out-Strengths a weak one), **archetype non-bias** (a solid stall Team stays
168
+ competitive in a head-to-head ladder of the eight archetypes and can beat
169
+ offense — the live check that the Pilot isn't biased against defense), legality,
170
+ and stable win-rates. Every Gauntlet Team is asserted legal Gen 3 OU.
171
+
172
+ ### Known limitation: the Pilot's offense lean
173
+
174
+ The shared greedy Pilot systematically **under-rates stall** — offense plays
175
+ near-optimally under greedy heuristics while stall needs long-term planning. In
176
+ an archetype-vs-archetype ladder the best stall Team lands mid-lower (≈38%
177
+ Strength, 6th of 8), not mid-pack.
178
+
179
+ This was probed exhaustively ([ADR 0004](docs/adr/0004-greedy-pilot-underrates-stall-search-deferred.md)):
180
+ three Pilot improvements (anticipatory switching, tempo tweaks, a positional-eval
181
+ Pilot) all failed to lift stall; real-sim look-ahead is infeasible (`@pkmn/sim`
182
+ state can't be resumed after deserialization); and the Designer's composite
183
+ fitness only *partly* corrects the lean — stall's worst matchups are genuinely
184
+ ~0% under the Pilot, so a robustness term can't recover them.
185
+
186
+ The lean is therefore a **known, bounded, documented property of the greedy-Pilot
187
+ design**, not a bug — every Strength number is explicitly relative to this Pilot
188
+ (ADR 0002). Closing it needs a stronger Pilot (a shallow search or ML policy —
189
+ deferred) or a non-Pilot signal of a Team's merit; both are future work. The
190
+ Critic caveats defense-dependent verdicts and the Designer's fitness avoids
191
+ rewarding glass cannons, so the slices degrade gracefully rather than silently
192
+ trusting the lean.
@@ -0,0 +1,25 @@
1
+ export interface PairingRecord {
2
+ /** Battles Team A won outright. */
3
+ wins: number;
4
+ ties: number;
5
+ battles: number;
6
+ /** Team A's win-rate in [0, 1]; ties count as half. */
7
+ winRate: number;
8
+ }
9
+ /** One Team-vs-opponent pairing to run, tagged with a caller key. */
10
+ export interface MatchupSpec {
11
+ key: string;
12
+ aPacked: string;
13
+ bPacked: string;
14
+ /** Stable indices for reproducible per-Battle seeds (same as RunPairingOptions). */
15
+ teamIndex: number;
16
+ opponentIndex: number;
17
+ }
18
+ /**
19
+ * Run many pairings at once. Every Battle across every pairing is flattened into
20
+ * one job list and dispatched together, so a worker pool stays saturated instead
21
+ * of idling between pairings (per-pairing dispatch left workers waiting on a
22
+ * single long stall mirror). Seeds are unchanged, so results are identical to
23
+ * running the pairings one at a time.
24
+ */
25
+ export declare function runMatchups(specs: MatchupSpec[], battles: number, master: string, maxTurns?: number): Promise<Map<string, PairingRecord>>;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Pairing runner — sits on top of {@link runBattle}. Given a batch of Team-vs-
3
+ * opponent pairings, it runs every Battle (sides swapped 50/50, so any lead or
4
+ * turn-order edge cancels out) from deterministically derived sub-seeds and
5
+ * returns a win-rate record per pairing. Battles are flattened into one job list
6
+ * so a worker pool stays saturated.
7
+ */
8
+ import { runBattle } from './run-battle.js';
9
+ import { getBattlePool } from './pool.js';
10
+ import { deriveSeed } from '../seed.js';
11
+ /**
12
+ * Run many pairings at once. Every Battle across every pairing is flattened into
13
+ * one job list and dispatched together, so a worker pool stays saturated instead
14
+ * of idling between pairings (per-pairing dispatch left workers waiting on a
15
+ * single long stall mirror). Seeds are unchanged, so results are identical to
16
+ * running the pairings one at a time.
17
+ */
18
+ export async function runMatchups(specs, battles, master, maxTurns) {
19
+ const tally = new Map();
20
+ for (const s of specs)
21
+ tally.set(s.key, { wins: 0, ties: 0 });
22
+ const record = (key, aIsP1, res) => {
23
+ const t = tally.get(key);
24
+ if (res.winner === null)
25
+ t.ties++;
26
+ else if (aIsP1 ? res.winner === 'p1' : res.winner === 'p2')
27
+ t.wins++;
28
+ };
29
+ const jobs = [];
30
+ for (const s of specs) {
31
+ for (let b = 0; b < battles; b++) {
32
+ const aIsP1 = b % 2 === 0;
33
+ jobs.push({
34
+ key: s.key,
35
+ aIsP1,
36
+ p1: aIsP1 ? s.aPacked : s.bPacked,
37
+ p2: aIsP1 ? s.bPacked : s.aPacked,
38
+ seed: deriveSeed(master, s.teamIndex, s.opponentIndex, b),
39
+ });
40
+ }
41
+ }
42
+ const pool = getBattlePool();
43
+ if (pool) {
44
+ await Promise.all(jobs.map((j) => pool.run({ p1: j.p1, p2: j.p2, seed: j.seed, maxTurns }).then((res) => record(j.key, j.aIsP1, res))));
45
+ }
46
+ else {
47
+ for (const j of jobs) {
48
+ const res = await runBattle(j.p1, j.p2, j.seed, { maxTurns });
49
+ record(j.key, j.aIsP1, res);
50
+ }
51
+ }
52
+ const out = new Map();
53
+ for (const s of specs) {
54
+ const { wins, ties } = tally.get(s.key);
55
+ out.set(s.key, { wins, ties, battles, winRate: battles > 0 ? (wins + 0.5 * ties) / battles : 0 });
56
+ }
57
+ return out;
58
+ }
59
+ //# sourceMappingURL=pairing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing.js","sourceRoot":"","sources":["../../src/battle/pairing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,SAAS,EAAqB,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAiB,MAAM,YAAY,CAAC;AAqBvD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAoB,EACpB,OAAe,EACf,MAAc,EACd,QAAiB;IAEjB,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0C,CAAC;IAChE,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,CAAC,GAAW,EAAE,KAAc,EAAE,GAAiB,EAAQ,EAAE;QACtE,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;QAC1B,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI;YAAE,CAAC,CAAC,IAAI,EAAE,CAAC;aAC7B,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,IAAI;YAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvE,CAAC,CAAC;IASF,MAAM,IAAI,GAAU,EAAE,CAAC;IACvB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC;gBACR,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,KAAK;gBACL,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO;gBACjC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO;gBACjC,IAAI,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC;aAC1D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;IAC7B,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,OAAO,CAAC,GAAG,CACf,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACb,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CACpG,CACF,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC9D,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAE,CAAC;QACzC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpG,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { BattleResult } from './run-battle.js';
2
+ import type { PRNGSeed } from '../seed.js';
3
+ export interface BattleJob {
4
+ p1: string;
5
+ p2: string;
6
+ seed: PRNGSeed;
7
+ maxTurns?: number;
8
+ }
9
+ /** One unit of the pool — wraps a real thread, or a fake in tests. */
10
+ export interface PoolWorker {
11
+ exec(job: BattleJob): Promise<BattleResult>;
12
+ terminate(): void;
13
+ }
14
+ export declare class BattlePool {
15
+ private readonly workers;
16
+ private readonly idle;
17
+ private readonly queue;
18
+ constructor(size: number, spawn: () => PoolWorker);
19
+ /** Run one Battle, queued until a worker is free. */
20
+ run(job: BattleJob): Promise<BattleResult>;
21
+ private pump;
22
+ close(): void;
23
+ }
24
+ /** The shared pool for this process, or null when concurrency ≤ 1 (run inline). */
25
+ export declare function getBattlePool(): BattlePool | null;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * A bounded worker-thread pool for running Battles in parallel. The sim is
3
+ * CPU-bound and single-threaded, so real speedup needs threads, not just async.
4
+ *
5
+ * The pool is deterministic-safe: every Battle carries its own derived seed, and
6
+ * callers aggregate results order-independently, so parallel and serial runs
7
+ * produce identical numbers.
8
+ *
9
+ * Concurrency comes from `POKE_CONCURRENCY` (default 1 = run inline, no pool).
10
+ * The worker spawn is injectable so the scheduling logic is unit-testable
11
+ * without real threads.
12
+ */
13
+ import { Worker } from 'node:worker_threads';
14
+ export class BattlePool {
15
+ workers;
16
+ idle;
17
+ queue = [];
18
+ constructor(size, spawn) {
19
+ this.workers = Array.from({ length: Math.max(1, size) }, spawn);
20
+ this.idle = [...this.workers];
21
+ }
22
+ /** Run one Battle, queued until a worker is free. */
23
+ run(job) {
24
+ return new Promise((resolve, reject) => {
25
+ this.queue.push({ job, resolve, reject });
26
+ this.pump();
27
+ });
28
+ }
29
+ pump() {
30
+ while (this.idle.length > 0 && this.queue.length > 0) {
31
+ const worker = this.idle.pop();
32
+ const { job, resolve, reject } = this.queue.shift();
33
+ worker.exec(job).then((res) => {
34
+ this.idle.push(worker);
35
+ resolve(res);
36
+ this.pump();
37
+ }, (err) => {
38
+ this.idle.push(worker);
39
+ reject(err);
40
+ this.pump();
41
+ });
42
+ }
43
+ }
44
+ close() {
45
+ for (const w of this.workers)
46
+ w.terminate();
47
+ }
48
+ }
49
+ /** A PoolWorker backed by a real `worker_threads` thread running `worker.js`. */
50
+ class ThreadWorker {
51
+ worker;
52
+ pending = null;
53
+ constructor() {
54
+ this.worker = new Worker(new URL('./worker.js', import.meta.url));
55
+ this.worker.on('message', (msg) => {
56
+ const p = this.settle();
57
+ if (!p)
58
+ return;
59
+ if (msg.ok && msg.res)
60
+ p.resolve(msg.res);
61
+ else
62
+ p.reject(new Error(msg.error ?? 'battle worker failed'));
63
+ });
64
+ this.worker.on('error', (err) => this.settle()?.reject(err));
65
+ // Idle workers don't keep the process alive; a worker is ref'd only while it
66
+ // has a job in flight (see exec), so the run stays alive but can still exit.
67
+ this.worker.unref();
68
+ }
69
+ /** Clear the pending state and stop keeping the loop alive. */
70
+ settle() {
71
+ const p = this.pending;
72
+ this.pending = null;
73
+ if (p)
74
+ this.worker.unref();
75
+ return p;
76
+ }
77
+ exec(job) {
78
+ return new Promise((resolve, reject) => {
79
+ this.pending = { resolve, reject };
80
+ this.worker.ref(); // hold the loop open until this Battle finishes
81
+ this.worker.postMessage(job);
82
+ });
83
+ }
84
+ terminate() {
85
+ void this.worker.terminate();
86
+ }
87
+ }
88
+ let cached;
89
+ /** The shared pool for this process, or null when concurrency ≤ 1 (run inline). */
90
+ export function getBattlePool() {
91
+ if (cached !== undefined)
92
+ return cached;
93
+ const n = Math.floor(Number(process.env['POKE_CONCURRENCY'] ?? '1'));
94
+ cached = Number.isFinite(n) && n > 1 ? new BattlePool(n, () => new ThreadWorker()) : null;
95
+ return cached;
96
+ }
97
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../../src/battle/pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { type SideID } from '../pilot/pilot.js';
2
+ import type { PRNGSeed } from '../seed.js';
3
+ export interface BattleResult {
4
+ /** 'p1', 'p2', or null for a tie. */
5
+ winner: SideID | null;
6
+ turns: number;
7
+ }
8
+ export interface RunBattleOptions {
9
+ /** Safety net against pathological stall mirrors; forces a tiebreak past this. */
10
+ maxTurns?: number;
11
+ }
12
+ /** Run a single Battle between two packed Teams and report the winner. */
13
+ export declare function runBattle(p1Packed: string, p2Packed: string, seed: PRNGSeed, options?: RunBattleOptions): Promise<BattleResult>;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Battle runner — runs one Gen 3 OU Battle in-process through `@pkmn/sim`'s
3
+ * BattleStream, both sides driven by the {@link PilotPlayer}, from a fixed seed,
4
+ * and reports the winner. No server, no network.
5
+ */
6
+ import { BattleStreams } from '@pkmn/sim';
7
+ import { FORMAT } from '../types.js';
8
+ import { PilotPlayer } from '../pilot/pilot.js';
9
+ /** Run a single Battle between two packed Teams and report the winner. */
10
+ export async function runBattle(p1Packed, p2Packed, seed, options = {}) {
11
+ const maxTurns = options.maxTurns ?? 1000;
12
+ const stream = new BattleStreams.BattleStream();
13
+ const streams = BattleStreams.getPlayerStreams(stream);
14
+ const getBattle = () => stream.battle;
15
+ const p1 = new PilotPlayer(streams.p1, getBattle, 'p1');
16
+ const p2 = new PilotPlayer(streams.p2, getBattle, 'p2');
17
+ void p1.start();
18
+ void p2.start();
19
+ let winner = null;
20
+ let turns = 0;
21
+ let forced = false;
22
+ const consume = (async () => {
23
+ for await (const chunk of streams.omniscient) {
24
+ for (const line of chunk.split('\n')) {
25
+ if (line.startsWith('|turn|')) {
26
+ turns = parseInt(line.slice('|turn|'.length), 10) || turns;
27
+ if (!forced && turns > maxTurns) {
28
+ forced = true;
29
+ void stream.write('>tiebreak');
30
+ }
31
+ }
32
+ else if (line.startsWith('|win|')) {
33
+ const name = line.slice('|win|'.length).trim();
34
+ winner = name === 'A' ? 'p1' : name === 'B' ? 'p2' : null;
35
+ }
36
+ else if (line.startsWith('|tie')) {
37
+ winner = null;
38
+ }
39
+ }
40
+ }
41
+ })();
42
+ const spec = { formatid: FORMAT, seed };
43
+ void stream.write(`>start ${JSON.stringify(spec)}`);
44
+ void stream.write(`>player p1 ${JSON.stringify({ name: 'A', team: p1Packed })}`);
45
+ void stream.write(`>player p2 ${JSON.stringify({ name: 'B', team: p2Packed })}`);
46
+ await consume;
47
+ return { winner, turns };
48
+ }
49
+ //# sourceMappingURL=run-battle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-battle.js","sourceRoot":"","sources":["../../src/battle/run-battle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Worker-thread entry: runs one Battle per message and posts the winner back.
3
+ * Battles are CPU-bound and synchronous in the sim, so a pool of these threads
4
+ * is what turns a many-battle run from serial into parallel. Loaded by the pool
5
+ * as the compiled `dist/battle/worker.js` (never imported directly).
6
+ */
7
+ import { parentPort } from 'node:worker_threads';
8
+ import { runBattle } from './run-battle.js';
9
+ if (parentPort) {
10
+ const port = parentPort;
11
+ port.on('message', async (job) => {
12
+ try {
13
+ const res = await runBattle(job.p1, job.p2, job.seed, { maxTurns: job.maxTurns });
14
+ port.postMessage({ ok: true, res });
15
+ }
16
+ catch (e) {
17
+ port.postMessage({ ok: false, error: e instanceof Error ? e.message : String(e) });
18
+ }
19
+ });
20
+ }
21
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/battle/worker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAU5C,IAAI,UAAU,EAAE,CAAC;IACf,MAAM,IAAI,GAAG,UAAU,CAAC;IACxB,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,GAAQ,EAAE,EAAE;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;YAClF,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACrF,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};