Complete the overworld foundation
Checks / web (push) Canceled after 0s

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:
Sébastien QUEROLandClaude Opus 5.5 committed 2026-09-25 16:47:29 +02:00
1 parent 9ac111f262
commit 451464f7c6
38 files changed
+5600 -490

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
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),
);
});
},
);