getcompetitive 4.5.0 → 4.6.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/README.md CHANGED
@@ -48,15 +48,15 @@ Built on [`@pkmn/dex`](https://github.com/pkmn/EPOKe) (Showdown data) and
48
48
  ### Team analysis
49
49
  | Tool | Purpose |
50
50
  | --- | --- |
51
- | `analyze_team` | Team synergy: stacked defensive weaknesses, offensive coverage gaps, speed placement, a heuristic score, and — with a regulation — coverage against the meta's real sets |
51
+ | `analyze_team` | Team synergy: stacked defensive weaknesses, offensive coverage gaps, speed placement, a heuristic score, and — with a regulation — coverage against the meta's real sets, with battle-math `answerClass` verdicts from the shared MatchupEvaluator |
52
52
 
53
53
  ### Team workflows
54
54
  | Tool | Purpose |
55
55
  | --- | --- |
56
56
  | `parse_team` | Turn a Showdown/Pokepaste block (or a `species @ item \| ability \| nature \| EVs \| moves` one-liner) into the canonical team every tool takes; unknown names become warnings, not errors |
57
57
  | `format_team` | Render a canonical team back into paste text, round-tripping through `parse_team` |
58
- | `diagnose_team` | "Fix my team": weaknesses with evidence, then concrete spread, move, item and member changes, each backed by exact math or usage data |
59
- | `prepare_matchup` | "Prepare me": their likely sets by usage, speed races with margins, key damage rolls, bring-four, leads, and win/loss conditions |
58
+ | `diagnose_team` | "Fix my team": weaknesses with evidence — battle-math matchup verdicts included when sets are known — then concrete spread, move, item and member changes, each backed by exact math or usage data |
59
+ | `prepare_matchup` | "Prepare me": their likely sets by usage, speed races with margins, key damage rolls, an exhaustively scored bring-four with alternates, scored lead pairings, and win/loss conditions |
60
60
  | `analyze_replay` | Post-match read of a battle log: teams, KOs with causes, observed Speed order, damage percentages, and a type-coverage read |
61
61
  | `infer_set` | Reverse constraint solving: battle observations (who moved first, damage dealt/taken) narrow the opponent's likely set, ranked against the meta |
62
62
  | `optimize_team` | Fill open team slots against constraints — cover these types, answer these threats — with reasons for every recommendation |
@@ -197,8 +197,13 @@ The Showdown dataset and battle math track `@pkmn/dex` / `@smogon/calc`. Officia
197
197
  (`src/threats.ts`) with `node scripts/build-threats.mjs <regulation>`, and the
198
198
  **two-window usage history** behind `compare_meta` with
199
199
  `node scripts/build-meta-history.mjs <regulation>` (it refuses to write unless
200
- every tournament in the window was fetched). All generated data files are
201
- committed, so the tools stay offline at runtime. See [CONTRIBUTING](CONTRIBUTING.md).
200
+ every tournament in the window was fetched). The **Champions game model** is
201
+ the single `getChampionsDex()` layer: `node scripts/verify-champions.mjs`
202
+ diffs base stats, types and abilities for every Champions-exclusive form, the
203
+ whole roster, and every threat species against official data and refreshes the
204
+ committed facts table, which `test/champions.mjs` pins as golden regression
205
+ tests. All generated data files are committed, so the tools stay offline at
206
+ runtime. See [CONTRIBUTING](CONTRIBUTING.md).
202
207
 
203
208
  ## Contributing
204
209
 
