The player's name in the start menu opens TrainerCard: page 1 with the name, ID, money, Pokédex count and play time, page 2 with the Johto leaders and the badges owned, turned and closed with the original buttons. The card picture, leader faces and animated badges come from the ROM with _CGB_TrainerCard's palettes. GameTimer now counts the play time from the end of the introduction, capped at 999:59:59, and saves keep it. Closes #23 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 KiB
Full native port plan
Direction
Translate Crystal's Game Boy assembly systems into a native TypeScript game. The browser runs the translated engine; the user's original ROM supplies data and assets. The emulator is a separate development reference for checking behavior, never a dependency of the native release.
PRET's disassembly is pinned at 7a7881d0d62e0ddbd82dcf10e7116807487ac651. Its crystal11 build with RGBDS 1.0.3 was verified byte-for-byte against the supplied English US/Europe 1.1 ROM. Required SHA-1: f2f52230b536214ef7c9924f483392993e226cfb. Other revisions require separate verified profiles.
The emulator-backed prototype is now at /reference.html. The default application is the native port. The previous plan to progressively modify an emulated ROM has been superseded.
Status overview
| Area | Status |
|---|---|
| Exploration foundation, rendering, saves and shell | Done |
| Opening story: New Bark Town to Mr. Pokémon, the rival and the return to Elm | Done |
| Route 29, Cherrygrove City, Route 30 and their buildings | Done |
| Wild and trainer battles, capture and the catching tutorial | Done for the areas that are open |
| 1. Complete the overworld foundation | Done |
| 2. Opening story and first battle slice | Mostly done |
| 3. Complete Crystal | Early |
| 4. Modern mobile presentation and delivery | First increment done |
| 5. World map and Pokédex | Not started (current-area minimap only) |
| 6. Internet Link and friends | Not started |
| 7. Optional Pokéwalker-style egg assistance | Not started |
The numbered sections below keep their full scope. Each one opens with a checklist: [x] is done and tested, [ ] is remaining. The "Implemented" sections record what each completed increment delivered.
Implemented: first native exploration milestone
- Bounds-checked ROM access, banked pointers and native LZ decompression.
- On-demand extraction of six maps: New Bark Town, both floors of the player's home, Elm's lab, the neighbor's house and Elm's house.
- Metatiles, collision quadrants, palettes, roofs, sprites and facing tables read directly from the user's ROM.
- Native canvas rendering, camera scrolling, walking, solid-tile/NPC collision, doors and stairs within the supported area.
- A bounded interpreter for a subset of the game's own event bytecode, plus text decoding. This does not execute GB instructions.
- Original initial event flags, readable signs, bedroom radio, bookshelves and supported conversations. Unknown interactions abort without committing partial changes.
- Native DOM dialogue, name/avatar choice, touch/keyboard controls and pause/resume. (The name and character are now chosen in the introduction; see below.)
- A minimal Crystal-style shell with Game, Savegames, Map, Pokédex, Link and Friends tabs across the top. The game uses the available native canvas, and leaving Game releases input and pauses play.
- Existing save, backup export and backup import actions in Savegames. Map shares a live local minimap with the game and shows the current supported area, player position and coordinates. Pokédex, Link and Friends are clearly marked as planned rather than populated with placeholder data.
- Native JSON state inside the backup envelope, separate IndexedDB storage and a separate tab lock. No emulator snapshot or cartridge save migration is implied.
- Synthetic tests, local-ROM tests, browser checks, and comparisons against the original decompression routine running in the reference emulator.
- Native production output excludes the emulator and original game assets.
Implemented: opening scenes
- Resumable event interpreter: text, yes/no and weekday choices, scripted movement,
follow, emotes, pauses, scene variables, phone contacts, string buffers and translated specials. Unknown commands still abort without committing. - Movement data decoder for turns, steps, slides, jumps, sleeps, facing locks, visibility and emotes, animated at the original 30 Hz overworld rate. Player walking now takes the original 8 ticks per tile, not half that.
- Coordinate events, scene scripts with
sdefer, and NEWMAP/TILES/OBJECTS map callbacks, including the bedroom's own initialization callback. Objects spawn at map load as in the original. - Mom's Pokégear, weekday and DST scene; the teacher at the town's west edge; the rival at the lab window; and Elm's introduction with the lab's exit guard, all from the ROM's scripts.
- Map headers for all 26 groups, ROM location names and connected maps drawn beyond the edges. Entering an unported connection is refused with its location name.
- Version 2 native saves with scenes, phone, game-clock settings and moved objects; version 1 saves migrate.
Implemented: starter and first items
- ROM decoders for species names, base data, learnsets, moves, items, pockets, growth rates and front pictures.
- Native party generation for script gifts, following
GeneratePartyMonStats: random DVs, level-up moves, PP, exp, stats, happiness and caught data. The Pokédex records the species as seen and caught. - Bag pockets with the original capacities and stacking rules.
verbosegiveitemruns the ROM's ownGiveItemScript. - Elm's Poké Balls, with a picture box, the nickname prompt with a native naming field, Elm's directions and phone number, and the aide's Potion.
- Version 3 saves with the player ID, party, bag and Pokédex; restores recompute stats and check item pockets.
Implemented: ledges and warp movement
- Walkable ledge tops, directional two-tile jumps over 16 ticks, and jump shadows.
- Exit mats wait for input in their exit direction. Arriving on a mat preserves the actual position; doors, stairs and caves animate the downward exit step.
- Mid-step saves replay from the source tile, including jumps and map connections, so reload cannot bypass landing scenes or warps.
- Synthetic movement tests run without a ROM. Local-ROM tests exercise five Route 29 ledge types; browser checks cover stairs, opening scenes and an exit-mat save/reload.
Done: Route 29 and Cherrygrove City
- Route 29, its Route 46 gate, Cherrygrove City and its five buildings are enabled. Route 46 remains closed.
- Item balls run the ROM's
FindItemInBallScript. Fruit trees runFruitTreeScript, with picked trees saved and regrown by a nativeCheckDailyResetTimerthat clears the daily engine flags once per game day. - The Pokémon Center nurse runs
PokecenterNurseScript:HealParty(withComputeMaxPP's PP Up clamp),CheckPokerusand the phone-call checks. Entering a Center from a town or route records the whiteout spawn, asEnterMapWarpdoes. - Standard Poké Marts run
pokemartnatively fromStandardMart,BuyMenuandSellMenu: stock from the ROM'sMartstable,ItemAttributesprices, half-price selling and the original texts, with keyboard, D-pad and pointer menus. Special mart types remain refused. VAR_BOXSPACEreads an empty box: nothing can be caught or deposited yet.- The battle engine gained
recoil(Struggle). A local stress run of 360 Route 29 battles across all starters and times of day finishes without refusals. - Save format 3 gains
fruitTrees,dailyResetandlastSpawnMap; older backups migrate by absence. - NPCs roam as
HandleNPCStepdoes: random walks (Y, X, XY) and slow/fast spins fromSpriteMovementData,CanObjectMoveInDirection(side walls, land, objects' current and previous tiles, radius stored plus one, screen edge), random sleeps, freezing during text andapplymovement, and deletion/respawn at home beyond the object window. Wandering positions are not saved; objects start from home on reload. - The catching tutorial (
catchtutorial) runs since the capture section below.
Implemented: first scripted trainer battle slice
- ROM decoder for 542 trainer records across 67 classes, all four party formats, class names, DVs, rewards and map trainer headers. SRAM-dependent Mystery Gift parties remain refused.
- Native move scoring for Basic, Setup, Types, Offensive, Opportunist, Aggressive, Cautious, Status and Risky, with deterministic tie selection and the original pre-variation damage estimate. Smart runs only for effects without a Smart handler (see the Mr. Pokémon section).
- Scripted single-Pokémon trainer battles support the challenge, sending out, RUN refusal, experience, win/loss text and prize money, including Amulet Coin. Script results, party changes and rewards commit together; interrupted saves replay from committed state.
- Local-ROM tests and Chromium/WebKit tests exercise Youngster Joey through a test-only script. No new maps or trainer encounters are enabled in normal play.
- Next: Smart AI handlers, trainer items and player switching.
Implemented: Route 30 trainers
- Map trainers run the ROM's
TalkToTrainerScriptandSeenByTrainerScript._CheckTrainerBattleruns before tile events after each step and on every idle tick: the first unbeaten trainer facing the player in line within its sight range spots them. The nativeTrainerWalkToPlayerpath is played fromwMovementBuffer, andwriteobjectxykeeps the trainer where it stopped (saved with the map's moved objects). - New script commands:
loadtemptrainer,trainertext,trainerflagaction,scripttalkafter,endifjustbattled,checkjustbattled,writeobjectxy,encountermusic(silent) andloadmemforwRunningTrainerBattleScript. - Trainers with several Pokémon:
HandleEnemyMonFaintsends out the next one throughEnemySwitch_SetMode,FindMonInOTPartyToSwitchIntoBattle(including itsBattleRandomfallback) andNewEnemyMonStatus.AI_SwitchOrTryItemtranslatesCheckAbleToSwitchwith its original bugs (every matchup uses the enemy's move type; the quarter-HP test rotates bits) andAI_Switch. The player's used moves are recorded for it. - Hidden items (
BGEVENT_ITEM) runHiddenItemScript. - Route 30 and its Berry house are enabled, and Mr. Pokémon's house since the section below. Route 31 stays closed; Youngster Joey (after the Egg) needs phone registration and stays hidden until then, as in the original.
- A refused trainer script does not restart until the player moves, so a rollback cannot trap the player in a trainer's sight.
- A local stress run of 1,080 Route 30 battles (three starters, four levels, three times of day, both trainers and wild encounters) found one refusal: Chikorita's Reflect at level 12 (
screen), now addressed by the screen increment below.
Implemented: Reflect and Light Screen
- Both moves execute their ROM effect scripts, set independent five-turn counters and fail without refreshing an active screen. The setup turn counts toward expiration.
- Screens persist across enemy send-outs and expire in the original non-Link order, with ROM messages. Physical/special defenses and critical-hit stage rules use the existing damage path.
- Local-ROM regressions cover both sides, repeated use and PP, concurrent durations, send-out persistence, expiration order and damage stats. These checks are not an emulator comparison of the screen routines.
Implemented: Mr. Pokémon's house and the return to Elm
- Mr. Pokémon's house is enabled. Its ROM scene gives the Mystery Egg and Pokédex, heals the party (
FadeOutToBlack,ReloadSpritesNoPalettesandFadeInFromBlackare presentation-only), sets the Cherrygrove whiteout spawn withblackoutmodand queues Elm's call withspecialphonecall. A refusal late in the scene rolls back the rewards, healing and Oak's exit. - Special calls: after trainer, warp and coordinate events, a queued call runs if its
SpecialPhoneCallListcondition allows (Elm's only outdoors). It runs the ROM'sCheckSpecialPhoneCall.scriptandScript_ReceivePhoneCall, withmemcallinto the entry's caller script andHangUp's timed "Click!". Ringing tones and the caller name box are not presented yet. Queued calls are saved. - Cherrygrove's rival scene runs both triggers and all three
RIVAL1_1parties as aBATTLETYPE_CANLOSEbattle.reloadmapreturns to the script, and a loss continues the scene without whiteout or money loss. The name shows as "RIVAL ?", as in the original. Smart AI accepts the rival's Normal Hit, Attack Down and Defense Down moves, becauseAI_Smart_EffectHandlershas no entries for them. Other Smart effects are still refused before any random draw, including for reserve Pokémon. - Elm's lab: the officer's
NameRivalopens the naming field with the ROM title "RIVAL'S NAME?". A blank name takes the ROM default, asInitNamedoes. The rival name is saved and used by<RIVAL>text. Handing over the Egg and the aide's five Poké Balls run from the ROM. - Save format 3 gained
rivalNameandspecialPhoneCall(migrated by absence to "???" and 0). - Next: the catching tutorial on Route 29 (
catchtutorial) with capture; then player switching and items in battle.
Implemented: capture and the catching tutorial
- Battle PACK opens the Balls pocket. Throwing a ball runs
PokeBallEffect: the catch rate with the Great/Ultra multipliers and the original HP arithmetic, the sleep/freeze bonus andHELD_CATCH_CHANCE, then oneRandomdraw. Master Balls always catch. The original bugs are kept: burn, poison and paralysis add nothing, and a maximum HP above 341 overflows the formula. A maximum HP of 342 would divide by zero and is refused. GetPokeBallWobbleruns withWobbleProbabilitiesfor each shake and picks the break-free message. Every throw uses the ball and, if the Pokémon breaks free, the turn. In trainer battles the ball is blocked, but the ball and turn are still used.- A caught Pokémon is reloaded as
LoadEnemyMondoes, which redraws the wild item roll (the item itself is kept). It keeps its current HP, status, item and DVs, and gets back the moves and full PP it appeared with. It joins the party withSetCaughtData, is registered as caught (with the new-data message once the Pokédex is owned) and can be nicknamed; a blank name keeps the species name. The Pokédex page itself is not presented yet. - Route 29's
Route29Tutorial1/2andCatchingTutorialDudeScriptrun from the ROM.catchtutorialruns the battle as "DUDE": no Pokémon is sent out, the Dude's own Poké Ball always catches, and the Rattata is only seen, not kept. Messages advance on their own, as withCatchTutorial's auto input. - A catch with six Pokémon in the party goes to the current box (see the PC section below). Other balls (Kurt's balls, Park and Safari) and non-ball items in battle are not usable yet.
- A local stress run of 2,160 wild battles (three starters, ten early species, six levels) caught 2,047 Pokémon without refusals.
- Next: the PC and boxes, player switching and other items in battle, then party and bag menus.
Implemented: the PC and boxes
TileCollisionStdScriptsis read from the ROM, so facing aCOLL_PCtile (in any direction, as in the original) runsPCScript. Bookshelves, TVs and similar tiles no longer need the player to face up.PokemonCenterPCchecks for a Pokémon, then lists the PCs fromPokemonCenterPC.WhichPC: Bill's PC and the player's PC, plus Oak's PC once the Pokédex is owned. The Hall of Fame PC is refused, since no Hall of Fame entry can exist yet.- Bill's PC: deposit (
BillsPC_CheckMail_PreventBlackoutkeeps the last Pokémon and the last one that has not fainted;RestorePPOfDepositedPokemon), withdraw (CalcLevel, thenCalcMonStatswith stat experience, full HP and no status), release with confirmation ("Bye," with the species name), and change box: switch with the ROM's confirmation, name (a blank name keeps the old one) and print (only the empty-box message). Menus and messages come from the ROM; lists are presented natively with each Pokémon's level or each box's count. - The player's PC withdraws, deposits (from every pocket; untossable items move one at a time) and tosses items (up to 50 stacks,
RemoveItemFromPocketslot rules), and shows the empty mail box. The house PC adds DECORATION, which closes the PC after a change. - Oak's PC rates the Pokédex from
OakRatings. - Capture with a full party runs
SendMonIntoBox: the Pokémon goes first in the current box, with restored PP andSetBoxMonCaughtData, can be nicknamed and showsBallSentToPCText. If the box is already full the ball is not used (Ball_BoxIsFullMessage). A catch that fills the box setsBATTLERESULT_BOX_FULL;reloadmapafterbattlethen queues Bill's call (Script_SpecialBillCall) as a memory script that runs at the nextPlayerEventsand is not saved, as in the original. VAR_BOXSPACEreads the current box.- Save format 3 gained
storage(boxes, the current box and box names; a null name is the ROM default) andpcItems, migrated by absence to empty storage. - Not ported: printing (MOVE PKMN W/O MAIL and the stats screen are ported since), a
givepokewith a full party (refused; no open map reaches it) and the tutorial with a full party and box (the original repeats the unusable ball). Changing boxes does not show the save messages; the native session saves continuously. - Next: the introduction's naming and clock screens, player switching and other items in battle, then party and bag menus.
Implemented: the introduction's naming and clock screens
- A new game runs
NewGame'sInitGenderandOakSpeechnatively before the bedroom: "Boy"/"Girl" (B disabled),InitClock, Oak's speech with Wooper, thenNamePlayer. Texts, menu items, the preset names, the naming title and the time strings come from the ROM (INTROtable in the profile, verified bycheck:reference). InitClock: the hour and minute steppers wrap asSetHourandSetMinutesdo, each confirmed with YES/NO; NO asks again. Labels followPrintHour("DAY 5 o'clock"), andOakText_ResponseToSetTimekeeps itsDAY_HOUR + 1comparison, so 10 AM still "overslept".- The chosen time is stored as a minute offset from the device clock (the shortest one, within ±12 hours). Time of day, the weekday, NPC hours and the daily reset all read this game clock. Mom's
SetDayOfWeekoffset is taken from the game clock's day. NamePlayer: NEW NAME opens the naming field; a blank name takes CHRIS or KRIS, asInitNamedoes. The other items are the gender's preset names.- Oak's picture decodes
TrainerPicPointersthroughFixPicBank; the player's is the uncompressedChrisPic/KrisPic. Both useTrainerPalettes(Chris class 0, Kris class 1), asDrawIntroPlayerPicsets them. - The intro covers the world and its minimap until it ends. It runs only when no session is saved. A save made during it keeps
intro: true, and loading that save starts it again. The Game tab's name and character fields are gone. - Save format 3 gained
introandclock.minuteOffset, migrated by absence to a finished intro on the device clock. - Intentional change: the steppers start at the device's current time rather than 10:00, so confirming keeps the device clock.
- Not presented: the palette fades, Wooper's cry, the picture slide during name selection,
ShrinkPlayerand the Route 30 music (no audio yet). - Next: MOVE PKMN W/O MAIL with the party menu.
Implemented: evolution data, reference vectors and the opening playthrough
PokemonData.evolutionsdecodes the evolution list before each learnset: level, item, trade (with or without a held item), happiness by time of day, and Tyrogue's four-byte stat records. All 251 lists match the pinned PRET source. Evolving itself is not ported yet.- The reference suite also runs the original
CalcMonStats(300 cases, with and without stat experience, including levels above 100 that reach the 999 cap) andCalcExpAtLevel(every growth rate at levels 1–100). tests/playthrough.test.tsplays a new game from the introduction to the catching tutorial with a seeded RNG: Mom, Elm, the starter and Potion, Route 29, Cherrygrove, Route 30, Mr. Pokémon, Elm's call, the rival battle, the officer, the Egg, the aide's Poké Balls and the Dude's demonstration. Wild battles are fought, or fled below half HP. Each stage ends with a JSON save loaded into a fresh world. It uses no fixtures or hand-set flags, but drivesGameWorlddirectly rather than the browser UI.
Implemented: switching, battle items and the stats screen
- PKMN opens the party menu; SWITCH sends out the chosen Pokémon and STATS opens the stats screen. The texts, the trapped and fainted checks, and the enemy's turn after the switch follow the original. Both Pokémon share the experience.
- After a faint, wild battles ask "Use next POKéMON?" (NO tries to run) and the party menu cannot be cancelled. Trainers' next Pokémon come with Shift mode's offer to switch; a double faint sends out both sides.
- PACK's Items pocket uses HP items (Potion, Berry and the rest of
HealingHPAmounts) and status healers (Antidote, Parlyz Heal, Awakening, PSNCUREBERRY and the rest ofStatusHealingActions) on any party Pokémon. Items with no battle use get Oak's warning. - Every other battle item effect runs too: Full Restore, revives, the bitter herbs, Bitter Berry, Ethers and Elixers, X items, X Accuracy, Guard Spec, Dire Hit and Poké Doll. Happiness changes come from the ROM's
HappinessChanges. hBattleTurnat the battle menu is modelled, soCalcPlayerStatsafter a status heal penalises the side the original does.- The stats screen shows its three pages from BattleMonMenu and Bill's PC.
- The reference suite compares the send-out and withdraw texts,
RestoreHealth,RestorePP,ChangeHappinessandCalcPlayerStatswith the original routines.
Implemented: the start menu, party menu and pack
- Start opens
StartMenuwith the itemsSetUpMenuItemslists for the player's progress (POKéDEX, POKéMON, PACK, POKéGEAR, the player's name, SAVE, OPTION, EXIT) and their ROM descriptions. POKéDEX and OPTION give a notice (POKéGEAR and the trainer card run since the sections below); SAVE points to the automatic session saves. - POKéMON: the party menu with the ROM's prompts and
GetMonSubmenuItems: field moves the Pokémon knows (fromMonMenuOptions), STATS, SWITCH, MOVE, ITEM or MAIL, and CANCEL when there is room. STATS opens the stats screen; SWITCH asks "Move to where?" and swaps the two Pokémon; MOVE (MoveScreenLoop) swaps two moves with their PP; ITEM gives an item from the pack (key items and untossable items "can't be held"), swaps it with the held item or takes it back, with the pack-full checks ofTryGiveItemToPartymonandTakePartyItem. - PACK: the Items, Balls, Key and TM pockets (left and right change pockets; there are no TMs or HMs yet), and each item's submenu chosen by its permissions and field menu (
USE,GIVE,TOSS,SEL,QUIT). TOSS asks how many and confirms. USE followsUseItem: Oak's warning without a field use, "You don't have a POKéMON!" for party items without one, and_DoItemEffectby the item'sItemEffectsaddress: Potions and the otherHealingHPAmountsitems, status healers, Full Restore, Revive and Max Revive, the bitter Energypowder, Energy Root, Heal Powder and Revival Herb, Ethers, Elixers, the MysteryBerry and PP Up. The Bicycle closes the menu and runsScript_GetOnBikeorScript_GetOffBikeoutdoors; elsewhere Oak objects, becausewFieldMoveSucceededshareswItemEffectSucceeded's byte. - PP Up keeps a quirk:
wUsePPUpshareswTempPP's byte, soComputeMaxPPadds one step (a fifth of the base PP, at most 7) to the current PP as well as the maximum. Sketch and moves with three PP Ups are "maxed out". - The menu runs on a script draft, like the PC. Changes commit when it closes; a save made while it is open keeps the state from before it. The start menu cursor, the party menu cursor and the last pocket stay in memory between openings and are cleared after a battle, as
CleanUpBattleRAMdoes. The party cursor reaches the next battle'sChangeHappiness. - Not ported: MAIL, moving a Pokémon's moves to another party member with left and right, item reordering with Select and the remaining field items such as Repel and Escape Rope (each gives a notice and changes nothing). Rare Candy, evolution stones and vitamins run since the evolution section below; field moves, SEL and Select since the section after it.
Implemented: evolution
EvolveAfterBattleruns after every won battle for the party members that levelled up (wEvolvableFlags, now recorded by the battle's experience routine), before the script resumes. Level, happiness (220 or more, by time of day) and Tyrogue's Attack/Defense evolutions follow the original checks, including the Everstone. The evolution data is walked byte by byte like the original, so a forced evolution skips a four-byteEVOLVE_STATrecord the way the original does. Trades never evolve without Link mode.- The evolution screen shows "What? … is evolving!", then the old picture flashing into the new one on the original schedule (80 frames, then seven rounds of flashes). B stops it only during the waits between flashes, and never for a stone. A stopped evolution prints "Huh? … stopped evolving!" and skips that Pokémon's other entries.
- After evolving: the species name replaces the nickname only if the nickname was the old species name, stats are recomputed with stat experience, HP rises by the maximum HP's increase, moves learned at exactly the current level are offered, and the Pokédex records the new species as seen and caught.
- Learning a move outside battle follows
LearnMove: an empty slot, or "delete an older move?", the choice of a move (HM moves can't be forgotten), "1, 2 and… Poof!", or "Stop learning …?" and "did not learn". - PACK: Rare Candy (
RareCandyEffect: one level at the level's minimum experience; the HP gains the maximum HP's increase even for a fainted Pokémon; the caught-location happiness bonus; "grew to level", the stat box, new moves and a possible evolution), evolution stones (EvoStoneEffect: a forcedEvolvePokemon; the party menu shows ABLE or NOT ABLE with the original's ten-byte, three-byte-step check; an Everstone or no matching evolution has no effect) and vitamins (VitaminEffect: ten more in the stat experience's high byte, up to 100, stats recomputed without changing the current HP, happiness). - Not presented: the evolution music, cries and the palette changes. Trade evolutions need Link.
Implemented: field moves from the menu, SEL and moving Pokémon
- Field moves in the Pokémon submenu run their menu functions: the world checks the badge (
CheckBadge: "Sorry! A new BADGE is required."), the facing tile, water or object, then the menu closes and the ROM's…FromMenuscript runs for the chosen Pokémon (no "Want to …?" question): Cut (CheckMapForSomethingToCut), Surf (including "already surfing" and forced cycling), Strength, Flash (aPALETTE_DARKmap or the Aerodactyl chamber;BlindingFlashsets the Flash flag), Whirlpool and Waterfall. Failures print the ROM's messages and return to the party menu. - Sweet Scent runs
SweetScentFromMenu: where wild Pokémon can appear and the map has a rate,ChooseWildEncounterpicks one without the rate roll and a battle starts; elsewhere "Looks like there's nothing here…". - Softboiled and Milk Drink give a fifth of the user's maximum HP to another Pokémon that has not fainted and is not at full HP; the user needs more than that fifth.
- Fly (outdoors with the Storm Badge), Dig (in caves), Teleport (outdoors with a spawn point), Headbutt (facing a tree) and Rock Smash (facing a rock) run their checks and messages, then give a notice: the Fly map, dungeon and spawn warps, tree encounters and rock encounters are not ported.
- SEL registers an item that
CANT_SELECTallows (in practice key items) inwWhichRegisteredItemandwRegisteredItem, which saves keep. Select runsSelectMenu:CheckRegisteredItem(a slot that no longer holds the item clears the registration; a registered ball is never found, as in the original) andUseRegisteredItemby field menu. The Bicycle usesScript_GetOnBike_RegisterandScript_GetOffBike_Register, without a message; elsewhere Oak objects. Without a registration Select explains SEL. This replaces the earlier Key Items shortcut. - Bill's PC's MOVE PKMN W/O MAIL: refused while a party Pokémon holds MAIL, then "Each time you move a POKéMON, data will be saved. OK?". Choose the party or a box, a Pokémon (MOVE, STATS, CANCEL), then a box and the position to insert before. The party keeps its last Pokémon and its last healthy one, a different full box or party has "no room", a Pokémon moved down its own list lands one place earlier (
.CheckTrivialMove), a boxed Pokémon joining the party gets its stats with full HP and no status (CalcBufferMonStats), and one entering a box gets its PP restored. The original's left/right box switching is presented as a list of boxes; after "no room" the box is chosen again rather than the position. - Save format 3 gained
registeredItem, migrated by absence to none.
Implemented: the Pokégear, phone calls and Mom's savings
- POKéGEAR in the start menu runs
PokeGearand returns to the start menu. The clock card shows the weekday (GearTodayText) andPrintHoursMins's 12-hour time with the ROM's AM and PM, follows the game clock while open, and exits on A, B, Start or Select. Left and right change cards in the original order, skipping the cards the player lacks (the map card from the Guide Gent, the phone card from Mom). - The map card draws the ROM's Johto or Kanto Town Map with the player's landmark (a special landmark uses the backup map's, as
TownMap_InitCursorAndPlayerIconPositionsdoes) and a cursor that up and down step through the region's landmarks with wrapping; Kanto's routes need the Hall of Fame. The cursor keeps its place while the Pokégear stays open. The landmark name comes from the ROM. - The phone card lists
wPhoneList's ten slots four at a time, withGetCallerClassAndName(non-trainer names, or a trainer's name over its class) and dashes for empty slots, and the signal bars from the map header's phone service nybble. A on a contact opens CALL/CANCEL, or CALL/DELETE/CANCEL for numbersCheckCanDeletePhoneNumberallows (never Mom or Elm). Deleting asks first. - Calls follow
PokegearPhone_MakePhoneCallandMakePhoneCallFromPokegear: "You're out of the service area." without service; otherwise two "……" rings, thenPhoneOutOfAreaScriptoutside the callee's hours,PhoneScript_JustTalkToThemon the callee's map, or the callee script fromPhoneContacts. The script runs on the start menu's draft, so its changes commit with the menu. The call ends withHangUp's "Click!" and boops. Elm's and Mom's callee scripts run; trainer contacts are not reachable yet. - Mom's savings:
wMomsMoneyis saved asmomsMoney(migrated by absence to 0). The interpreter gainedgivemoney,takemoney,checkmoneyandgetmoneyfor both accounts,getcurlandmarkname,getlandmarknameandrandom. After the catching lesson, calling Mom runs her lecture, which setsENGINE_MOM_SAVING_MONEYon a yes; later calls report where the player is and her balance. While she saves, a won trainer battle sends a quarter of the prize to her (WinTrainerBattle.give_money, "Sent some to MOM!"), unless her account is full. HangUpnow shows "Click!" for its 20 frames and the boops' "……" for the remaining 120, instead of "Click!" for twice the whole duration.- Not ported: the radio card (it is only obtainable in Goldenrod; it gives a notice); trainer callee scripts; random incoming calls; ringtones, music and the original card graphics.
Implemented: Mom's bank and her purchases
- Talking to Mom after the Mystery Egg runs
BankOfMom. The first time she offers to save money (MomLeavingText1–3); until New Bark Town's flypoint callback clearsEVENT_FIRST_TIME_BANKING_WITH_MOMshe only cheers the player on. Later visits ask "Or is this about your money?": GET, SAVE, CHANGE or CANCEL. - GET and SAVE open
Mom_ContinueMenuSetup's box (SAVED, HELD, then WITHDRAW or DEPOSIT and a six-digit amount with a blinking digit).Mom_WithdrawDepositMenuJoypad: left and right move the digit cursor (starting on the last digit, stopping at both ends), up and down add or take the digit'sDigitQuantitiesstep throughGiveMoney/TakeMoney's ¥999999 and zero limits, A confirms and B cancels. A zero amount cancels. Refusals ("You don't have that much.", "You can't save that much.", "You haven't saved that much.", "You can't take that much.") return to a fresh box. CHANGE starts or stops her savings. - Declining the money question runs
DSTChecks: switch Daylight Saving Time on or off, moving the clock an hour, unless that would change the day (within an hour of midnight), when Mom has lost the Pokégear's booklet. wMomSavingMoneystays two engine flags: nothing in the ROM sets the half and all bits.- After a won trainer battle with phone service,
MomTriesToBuySomethingbuys MomItems_2's next item once her savings reach its trigger, or a random MomItems_1 item whenwMomItemTriggerBalance's ¥2300 steps land exactly on her savings. Items go to the PC (nothing happens when it is full), dolls to the room's decorations, and her call follows ("I bought it with your money. Sorry!"). - Save format 3 gained
momItemandmomItemTriggerBalance(wWhichMomItem,wMomItemTriggerBalance), migrated by absence to 0 and ¥2300. - The amount box has no key repeat: like the other native menus, a held direction counts once.
Implemented: the trainer card and the play time
- The player's name in the start menu runs
TrainerCardand returns to the start menu. Page 1 shows.Name_Money's NAME and MONEY, the ID number with leading zeros, the Pokédex's caught count (CountSetBitsover the caught species; the line is cleared without the Pokédex), the play time and "BADGES▶". A or right turns to page 2, the eight Johto leaders with the badges owned; there A leaves and left turns back. B leaves from either page. The Kanto page is reachable only through unreferenced code and is not shown. - The card picture (
GetCardPic: Chris or Kris), the leader faces (TrainerCard_Page2_3_PlaceLeadersFaces) and the badges come from the ROM, coloured by_CGB_TrainerCard's palettes and attribute map: the frame in the other gender's palette, the picture in the player's, each leader's own andPREDEFPAL_CGB_BADGEfor the badges. The badges step throughTrainerCard_JohtoBadgesOAMevery eight frames (the Rising Badge mirrors on alternate cycles), and the play time's colon blinks every 32 frames. Both timings start when the page loads; the original's phase also depends on the VBlank counter. wGameTimeis new:GameTimercounts 60 frames to the second, two per overworld tick, from the end of the introduction through scenes, menus and battles, and caps at 999:59:59. A save made during a scene keeps the current play time; everything else in it is still the state before the scene. Save format 3 gainedgameTime, migrated by absence to zero. The save screen and the Hall of Fame, which pause it in the original, are not ported.- The labels drawn as tiles ("ID NO", "STATUS" and "BADGES") are presented as text; the other strings come from the ROM.
Native battles: current state
The battle core, ROM tables, encounter hooks, battle UI and whiteout hooks are present. Unit tests cover wins, escape, loss, experience, move learning and capture; reference tests compare damage, type effectiveness, stat calculation and experience curves. Browser tests drive a test-only wild encounter through the real UI: victory with level-up and move learning, escape, whiteout, catching with a nickname, keyboard and on-screen D-pad menu navigation, mid-battle backup restore, rollback of unsupported effects, switching through the party menu and a Potion from the Items pocket. Real Route 29 encounters now use the ROM's encounter tables. Only the special Kurt and Park balls remain unported among battle items.
This is still not a complete new game. The Pokégear's radio, the Pokédex screen, the options, story progression beyond Route 30 and Mr. Pokémon's house, and audio remain unfinished. The start menu opens the party menu and the pack, and Select uses the registered item, such as the Bicycle; the PCs store Pokémon, items and decorations. Surfing and terrain animation now have native implementations; later maps remain gated. Save slots, a Johto/Kanto atlas, Pokédex records, online Link, friends and real-world steps come later.
1. Complete the overworld foundation
- Map headers for all 26 groups, location names, connection offsets and connected-border drawing
- Walking, collisions, doors, stairs, ledges and directional warp mats
- Coordinate events, scene scripts, map callbacks and scripted movement
- NPC random walks and spins, trainer sight lines
- Item balls, hidden items and fruit trees with the daily reset
- Connected transitions for every enabled map (New Bark Town to Route 30)
- Dynamic warps, elevators, ice, currents and forced movement
- Native Surf, Cut and Strength interactions, cycling rules and boulder holes
- Bicycle access from the pack or a registered Select, with keyboard and touch controls
- Tile changes (
changeblock,changemapblocks) with rollback and minimap refresh - Room decorations, ownership, selection, removal, save validation and migration
- Additional NPC movement types, variable sprites, Pokémon icons and movement-command decoding
- Expanded original-engine coverage for scripted NPC poses and movement
- Tileset animation, grass sprite priority and special map palettes
- Restless forest animation and broader palette/animation reference comparisons
- Reference comparison of player turn timing
- Acceptance: outdoor, interior, cave and connected-map traces match the reference engine
Movement now covers turn-in-place delays, bumps, ice, currents, forced tiles, dynamic return warps, elevator menus, pits, Surf, Cut, Strength and cycling rules. Map block changes use a working copy and roll back with a refused script. Local-ROM tests exercise elevator travel, Burned Tower pits, Ice Path boulders, field moves and cave/water encounters without enabling those later areas.
Fourteen original-engine traces cover outdoor turns and bumps, connections in both directions, ledges, house stairs and doors, the teacher's follow scene, Ice Path, and eight controlled movement sequences: spin, teleport out/in, skyfall, skyfall departure, tree shake, fishing, flicker and spin-flicker. They compare positions, facing, selected actors and event flags. Map-entry ticks compare the destination against the original's completed transition; other ticks compare directly. Pose checks compare direction, visibility and vertical offset during scripted movement, excluding blocking-text boundaries and subsequent random NPC behavior.
Tileset animation runs from ROM command tables on a private graphics copy. Johto, Ice Path, restless Ilex Forest and Tin Tower animation each match 96 original VRAM frames. All 36 tilesets run for 2,048 frames without modifying cached graphics. An executed original palette routine matches 192 native cases. These checks establish sampled engine behavior, not exhaustive pixel-perfect screenshots or full Celebi story support.
The bedroom PC selects and puts away owned decorations, including left/right doll placement. Room blocks, sprites and descriptions reflect saved choices. The Town Map poster opens the native ROM-backed map. Start opens the start menu and Select the registered item; using the Bicycle follows the native outdoor/indoor rules. Browser checks cover both controls and decoration flows in Chromium and WebKit. Full item/Pokémon PC storage and the other bag pockets belong to later work.
The foundation milestone is complete within these acceptance boundaries. Next: finish the opening-story milestone below. Later maps remain gated until their scripts and story progression are supported.
2. Opening story and first battle slice
- Native player, party, Pokémon, bag, money and quest models with versioned saves
- Typed ROM decoders for species, moves, growth rates, items, encounters and trainers
- Decoder for evolutions, and evolution after battles and from items (see section 3)
- Mom, Elm's introduction, the starter, naming and the aide's Potion
- Route 29, Cherrygrove City, the Poké Mart and the Pokémon Center
- Route 30's trainers, Mr. Pokémon's house, the Mystery Egg and the Pokédex
- The Cherrygrove rival battle, Elm's call and the return to Elm
- Capture and the catching tutorial
- Battles: stats, damage, types, accuracy, criticals, status, turn order, fainting, experience, move learning, trainer AI, trainer send-outs and prize money
- Player switching and every non-ball battle item, with the stats screen
- The PC and boxes (catching with a full party)
- The introduction's naming and clock screens
- Controlled-seed comparisons with the original for accuracy, criticals and damage variation, including random-stream consumption. Damage, types, stats and experience curves are also compared.
- Controlled-input comparisons of capture odds, success and ball shakes with the original, including random-draw counts. Hardware RNG timing and the full capture sequence remain outside this comparison.
- Acceptance: one continuous playthrough of the opening quest with save/reload (
tests/playthrough.test.ts, driven through the world's inputs rather than the browser UI)
Introduce native player, party, Pokémon, inventory, money and quest models. Read species, moves, growth curves, evolutions, encounters, trainers and items through typed ROM decoders. Define serialization/migrations before persisting new systems.
Expand script calls, choices, scene state, inventory commands and native special handlers. Translate routines reached through assembly calls into explicit native services; never fall back to emulator execution in the shipping game.
Meeting Mom, Elm's introduction, the starter, the aide's Potion, Route 29, Cherrygrove, Route 30's trainers, Mr. Pokémon's house, the Cherrygrove rival battle, the return to Elm, capture, the catching tutorial, the PC, the introduction's naming and clock screens, and switching and healing items in battle run now. The opening milestone is complete within the recorded acceptance boundaries. The start menu, party menu and pack now run outside battle, and Pokémon evolve. Field moves from the menu, SEL/Select and MOVE PKMN W/O MAIL run too, and the Pokégear's clock, map and phone cards, with calls to Mom and Elm, Mom's bank at home and her purchases, and the trainer card with the play time. Next: the options.
Complete and validate the native battle engine: deterministic randomness, stats, move selection, accuracy/critical checks, damage, status, turn order, switching, fainting, experience, capture and rewards. Compare controlled seeds and states with the original, including integer rounding and overflow behavior.
Acceptance: finish the opening quest, including encounters and the rival battle, party/inventory changes and save/reload, without emulation or manually setting story flags.
3. Complete Crystal
- Pokémon Center healing, standard Poké Marts and Reflect/Light Screen
- Remaining battle commands, move and item effects, AI layers and special battles
- Evolution by level, happiness, Tyrogue's stats and stones, with Rare Candy and vitamins (trade evolutions need Link)
- Breeding, eggs and the rest of PC storage
- Field moves, gyms, Rocket events, the phone and Pokégear, puzzles, minigames, daily events, roaming Pokémon and the postgame
- Johto beyond Route 30, the League, Kanto and the final battle
- Music and sound through Web Audio
- Acceptance: main-game and postgame playthroughs, battle regression vectors, long sessions and save recovery
Port remaining battle commands, move/item effects, AI, special battles, evolutions, breeding, happiness, eggs and PC storage. Gen II behavior is the baseline; list intentional changes separately.
Complete field moves, gyms, Rocket events, phone/Pokégear, shops, healing, puzzles, minigames, daily events, roaming Pokémon and postgame. Cover Johto, the League, Kanto and the final battle. Track unsupported script/special handlers and add fixtures before translating each one.
Port the music/sound sequencer and synthesize its channels through Web Audio, reading music, effects and wave data from the local ROM. No recordings or CPU emulator are needed in the release. Verify timing and audio unlocking on phones.
Acceptance: main-game/postgame playthroughs, representative optional content, battle regression vectors, long sessions, clock rollover, interruption and save recovery. No unknown command is silently skipped. The full port is not complete until these checks pass.
4. Modern mobile presentation and delivery
- Crystal-style shell with Game, Savegames, Map, Pokédex, Link and Friends tabs
- Adaptive canvas, live local minimap, keyboard and on-screen controls
- Save, backup export and import for the single native save
- Touch battle commands, move list, PKMN party menu, PACK (Items and Balls pockets) and naming fields
- Start menu, party menu and pack outside battle (switching, moves, held items, healing and PP items, tossing, the Bicycle)
- Bill's PC: MOVE PKMN W/O MAIL between the party and every box
- Pokégear clock, map and phone cards (the radio card waits for its stations)
- Mom's bank: the amount box with its digit cursor, on keyboard, D-pad and on-screen buttons
- Trainer card: both pages, with the ROM's card picture, leader faces and animated badges
- Pokédex and options screens
- Scalable text, configurable controls and fuller accessibility
- Save slots with migration, atomic writes and import previews
- Installable web app with offline app shell, safe updates and quota handling
- Acceptance on physical iPhone and Android devices
The first presentation increment replaces the landing page and handheld frame with a restrained Crystal-style interface. Game, Savegames, Map, Pokédex, Link and Friends stay above a viewport-filling native canvas. The renderer draws more world tiles as space grows instead of stretching a 160 × 144 image. The game and Map tab share a live local minimap; the atlas and unsupported tabs remain honest planned states. The linked UI reference could not be inspected through automated access because the site returned a Cloudflare challenge, so exact screenshot matching is not claimed.
Build touch interfaces directly over native models: party, pack, battle commands, PC and Pokégear. Keep UI separate from gameplay calculations. Add scalable text, configurable controls and accessible focus/announcements.
Replace the single logical native save with real slots only after party, inventory and story state have stable schemas. Each slot needs a durable ID, display metadata, independent autosave and explicit create, load, rename, duplicate, export, import and delete flows. Version every persisted model. Migrate the current singleton save into a default slot exactly once, preserve unknown or newer backups without rewriting them, and use atomic writes with recovery from the last valid record. Import must preview identity, version and conflicts before replacement.
Dependencies: stable serialization for every system stored in a slot, migration fixtures for every released schema, quota/error handling and a transaction boundary that also covers future Link and walking credits.
Acceptance: migrate existing browser data without loss, keep several independent playthroughs, recover from interrupted writes and reject incompatible or corrupt imports without changing a valid slot. Test create/load/delete/rename/duplicate, autosave isolation, export/import conflicts and upgrades from every released schema.
Add PWA installation/offline caching of the app shell only. ROM/data retention remains a separate explicit local preference; otherwise reselect the ROM after reopening. Add safe application updates, schema migrations, storage persistence/quota handling and portable backups. Investigate original .sav import only with verified structure, checksums and RTC conversion.
Acceptance: installation, offline startup, portrait/landscape, touch/audio, background/resume and updates on physical iPhone/Safari and Android/Chrome. Browser automation does not establish physical-device compatibility.
5. World map and Pokédex
- Current-area minimap in the game and Map tab
- Seen and caught sets saved from real game state
- Johto/Kanto atlas with visited areas and Fly destinations
- Pokédex tab with ROM-derived records and habitat views
Promote the current-area minimap into a Johto/Kanto atlas after all map groups, connections and location names can be decoded from the selected ROM. Derive region geometry, visited areas, player location and Fly destinations from native map and story state. Do not ship copied map artwork or claim navigation through maps whose collisions and events are still unsupported.
Build the Pokédex from typed ROM decoders for species names, base data, evolutions, moves and graphics. Seen, caught, forms and habitat or area views must come from native game state and encounter tables. Keep ROM-derived records unavailable until a verified ROM is selected.
Dependencies: complete map indexing and connections, stable player/story flags, party and capture flow, encounter decoding, species graphics and save migrations.
Acceptance: atlas positions and Pokédex records match pinned PRET fixtures and local-ROM comparisons; visited, seen and caught state survives slot switching, backup round trips and schema upgrades. The UI must distinguish decoded data from progress the player has actually unlocked.
6. Internet Link and friends
- Not started. Link and Friends tabs show their planned scope only.
Implement a game-level protocol over native state. This architecture does not need to tunnel serial timing or synchronize GB CPU cycles.
Separate LinkTransport, TradeSession and BattleSession. Start with local peers, then private invite rooms using signaling and WebRTC data channels or a relay. Negotiate compatible schema/game versions and exchange only required game data. Never transmit ROMs or extracted assets.
Trades need durable preparation, a transaction ID, agreed completion, idempotent recovery and pre-trade backups for both players. Do not retry ambiguous completion automatically. Battles need deterministic state, agreed randomness, turn commands, state checks and a disconnect policy. Define validation, room expiry and persistence before public service access.
Add a separate account and relationship layer before Friends becomes active. Define a stable public identity that does not expose save-slot or ROM data. Support private invite creation, acceptance and expiry; friend removal; user blocking; and abuse reporting if user-generated text is ever added. Presence is opt-in, coarse and independently disableable. Privacy controls must cover discoverability, invite permissions and online status. Blocking must prevent new invites, presence disclosure and Link sessions in both directions.
Dependencies: native party, PC, battle and save-slot transactions; a versioned wire protocol; authenticated identity; signaling/relay operations; account recovery and deletion; rate limits, moderation operations and a published privacy policy. Link and Friends remain unavailable until these dependencies have production ownership and failure handling.
Acceptance: cross-network trades/battles and induced duplicate messages, latency, disconnects and reconnects at every phase, without duplication or lost Pokémon. Verify invite expiry, simultaneous relationship changes, remove/re-add, block/unblock, hidden presence, account deletion and attempts to join with incompatible game or schema versions.
7. Optional Pokéwalker-style egg assistance
- Not started. Depends on eggs, hatching and a native companion app.
This optional mobile feature uses real-world walking to assist egg hatching after native party, breeding, eggs and faithful in-game hatching are complete. The pinned Crystal engine decrements party egg counters from overworld walking in CountStep and DoEggStep; battles do not advance them. A battle-based bonus would therefore be an explicit, separately enabled extension rather than Crystal behavior.
Separate a platform StepProvider, local activity ledger and reward rules. Request read access to steps only, after an explicit explanation and opt-in. Keep real-world steps distinct from the in-game tile counter, show what interval will be credited and provide a disconnect/delete control. Do not request distance, heart rate, workouts or permission to write health records. Keep raw activity data on device by default.
An ordinary PWA cannot directly query Apple HealthKit, Core Motion pedometer history, Android Health Connect or Android step sensors. Reliable pocket and locked-screen behavior therefore needs a native companion or wrapper and a narrow bridge into the web game. On Apple platforms, evaluate read-only HealthKit step counts and Core Motion CMPedometer; HealthKit requires the capability, a purpose string and per-type authorization, while Core Motion exposes historical pedometer queries. On Android, prefer Health Connect READ_STEPS where available and evaluate TYPE_STEP_COUNTER with ACTIVITY_RECOGNITION as a device-local fallback. Handle absent hardware, unavailable services, denied or revoked access and partial history without blocking the game.
Normalize provider records into immutable local intervals with provider/source IDs, start/end times and step totals. Deduplicate overlapping samples before reward calculation. Commit each credit as one transaction containing a unique credit ID, the source interval set, the target slot and egg identity, the applied hatch-counter delta and resulting state version. Persist the ledger and game mutation atomically. Restoring an older backup must not make an already consumed interval eligible again; trading, moving or hatching an egg must close or retarget pending credits explicitly; wall-clock or timezone changes must not create new credit. Sensor callbacks only append provider observations, never mutate game state.
The first reward is bounded egg-hatch assistance for an egg currently in the party. Define and test the conversion rate and daily cap before release, never reduce an egg below the normal hatch trigger outside a safe game checkpoint, and show the pending effect before applying it. No party or no eligible egg means steps remain unspent or expire according to a documented rule; do not silently redirect them to another reward.
Store distribution is a release dependency, not an assumption. Apple says HealthKit must be used for health and fitness purposes and its review rules restrict health and Motion data use, so a game-only reward may not qualify for HealthKit/App Store approval. Google Play lists health-integrated games as an approved health-permission use case, but still requires a Health apps declaration and review of the requested permissions. Recheck both policies before implementation and submission. Core Motion or a device step sensor may reduce health-store scope, but still requires native permissions and review.
Dependencies: native party, breeding, eggs and hatching; stable egg identity; save-slot transactions and migrations; a native iOS and Android host; permission/privacy UI; and confirmed store eligibility for the chosen provider.
Acceptance: on supported physical iPhone and Android devices, a real walk with the phone locked is read with steps-only consent, credited to an eligible egg exactly once after reopening, and safely reaches the normal hatch flow. Exercise duplicate and overlapping samples, provider corrections, permission revocation, device reboot, offline use, timezone and clock changes, backup restore, slot switching, trade, egg movement and hatch between observation and credit. Denial, unsupported hardware and failed review eligibility leave the base game fully usable.
Official platform references: Apple HealthKit authorization and capability setup, Core Motion pedometer history and App Review Guidelines 2.5.1 and 5.1.3; Android Health Connect step access, step counter sensor guidance and Google Play Android health permissions guidance.
Working method
For each subsystem: identify PRET routines/data, implement native code behind a narrow interface, compare controlled inputs against the reference, then integrate a playable scenario. Retain synthetic fixtures for public CI. Keep real-ROM state, graphics, audio, screenshots and reference builds in ignored local locations.
See NATIVE_PORT.md for the current routine map and verification commands.