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

+75 -49
View File
@@ -1,4 +1,4 @@
import type { HallOfFameTeam } from "../party/hall-of-fame";
import type { HallOfFameMon, HallOfFameTeam } from "../party/hall-of-fame";
import { PokemonData } from "../rom/pokemon";
import { JOHTO } from "../rom/profile";
import { fontTiles, FRAME_TILE, SPACE_TILE, textTiles } from "../rom/font";
@@ -7,63 +7,67 @@ import { blankScreen, SCREEN_BUTTONS, type TileScreen } from "./screen";
import { defaultTextContext, readString } from "./text";
import type { GameSound } from "../audio/sound";
/** _HallOfFamePC.DisplayMonAndStrings: DisplayHOFMon's page for one
* Pokémon of a team, over _LoadFontsBattleExtra's tiles, in
* SCGB_PLAYER_OR_MON_FRONTPIC_PALS's single palette. The picture is shown
* at rest: HOF_AnimateFrontpic's ANIM_MON_HOF animation is not ported. */
export function hallOfFameScreen(
data: PokemonData,
team: HallOfFameTeam,
slot: number,
frame: number,
): TileScreen {
const mon = team.mons[slot],
r = data.reader;
const tiles = fontTiles(r, frame);
tiles.set(r.slice(JOHTO.FontBattleExtra, 25 * 16), 0x60 * 16);
tiles.set(data.frontPic(mon.species, PokemonData.unownLetter(mon.dvs)));
const title = readString(
r,
JOHTO["_HallOfFamePC.TimeFamer"],
defaultTextContext(""),
/** Place text on a screen as tile numbers; '"' is <PK> in a nickname. */
export function placeText(screen: TileScreen, x: number, y: number, value: string) {
const tiles = (value.match(/ᴾᴷ|ᴹᴺ|[\s\S]/gu) ?? []).flatMap((character) =>
character === '"' ? [0xe1] : textTiles(character),
);
screen.map.splice(y * 20 + x, tiles.length, ...tiles);
}
/** Textbox: a frame with `width` interior columns and `height` interior
* rows whose top-left corner is at (x, y). */
export function placeTextbox(
screen: TileScreen,
x: number,
y: number,
height: number,
width = 18,
) {
const put = (column: number, row: number, values: readonly number[]) =>
screen.map.splice(row * 20 + column, values.length, ...values);
put(x, y, [FRAME_TILE, ...Array(width).fill(FRAME_TILE + 1), FRAME_TILE + 2]);
for (let row = y + 1; row <= y + height; row++) {
put(x, row, [FRAME_TILE + 3, ...Array(width).fill(SPACE_TILE), FRAME_TILE + 3]);
}
put(x, y + height + 1, [
FRAME_TILE + 4,
...Array(width).fill(FRAME_TILE + 1),
FRAME_TILE + 5,
]);
}
/** The Hall of Fame screens' tiles: the standard font, the textbox frame
* and _LoadFontsBattleExtra's $60-$78 (InitDisplayForHallOfFame and
* _HallOfFamePC load the same). */
export function hallOfFameTiles(data: PokemonData, frame: number) {
const tiles = fontTiles(data.reader, frame);
tiles.set(data.reader.slice(JOHTO.FontBattleExtra, 25 * 16), 0x60 * 16);
return tiles;
}
/** DisplayHOFMon: one Pokémon's page, over the Hall of Fame tiles, in
* SCGB_PLAYER_OR_MON_FRONTPIC_PALS's single palette, without the title
* line its callers print at (1, 2). The picture is shown at rest:
* HOF_AnimateFrontpic's ANIM_MON_HOF animation is not ported. */
export function displayHOFMon(
data: PokemonData,
mon: HallOfFameMon,
frame: number,
label: string,
): TileScreen {
const tiles = hallOfFameTiles(data, frame);
tiles.set(data.frontPic(mon.species, PokemonData.unownLetter(mon.dvs)));
const screen = blankScreen(
tiles,
`hof-${mon.species}-${mon.dvs}-${frame}`,
[data.palette(mon.species, PokemonData.shiny(mon.dvs))],
`${team.win}${title.trimStart()}: ${mon.nickname}, ${data.speciesName(mon.species)}, level ${mon.level}, ID ${String(mon.otId).padStart(5, "0")}`,
`${label}: ${mon.nickname}, ${data.speciesName(mon.species)}, level ${mon.level}, ID ${String(mon.otId).padStart(5, "0")}`,
);
screen.map.fill(SPACE_TILE);
const put = (x: number, y: number, values: readonly number[]) =>
screen.map.splice(y * 20 + x, values.length, ...values);
const text = (x: number, y: number, value: string) =>
put(
x,
y,
(value.match(/ᴾᴷ|ᴹᴺ|[\s\S]/gu) ?? []).flatMap((character) =>
character === '"' ? [0xe1] : textTiles(character),
),
);
// Textbox with 18 interior columns and three/four interior rows.
const box = (y: number, height: number) => {
put(0, y, [FRAME_TILE, ...Array(18).fill(FRAME_TILE + 1), FRAME_TILE + 2]);
for (let row = y + 1; row <= y + height; row++) {
put(0, row, [FRAME_TILE + 3]);
put(19, row, [FRAME_TILE + 3]);
}
put(0, y + height + 1, [
FRAME_TILE + 4,
...Array(18).fill(FRAME_TILE + 1),
FRAME_TILE + 5,
]);
};
box(0, 3);
box(12, 4);
// "HOF Master!" needs a count of HOF_MASTER_COUNT + 1, which the capped
// count never reaches: the original's bug (docs/bugs_and_glitches.md)
// leaves every team "-Time Famer".
text(1, 2, title);
text(2, 2, String(team.win).padStart(3, " "));
const text = (x: number, y: number, value: string) => placeText(screen, x, y, value);
placeTextbox(screen, 0, 0, 3);
placeTextbox(screen, 0, 12, 4);
for (let x = 0; x < 7; x++)
for (let y = 0; y < 7; y++) put(x + 6, y + 5, [x * 7 + y]);
put(1, 13, [0x74, 0xf2]); // № and <DOT>.
@@ -83,6 +87,28 @@ export function hallOfFameScreen(
return screen;
}
/** _HallOfFamePC.DisplayMonAndStrings: DisplayHOFMon's page for one
* Pokémon of a team with the team's "-Time Famer" title. */
export function hallOfFameScreen(
data: PokemonData,
team: HallOfFameTeam,
slot: number,
frame: number,
): TileScreen {
const title = readString(
data.reader,
JOHTO["_HallOfFamePC.TimeFamer"],
defaultTextContext(""),
);
const screen = displayHOFMon(data, team.mons[slot], frame, `${team.win}${title.trimStart()}`);
// "HOF Master!" needs a count of HOF_MASTER_COUNT + 1, which the capped
// count never reaches: the original's bug (docs/bugs_and_glitches.md)
// leaves every team "-Time Famer".
placeText(screen, 1, 2, title);
placeText(screen, 2, 2, String(team.win).padStart(3, " "));
return screen;
}
/** _HallOfFamePC: LoadHOFTeam takes the teams newest first until an absent
* one (or NUM_HOF_TEAMS); A shows the team's next Pokémon, and after the
* last one the next team, Start skips to the next team and B leaves. Each