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 +10 -5
- package/dist/champions.data.js +394 -0
- package/dist/champions.data.js.map +1 -0
- package/dist/champions.js +23 -0
- package/dist/champions.js.map +1 -0
- package/dist/evaluator.js +200 -0
- package/dist/evaluator.js.map +1 -0
- package/dist/tools/analyze.js +36 -2
- package/dist/tools/analyze.js.map +1 -1
- package/dist/tools/calc.js +7 -6
- package/dist/tools/calc.js.map +1 -1
- package/dist/tools/data.js +16 -24
- package/dist/tools/data.js.map +1 -1
- package/dist/tools/doctor.js +61 -6
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/infer.js +3 -2
- package/dist/tools/infer.js.map +1 -1
- package/dist/tools/matchup.js +113 -27
- package/dist/tools/matchup.js.map +1 -1
- package/dist/tools/optimize.js +3 -2
- package/dist/tools/optimize.js.map +1 -1
- package/dist/tools/regulations.js +3 -2
- package/dist/tools/regulations.js.map +1 -1
- package/dist/tools/replay.js +4 -3
- package/dist/tools/replay.js.map +1 -1
- package/dist/tools/sprites.js +3 -2
- package/dist/tools/sprites.js.map +1 -1
- package/dist/tools/team.js +6 -5
- package/dist/tools/team.js.map +1 -1
- package/package.json +2 -2
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
|
|
59
|
-
| `prepare_matchup` | "Prepare me": their likely sets by usage, speed races with margins, key damage rolls, bring-four,
|
|
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).
|
|
201
|
-
|
|
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
|