Open the League: Victory Road, the Elite Four, the Hall of Fame and the credits (#33)
Checks / web (push) Failing after 18s
Private ROM checks / private-rom (push) Failing after 25m31s

Routes 26 and 27 with their houses, Tohjo Falls, the Victory Road gate,
Victory Road, Route 23, Indigo Plateau, the Elite Four's rooms, Lance's
room and the Hall of Fame are open.

- halloffame (game/scripts/league.ts): the music fades as SAVING RECORD
  shows, the win and the team (without Eggs) are recorded, each Pokémon's
  back picture passes and its front picture slides in for its "New Hall of
  Famer!" page and cry, then the player's card with the play time and
  Professor Oak's rating; the play time stops meanwhile. The world then
  continues at New Bark as Continue does after the cartridge's reset.
- credits and the credits after the Hall of Fame (CreditsMachine): the
  ROM's script, strings, banner Pokémon and THE END, frame by frame as
  VBlank_Credits presents them, with the frames each command group runs
  over measured on the cartridge. B shortens credits seen before.
- wait, appear/disappear of the player, and rival names in battle texts
  (PlaceEnemysName prints wRivalName alone: "???" before he is named).
- The tile-screen renderer wraps hSCX/hSCY at 256 pixels and gives rows
  their own hSCX.
- Reference: the credits match the cartridge on every frame (BG map,
  banner tiles, CRAM palettes, LY overrides, parser), with and without B;
  the ceremony's pages match for every species and the player's card for
  Chris and Kris.
- Playthrough: five stages from Tin Tower to the Hall of Fame (HM07 from
  Ice Path, Tohjo Falls' waterfall, Victory Road's ladders, the Elite Four
  and Lance); the driver follows warps within a map and fights carefully
  from here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kawaandClaude Opus 5.5 committed 2026-10-17 14:52:14 +02:00
1 parent b6fdc23508
commit f05fd8ebee
23 files changed
+1943 -107

No files matched your search

+168 -1
View File
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, onTestFailed } from "vitest";
import { existsSync, readFileSync } from "node:fs";
import { CrystalAssets } from "../src/game/rom/assets";
import { C } from "../src/game/battle/constants";
import { tmhmItem } from "../src/game/rom/pokemon";
import { Playthrough } from "./playthrough-driver";
import { findPath } from "./world-driver";
@@ -214,6 +215,29 @@ const ROUTE_46 = "5:9",
TIN_TOWER_1F = "3:4",
EVENT_RANG_CLEAR_BELL_2 = 0x767,
EVENT_FOUGHT_SUICUNE = 0x335;
const WISE_TRIOS_ROOM_EXIT = "4:2",
MAHOGANY_ROUTE_44 = "2:6",
ROUTE_27 = "24:2",
TOHJO_FALLS = "3:83",
ROUTE_26 = "24:1",
VICTORY_ROAD_GATE = "23:13",
VICTORY_ROAD = "3:91",
ROUTE_23 = "16:1",
INDIGO_PLATEAU = "16:2",
ELITE_FOUR = ["16:3", "16:4", "16:5", "16:6"],
LANCES_ROOM = "16:7";
const HM07 = 0xf9,
WATERFALL = 127,
IRON_TAIL = 231,
TYPHLOSION = 157,
EVENT_GOT_HM07_WATERFALL = 0x688,
EVENT_RIVAL_VICTORY_ROAD = 0x6c2,
EVENT_BEAT_ELITE_4_WILL = 0x5b8,
EVENT_BEAT_ELITE_4_KOGA = 0x5b9,
EVENT_BEAT_ELITE_4_BRUNO = 0x5ba,
EVENT_BEAT_ELITE_4_KAREN = 0x5bb,
EVENT_BEAT_CHAMPION_LANCE = 0x5bc,
EVENT_BEAT_ELITE_FOUR = 0x44;
/** The acceptance run for PLAN section 2: a new game played from the
* introduction to the catching tutorial through the world's own inputs,
@@ -343,7 +367,8 @@ describe.skipIf(!existsSync(romPath))(
// The rival's scene blocks the way east to Route 29.
cross("right", ROUTE_29);
const rival = p.log.join("\n");
expect(rival).toContain("RIVAL ?");
// PlaceEnemysName: a rival is wRivalName alone, "???" until named.
expect(rival).toContain("???\nwants to battle!");
expect(rival).toContain("My name's ???.");
expect(p.world.state.scenes[CHERRYGROVE]).toBe(0);
reload();
@@ -1486,5 +1511,147 @@ describe.skipIf(!existsSync(romPath))(
reload();
writeTestSave("tin-tower");
}, 240_000);
it("goes back to Ice Path for HM07 and walks home to New Bark Town", () => {
traceFailures();
// From here on the run fights as a player facing the League would.
p.careful = true;
// Tin Tower's grounds lead back through the Wise Trio's room and the
// entrance building, whose far side comes down by its stairs.
stairs(ECRUTEAK);
stairs(WISE_TRIOS_ROOM_EXIT);
stairs(TIN_TOWER_ENTRANCE);
walk(17, 14);
p.world.move("down");
play();
stairs(ECRUTEAK);
stairs(ROUTE_42_GATE);
stairs(ROUTE_42);
cross("right", MAHOGANY, { water: true });
heal(MAHOGANY_CENTER, MAHOGANY);
cross("right", MAHOGANY_ROUTE_44);
stairs(ICE_PATH[0]);
// The HM07 ball at (31, 7), reached from its left over the ice.
walk(30, 7);
if (p.world.save().direction !== "right") p.world.move("right");
p.world.interact();
play();
expect(p.world.state.events.has(EVENT_GOT_HM07_WATERFALL)).toBe(true);
expect(p.world.state.bag.tmsHms[56]).toBe(1);
stairs(MAHOGANY_ROUTE_44);
cross("left", MAHOGANY);
cross("left", ROUTE_42, { water: true });
sail(2, 8);
stairs(ROUTE_42_GATE);
stairs(ECRUTEAK);
cross("down", ROUTE_37);
cross("down", ROUTE_36);
cross("right", VIOLET);
stairs(ROUTE_31_GATE);
stairs(ROUTE_31);
cross("down", ROUTE_30);
cross("down", CHERRYGROVE);
cross("right", ROUTE_29);
cross("right", NEW_BARK);
reload();
}, 240_000);
it("surfs Route 27, climbs Tohjo Falls and takes Victory Road to the Plateau", () => {
traceFailures();
// Dratini learns Waterfall; Typhlosion takes Iron Tail for Leer.
p.teach(HM07, 5, 0);
p.teach(tmhmItem(23), 0, 1);
expect(p.world.state.party[5].moves[0]).toBe(WATERFALL);
expect(p.world.state.party[0].moves[1]).toBe(IRON_TAIL);
cross("right", ROUTE_27, { water: true });
travel(TOHJO_FALLS, [], { water: true });
// Up the western waterfall, along the top pool and down the eastern
// one to the far shore and Route 27's other half.
sail(9, 12);
if (p.world.save().direction !== "up") p.world.move("up");
p.world.interact();
play([1]);
expect({ x: p.world.x, y: p.world.y }).toEqual({ x: 9, y: 7 });
sail(22, 14);
walk(25, 15);
if (p.world.mapId === TOHJO_FALLS) {
p.world.move("down");
play();
}
expect(p.world.mapId).toBe(ROUTE_27);
cross("right", ROUTE_26, { water: true });
travel(VICTORY_ROAD_GATE, [], { water: true });
// VictoryRoadGateBadgeCheckScript on the way north.
stairs(VICTORY_ROAD);
expect(p.log.join(" ")).toContain("BADGES of JOHTO!");
// Victory Road's floors are joined by its own ladders; the rival
// waits by the exit.
travel(ROUTE_23, [VICTORY_ROAD]);
// The rival leaves (EVENT_RIVAL_VICTORY_ROAD hides him) after the battle.
expect(p.world.state.events.has(EVENT_RIVAL_VICTORY_ROAD)).toBe(true);
expect(p.log.join(" ")).toContain("SILVER\nwants to battle!");
reload();
}, 240_000);
it("stocks up at Indigo Plateau and beats Will", () => {
traceFailures();
stairs(INDIGO_PLATEAU);
talkTo(2); // the nurse, across the counter
play();
expect(p.world.state.party.every((mon) => mon.hp === mon.stats[0])).toBe(true);
writeTestSave("indigo-plateau");
buy(3, C.FULL_HEAL, 15);
buy(3, C.MAX_POTION, Math.floor((p.world.state.money - 2000) / 2500));
stairs(ELITE_FOUR[0]);
play(); // the door locks behind the player
talkTo(p.world.map.objects[0].id);
play();
expect(p.world.state.events.has(EVENT_BEAT_ELITE_4_WILL)).toBe(true);
reload();
}, 240_000);
it("beats Koga, Bruno and Karen", () => {
traceFailures();
const events = [EVENT_BEAT_ELITE_4_KOGA, EVENT_BEAT_ELITE_4_BRUNO, EVENT_BEAT_ELITE_4_KAREN];
ELITE_FOUR.slice(1).forEach((room, index) => {
stairs(room);
play();
talkTo(p.world.map.objects[0].id);
play();
expect(p.world.mapId).toBe(room);
expect(p.world.state.events.has(events[index])).toBe(true);
});
reload();
}, 240_000);
it("beats Lance and enters the Hall of Fame, then wakes in New Bark Town", () => {
traceFailures();
stairs(LANCES_ROOM);
play();
// Script_ApproachLanceFromRight: the step towards Lance starts it;
// after the battle Mary and Oak rush in and Lance leads the way to
// the Hall of Fame, where the ceremony and the credits play.
walk(5, 6);
p.world.move("up");
play();
const log = p.log.join(" ");
expect(log).toContain("CHAMPION LANCE\nwas defeated!");
expect(log).toContain("MARY: Let's inter-");
expect(log).toContain("and your partners\nas CHAMPIONS!");
expect(p.world.state.events.has(EVENT_BEAT_CHAMPION_LANCE)).toBe(true);
expect(p.world.state.events.has(EVENT_BEAT_ELITE_FOUR)).toBe(true);
expect(p.world.state.hallOfFame).toBe(true);
expect(p.world.state.hallOfFameCount).toBe(1);
expect(p.world.state.hallOfFameTeams[0].mons.map((mon) => mon.species)).toEqual(
p.world.state.party.map((mon) => mon.species),
);
expect(p.world.state.hallOfFameTeams[0].mons[0].species).toBe(TYPHLOSION);
// Continue after the credits: SPAWN_NEW_BARK, outside the lab.
expect(p.world.mapId).toBe(NEW_BARK);
expect({ x: p.world.x, y: p.world.y }).toEqual({ x: 13, y: 6 });
expect(p.world.busy).toBe(false);
reload();
writeTestSave("hall-of-fame-new-bark");
}, 300_000);
},
);