import { describe, expect, it } from "vitest"; import { existsSync, readFileSync } from "node:fs"; import { CrystalAssets } from "../src/game/rom/assets"; import { romRevision } from "../src/game/rom/revision"; import { CRYSTAL_ROMS, validateRom } from "../src/rom/validate"; import { emptyBattleTowerState, loadOpponent } from "../src/game/scripts/battle-tower"; import type { SpecialContext } from "../src/game/scripts/special-context"; /** The user's 1.1 cartridge and PRET's rebuilds of the other English ones * (`make crystal crystal_au` in .reference/pokecrystal), each skipped when * absent. */ const cartridges = [ { revision: "1.1", path: process.env.CRYSTAL_ROM_PATH ?? "Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc", rom: CRYSTAL_ROMS[0] }, { revision: "1.0", path: ".reference/pokecrystal/pokecrystal.gbc", rom: CRYSTAL_ROMS[1] }, { revision: "au", path: ".reference/pokecrystal/pokecrystal_au.gbc", rom: CRYSTAL_ROMS[2] }, ] as const; for (const { revision, path, rom } of cartridges) describe.skipIf(!existsSync(path))(`the ${revision} cartridge`, () => { const bytes = existsSync(path) ? new Uint8Array(readFileSync(path)) : new Uint8Array(); it("is accepted under its name and recognised from its header", async () => { // CRYSTAL_ROM_PATH may point the 1.1 entry at another cartridge. const assets = new CrystalAssets(bytes); const own = cartridges.find((c) => c.revision === romRevision(assets.reader))!; expect(await validateRom(new File([bytes], "crystal.gbc"))).toMatchObject({ sha1: own.rom.sha1, name: own.rom.name }); if (revision !== "1.1" || !process.env.CRYSTAL_ROM_PATH) expect(own.revision).toBe(revision); // One changed byte is a modified ROM. const changed = bytes.slice(); changed[0x7fff] ^= 1; await expect(validateRom(new File([changed], "crystal.gbc"))).rejects.toThrow("not a supported English"); }); it("draws Battle Tower trainers from all 70, or from the first 21 on 1.0", () => { const assets = new CrystalAssets(bytes); let seed = 84; const context = { reader: assets.reader, data: assets.pokemon, random: () => { seed = (seed * 1103515245 + 12345) >>> 0; return (seed >>> 16) & 255; }, text: () => ({ player: "KAWA", rival: "SILVER", mom: "MOM", strings: {}, weekday: "SUNDAY" }), } as unknown as SpecialContext; const drawn = new Set(); for (let draw = 0; draw < 2000; draw++) { const bt = { ...emptyBattleTowerState(), levelGroup: 1 + (draw % 10) }; drawn.add(loadOpponent(context, bt).trainer); } const limit = romRevision(assets.reader) === "1.0" ? 21 : 70; expect(Math.max(...drawn)).toBe(limit - 1); expect(drawn.size).toBe(limit); }); });