Port the introduction's naming and clock screens
Checks / web (push) Canceled after 0s

A new game now runs NewGame's InitGender and OakSpeech natively before
the bedroom: Boy or Girl, InitClock's hour and minute steppers with
their YES/NO confirmations, Oak's speech with Wooper, and NamePlayer's
preset names or naming field (a blank name takes CHRIS or KRIS). Texts,
menus and time strings come from a new INTRO profile table verified by
check:reference; Oak's and the player's pictures decode from
TrainerPicPointers, ChrisPic/KrisPic and TrainerPalettes.

The chosen time is kept as a minute offset from the device clock, which
time of day, the weekday, NPC hours and the daily reset now read. The
intro runs only when nothing is saved; a save made during it starts it
again. Saves gain intro and clock.minuteOffset, migrated by absence. The
shell's name and character fields are gone. Intentional change: the
steppers start at the device time rather than 10:00.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sébastien QUEROLandClaude Opus 5.5 committed 2026-09-30 09:53:52 +02:00
1 parent 5a0fe1e313
commit b13fd20be4
28 files changed
+975 -101

No files matched your search

+44 -2
View File
@@ -1,6 +1,12 @@
import { RomReader } from "./reader";
import { decompress } from "./lz";
import { NUM_ITEMS, NUM_POKEMON, SYMBOLS } from "./profile";
import {
INTRO,
NUM_ITEMS,
NUM_POKEMON,
NUM_TRAINER_CLASSES,
SYMBOLS,
} from "./profile";
import { defaultTextContext, readString } from "../scripts/text";
import type { Palette } from "../world/types";
@@ -254,8 +260,44 @@ export class PokemonData {
/** Normal colors from PokemonPalettes, between white and black. */
palette(species: number, shiny = false): Palette {
this.checkSpecies(species);
return this.middleColors(
SYMBOLS.pokemonPalettes + species * 8 + (shiny ? 4 : 0),
);
}
/** A trainer class's front picture (GetTrainerPic): 7x7 tiles, column
* by column. */
trainerPic(classId: number): Uint8Array {
if (
!Number.isInteger(classId) ||
classId < 1 ||
classId > NUM_TRAINER_CLASSES
)
throw new Error("Unknown trainer class.");
const r = this.reader,
p = SYMBOLS.pokemonPalettes + species * 8 + (shiny ? 4 : 0);
entry = INTRO.TrainerPicPointers + (classId - 1) * 3;
const pics1 = r.byte(SYMBOLS.picsBanks);
const bank = r.byte(SYMBOLS.picsBanks + r.byte(entry) - (pics1 - 0x36));
const tiles = decompress(r, r.address(bank, r.word(entry + 1)), 0x1000);
if (tiles.length < 49 * 16) throw new Error("Picture data is truncated.");
return tiles.slice(0, 49 * 16);
}
/** DrawIntroPlayerPic's uncompressed ChrisPic or KrisPic. */
playerPic(female: boolean): Uint8Array {
return this.reader.slice(female ? INTRO.KrisPic : INTRO.ChrisPic, 49 * 16);
}
/** TrainerPalettes, by class: CHRIS is 0 and KRIS shares class 1. */
trainerPalette(classId: number): Palette {
if (
!Number.isInteger(classId) ||
classId < 0 ||
classId > NUM_TRAINER_CLASSES
)
throw new Error("Unknown trainer class.");
return this.middleColors(INTRO.TrainerPalettes + classId * 4);
}
/** Two stored colors between white and black. */
private middleColors(p: number): Palette {
const r = this.reader;
const color = (value: number) =>
[
Math.round(((value & 31) * 255) / 31),
+38
View File
@@ -281,6 +281,44 @@ export const OVERWORLD = {
MansionPalette2: 0x496fe,
} as const;
/** A new game's introduction (engine/menus/intro_menu.asm NewGame):
* InitGender, OakSpeech with InitClock and NamePlayer, and the pictures
* they show, keyed by their PRET labels for \`npm run check:reference\`. */
export const INTRO = {
TrainerPicPointers: 0x128000,
TrainerPalettes: 0xb0ce,
ChrisPic: 0x888a9,
KrisPic: 0x88bb9,
_AreYouABoyOrAreYouAGirlText: 0x1c0ca3,
"InitGender.MenuData": 0x48e04,
_OakText1: 0x1c1d35,
_OakText2: 0x1c1da4,
_OakText4: 0x1c1de5,
_OakText5: 0x1c1e51,
_OakText6: 0x1c4000,
_OakText7: 0x1c4026,
_OakTimeWokeUpText: 0x1bc29c,
_OakTimeWhatTimeIsItText: 0x1bc2eb,
_OakTimeWhatHoursText: 0x1bc2fd,
_OakTimeHoursQuestionMarkText: 0x1bc305,
_OakTimeHowManyMinutesText: 0x1bc308,
_OakTimeWhoaMinutesText: 0x1bc31b,
_OakTimeMinutesQuestionMarkText: 0x1bc323,
_OakTimeOversleptText: 0x1bc326,
_OakTimeYikesText: 0x1bc336,
_OakTimeSoDarkText: 0x1bc34f,
"ChrisNameMenuHeader.MaleNames": 0x882be,
"KrisNameMenuHeader.FemaleNames": 0x882ee,
"NamingScreenJumptable.PlayerNameString": 0x117a3,
"NamePlayer.Chris": 0x60d3,
"NamePlayer.Kris": 0x60de,
String_oclock: 0x9087e,
String_min: 0x9089f,
"GetTimeOfDayString.nite_string": 0x90b71,
"GetTimeOfDayString.morn_string": 0x90b76,
"GetTimeOfDayString.day_string": 0x90b7b,
} as const;
/** Pokémon Center and house PCs, Bill's PC and Oak's PC: routines, menu
* data, strings and texts, keyed by their PRET labels so that
* \`npm run check:reference\` can verify each address by name. */