Breed at the Day-Care, hatch Eggs, draw MAIL stationery and browse the Hall of Fame (Closes #29)

- Day-Care and breeding: deposits, withdrawal pricing and growth,
  compatibility, egg species, DVs and moves, DayCareStep, egg
  collection and the Odd Egg, compared with the cartridge's routines.
- CountStep: walking happiness, DoEggStep, DayCareStep and DoBikeStep
  in the original's order; HatchEggs with the ROM's hatch animation,
  compared frame by frame with the original.
- MAIL: all ten stationery types and the Portrait MAIL picture,
  compared with the cartridge's tilemaps, graphics and palettes.
- Printing: Bill's PC boxes, Pokedex PRNT, MAIL and the Photo Studio
  take the original's absent-printer path.
- Hall of Fame records (saved apart from the League flag, ready for the
  League to call) and the Hall of Fame PC, compared with the cartridge's
  _HallOfFamePC on 255 pages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kawaandClaude Opus 5.5 committed 2026-10-17 08:52:30 +02:00
1 parent 7d03f5c5ca
commit e49bc6de9a
49 files changed
+6028 -88

No files matched your search

+16 -1
View File
@@ -5,6 +5,7 @@ import {
MAP_GROUP_COUNT,
NATIVE_MAPS,
NUM_ENGINE_FLAGS,
NUM_POKEMON,
NUM_SPRITEMOVEDATA,
OVERWORLD,
PHONE,
@@ -24,6 +25,10 @@ import type {
FacingTile,
} from "../world/types";
/** Sprite ids above the one-byte range ask for a Pokémon's overworld icon by
* species (MON_SPRITE_ID + species). */
export const MON_SPRITE_ID = 0x100;
/** The only layer that knows where Crystal stores its data. Everything is
* decoded from the supplied ROM, on demand, and cached only in memory. */
export class CrystalAssets {
@@ -566,11 +571,21 @@ export class CrystalAssets {
}
/** GetSprite: overworld sprites, and Pokémon drawn from their party
* menu icons (GetMonSprite). Variable sprites are resolved by the world
* from wVariableSprites first; day-care Pokémon are not ported. */
* from wVariableSprites first. A Day-Care Pokémon (SPRITE_DAY_CARE_MON_1
* and 2, which GetMonSprite resolves to wBreedMon1/2's species) is
* asked for as MON_SPRITE_ID + its species, as LoadOverworldMonIcon
* draws it. */
sprite(id: number): Sprite | undefined {
const cached = this.sprites.get(id);
if (cached) return cached;
const r = this.reader;
if (id > MON_SPRITE_ID) {
const species = id - MON_SPRITE_ID;
if (species > NUM_POKEMON) return undefined;
const sprite = { graphics: this.monIcon(species), palette: 0, walking: true };
this.sprites.set(id, sprite);
return sprite;
}
if (id >= OW.SPRITE_POKEMON && id < OW.SPRITE_POKEMON + OW.NUM_POKEMON_SPRITES) {
// LoadOverworldMonIcon: eight tiles, two frames in the down and up
// standing slots, with palette 0.
+2
View File
@@ -43,6 +43,8 @@ export function fontTiles(reader: RomReader, frame: number) {
* (constants/charmap.asm). */
const CODES: Record<string, number> = {
" ": 0x7f,
"“": 0x72,
"”": 0x73,
"(": 0x9a,
")": 0x9b,
":": 0x9c,
+26 -2
View File
@@ -359,6 +359,30 @@ export class PokemonData {
return evolutions;
}
/** GetPreEvolution: the first species (in dex order) with an evolution
* into this one, or the species itself. */
preEvolution(species: number): number {
this.checkSpecies(species);
for (let from = 1; from <= NUM_POKEMON; from++)
if (this.evolutions(from).some((evolution) => evolution.species === species))
return from;
return species;
}
/** EggMovePointers' list for a species (data/pokemon/egg_moves.asm), up
* to its -1 terminator; the shared NoEggMoves list is empty. */
eggMoves(species: number): number[] {
this.checkSpecies(species);
const r = this.reader,
table = JOHTO.EggMovePointers + (species - 1) * 2;
const moves: number[] = [];
for (let p = r.near(table, r.bank(table)); r.byte(p) !== 0xff; p++) {
if (moves.length > 16) throw new Error("Invalid egg moves.");
moves.push(r.byte(p));
}
return moves;
}
/** Level-up moves (data/pokemon/evos_attacks.asm). Like FillMoves, the
* evolution list is skipped up to its terminating zero byte. */
learnset(species: number): { level: number; move: number }[] {
@@ -439,8 +463,8 @@ export class PokemonData {
padded.set(tiles.slice(column * 80, (column + 1) * 80), ((column + 1) * 7 + 2) * 16);
return padded;
}
eggPalette(): Palette {
return this.middleColors(SYMBOLS.pokemonPalettes + 0xfd * 8);
eggPalette(shiny = false): Palette {
return this.middleColors(SYMBOLS.pokemonPalettes + 0xfd * 8 + (shiny ? 4 : 0));
}
/** Front picture tiles, padded into the 7x7 box as PadFrontpic does:
* column-major, bottom-aligned, with a blank left column for 5 and 6. */
+88
View File
@@ -202,6 +202,8 @@ export const EVENTS = {
bugCatchingContestant8A: 1821,
bugCatchingContestant9A: 1822,
bugCatchingContestant10A: 1823,
/** HatchEggs sets this when a Togepi hatches. */
togepiHatched: 84,
} as const;
/** Engine flag indices named directly by translated routines. */
@@ -209,10 +211,14 @@ export const ENGINE_FLAGS = {
pokegear: 4,
pokedex: 11,
allFruitTrees: 84,
bikeShopCall: 20,
/** STATUSFLAGS2_BUG_CONTEST_TIMER_F is the same WRAM bit. */
bugContestTimer: 17,
rocketsInRadioTower: 19,
rocketsInMahogany: 23,
dayCareManHasEgg: 5,
dayCareManHasMon: 6,
dayCareLadyHasMon: 7,
expnCard: 3,
rocketSignal: 14,
fishSwarm: 82,
@@ -1211,9 +1217,23 @@ export const AUDIO = {
PokemonCries: 0xf2787,
} as const;
/** Egg hatching (engine/pokemon/breeding.asm and the sprite animations it
* uses), keyed by PRET labels for `npm run check:reference`. */
export const EGGS = {
Text_BreedHuh: 0x1c0db0,
_BreedClearboxText: 0x1c0db8,
_BreedEggHatchText: 0x1c0dba,
_BreedAskNicknameText: 0x1c0dd8,
EggHatchGFX: 0x17393,
"Hatch_InitShellFragments.SpriteData": 0x173ef,
BattleObjectPals: 0x979c,
} as const;
/** Johto's scripts, routines and tables from Route 32 on, keyed by their
* PRET labels for `npm run check:reference`. */
export const JOHTO = {
"_HallOfFamePC.TimeFamer": 0x8670c,
FontBattleExtra: 0xf8600,
CheckMagikarpLength: 0xfbb32,
MagikarpHouseSign: 0xfbcd2,
FindPartyMonThatSpecies: 0xc276,
@@ -1270,6 +1290,30 @@ export const JOHTO = {
"PokeSeer": 0x4f0bc,
"PhotoStudio": 0x16dc7,
"PlaySlowCry": 0xfb841,
DayCareMan: 0x166d6,
DayCareLady: 0x1672a,
DayCareManOutside: 0x16936,
DayCareMon1: 0x17421,
DayCareMon2: 0x17440,
GiveOddEgg: 0x117656,
"PrintDayCareText.TextTable": 0x168aa,
"DayCareManOutside.NotYetText": 0x16944,
"DayCareManOutside.FoundAnEggText": 0x16993,
"DayCareManOutside.ReceivedEggText": 0x16998,
"DayCareManOutside.TakeGoodCareOfEggText": 0x1699d,
"DayCareManOutside.IllKeepItThanksText": 0x169a2,
"DayCareManOutside.NoRoomForEggText": 0x169a7,
LeftWithDayCareLadyText: 0x17462,
LeftWithDayCareManText: 0x17467,
"DayCareMonCompatibilityText.BreedBrimmingWithEnergyText": 0x1749c,
"DayCareMonCompatibilityText.BreedNoInterestText": 0x174a1,
"DayCareMonCompatibilityText.BreedAppearsToCareForText": 0x174a6,
"DayCareMonCompatibilityText.BreedFriendlyText": 0x174ab,
"DayCareMonCompatibilityText.BreedShowsInterestText": 0x174b0,
EggMovePointers: 0x23b11,
OddEggProbabilities: 0x1fb552,
OddEggs: 0x1fb56e,
"_GiveOddEgg.Odd": 0x1fb546,
"SpecialShuckleOT": 0x7370,
"SpecialShuckleNickname": 0x7376,
"SeerSeeAllText": 0x4f28c,
@@ -1518,6 +1562,8 @@ export const WRAM = {
wMagikarpRecordHoldersName: 0xdfea,
wcd49: 0xcd49,
wNrOfBeatenBattleTowerTrainers: 0xcf64,
wBreedMon1Nickname: 0xdef6,
wBreedMon2Nickname: 0xdf2f,
} as const;
export const MAP_GROUP_COUNT = 26;
@@ -1743,3 +1789,45 @@ export const NATIVE_MAPS: Record<string, string> = {
"3:16": "National Park (Bug-Catching Contest)",
};
export const INITIAL_POSITION = { map: "24:7", x: 3, y: 3 } as const;
/** ReadAnyMail stationery graphics and palettes. */
export const MAIL = {
"MorphMailDividerGFX": 0xb9926,
"BlueSkyMailGrassGFX": 0xb992e,
"PortraitMailSmallPokeballGFX": 0xb9936,
"MorphMailBorderGFX": 0xb993e,
"MusicMailSmallNoteGFX": 0xb9946,
"SurfMailWaveGFX": 0xb994e,
"PortraitMailUnderlineGFX": 0xb995e,
"LovelyMailUnderlineGFX": 0xb9966,
"LovelyMailSmallHeartGFX": 0xb996e,
"SurfLiteBlueMailSmallShapesGFX": 0xb9976,
"EonMailBorder1GFX": 0xb997e,
"EonMailBorder2GFX": 0xb998e,
"MailNatuGFX": 0xb9996,
"MailDratiniGFX": 0xb99c6,
"MailPoliwagGFX": 0xb99f6,
"MailLaprasGFX": 0xb9a26,
"MailEeveeGFX": 0xb9a56,
"MailDittoGFX": 0xb9a86,
"MailMewGFX": 0xb9ab6,
"MailDragoniteAndSentretGFX": 0xb9b46,
"PortraitMailLargePokeballGFX": 0xb9bfe,
"MailOddishGFX": 0xb9c1e,
"SurfLiteBlueMailLargeShapesGFX": 0xb9c3e,
"LovelyMailLargeHeartGFX": 0xb9c5e,
"MorphMailBorderCornerGFX": 0xb9c7e,
"MailLargeCircleGFX": 0xb9c9e,
"FlowerMailFlowerGFX": 0xb9cbe,
"MusicMailLargeNoteGFX": 0xb9cfe,
"MailCloudGFX": 0xb9d16,
"SurfMailBorderGFX": 0xb9d46,
"FlowerMailBorderGFX": 0xb9d86,
"LiteBlueMailBorderGFX": 0xb9dc6,
"MusicMailBorderGFX": 0xb9e06,
"LovelyMailBorderGFX": 0xb9e26,
"PortraitMailBorderGFX": 0xb9e4e,
"MailGFXPointers": 0xb92f8,
"LoadMailPalettes.MailPals": 0x8d05,
"StandardEnglishFont": 0x1de5e6,
} as const;