Checks / web (push) Canceled after 0s
Complete the weather, Substitute, Transform, called-move and forced-switch effects. Add held-item behavior, Exp. Share awards and badge-based traded Pokemon obedience, and persist trainer berry consumption. Validated with 576 passing unit tests and 149 browser tests, native/reference builds, reference/vendor/release checks and 320 Metal Powder emulator comparisons per browser. Smart AI and special balls/battle types remain on #28.
83 lines
4.0 KiB
TypeScript
83 lines
4.0 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
import { existsSync } from "node:fs";
|
|
import { C } from "../src/game/battle/constants";
|
|
import { romPath, symbolsPath, loadSymbols, openReference } from "./reference-fixture";
|
|
|
|
test.skip(!existsSync(romPath) || !existsSync(symbolsPath), "Needs the private ROM and pinned reference rebuild.");
|
|
|
|
test("Metal Powder matches the original species check and byte overflow", async ({ page }) => {
|
|
const { at, routine } = loadSymbols();
|
|
const addresses = {
|
|
command: routine("DittoMetalPowder"),
|
|
romBank: at("hROMBank"),
|
|
turn: at("hBattleTurn"),
|
|
curBattleMon: at("wCurBattleMon"),
|
|
partySpecies: at("wPartyMon1Species"),
|
|
enemySpecies: at("wTempEnemyMonSpecies"),
|
|
playerItem: at("wBattleMonItem"),
|
|
enemyItem: at("wEnemyMonItem"),
|
|
};
|
|
await openReference(page);
|
|
const differences = await page.evaluate(async ({ addresses, ditto, powder }) => {
|
|
const { BinjgbEmulator } = await import("/src/emulator/binjgb.ts");
|
|
const { CrystalAssets } = await import("/src/game/rom/assets.ts");
|
|
const { Battle } = await import("/src/game/battle/battle.ts");
|
|
const file = (document.querySelector("#reference-fixture") as HTMLInputElement).files![0];
|
|
const rom = new Uint8Array(await file.arrayBuffer());
|
|
const assets = new CrystalAssets(rom);
|
|
const reference = new BinjgbEmulator();
|
|
await reference.load(rom, document.createElement("canvas"));
|
|
const write = (address: number, value: number) => reference.writeMemory(address, value & 255);
|
|
const read = (address: number) => reference.readMemory(address);
|
|
const failures: unknown[] = [];
|
|
let compared = 0;
|
|
write(0xff70, 1);
|
|
write(addresses.curBattleMon, 0);
|
|
for (const turn of [0, 1]) for (const species of [ditto, 25]) for (const item of [powder, 0])
|
|
for (const attack of [1, 2, 101, 255]) for (const defense of [1, 2, 100, 169, 170, 171, 172, 200, 254, 255]) {
|
|
const battle = new Battle({
|
|
data: assets.pokemon, party: [{ species }] as never,
|
|
pokedex: { seen: new Set(), caught: new Set() },
|
|
playerId: 1, playerName: "CHRIS", timeOfDay: 1,
|
|
text: () => ({}) as never, random: () => 0,
|
|
});
|
|
const internal = battle as unknown as {
|
|
enemySpecies: number;
|
|
dittoMetalPowder(side: unknown, attack: number, defense: number): number[];
|
|
};
|
|
internal.enemySpecies = species;
|
|
const target = turn ? battle.player : battle.enemy;
|
|
target.mon = { species: 25, item } as never; // transformed battle species
|
|
const native = internal.dittoMetalPowder(target, attack, defense);
|
|
write(addresses.turn, turn);
|
|
write(addresses.partySpecies, species);
|
|
write(addresses.enemySpecies, species);
|
|
write(addresses.playerItem, item);
|
|
write(addresses.enemyItem, item);
|
|
const { bank, address } = addresses.command;
|
|
// DI; SP; ROM bank; BC; call the unmodified routine; save B/C;
|
|
// sentinel in audio RAM (no interrupts); loop.
|
|
const program = [
|
|
0xf3, 0x31, 0xfe, 0xc7,
|
|
0x3e, bank, 0xe0, addresses.romBank & 255, 0xea, 0x00, 0x20,
|
|
0x06, attack, 0x0e, defense, 0xcd, address & 255, address >> 8,
|
|
0x78, 0xea, 0xe0, 0xc0, 0x79, 0xea, 0xe1, 0xc0,
|
|
0x3e, 0x42, 0xea, 0xf0, 0xc0, 0x18, 0xfe,
|
|
];
|
|
write(0xc0f0, 0);
|
|
program.forEach((byte, i) => write(0xc000 + i, byte));
|
|
reference.setProgramCounter(0xc000);
|
|
for (let frame = 0; frame < 60 && read(0xc0f0) !== 0x42; frame++) reference.advanceFrames(1);
|
|
if (read(0xc0f0) !== 0x42) throw new Error("DittoMetalPowder did not return");
|
|
const original = [read(0xc0e0), read(0xc0e1)];
|
|
if (original.some((value, i) => value !== native[i]))
|
|
failures.push({ turn, species, item, attack, defense, original, native });
|
|
compared++;
|
|
}
|
|
reference.dispose();
|
|
return { compared, failures };
|
|
}, { addresses, ditto: C.DITTO, powder: C.METAL_POWDER });
|
|
expect(differences.compared).toBe(320);
|
|
expect(differences.failures).toEqual([]);
|
|
});
|