import { describe, expect, it } from "vitest"; import { existsSync, readFileSync } from "node:fs"; import { drawTile } from "../src/game/rendering/pixels"; import { TilesetAnimator } from "../src/game/rendering/tileset-animation"; import { CrystalAssets } from "../src/game/rom/assets"; import { OW } from "../src/game/world/constants"; import type { Palette } from "../src/game/world/types"; it("grass priority uses background indices, preserving transparent sprite pixels", () => { const palette: Palette = [ [255, 255, 255], [80, 160, 80], [32, 64, 32], [0, 0, 0], ]; const target = new Uint8ClampedArray(8 * 8 * 4); const indices = new Uint8Array(8 * 8); const background = new Uint8Array(16); background[0] = 0x80; // Colored grass at x=0; color zero elsewhere. drawTile(target, 8, 8, background, 0, 0, 0, palette, { indices }); const sprite = new Uint8Array(16); sprite[1] = 0xc0; // Sprite color 2 at x=0 and x=1, transparent at x=2. drawTile(target, 8, 8, sprite, 0, 0, 0, palette, { indices, behind: true, transparent: true, }); expect([...target.slice(0, 12)]).toEqual([ 80, 160, 80, 255, 32, 64, 32, 255, 255, 255, 255, 255, ]); expect([...indices.slice(0, 3)]).toEqual([1, 0, 0]); drawTile(target, 8, 8, sprite, 0, 0, 0, palette, { indices, transparent: true, }); expect([...target.slice(0, 4)]).toEqual([32, 64, 32, 255]); }); const romPath = process.env.CRYSTAL_ROM_PATH ?? "Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc"; describe.skipIf(!existsSync(romPath))( "overworld rendering with the local ROM", () => { it("runs every tileset animation without changing the cached ROM graphics", () => { const assets = new CrystalAssets(new Uint8Array(readFileSync(romPath))); const base = assets.map("24:4"); for (let tileset = 1; tileset <= OW.NUM_TILESETS; tileset++) { const map = { ...base, tileset }; const graphics = assets.tileset(map).graphics; const original = graphics.slice(); const animator = new TilesetAnimator(assets, map, graphics); animator.advance(1024, false); animator.advance(1024, true); expect(graphics, `tileset ${tileset}`).toEqual(original); expect(animator.graphics.length).toBe(original.length); } }); it("keeps special indoor palettes fixed and uses normal palettes for the Hall of Fame", () => { const assets = new CrystalAssets(new Uint8Array(readFileSync(romPath))); const base = assets.map("24:6"); for (const tileset of [ OW.TILESET_POKECOM_CENTER, OW.TILESET_BATTLE_TOWER_INSIDE, OW.TILESET_HOUSE, OW.TILESET_RADIO_TOWER, OW.TILESET_MANSION, ]) { const map = { ...base, tileset }; expect(assets.palettes(map, 0)).toEqual(assets.palettes(map, 2)); expect(assets.palettes(map, 0)).toHaveLength(8); } const ice = { ...base, tileset: OW.TILESET_ICE_PATH, environment: OW.CAVE, }; expect(assets.palettes(ice, 0)).toEqual(assets.palettes(ice, 2)); const hall = { ...ice, environment: OW.INDOOR }; expect(assets.palettes(hall, 1)).not.toEqual(assets.palettes(ice, 1)); expect(assets.palettes(hall, 1)).toEqual( assets.palettes({ ...hall, tileset: OW.TILESET_POKECENTER }, 1), ); }); }, );