Skip to content

Commit

Permalink
update deps
Browse files Browse the repository at this point in the history
not react though because DefinitelyTyped/DefinitelyTyped#64464
  • Loading branch information
scheibo committed Jan 13, 2025
1 parent 4baa5a4 commit 00f4e74
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 104 deletions.
10 changes: 5 additions & 5 deletions examples/js/package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"dependencies": {
"@pkmn/data": "^0.9.23",
"@pkmn/dex": "^0.9.23",
"@pkmn/data": "^0.9.27",
"@pkmn/dex": "^0.9.27",
"@pkmn/engine": "file:../..",
"@pkmn/sets": "^5.1.2"
},
"devDependencies": {
"parcel-reporter-static-files-copy": "^1.5.3",
"parcel": "^2.13.0",
"parcel": "^2.13.3",
"vite-plugin-commonjs": "^0.10.4",
"vite": "^5.4.11",
"typescript": "^5.7.2"
"vite": "^6.0.7",
"typescript": "^5.7.3"
},
"scripts": {
"prestart": "tsc -p .",
Expand Down
30 changes: 15 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,38 +94,38 @@
},
"publishConfig": {"access": "public"},
"dependencies": {
"@pkmn/data": "^0.9.23",
"@pkmn/protocol": "^0.6.29"
"@pkmn/data": "^0.9.27",
"@pkmn/protocol": "^0.6.30"
},
"devDependencies": {
"@pkmn/eslint-config": "^9.3.0",
"@pkmn/img": "^0.2.33",
"@pkmn/eslint-config": "^9.5.0",
"@pkmn/img": "^0.2.34",
"@pkmn/sets": "^5.1.2",
"@pkmn/sim": "0.9.23",
"@pkmn/smogon": "^0.5.15",
"@pkmn/sim": "0.9.27",
"@pkmn/smogon": "^0.5.17",
"@types/html-minifier": "^4.0.5",
"@types/jest": "^29.5.14",
"@types/minimist": "^1.2.5",
"@types/mustache": "^4.2.5",
"@types/node": "^22.9.3",
"@types/node": "^22.10.6",
"@types/react": "^18.3.12",
"@types/semver": "^7.5.8",
"@types/vscode": "^1.95.0",
"@vitest/coverage-v8": "^2.1.5",
"@types/vscode": "^1.96.0",
"@vitest/coverage-v8": "^2.1.8",
"@vscode/vsce": "^3.2.1",
"binaryen": "^120.0.0",
"esbuild": "^0.24.0",
"eslint": "^9.15.0",
"binaryen": "^121.0.0",
"esbuild": "^0.24.2",
"eslint": "^9.18.0",
"html-minifier": "^4.0.0",
"json-stringify-pretty-compact": "3.0.0",
"minimist": "^1.2.8",
"mustache": "^4.2.0",
"semver": "^7.6.3",
"source-map-support": "^0.5.21",
"table": "^6.8.2",
"table": "^6.9.0",
"trakr": "^0.2.0",
"typescript": "^5.7.2",
"vitest": "^2.1.5"
"typescript": "^5.7.3",
"vitest": "^2.1.8"
},
"scripts": {
"lint": "eslint --cache src",
Expand Down
29 changes: 12 additions & 17 deletions src/lib/gen1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -560,10 +560,8 @@ Smogon](https://www.smogon.com/forums/posts/5933177/show):
- Pokémon Showdown doesn't implement type effectiveness precedence correctly.
- Pokémon Showdown checks for type and OHKO immunity before accuracy.
- Confusion self-hits use the wrong damage formula resulting in off-by-one errors (and also fail to
account for an opponent's Reflect). Furthermore, the confusion self-hit damage erroneously gets
inflicted on the confused user's substitute if the confused user had been attempting to use a
self-targeting move. Finally, Pokémon Showdown erroneously considers the *uncapped* self-hit
damage for the purposes of tracking the battle's last damage.
account for an opponent's Reflect). Furthermore, Pokémon Showdown erroneously considers the
*uncapped* self-hit damage for the purposes of tracking the battle's last damage.

Beyond these general bugs, several move effects are implemented incorrectly by Pokémon Showdown.
Some of these moves are [too fundamentally broken to be implemented](#unimplementable) by the pkmn
Expand Down Expand Up @@ -615,19 +613,16 @@ engine, but the following moves have their broken behavior preserved in `-Dshowd
the RNG). More importantly, these moves should *not* cause the tracked last battle damage to be
zeroed, but on Pokémon Showdown they do. These should also `|-fail|...|[still]` instead of doing
nothing.
- **Substitute**: in addition to the [Substitute + Confusion
glitch](https://pkmn.cc/bulba-glitch-1#Substitute_.2B_Confusion_glitch) not being implemented
correctly (covered earlier), the [Substitute 1/4
glitch](https://glitchcity.wiki/Substitute_%C2%BC_HP_glitch) also fails in many cases due to
Pokémon Showdown implementing the health check based on floating point division instead of integer
division like on the cartridge (meaning the Substitute 1/4 glitch only occurs if the Pokémon's
maximum HP is evenly divisible by 4). Substitute also incorrectly blocks Dream Eater on
Pokémon Showdown and incorrectly still heals 1 HP for any draining moves if the attack does 0
damage. Finally, Pokémon Showdown uses a `subFainted` field to track whether a Substitute was
broken to know when to nullify a move's effect, only it doesn't get cleared at the end of
the turn and can result in incorrect behavior on subsequent turns with the moves Mirror Move and
Metronome that invoke `runMove` (which is where `subFainted` gets cleared) on the user but skips
calling it for the eventual true target.
- **Substitute**: the [Substitute 1/4 glitch](https://glitchcity.wiki/Substitute_%C2%BC_HP_glitch)
fails in many cases due to Pokémon Showdown implementing the health check based on floating point
division instead of integer division like on the cartridge (meaning the Substitute 1/4 glitch only
occurs if the Pokémon's maximum HP is evenly divisible by 4). Substitute also incorrectly blocks
Dream Eater on Pokémon Showdown and incorrectly still heals 1 HP for any draining moves if the
attack does 0 damage. Finally, Pokémon Showdown uses a `subFainted` field to track whether a
Substitute was broken to know when to nullify a move's effect, only it doesn't get cleared at the
end of the turn and can result in incorrect behavior on subsequent turns with the moves Mirror
Move and Metronome that invoke `runMove` (which is where `subFainted` gets cleared) on the user
but skips calling it for the eventual true target.

In addition to numerous cases where Pokémon Showdown uses the wrong type of message (e.g. `|-fail`
vs. `|-miss|` vs. `|-immune|`, e.g. in the case of Leech Seed) which aren't documented here, Pokémon
Expand Down
9 changes: 1 addition & 8 deletions src/lib/gen1/mechanics.zig
Original file line number Diff line number Diff line change
Expand Up @@ -618,17 +618,10 @@ fn beforeMove(
return .err;
}
}
// Pokémon Showdown incorrectly changes the "target" of the confusion self-hit based
// on the targeting behavior of the confused Pokémon's selected move which results
// in the wrong behavior with respect to the Substitute + Confusion glitch
const target = if (showdown and Move.get(side.last_selected_move).target == .Self)
player
else
player.foe();

const uncapped = battle.last_damage;
// Skipping adjustDamage / randomizeDamage / checkHit
_ = try applyDamage(battle, player, target, .Confusion, options);
_ = try applyDamage(battle, player, player.foe(), .Confusion, options);
// Pokémon Showdown thinks that confusion damage is uncapped ¯\_(ツ)_/¯
if (showdown) battle.last_damage = uncapped;

Expand Down
3 changes: 1 addition & 2 deletions src/lib/gen1/test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9688,15 +9688,14 @@ test "Substitute + Confusion glitch" {
try t.log.expected.move(.{ P2.ident(1), Move.Supersonic, P1.ident(1) });
try t.log.expected.fail(.{ P1.ident(1), .None });
try t.log.expected.activate(.{ P1.ident(1), .Confusion });
if (showdown) try t.log.expected.activate(.{ P1.ident(1), .Substitute });
try t.log.expected.turn(.{4});

// Pokémon Showdown incorrectly applies damage to the confused Pokémon's sub when
// selecting a self-targeting move
try expectEqual(Result.Default, try t.update(move(3), move(1)));
// (140/256) * (2/3) * (1/2) vs. (2/3) * (1/2)
try if (showdown) t.expectProbability(35, 192) else t.expectProbability(1, 3);
try expectEqual(@as(u8, if (showdown) 2 else 7), t.actual.p1.active.volatiles.substitute);
try expectEqual(@as(u8, 7), t.actual.p1.active.volatiles.substitute);

try t.verify();
}
Expand Down
51 changes: 26 additions & 25 deletions src/test/benchmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import * as fs from 'fs';
import * as path from 'path';

import {Generation, Generations, ID, PokemonSet, StatsTable} from '@pkmn/data';
import {Battle, Dex, PRNG, PRNGSeed, Pokemon, Side, SideID, Teams} from '@pkmn/sim';
import {Battle, Dex, PRNG, Pokemon, Side, SideID, Teams} from '@pkmn/sim';
import minimist from 'minimist';

import * as engine from '../pkg';

import {newSeed, toBigInt} from './integration';
import {Choices, formatFor, patch} from './showdown';
import {Choices, PRNGSeed, formatFor, patch} from './showdown';
import {decimal, regression, summarize} from './stats';

const BLOCKLIST = ['mimic', 'metronome', 'mirrormove', 'transform'] as ID[];
Expand Down Expand Up @@ -43,32 +43,32 @@ export const Options = new class {
const lookup = engine.Lookup.get(gen);

const team: Partial<PokemonSet>[] = [];
const n = prng.randomChance(1, 100) ? prng.next(1, 5 + 1) : 6;
const n = prng.randomChance(1, 100) ? prng.random(1, 5 + 1) : 6;
for (let i = 0; i < n; i++) {
const species = lookup.speciesByNum(prng.next(1, 151 + 1));
const level = prng.randomChance(1, 20) ? prng.next(1, 99 + 1) : 100;
const species = lookup.speciesByNum(prng.random(1, 151 + 1));
const level = prng.randomChance(1, 20) ? prng.random(1, 99 + 1) : 100;

const ivs = {} as StatsTable;
for (const stat of gen.stats) {
if (stat === 'hp' || stat === 'spd') continue;
ivs[stat] = gen.stats.toIV(prng.randomChance(1, 5) ? prng.next(1, 15 + 1) : 15);
ivs[stat] = gen.stats.toIV(prng.randomChance(1, 5) ? prng.random(1, 15 + 1) : 15);
}
ivs.hp = gen.stats.toIV(gen.stats.getHPDV(ivs));
ivs.spd = ivs.spa;

const evs = {} as StatsTable;
for (const stat of gen.stats) {
if (stat === 'spd') break;
const exp = prng.randomChance(1, 20) ? prng.next(0, 0xFFFF + 1) : 0xFFFF;
const exp = prng.randomChance(1, 20) ? prng.random(0, 0xFFFF + 1) : 0xFFFF;
evs[stat] = Math.min(255, Math.trunc(Math.ceil(Math.sqrt(exp))));
}
evs.spd = evs.spa;

const moves: ID[] = [];
const m = prng.randomChance(1, 100) ? prng.next(1, 3 + 1) : 4;
const m = prng.randomChance(1, 100) ? prng.random(1, 3 + 1) : 4;
for (let j = 0; j < m; j++) {
let move: ID;
while (moves.includes((move = lookup.moveByNum(prng.next(1, 164 + 1)))) ||
while (moves.includes((move = lookup.moveByNum(prng.random(1, 164 + 1)))) ||
BLOCKLIST.includes(move));
moves.push(move);
}
Expand All @@ -83,36 +83,36 @@ export const Options = new class {
const lookup = engine.Lookup.get(gen);

const team: Partial<PokemonSet>[] = [];
const n = prng.randomChance(1, 100) ? prng.next(1, 5 + 1) : 6;
const n = prng.randomChance(1, 100) ? prng.random(1, 5 + 1) : 6;
for (let i = 0; i < n; i++) {
const species = lookup.speciesByNum(prng.next(1, 251 + 1));
const level = prng.randomChance(1, 20) ? prng.next(1, 99 + 1) : 100;
const species = lookup.speciesByNum(prng.random(1, 251 + 1));
const level = prng.randomChance(1, 20) ? prng.random(1, 99 + 1) : 100;
const item = prng.randomChance(1, 10) ? undefined
: lookup.itemByNum(prng.next(1, 62 + 1));
: lookup.itemByNum(prng.random(1, 62 + 1));

const ivs = {} as StatsTable;
for (const stat of gen.stats) {
if (stat === 'hp' || stat === 'spd') continue;
ivs[stat] = gen.stats.toIV(prng.randomChance(1, 5) ? prng.next(1, 15 + 1) : 15);
ivs[stat] = gen.stats.toIV(prng.randomChance(1, 5) ? prng.random(1, 15 + 1) : 15);
}
ivs.hp = gen.stats.toIV(gen.stats.getHPDV(ivs));
ivs.spd = ivs.spa;

const evs = {} as StatsTable;
for (const stat of gen.stats) {
const exp = prng.randomChance(1, 20) ? prng.next(0, 0xFFFF + 1) : 0xFFFF;
const exp = prng.randomChance(1, 20) ? prng.random(0, 0xFFFF + 1) : 0xFFFF;
evs[stat] = Math.min(255, Math.trunc(Math.ceil(Math.sqrt(exp))));
}

const moves: ID[] = [];
const m = prng.randomChance(1, 100) ? prng.next(1, 3 + 1) : 4;
const m = prng.randomChance(1, 100) ? prng.random(1, 3 + 1) : 4;
for (let j = 0; j < m; j++) {
let move: ID;
while (moves.includes((move = lookup.moveByNum(prng.next(1, 250 + 1)))));
while (moves.includes((move = lookup.moveByNum(prng.random(1, 250 + 1)))));
moves.push(move);
}

const happiness = prng.randomChance(1, 10 + 1) ? prng.next(0, 255) : 255;
const happiness = prng.randomChance(1, 10 + 1) ? prng.random(0, 255) : 255;
team.push({species, level, item, ivs, evs, moves, happiness});
}

Expand Down Expand Up @@ -202,9 +202,9 @@ const CONFIGURATIONS: {[name: string]: Configuration} = {
battle.setPlayer('p2', {name: 'Player B', team: team2});
while (!battle.ended) {
let possible = choices(battle, 'p1');
const c1 = possible[p1.next(possible.length)];
const c1 = possible[p1.random(possible.length)];
possible = choices(battle, 'p2');
const c2 = possible[p2.next(possible.length)];
const c2 = possible[p2.random(possible.length)];
battle.makeChoices(c1, c2);
}
turns += battle.turn;
Expand All @@ -213,7 +213,7 @@ const CONFIGURATIONS: {[name: string]: Configuration} = {
}
}

return Promise.resolve([duration, turns, serialize(prng.seed)] as const);
return Promise.resolve([duration, turns, serialize(prng.getSeed() as PRNGSeed)] as const);
},
},
'@pkmn/engine': {
Expand All @@ -232,7 +232,7 @@ const CONFIGURATIONS: {[name: string]: Configuration} = {
// since this code only runs in Pokémon Showdown compatibility mode we
// can avoid handling that (and save a branch) as it doesn't implement
// the softlock
const choose = (p: PRNG, choices: engine.Choice[]) => choices[p.next(choices.length)];
const choose = (p: PRNG, choices: engine.Choice[]) => choices[p.random(choices.length)];
const p1 = new PRNG(newSeed(prng));
const p2 = new PRNG(newSeed(prng));

Expand All @@ -252,7 +252,7 @@ const CONFIGURATIONS: {[name: string]: Configuration} = {
}
}

return Promise.resolve([duration, turns, serialize(prng.seed)] as const);
return Promise.resolve([duration, turns, serialize(prng.getSeed() as PRNGSeed)] as const);
},
},
'libpkmn': {
Expand All @@ -266,7 +266,8 @@ const CONFIGURATIONS: {[name: string]: Configuration} = {
const libpkmn = (format: ID, prng: PRNG, battles: number, showdown = true) => {
const warmup = Math.min(1000, Math.max(Math.floor(battles / 10), 1));
const exe = path.resolve(ROOT, 'build', 'bin', `benchmark${showdown ? '-showdown' : ''}`);
const stdout = sh(exe, [format[3], `${warmup}/${battles}`, serialize(prng.seed)]);
const stdout =
sh(exe, [format[3], `${warmup}/${battles}`, serialize(prng.getSeed() as PRNGSeed)]);
const [duration, turn, seed] = stdout.split(',');
return [BigInt(duration), Number(turn), seed.trim()] as const;
};
Expand Down Expand Up @@ -406,7 +407,7 @@ if (require.main === module) {
summary(data);
} else if (previous) {
const prng = new PRNG(seed);
regression(previous, data, (min, max) => prng.next(min, max));
regression(previous, data, (min, max) => prng.random(min, max));
} else {
for (const [name, samples] of Object.entries(data)) {
console.log(`${name}\t${samples.join(',')}`);
Expand Down
18 changes: 9 additions & 9 deletions src/test/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as tty from 'tty';

import {Generation, GenerationNum, Generations} from '@pkmn/data';
import {Protocol} from '@pkmn/protocol';
import {Battle, BattleStreams, Dex, ID, PRNG, PRNGSeed, Streams, Teams, toID} from '@pkmn/sim';
import {Battle, BattleStreams, Dex, ID, PRNG, Streams, Teams, toID} from '@pkmn/sim';
import * as sim from '@pkmn/sim/tools';
import {minify} from 'html-minifier';
import minimist from 'minimist';
Expand All @@ -19,7 +19,7 @@ import {LAYOUT, LE} from '../pkg/data';
import {error, render} from '../tools/debug';
import * as display from '../tools/display';

import {Choices, FILTER, formatFor, patch} from './showdown';
import {Choices, FILTER, PRNGSeed, formatFor, patch} from './showdown';

const ROOT = path.resolve(__dirname, '..', '..');
const ANSI = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
Expand Down Expand Up @@ -73,7 +73,7 @@ class Runner {
// try again! (note that validate marks used to ensure progress)
if (this.skip) return Promise.resolve();

const seed = this.prng.seed.slice() as PRNGSeed;
const seed = this.prng.getSeed().slice() as PRNGSeed;
const create = (o: sim.AIOptions) => (s: Streams.ObjectReadWriteStream<string>) =>
o.createAI(s, {seed: newSeed(this.prng), move: 0.7, mega: 0.6, ...o});

Expand Down Expand Up @@ -238,7 +238,7 @@ function play(
}

const request = partial.showdown.result = toResult(control, p1options.spec.name);
partial.showdown.seed = control.prng.seed.slice();
partial.showdown.seed = control.prng.getSeed().slice() as PRNGSeed;

const chunk = control.getDebugLog();
partial.showdown.chunk = chunk;
Expand All @@ -253,7 +253,7 @@ function play(

compare(chunk, parsed);
assert.deepEqual(result, request);
assert.deepEqual(battle.prng, control.prng.seed);
assert.deepEqual(battle.prng, control.prng.getSeed());

if (replay && index >= replay.length) break;
[c1, c2] = getChoices();
Expand All @@ -272,7 +272,7 @@ function play(
} while (!control.ended);

if (control.ended) assert.notEqual(result.type, undefined);
assert.deepEqual(battle.prng, control.prng.seed);
assert.deepEqual(battle.prng, control.prng.getSeed());
} catch (err: any) {
if (!replay) {
const num = toBigInt(seed);
Expand Down Expand Up @@ -831,7 +831,7 @@ export async function run(gens: Generations, options: string | Flags, errors?: E
}

export function newSeed(prng: PRNG): PRNGSeed {
return [prng.next(0x10000), prng.next(0x10000), prng.next(0x10000), prng.next(0x10000)];
return [prng.random(0x10000), prng.random(0x10000), prng.random(0x10000), prng.random(0x10000)];
}

export function toBigInt(seed: PRNGSeed): bigint {
Expand Down Expand Up @@ -896,13 +896,13 @@ if (require.main === module) {
unit ? +argv.duration.slice(0, -1) * {s: 1e3, m: 6e4, h: 3.6e6}[unit]! : argv.duration;
argv.cycles = argv.cycles ?? (duration ? 1 : 10);
const prng = new PRNG(argv.seed ? argv.seed.split(',').map((s: string) => Number(s)) : null);
if (!argv.seed) console.error('Seed:', prng.seed.join(','));
if (!argv.seed) console.error('Seed:', prng.getSeed().join(','));
const options = {prng, log: process.stdout.isTTY, ...argv, duration};

const errors = argv.summary ? new Errors() : undefined;
const code = await run(gens, options, errors);
if (code && errors) {
const file = path.join(ROOT, 'logs', `${prng.seed.join('-')}.html`);
const file = path.join(ROOT, 'logs', `${prng.getSeed().join('-')}.html`);
const link = path.join(ROOT, 'logs', 'summary.html');
fs.writeFileSync(file, errors.toString());
symlink(file, link);
Expand Down
Loading

0 comments on commit 00f4e74

Please sign in to comment.