Open Route 29 and Cherrygrove City
Checks / web (push) Canceled after 0s

- Enable Route 29, its Route 46 gate, Cherrygrove City and its buildings.
- Run item balls through FindItemInBallScript and fruit trees through
  FruitTreeScript, with a native daily reset of the daily engine flags.
- Port the Pokémon Center nurse (HealParty with the PP Up clamp,
  CheckPokerus, phone-call checks) and record the whiteout spawn on
  entering a Pokémon Center, as EnterMapWarp does.
- Port standard Poké Marts with keyboard, D-pad and pointer menus.
- Add the recoil battle command (Struggle) and VAR_BOXSPACE.
- Save fruit trees, the daily reset day and the last spawn map; older
  version-3 backups migrate by absence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sébastien QUEROLandClaude Opus 5.5 committed 2026-09-23 17:25:02 +02:00
1 parent 11d1153802
commit 796fe8f6d5
24 files changed
+1549 -59

No files matched your search

+50
View File
@@ -3,6 +3,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { createHash } from "node:crypto";
import { resolve } from "node:path";
import { CrystalAssets } from "../src/game/rom/assets";
import { generatePartyMon } from "../src/game/party/pokemon";
import { GameWorld } from "../src/game/world/world";
import type { Direction } from "../src/game/world/types";
import { VECTORS } from "../src/game/world/types";
@@ -362,6 +363,55 @@ test("a home exit mat survives save/reload and waits for the exit direction", as
await expect(page.locator("#game-notice")).toHaveText("");
});
test("walks from New Bark Town onto Route 29 after receiving a starter", async ({
page,
}, testInfo) => {
test.skip(!existsSync(romPath), "Requires a local user-supplied ROM.");
const bytes = readFileSync(romPath);
const assets = new CrystalAssets(new Uint8Array(bytes));
const world = new GameWorld(assets);
const town = assets.map("24:4");
const edge = Array.from({ length: town.height }, (_, y) => y).find(
(y) => assets.permission(assets.collision(town, 0, y)) === 0,
)!;
world.state.party.push(
generatePartyMon(assets.pokemon, {
species: 155, level: 5, item: 0, otId: world.playerId,
ot: "CHRIS", dvs: 0x823b, caughtData: 0,
}),
);
// SCENE_NEWBARKTOWN_NOOP: after the starter the teacher no longer stops you.
world.restore({ ...world.save(), map: "24:4", x: 0, y: edge, direction: "left", scenes: { "24:4": 1 } });
const backup = testInfo.outputPath("new-bark-edge.crystal-save.json");
writeFileSync(
backup,
JSON.stringify({
version: 1,
romHash: createHash("sha1").update(bytes).digest("hex"),
coreId: "crystal-native-world-v1",
savedAt: new Date().toISOString(),
state: Buffer.from(JSON.stringify(world.save())).toString("base64"),
}),
);
await page.goto("/");
await page.locator("#rom-file").setInputFiles(romPath);
await expect(page.locator("#status")).toContainText("verified");
await page.locator("#tab-saves").click();
page.once("dialog", (dialog) => dialog.accept());
await page.locator("#save-file").setInputFiles(backup);
await expect(page.locator("#status")).toContainText("Backup restored");
await page.locator("#play").click();
const screen = page.locator("#game-screen");
await page.keyboard.down("ArrowLeft");
await page.waitForFunction(
() => document.querySelector("#game-screen")?.getAttribute("data-map") === "24:3",
);
await page.keyboard.up("ArrowLeft");
await expect(screen).toHaveAttribute("data-activity", "idle");
await expect(page.locator("#location")).toHaveText("Route 29");
await expect(page.locator("#game-notice")).toHaveText("");
});
test("development server does not serve ROMs or the private reference checkout", async ({
request,
}) => {