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

+53 -7
View File
@@ -39,6 +39,11 @@ export class Playthrough {
/** Plan ledge jumps (Tin Tower, Route 45). Off for the earlier stages,
* whose seeded runs were recorded walking around ledges. */
ledges = false;
/** Fight as a player facing the League would (off for the stages
* recorded before it): weigh each move by its accuracy, cure poison,
* paralysis and burns with a Full Heal in a trainer battle, and heal
* below two thirds of the HP rather than half. */
careful = false;
seed = 0x2a;
private stage = 0;
readonly now = () => new Date("2026-09-22T12:00:00");
@@ -82,14 +87,18 @@ export class Playthrough {
targetTypes,
identified: false,
}).damage;
const weighed = (move: number) =>
this.careful ? score(move) * this.assets.pokemon.move(move).accuracy : score(move);
let best = -1;
mon.moves.forEach((move, i) => {
if (!move || !(mon.pp[i] & 63) || i === disabled) return;
if (best < 0 || score(move) > score(mon.moves[best])) best = i;
if (best < 0 || weighed(move) > weighed(mon.moves[best])) best = i;
});
return Math.max(0, best);
};
private matchups?: ReturnType<BattleData["typeMatchups"]>;
private screenLabel = "";
private screenStill = 0;
/** Run scripts and battles until the player is free again. Battles use
* the strongest move with PP; a wild battle below half HP is run from,
* and a trainer battle (which cannot be run from) instead reaches for
@@ -122,6 +131,9 @@ export class Playthrough {
const awakening = world.liveState.bag.items.findIndex(
(slot) => slot.item === C.AWAKENING,
);
const fullHeal = world.liveState.bag.items.findIndex(
(slot) => slot.item === C.FULL_HEAL,
);
if (
engine.battleMode === C.WILD_BATTLE &&
engine.enemy.mon.species === this.wanted &&
@@ -143,9 +155,17 @@ export class Playthrough {
)
world.battleAction({ action: "item", item: C.AWAKENING, slot: awakening });
else if (
this.careful &&
engine.battleMode !== C.WILD_BATTLE &&
!prompt.noItems &&
mon.hp * 2 < mon.maxHp &&
mon.status & ((1 << C.PSN) | (1 << C.PAR) | (1 << C.BRN)) &&
fullHeal >= 0
)
world.battleAction({ action: "item", item: C.FULL_HEAL, slot: fullHeal });
else if (
engine.battleMode !== C.WILD_BATTLE &&
!prompt.noItems &&
mon.hp * (this.careful ? 3 : 2) < mon.maxHp * (this.careful ? 2 : 1) &&
potion >= 0
)
world.battleAction({
@@ -192,6 +212,15 @@ export class Playthrough {
const last = dialogue.index === dialogue.pages.length - 1;
if (log.at(-1) !== page) log.push(page);
if (last && dialogue.choice?.kind === "screen" && dialogue.choice.realtime) {
// A player watching a screen that runs on its own (the Hall of
// Fame's rating, THE END) presses A once it has stood still for
// four seconds.
const label = dialogue.choice.screen.label;
if (label !== this.screenLabel) [this.screenLabel, this.screenStill] = [label, 0];
else if (++this.screenStill >= 120) {
world.bufferScreenButton("a");
this.screenStill = 0;
}
world.tick();
continue;
}
@@ -223,15 +252,25 @@ export class Playthrough {
throw new Error(`The world did not settle: ${world.diagnostic}`);
};
/** Where a warp of the current map lands when it leads elsewhere on the
* same map (Victory Road's ladders, a building's inner stairs). */
private sameMapArrival = (x: number, y: number) => {
const world = this.world;
const warp = world.map.warps.find((warp) => warp.x === x && warp.y === y);
return warp?.targetMap === world.mapId ? world.map.warps[warp.targetWarp - 1] : undefined;
};
/** Walk to a tile on the current map, replanning after ledges, roaming
* NPCs and scenes that move the player. A warp target leaves the map. */
* NPCs and scenes that move the player. A warp target leaves the map, or
* lands at its destination on the same map. */
walk = (x: number, y: number) => {
const world = this.world,
map = world.mapId,
warp = world.map.warps.some((warp) => warp.x === x && warp.y === y);
warp = world.map.warps.some((warp) => warp.x === x && warp.y === y),
arrival = this.sameMapArrival(x, y);
for (let attempt = 0; attempt < 40; attempt++) {
this.play();
if (warp && world.mapId !== map) return;
if (arrival && world.x === arrival.x && world.y === arrival.y) return;
expect(world.mapId, `walking to ${x},${y}`).toBe(map);
if (world.x === x && world.y === y) return;
// A surfer crosses water until it steps ashore.
@@ -511,22 +550,29 @@ export class Playthrough {
continue;
}
// A surfer reaches a cave's stairs over water, then steps on.
const landedHere = () => {
const arrival = warp.targetMap === from ? world.map.warps[warp.targetWarp - 1] : undefined;
return Boolean(arrival && world.x === arrival.x && world.y === arrival.y);
};
if (water) {
try {
this.sail(warp.x, warp.y, { whirlpools: true });
} catch {
if (world.mapId === from) continue;
if (world.mapId === from && !landedHere()) continue;
}
} else
try {
this.walk(warp.x, warp.y);
} catch {
// A warp panel on the way can land elsewhere on the same map.
if (world.mapId === from) continue;
if (world.mapId === from && !landedHere()) continue;
}
const exit = WARP_EXITS[world.assets.collision(world.map, warp.x, warp.y)];
if (world.mapId === from && exit && world.move(exit)) this.play();
if (world.mapId === from) continue;
// A warp to another place on the same map lands at its partner.
const arrival = warp.targetMap === from ? world.map.warps[warp.targetWarp - 1] : undefined;
const moved = arrival ? world.x === arrival.x && world.y === arrival.y : world.mapId !== from;
if (!moved) continue;
if (world.mapId === target) return;
const key = `${world.mapId} ${world.x},${world.y}`;
if (seen.has(key)) continue;