Move items with Select and browse the party's moves

In the start menu's pack, Select marks an item and asks where it should
go; A or Select on another row moves it there, merging stacks of the
same item unless either holds 99, and B ends the move. The packs that
GIVE and the PC open ignore Select, as in the original.

Left and right on the MOVE screen show the previous or next party
member's moves, skipping Eggs, unless a move is being moved.

Part of #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sébastien QUEROLandClaude Opus 5.5 committed 2026-10-03 00:56:51 +02:00
1 parent 397dcef44c
commit e2dc5ba520
9 files changed
+362 -34

No files matched your search

+31
View File
@@ -508,6 +508,37 @@ test("Headbutt shakes a tree and a sleeping Hoothoot falls out", async ({
await expect(screen).toHaveAttribute("data-map", "24:3");
});
test("Select moves an item within the pack", async ({ page }) => {
await start(page, "candy");
const choices = page.locator("#dialogue-choice"),
pack = page.locator("#mart");
await page.keyboard.press("Enter");
await choices.getByRole("option", { name: /^PACK/ }).click();
const entries = pack.locator("#mart-menu button");
await expect(entries.first()).toContainText("POTION");
// Select on the highlighted Potions, down to the Rare Candy, Select.
await page.keyboard.press("ShiftLeft");
await expect(page.locator("#mart-prompt")).toHaveText(
"Where should this be moved to?",
);
await expect(entries.first()).toContainText("▷POTION");
await page.keyboard.press("ArrowDown");
await page.keyboard.press("ShiftLeft");
await expect(page.locator("#mart-prompt")).toHaveText("");
await expect(entries.nth(0)).toContainText("RARE CANDY");
await expect(entries.nth(1)).toContainText("POTION");
// B leaves the pack, then the start menu, which commits the change.
await page.keyboard.press("KeyZ");
await expect(pack).toBeHidden();
await page.keyboard.press("KeyZ");
await page.clock.runFor(500);
await expect(page.locator("#native-dialogue")).toBeHidden();
const { state } = await backup(page);
expect(state.bag.items.map((slot: { item: number }) => slot.item)).toEqual([
0x20, 18,
]);
});
test("the Old Rod hooks a Magikarp from New Bark Town's shore", async ({
page,
}) => {
+137
View File
@@ -17,6 +17,7 @@ const NEW_BARK_TOWN = "24:4",
PIKACHU = 25,
RAICHU = 26;
const BICYCLE = 7,
COIN_CASE = 0x36,
ANTIDOTE = 9,
FULL_RESTORE = 0x0e,
POTION = 0x12,
@@ -313,6 +314,142 @@ describe.skipIf(!existsSync(romPath))("the start menu", () => {
expect(world.state.party[0].pp).toEqual([20, 30, 30, 0]);
});
it("shows the other party members' moves with left and right, skipping Eggs", () => {
const world = fixture();
const party = world.state.party;
party.push({ ...structuredClone(party[1]), species: 0xfd }); // EGG
open(world);
pick(world, "POKéMON");
pick(world, "CYNDAQUIL");
expect(pick(world, "MOVE")).toEqual(["CYNDAQUIL :L8"]);
const heading = (direction: "left" | "right") => {
world.moveCursor(direction);
return until(world)[0];
};
expect(heading("right")).toBe("PIDGEY :L4");
expect(menuItems(world)).toEqual(["TACKLE"]);
// Past the last Pokémon that is not an Egg, .cycle_right comes back.
expect(heading("right")).toBe("PIDGEY :L4");
expect(heading("left")).toBe("CYNDAQUIL :L8");
expect(heading("left")).toBe("CYNDAQUIL :L8");
// Not while a move is being moved.
pick(world, "LEER");
world.moveCursor("right");
expect(world.dialogue?.pages[0]).toBe("Where?");
world.advanceDialogue();
until(world);
expect(heading("right")).toBe("PIDGEY :L4");
// B returns to the party menu, on the Pokémon shown last.
world.advanceDialogue();
until(world);
const menu = world.dialogue?.choice;
expect(menu?.kind === "menu" && menu.items[menu.selected]).toBe("PIDGEY");
close(world);
});
it("moves items within a pocket with Select, merging stacks of the same item", () => {
const world = fixture();
world.state.bag.items = [
{ item: POTION, quantity: 5 },
{ item: ANTIDOTE, quantity: 1 },
{ item: ETHER, quantity: 1 },
{ item: POTION, quantity: 98 },
];
world.state.bag.keyItems = [BICYCLE, COIN_CASE];
open(world);
pick(world, "PACK");
const pack = () => {
const choice = world.dialogue?.choice;
if (choice?.kind !== "pack") throw new Error(`Expected the pack, got ${choice?.kind}`);
return choice;
};
const items = () => world.state.bag.items.map((slot) => [slot.item, slot.quantity]);
// Select marks the Potions and asks where; the pocket stays put.
world.useSelect();
until(world);
expect([pack().switching, pack().prompt]).toEqual([
0,
"Where should this\nbe moved to?",
]);
world.moveCursor("right");
expect(pack().pocket).toBe(0);
// A (or Select) on another row moves them there.
world.choosePackEntry(0, 2);
until(world);
expect(pack().switching).toBeUndefined();
expect(pack().selected).toBe(2);
// Nothing commits until the menu closes.
expect(world.save().bag.items[0].item).toBe(POTION);
world.useSelect();
until(world);
world.choosePackEntry(0, 3, true);
until(world);
// 5 + 98: the destination fills to 99 and 4 stay behind.
world.useSelect();
until(world);
world.advanceDialogue(); // B ends the move, still in the pack
until(world);
expect([pack().kind, pack().switching]).toEqual(["pack", undefined]);
world.choosePackEntry(0, 1, true);
until(world);
world.choosePackEntry(0, 1, true); // onto itself: nothing moves
until(world);
world.moveCursor("right");
world.moveCursor("right");
world.choosePackEntry(2, 1, true);
until(world);
world.choosePackEntry(2, 0, true);
until(world);
close(world);
expect(items()).toEqual([
[ANTIDOTE, 1],
[ETHER, 1],
[POTION, 4],
[POTION, 99],
]);
expect(world.state.bag.keyItems).toEqual([COIN_CASE, BICYCLE]);
// Two stacks that fit merge into one; a full stack never merges.
world.state.bag.items = [
{ item: POTION, quantity: 2 },
{ item: POTION, quantity: 3 },
{ item: ETHER, quantity: 99 },
{ item: ETHER, quantity: 1 },
];
open(world);
pick(world, "PACK");
world.choosePackEntry(0, 0, true);
until(world);
world.choosePackEntry(0, 1, true);
until(world);
world.choosePackEntry(0, 2, true);
until(world);
world.choosePackEntry(0, 1, true);
until(world);
close(world);
expect(items()).toEqual([
[POTION, 5],
[ETHER, 1],
[ETHER, 99],
]);
});
it("does not move items in the pack that GIVE opens", () => {
const world = fixture();
open(world);
pick(world, "POKéMON");
pick(world, "CYNDAQUIL");
pick(world, "ITEM");
pick(world, "GIVE");
world.useSelect();
until(world);
const choice = world.dialogue?.choice;
expect(choice?.kind).toBe("pack");
expect(choice?.kind === "pack" && choice.switching).toBeUndefined();
close(world);
expect(world.state.bag.items.map((slot) => slot.item)).toEqual([POTION, ANTIDOTE]);
});
it("gives, swaps and takes held items", () => {
const world = fixture();
world.state.bag.items.push({ item: LEFTOVERS, quantity: 1 });