@@ -0,0 +1,394 @@
1
+ // Generated by scripts/verify-champions.mjs — do not edit by hand.
2
+ // The Champions-exclusive forms bundled with the gen-9 dataset, with the typing
3
+ // and base-stat total the official game data was verified against. Every entry
4
+ // here is pinned by test/champions.mjs, so a dataset upgrade that silently
5
+ // changes a form fails the suite instead of corrupting every analysis.
6
+ export default {
7
+ source: "official Champions data via the MunchStats aggregator, diffed by scripts/verify-champions.mjs",
8
+ verifiedAsOf: "2026-09-19",
9
+ forms: [
10
+ {
11
+ "species": "Absol-Mega-Z",
12
+ "types": [
13
+ "Dark",
14
+ "Ghost"
15
+ ],
16
+ "bst": 565
17
+ },
18
+ {
19
+ "species": "Barbaracle-Mega",
20
+ "types": [
21
+ "Rock",
22
+ "Fighting"
23
+ ],
24
+ "bst": 600
25
+ },
26
+ {
27
+ "species": "Baxcalibur-Mega",
28
+ "types": [
29
+ "Dragon",
30
+ "Ice"
31
+ ],
32
+ "bst": 700
33
+ },
34
+ {
35
+ "species": "Chandelure-Mega",
36
+ "types": [
37
+ "Ghost",
38
+ "Fire"
39
+ ],
40
+ "bst": 620
41
+ },
42
+ {
43
+ "species": "Chesnaught-Mega",
44
+ "types": [
45
+ "Grass",
46
+ "Fighting"
47
+ ],
48
+ "bst": 630
49
+ },
50
+ {
51
+ "species": "Chimecho-Mega",
52
+ "types": [
53
+ "Psychic",
54
+ "Steel"
55
+ ],
56
+ "bst": 555
57
+ },
58
+ {
59
+ "species": "Clefable-Mega",
60
+ "types": [
61
+ "Fairy",
62
+ "Flying"
63
+ ],
64
+ "bst": 583
65
+ },
66
+ {
67
+ "species": "Crabominable-Mega",
68
+ "types": [
69
+ "Fighting",
70
+ "Ice"
71
+ ],
72
+ "bst": 578
73
+ },
74
+ {
75
+ "species": "Darkrai-Mega",
76
+ "types": [
77
+ "Dark"
78
+ ],
79
+ "bst": 700
80
+ },
81
+ {
82
+ "species": "Delphox-Mega",
83
+ "types": [
84
+ "Fire",
85
+ "Psychic"
86
+ ],
87
+ "bst": 634
88
+ },
89
+ {
90
+ "species": "Dragalge-Mega",
91
+ "types": [
92
+ "Poison",
93
+ "Dragon"
94
+ ],
95
+ "bst": 594
96
+ },
97
+ {
98
+ "species": "Dragonite-Mega",
99
+ "types": [
100
+ "Dragon",
101
+ "Flying"
102
+ ],
103
+ "bst": 700
104
+ },
105
+ {
106
+ "species": "Drampa-Mega",
107
+ "types": [
108
+ "Normal",
109
+ "Dragon"
110
+ ],
111
+ "bst": 585
112
+ },
113
+ {
114
+ "species": "Eelektross-Mega",
115
+ "types": [
116
+ "Electric"
117
+ ],
118
+ "bst": 615
119
+ },
120
+ {
121
+ "species": "Emboar-Mega",
122
+ "types": [
123
+ "Fire",
124
+ "Fighting"
125
+ ],
126
+ "bst": 628
127
+ },
128
+ {
129
+ "species": "Excadrill-Mega",
130
+ "types": [
131
+ "Ground",
132
+ "Steel"
133
+ ],
134
+ "bst": 608
135
+ },
136
+ {
137
+ "species": "Falinks-Mega",
138
+ "types": [
139
+ "Fighting"
140
+ ],
141
+ "bst": 570
142
+ },
143
+ {
144
+ "species": "Feraligatr-Mega",
145
+ "types": [
146
+ "Water",
147
+ "Dragon"
148
+ ],
149
+ "bst": 630
150
+ },
151
+ {
152
+ "species": "Floette-Mega",
153
+ "types": [
154
+ "Fairy"
155
+ ],
156
+ "bst": 651
157
+ },
158
+ {
159
+ "species": "Froslass-Mega",
160
+ "types": [
161
+ "Ice",
162
+ "Ghost"
163
+ ],
164
+ "bst": 580
165
+ },
166
+ {
167
+ "species": "Garchomp-Mega-Z",
168
+ "types": [
169
+ "Dragon"
170
+ ],
171
+ "bst": 700
172
+ },
173
+ {
174
+ "species": "Glimmora-Mega",
175
+ "types": [
176
+ "Rock",
177
+ "Poison"
178
+ ],
179
+ "bst": 625
180
+ },
181
+ {
182
+ "species": "Golisopod-Mega",
183
+ "types": [
184
+ "Bug",
185
+ "Steel"
186
+ ],
187
+ "bst": 630
188
+ },
189
+ {
190
+ "species": "Golurk-Mega",
191
+ "types": [
192
+ "Ground",
193
+ "Ghost"
194
+ ],
195
+ "bst": 583
196
+ },
197
+ {
198
+ "species": "Greninja-Mega",
199
+ "types": [
200
+ "Water",
201
+ "Dark"
202
+ ],
203
+ "bst": 630
204
+ },
205
+ {
206
+ "species": "Hawlucha-Mega",
207
+ "types": [
208
+ "Fighting",
209
+ "Flying"
210
+ ],
211
+ "bst": 600
212
+ },
213
+ {
214
+ "species": "Heatran-Mega",
215
+ "types": [
216
+ "Fire",
217
+ "Steel"
218
+ ],
219
+ "bst": 700
220
+ },
221
+ {
222
+ "species": "Lucario-Mega-Z",
223
+ "types": [
224
+ "Fighting",
225
+ "Steel"
226
+ ],
227
+ "bst": 625
228
+ },
229
+ {
230
+ "species": "Magearna-Mega",
231
+ "types": [
232
+ "Steel",
233
+ "Fairy"
234
+ ],
235
+ "bst": 700
236
+ },
237
+ {
238
+ "species": "Magearna-Original-Mega",
239
+ "types": [
240
+ "Steel",
241
+ "Fairy"
242
+ ],
243
+ "bst": 700
244
+ },
245
+ {
246
+ "species": "Malamar-Mega",
247
+ "types": [
248
+ "Dark",
249
+ "Psychic"
250
+ ],
251
+ "bst": 582
252
+ },
253
+ {
254
+ "species": "Meganium-Mega",
255
+ "types": [
256
+ "Grass",
257
+ "Fairy"
258
+ ],
259
+ "bst": 625
260
+ },
261
+ {
262
+ "species": "Meowstic-F-Mega",
263
+ "types": [
264
+ "Psychic"
265
+ ],
266
+ "bst": 566
267
+ },
268
+ {
269
+ "species": "Meowstic-M-Mega",
270
+ "types": [
271
+ "Psychic"
272
+ ],
273
+ "bst": 566
274
+ },
275
+ {
276
+ "species": "Pyroar-Mega",
277
+ "types": [
278
+ "Fire",
279
+ "Normal"
280
+ ],
281
+ "bst": 607
282
+ },
283
+ {
284
+ "species": "Raichu-Mega-X",
285
+ "types": [
286
+ "Electric"
287
+ ],
288
+ "bst": 585
289
+ },
290
+ {
291
+ "species": "Raichu-Mega-Y",
292
+ "types": [
293
+ "Electric"
294
+ ],
295
+ "bst": 585
296
+ },
297
+ {
298
+ "species": "Scolipede-Mega",
299
+ "types": [
300
+ "Bug",
301
+ "Poison"
302
+ ],
303
+ "bst": 585
304
+ },
305
+ {
306
+ "species": "Scovillain-Mega",
307
+ "types": [
308
+ "Grass",
309
+ "Fire"
310
+ ],
311
+ "bst": 586
312
+ },
313
+ {
314
+ "species": "Scrafty-Mega",
315
+ "types": [
316
+ "Dark",
317
+ "Fighting"
318
+ ],
319
+ "bst": 588
320
+ },
321
+ {
322
+ "species": "Skarmory-Mega",
323
+ "types": [
324
+ "Steel",
325
+ "Flying"
326
+ ],
327
+ "bst": 565
328
+ },
329
+ {
330
+ "species": "Staraptor-Mega",
331
+ "types": [
332
+ "Fighting",
333
+ "Flying"
334
+ ],
335
+ "bst": 585
336
+ },
337
+ {
338
+ "species": "Starmie-Mega",
339
+ "types": [
340
+ "Water",
341
+ "Psychic"
342
+ ],
343
+ "bst": 620
344
+ },
345
+ {
346
+ "species": "Tatsugiri-Curly-Mega",
347
+ "types": [
348
+ "Dragon",
349
+ "Water"
350
+ ],
351
+ "bst": 575
352
+ },
353
+ {
354
+ "species": "Tatsugiri-Droopy-Mega",
355
+ "types": [
356
+ "Dragon",
357
+ "Water"
358
+ ],
359
+ "bst": 575
360
+ },
361
+ {
362
+ "species": "Tatsugiri-Stretchy-Mega",
363
+ "types": [
364
+ "Dragon",
365
+ "Water"
366
+ ],
367
+ "bst": 575
368
+ },
369
+ {
370
+ "species": "Victreebel-Mega",
371
+ "types": [
372
+ "Grass",
373
+ "Poison"
374
+ ],
375
+ "bst": 590
376
+ },
377
+ {
378
+ "species": "Zeraora-Mega",
379
+ "types": [
380
+ "Electric"
381
+ ],
382
+ "bst": 700
383
+ },
384
+ {
385
+ "species": "Zygarde-Mega",
386
+ "types": [
387
+ "Dragon",
388
+ "Ground"
389
+ ],
390
+ "bst": 778
391
+ }
392
+ ]
393
+ };
394
+ //# sourceMappingURL=champions.data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"champions.data.js","sourceRoot":"","sources":["../src/champions.data.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * ChampionsDex: the single canonical game model every gameplay system reads
3
+ * from. The bundled gen-9 dataset is verified against official Champions data
4
+ * by `scripts/verify-champions.mjs`, which diffs base stats, types and
5
+ * abilities for every Champions-exclusive form, the whole regulation roster,
6
+ * and every threat species; the verified facts are committed in
7
+ * `champions.data.ts` and pinned by `test/champions.mjs`.
8
+ *
9
+ * `getChampionsDex()` is currently a passthrough because verification found no
10
+ * divergence. If an override ever becomes necessary, it lands here — exactly
11
+ * once, in front of every tool — instead of being sprinkled across call sites.
12
+ */
13
+ import { getDex } from './dex.js';
14
+ import raw from './champions.data.js';
15
+ export const CHAMPIONS_VERIFIED_AS_OF = raw.verifiedAsOf;
16
+ export const CHAMPIONS_SOURCE = raw.source;
17
+ /** Champions-exclusive forms with the typing and BST they were verified to have. */
18
+ export const CHAMPIONS_EXCLUSIVE_FORMS = raw.forms;
19
+ /** The canonical Champions game model; the only dex access gameplay tools should use. */
20
+ export function getChampionsDex() {
21
+ return getDex(9);
22
+ }
23
+ //# sourceMappingURL=champions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"champions.js","sourceRoot":"","sources":["../src/champions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,GAAG,MAAM,qBAAqB,CAAC;AAEtC,MAAM,CAAC,MAAM,wBAAwB,GAAW,GAAG,CAAC,YAAY,CAAC;AACjE,MAAM,CAAC,MAAM,gBAAgB,GAAW,GAAG,CAAC,MAAM,CAAC;AAEnD,oFAAoF;AACpF,MAAM,CAAC,MAAM,yBAAyB,GAA0E,GAAG,CAAC,KAAK,CAAC;AAE1H,yFAAyF;AACzF,MAAM,UAAU,eAAe;IAC7B,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC"}
@@ -0,0 +1,200 @@
1
+ /**
2
+ * MatchupEvaluator: one reusable definition of what constitutes an actual
3
+ * competitive answer. "Has a super-effective move" is not an answer; this
4
+ * engine runs the real math both directions — each side's hardest hit and
5
+ * damage range, level-50 Speed with priority taken into account, and a
6
+ * conservative turn simulation — and classifies the matchup.
7
+ *
8
+ * Determinism contract: the classification never consults a random roll. Our
9
+ * side is always simulated with its minimum roll and their side with its
10
+ * maximum, so a classification of HARD_ANSWER or SOFT_ANSWER is a worst-case
11
+ * guarantee, and SPEED_DEPENDENT/TRADE mean exactly that order or rolls can
12
+ * flip the result.
13
+ */
14
+ import { getChampionsDex } from './champions.js';
15
+ import { damageResult, finalStat, typeEffectiveness } from './dex.js';
16
+ /** Better answers rank lower; shared by every consumer that picks the best member. */
17
+ export const ANSWER_CLASS_RANK = {
18
+ HARD_ANSWER: 0,
19
+ SOFT_ANSWER: 1,
20
+ REVENGE: 2,
21
+ SPEED_DEPENDENT: 3,
22
+ TRADE: 4,
23
+ UNFAVORABLE: 5,
24
+ UNKNOWN: 6,
25
+ };
26
+ /** Strongest hit one side has into the other's typing, by eff x power x STAB. */
27
+ function bestMove(dex, side, targetTypes) {
28
+ let best = null;
29
+ for (const mv of side.moves ?? []) {
30
+ const m = dex.moves.get(mv);
31
+ if (!m.exists || !(m.basePower || 0))
32
+ continue;
33
+ const eff = typeEffectiveness(m.type, targetTypes, 9);
34
+ if (eff === 0)
35
+ continue;
36
+ const stab = dex.species.get(side.species).types.includes(m.type) ? 1.5 : 1;
37
+ const score = eff * m.basePower * stab;
38
+ if (!best || score > best.score)
39
+ best = { name: m.name, score, priority: m.priority ?? 0 };
40
+ }
41
+ return best ? { name: best.name, priority: best.priority } : null;
42
+ }
43
+ /**
44
+ * Conservative two-hit race simulation: our minimum roll against their maximum,
45
+ * alternating by Speed unless priority intervenes. Returns whether we win and
46
+ * the share of our HP left in the worst case.
47
+ */
48
+ function simulateRace(ourMove, theirMove, ourRange, theirRange, ourHp, theirHp, ourPriority, theirPriority, weMoveFirst) {
49
+ let usHp = ourHp;
50
+ let themHp = theirHp;
51
+ // Conservative rolls: our worst, their best.
52
+ const ourHit = ourRange[0];
53
+ const theirHit = theirRange[1];
54
+ let turn = 0;
55
+ while (usHp > 0 && themHp > 0 && turn < 12) {
56
+ turn++;
57
+ const ourTurn = ourPriority !== theirPriority
58
+ ? ourPriority > theirPriority
59
+ : weMoveFirst
60
+ ? turn % 2 === 1
61
+ : turn % 2 === 0;
62
+ if (ourTurn) {
63
+ themHp -= ourHit;
64
+ if (themHp <= 0)
65
+ return { weWin: true, hpLeftPct: (usHp / ourHp) * 100 };
66
+ }
67
+ else {
68
+ usHp -= theirHit;
69
+ if (usHp <= 0)
70
+ return { weWin: false, hpLeftPct: (usHp / ourHp) * 100 };
71
+ }
72
+ }
73
+ // 12 turns without a faint: effectively unwinnable by damage.
74
+ return { weWin: false, hpLeftPct: (usHp / ourHp) * 100 };
75
+ }
76
+ /** True when the side has at least one damaging move that is not fully resisted or immune into the target. */
77
+ function canHit(dex, side, targetTypes) {
78
+ for (const mv of side.moves ?? []) {
79
+ const m = dex.moves.get(mv);
80
+ if (m.exists && (m.basePower || 0) && typeEffectiveness(m.type, targetTypes, 9) > 0)
81
+ return true;
82
+ }
83
+ return false;
84
+ }
85
+ /**
86
+ * Classify the matchup between two sets. Both sides must carry at least one
87
+ * usable damaging move, else the class is UNKNOWN — a type chart is not a
88
+ * battle.
89
+ */
90
+ export function evaluateMatchup(us, them) {
91
+ const dex = getChampionsDex();
92
+ const usSp = dex.species.get(us.species);
93
+ const themSp = dex.species.get(them.species);
94
+ const ourMove = bestMove(dex, us, themSp.types);
95
+ const theirMove = bestMove(dex, them, usSp.types);
96
+ const speed = (side, sp) => {
97
+ const nature = side.nature ?? 'Serious';
98
+ const evs = side.championsPoints
99
+ ? Object.fromEntries(Object.entries(side.championsPoints).map(([k, v]) => [k, v * 8]))
100
+ : (side.evs ?? {});
101
+ let s = finalStat(9, 'spe', sp.baseStats.spe, 31, evs.spe ?? 0, 50, nature);
102
+ if (side.item === 'Choice Scarf')
103
+ s = Math.floor(s * 1.5);
104
+ return s;
105
+ };
106
+ const ourSpeed = speed(us, usSp);
107
+ const theirSpeed = speed(them, themSp);
108
+ const weMoveFirst = ourSpeed >= theirSpeed;
109
+ const empty = {
110
+ ourBest: null,
111
+ theirBest: null,
112
+ speed: { ours: ourSpeed, theirs: theirSpeed, weMoveFirst },
113
+ };
114
+ if (!ourMove || !theirMove) {
115
+ const weBlocked = !ourMove && !!us.moves?.length && !canHit(dex, us, themSp.types);
116
+ const theyBlocked = !theirMove && !!them.moves?.length && !canHit(dex, them, usSp.types);
117
+ if (weBlocked || theyBlocked) {
118
+ return {
119
+ ...empty,
120
+ answerClass: 'UNFAVORABLE',
121
+ reason: weBlocked
122
+ ? `every damaging move ${us.species} knows is resisted or immune into ${them.species}\u2019s typing.`
123
+ : `every damaging move ${them.species} knows is resisted or immune into ${us.species}\u2019s typing.`,
124
+ };
125
+ }
126
+ return {
127
+ ...empty,
128
+ answerClass: 'UNKNOWN',
129
+ reason: !ourMove ? `no damaging move known for ${us.species}` : `no damaging move known for ${them.species}`,
130
+ };
131
+ }
132
+ const ourRes = damageResult(9, { species: us.species, level: 50, nature: us.nature, evs: us.evs, championsPoints: us.championsPoints, item: us.item, ability: us.ability, moves: [ourMove.name] }, { species: them.species, level: 50, nature: them.nature, evs: them.evs, championsPoints: them.championsPoints, item: them.item, ability: them.ability, moves: them.moves }, ourMove.name);
133
+ const theirRes = damageResult(9, { species: them.species, level: 50, nature: them.nature, evs: them.evs, championsPoints: them.championsPoints, item: them.item, ability: them.ability, moves: [theirMove.name] }, { species: us.species, level: 50, nature: us.nature, evs: us.evs, championsPoints: us.championsPoints, item: us.item, ability: us.ability, moves: us.moves }, theirMove.name);
134
+ const ourHp = ourRes.attacker.stats.hp;
135
+ const theirHp = theirRes.attacker.stats.hp;
136
+ const ourRange = ourRes.damageRange;
137
+ const theirRange = theirRes.damageRange;
138
+ const base = {
139
+ ourBest: { move: ourMove.name, damageRange: ourRange, koChance: ourRes.koChance },
140
+ theirBest: { move: theirMove.name, damageRange: theirRange, koChance: theirRes.koChance },
141
+ speed: { ours: ourSpeed, theirs: theirSpeed, weMoveFirst },
142
+ };
143
+ const ourReliableKO = ourRange[0] >= theirHp;
144
+ const theirReliableKO = theirRange[0] >= ourHp;
145
+ const theirPossibleKO = theirRange[1] >= ourHp;
146
+ if (ourReliableKO && theirReliableKO) {
147
+ return {
148
+ ...base,
149
+ answerClass: weMoveFirst ? 'REVENGE' : 'UNFAVORABLE',
150
+ reason: `both sides one-shot the other; ${weMoveFirst ? `we move first (${ourSpeed} vs ${theirSpeed}), so we win on initiative only and cannot switch in` : `they move first (${theirSpeed} vs ${ourSpeed})`}.`,
151
+ };
152
+ }
153
+ if (ourReliableKO) {
154
+ if (theirPossibleKO && !weMoveFirst) {
155
+ return {
156
+ ...base,
157
+ answerClass: 'TRADE',
158
+ reason: `we one-shot reliably but move second; their best roll (${theirRange[1]} of ${ourHp}) can one-shot us, so the result rides on their roll.`,
159
+ };
160
+ }
161
+ return {
162
+ ...base,
163
+ answerClass: 'HARD_ANSWER',
164
+ reason: `we one-shot (${ourRange[0]}-${ourRange[1]} of ${theirHp})${theirPossibleKO ? ' and move first' : ' and survive their best hit'}.`,
165
+ };
166
+ }
167
+ if (theirReliableKO) {
168
+ return {
169
+ ...base,
170
+ answerClass: 'UNFAVORABLE',
171
+ reason: `they one-shot us (${theirRange[0]}-${theirRange[1]} of ${ourHp}) and our best hit does not one-shot back.`,
172
+ };
173
+ }
174
+ // Neither side one-shots: the two-hit race, simulated conservatively, and
175
+ // re-simulated with the order flipped to see whether speed decides it.
176
+ const worstCase = simulateRace(ourMove.name, theirMove.name, ourRange, theirRange, ourHp, theirHp, ourMove.priority, theirMove.priority, weMoveFirst);
177
+ const flipped = simulateRace(ourMove.name, theirMove.name, ourRange, theirRange, ourHp, theirHp, ourMove.priority, theirMove.priority, !weMoveFirst);
178
+ if (worstCase.weWin && flipped.weWin) {
179
+ return {
180
+ ...base,
181
+ answerClass: worstCase.hpLeftPct >= 50 ? 'HARD_ANSWER' : 'SOFT_ANSWER',
182
+ reason: worstCase.hpLeftPct >= 50
183
+ ? `we win the exchange regardless of turn order, finishing above half HP (${worstCase.hpLeftPct.toFixed(0)}%).`
184
+ : `we win the exchange regardless of turn order, but finish at ${worstCase.hpLeftPct.toFixed(0)}% HP against their best rolls.`,
185
+ };
186
+ }
187
+ if (worstCase.weWin !== flipped.weWin) {
188
+ return {
189
+ ...base,
190
+ answerClass: 'SPEED_DEPENDENT',
191
+ reason: `the winner flips with turn order: both sides need at least two hits, and ${weMoveFirst ? `we move first (${ourSpeed} vs ${theirSpeed})` : `they move first (${theirSpeed} vs ${ourSpeed})`}.`,
192
+ };
193
+ }
194
+ return {
195
+ ...base,
196
+ answerClass: 'UNFAVORABLE',
197
+ reason: `they win the exchange on both orderings; our best is ${ourRange[0]}-${ourRange[1]} of ${theirHp}, theirs ${theirRange[0]}-${theirRange[1]} of ${ourHp}.`,
198
+ };
199
+ }
200
+ //# sourceMappingURL=evaluator.js.map