Port forced movement (turn delays, bumps, ice, currents, pits), dynamic return warps, elevators, Surf, Cut, Strength and cycling rules, working map blocks with changeblock/changemapblocks, and the remaining NPC movement types and movement commands. Add tileset animation from the ROM's command tables, grass sprite priority and special map palettes; room decorations with ownership, the bedroom PC and save migration; and Key Items access to the Bicycle. Fourteen original-engine traces compare positions, facing, actors, event flags and scripted poses. Tile animation matches original VRAM for four tilesets, and LoadMapPals matches 192 palette cases. skyfall_top now keeps the $60 sprite offset its step function leaves behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9ac111f262
commit
451464f7c6
38 files changed
+5600
-490
No files matched your search
@@ -0,0 +1,101 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { CrystalAssets } from "../src/game/rom/assets";
|
||||
|
||||
const romPath =
|
||||
process.env.CRYSTAL_ROM_PATH ??
|
||||
"Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc";
|
||||
const symbolsPath = ".reference/pokecrystal/pokecrystal11.sym";
|
||||
test("all tileset palettes match the original LoadMapPals routine", async ({
|
||||
page,
|
||||
}, info) => {
|
||||
test.skip(
|
||||
info.project.name !== "chromium" ||
|
||||
!existsSync(romPath) ||
|
||||
!existsSync(symbolsPath),
|
||||
"Requires the local ROM/reference; engine comparison runs in Chromium.",
|
||||
);
|
||||
const symbols: Record<string, { bank: number; address: number }> = {};
|
||||
for (const line of readFileSync(symbolsPath, "utf8").split("\n")) {
|
||||
const m = /^([0-9a-f]{2}):([0-9a-f]{4}) (\S+)$/.exec(line);
|
||||
if (m)
|
||||
symbols[m[3]] = { bank: parseInt(m[1], 16), address: parseInt(m[2], 16) };
|
||||
}
|
||||
const bytes = new Uint8Array(readFileSync(romPath));
|
||||
const cases = Array.from({ length: 36 * 4 }, (_, i) => ({
|
||||
tileset: 1 + Math.floor(i / 4),
|
||||
time: i % 4,
|
||||
environment: 3,
|
||||
group: 24,
|
||||
}));
|
||||
for (const environment of [1, 2, 4, 7])
|
||||
for (const time of [0, 1, 2, 3])
|
||||
for (const tileset of [1, 29, 30])
|
||||
cases.push({ tileset, time, environment, group: 24 });
|
||||
await page.goto("/reference.html");
|
||||
const original = await page.evaluate(
|
||||
async ({ rom, symbols, cases }) => {
|
||||
const { BinjgbEmulator } = await import("/src/emulator/binjgb.ts");
|
||||
const emu = new BinjgbEmulator();
|
||||
await emu.load(new Uint8Array(rom), document.createElement("canvas"));
|
||||
const write = (name: string, value: number) =>
|
||||
emu.writeMemory(symbols[name].address, value);
|
||||
const target = symbols.LoadMapPals;
|
||||
const program = [
|
||||
0xf3,
|
||||
0x31,
|
||||
0xfe,
|
||||
0xc7,
|
||||
0x3e,
|
||||
target.bank,
|
||||
0xe0,
|
||||
0x9d,
|
||||
0xea,
|
||||
0,
|
||||
0x20,
|
||||
0xcd,
|
||||
target.address & 255,
|
||||
target.address >> 8,
|
||||
0x3e,
|
||||
0x42,
|
||||
0xea,
|
||||
0xff,
|
||||
0xc6,
|
||||
0x18,
|
||||
0xfe,
|
||||
];
|
||||
const output = cases.map((c) => {
|
||||
emu.writeMemory(0xff70, 1);
|
||||
write("wMapTileset", c.tileset);
|
||||
write("wEnvironment", c.environment);
|
||||
write("wTimeOfDayPal", c.time);
|
||||
write("wMapGroup", c.group);
|
||||
emu.writeMemory(0xc6ff, 0);
|
||||
program.forEach((v, i) => emu.writeMemory(0xc000 + i, v));
|
||||
emu.setProgramCounter(0xc000);
|
||||
emu.advanceFrames(2);
|
||||
if (emu.readMemory(0xc6ff) !== 0x42)
|
||||
throw new Error("LoadMapPals did not return");
|
||||
emu.writeMemory(0xff70, symbols.wBGPals1.bank);
|
||||
return Array.from({ length: 32 }, (_, i) => {
|
||||
const p = symbols.wBGPals1.address + i * 2;
|
||||
const value = emu.readMemory(p) | (emu.readMemory(p + 1) << 8);
|
||||
return [0, 5, 10].map((shift) =>
|
||||
Math.round((((value >> shift) & 31) * 255) / 31),
|
||||
);
|
||||
});
|
||||
});
|
||||
emu.dispose();
|
||||
return output;
|
||||
},
|
||||
{ rom: [...bytes], symbols, cases },
|
||||
);
|
||||
const assets = new CrystalAssets(bytes),
|
||||
base = assets.map("24:6");
|
||||
cases.forEach((c, i) =>
|
||||
expect(
|
||||
assets.palettes({ ...base, ...c }, c.time).flat(),
|
||||
JSON.stringify(c),
|
||||
).toEqual(original[i]),
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user