Files
gen1recomp/docs/gold-walkthrough/asm-walk/section-30-lapras.md
T
bryanthaboi ae6cac89e1 G2 support
2026-08-11 11:53:30 -04:00

38 KiB

Section 30 - Lapras

Source: ../section-30-lapras.txt Maps covered: MAP_UNION_CAVE_1F, MAP_UNION_CAVE_B1F, MAP_UNION_CAVE_B2F (approach only: MAP_ROUTE_32, whose warp into the cave is cited but which belongs to section 03) Badges / key milestones in this section: no badge. The milestone is the static level-20 Lapras on Union Cave B2F, which only exists on a Friday and only once per in-game day (ENGINE_UNION_CAVE_LAPRAS). Five optional trainers (Andrew, Calvin, Nick, Gwen, Emma) and two item balls sit on the way in.

This is a revisit section: Union Cave 1F/B1F were first walked in section 03. Everything here is reachable only with Surf, i.e. after the Fog Badge.

1. Route order

# Map constant asm file Enter via Leaves via Why (walkthrough beat)
1 MAP_ROUTE_32 maps/Route32.asm walked south from Violet City (connection south, Route32, ROUTE_32, 0 in data/maps/attributes.asm) warp_event 6, 79, UNION_CAVE_1F, 4 "head to Violet City and then go south through Route 32 into Union Cave"
2 MAP_UNION_CAVE_1F maps/UnionCave1F.asm warp 4 at 17, 3 (from Route 32) warp_event 3, 33, UNION_CAVE_B1F, 4 walk past Daniel / Russell / Bill, surf the south-west lake, take the bottom-left ladder
3 MAP_UNION_CAVE_B1F maps/UnionCaveB1F.asm warp 4 at 3, 33 warp_event 17, 31, UNION_CAVE_B2F, 1 Pokemaniac Andrew stands at 5, 32 right next to the arrival ladder; surf east to Calvin at 17, 30, then the ladder beside him
4 MAP_UNION_CAVE_B2F maps/UnionCaveB2F.asm warp 1 at 5, 3 same warp back up, or Escape Rope / Dig Hyper Potion, Nick, Gwen, Emma, then the Lapras object at 11, 31

The walkthrough ends inside Union Cave B2F ("You can then use Escape Rope to leave Union Cave"); it does not spill into a neighbouring section's map.

2. Maps

MAP_UNION_CAVE_1F

  • Script: maps/UnionCave1F.asm
  • Blocks: maps/UnionCave1F.blk
  • Header: data/maps/maps.asm:107 -> map UnionCave1F, TILESET_CAVE, CAVE, LANDMARK_UNION_CAVE, MUSIC_UNION_CAVE, TRUE, PALETTE_NITE, FISHGROUP_LAKE (the TRUE field is the phone-service flag: no phone calls in Union Cave)
  • Dimensions: constants/map_constants.asm:94 -> map_const UNION_CAVE_1F, 10, 18 (10x18 blocks = 20x36 walk cells)
  • Attributes: data/maps/attributes.asm:426 -> map_attributes UnionCave1F, UNION_CAVE_1F, $09
  • Connections: none (no connection rows follow the attributes line)
  • Map group: MapGroup_Dungeons (data/maps/maps.asm:77)

Warps (def_warp_events)

idx x y destination map dest warp
1 5 19 UNION_CAVE_B1F 3
2 3 33 UNION_CAVE_B1F 4
3 17 31 ROUTE_33 1
4 17 3 ROUTE_32 4

Coord events (def_coord_events)

None - the block is empty.

BG events (def_bg_events)

None. (UnionCave1FUnusedSign / UnionCave1FUnusedSignText exist in the file but are marked ; unreferenced and are not in any bg_event row.)

Object events (def_object_events)

const sprite x y movement type script label event flag
UNIONCAVE1F_POKEFAN_M1 SPRITE_POKEFAN_M 4 4 SPRITEMOVEDATA_SPINRANDOM_FAST OBJECTTYPE_TRAINER, sight 1 TrainerHikerDaniel -1
UNIONCAVE1F_SUPER_NERD SPRITE_SUPER_NERD 4 21 SPRITEMOVEDATA_STANDING_DOWN OBJECTTYPE_TRAINER, sight 2 TrainerPokemaniacLarry -1
UNIONCAVE1F_POKEFAN_M2 SPRITE_POKEFAN_M 15 8 SPRITEMOVEDATA_STANDING_LEFT OBJECTTYPE_TRAINER, sight 3 TrainerHikerRussell -1
UNIONCAVE1F_FISHER1 SPRITE_FISHER 16 31 SPRITEMOVEDATA_STANDING_UP OBJECTTYPE_TRAINER, sight 4 TrainerFirebreatherRay -1
UNIONCAVE1F_FISHER2 SPRITE_FISHER 15 15 SPRITEMOVEDATA_SPINRANDOM_FAST OBJECTTYPE_TRAINER, sight 2 TrainerFirebreatherBill -1
UNIONCAVE1F_POKE_BALL1 SPRITE_POKE_BALL 17 21 SPRITEMOVEDATA_STILL OBJECTTYPE_ITEMBALL UnionCave1FGreatBall EVENT_UNION_CAVE_1F_GREAT_BALL
UNIONCAVE1F_POKE_BALL2 SPRITE_POKE_BALL 4 2 SPRITEMOVEDATA_STILL OBJECTTYPE_ITEMBALL UnionCave1FPotion EVENT_UNION_CAVE_1F_POTION
UNIONCAVE1F_POKE_BALL3 SPRITE_POKE_BALL 4 17 SPRITEMOVEDATA_STILL OBJECTTYPE_ITEMBALL UnionCave1FXAttack EVENT_UNION_CAVE_1F_X_ATTACK
UNIONCAVE1F_POKE_BALL4 SPRITE_POKE_BALL 12 33 SPRITEMOVEDATA_STILL OBJECTTYPE_ITEMBALL UnionCave1FAwakening EVENT_UNION_CAVE_1F_AWAKENING

Scripts of interest

  • UnionCave1F_MapScripts - def_scene_scripts and def_callbacks are both empty. Nothing on 1F is time- or flag-gated; the floor is pure walking.
  • PokemaniacLarryAfterBattleText and FirebreatherBillAfterBattleText are the in-game hints the walkthrough is acting on: "Every Friday, you can hear #MON roars from deep inside the cave." and "On weekends, you can hear strange roars from deep in the cave." Only Larry's Friday line matches the code (see B2F callback below).

Flags and events

constant defined in read/written by meaning for a bot
EVENT_UNION_CAVE_1F_GREAT_BALL constants/event_flags.asm itemball object row set once the ball is taken; masks the object
EVENT_UNION_CAVE_1F_POTION constants/event_flags.asm itemball object row same
EVENT_UNION_CAVE_1F_X_ATTACK constants/event_flags.asm itemball object row same
EVENT_UNION_CAVE_1F_AWAKENING constants/event_flags.asm itemball object row same
EVENT_BEAT_HIKER_DANIEL, EVENT_BEAT_HIKER_RUSSELL, EVENT_BEAT_POKEMANIAC_LARRY, EVENT_BEAT_FIREBREATHER_BILL, EVENT_BEAT_FIREBREATHER_RAY constants/event_flags.asm 3rd argument of each trainer macro row set = already beaten, no eyesight trigger

Items

item how obtained source (label / bg_event / hidden) one-time flag
GREAT_BALL item ball at 17,21 UnionCave1FGreatBall (itemball GREAT_BALL) EVENT_UNION_CAVE_1F_GREAT_BALL
POTION item ball at 4,2 UnionCave1FPotion EVENT_UNION_CAVE_1F_POTION
X_ATTACK item ball at 4,17 UnionCave1FXAttack EVENT_UNION_CAVE_1F_X_ATTACK
AWAKENING item ball at 12,33 UnionCave1FAwakening EVENT_UNION_CAVE_1F_AWAKENING

Trainers (all optional on this pass; the walkthrough only mentions walking past them)

const class id party (data/trainers/parties.asm label) script label rematch/phone
HIKER, DANIEL HIKER DANIEL "DANIEL@" TrainerHikerDaniel phone service disabled on this map (TRUE in the header)
HIKER, RUSSELL HIKER RUSSELL "RUSSELL@" TrainerHikerRussell as above
POKEMANIAC, LARRY POKEMANIAC LARRY "LARRY@" TrainerPokemaniacLarry as above
FIREBREATHER, BILL FIREBREATHER BILL "BILL@" TrainerFirebreatherBill as above
FIREBREATHER, RAY FIREBREATHER RAY "RAY@" TrainerFirebreatherRay as above

(Party rows for these five were not transcribed - they belong to section 03 and the walkthrough tells the bot to walk past them, not fight them.)

Wild encounters

Grass/cave, data/wild/johto_grass.asm:444 def_grass_wildmons UNION_CAVE_1F, rate 6 percent for morn/day/nite alike. Gold list (identical across all three time slots):

slot level species
1 6 GEODUDE
2 6 SANDSHREW (Silver: RATTATA)
3 5 ZUBAT
4 4 RATTATA
5 7 ZUBAT
6 6 ONIX
7 6 ONIX

Water, data/wild/johto_water.asm:12 def_water_wildmons UNION_CAVE_1F, rate 2 percent: 15 WOOPER, 20 QUAGSIRE, 15 QUAGSIRE.

Fishing group is FISHGROUP_LAKE (header row).

MAP_UNION_CAVE_B1F

  • Script: maps/UnionCaveB1F.asm
  • Blocks: maps/UnionCaveB1F.blk
  • Header: data/maps/maps.asm:108 -> map UnionCaveB1F, TILESET_CAVE, CAVE, LANDMARK_UNION_CAVE, MUSIC_UNION_CAVE, TRUE, PALETTE_NITE, FISHGROUP_LAKE
  • Dimensions: constants/map_constants.asm:95 -> map_const UNION_CAVE_B1F, 10, 18
  • Attributes: data/maps/attributes.asm:427 -> map_attributes UnionCaveB1F, UNION_CAVE_B1F, $09
  • Connections: none
  • Symbols: 44:5415 UnionCaveB1F_MapScripts, 44:56df UnionCaveB1F_MapEvents (pokegold.sym)

Warps (def_warp_events)

idx x y destination map dest warp
1 3 3 RUINS_OF_ALPH_OUTSIDE 7
2 3 11 RUINS_OF_ALPH_OUTSIDE 8
3 7 19 UNION_CAVE_1F 1
4 3 33 UNION_CAVE_1F 2
5 17 31 UNION_CAVE_B2F 1

Coord events (def_coord_events)

None.

BG events (def_bg_events)

None.

Object events (def_object_events)

const sprite x y movement type script label event flag
UNIONCAVEB1F_POKEFAN_M1 SPRITE_POKEFAN_M 10 4 SPRITEMOVEDATA_SPINRANDOM_FAST OBJECTTYPE_TRAINER, sight 1 TrainerHikerPhillip -1
UNIONCAVEB1F_POKEFAN_M2 SPRITE_POKEFAN_M 17 10 SPRITEMOVEDATA_STANDING_DOWN OBJECTTYPE_TRAINER, sight 3 TrainerHikerLeonard -1
UNIONCAVEB1F_SUPER_NERD1 SPRITE_SUPER_NERD 5 32 SPRITEMOVEDATA_STANDING_LEFT OBJECTTYPE_TRAINER, sight 3 TrainerPokemaniacAndrew -1
UNIONCAVEB1F_SUPER_NERD2 SPRITE_SUPER_NERD 17 30 SPRITEMOVEDATA_STANDING_LEFT OBJECTTYPE_TRAINER, sight 3 TrainerPokemaniacCalvin -1
UNIONCAVEB1F_POKE_BALL1 SPRITE_POKE_BALL 2 16 SPRITEMOVEDATA_STILL OBJECTTYPE_ITEMBALL UnionCaveB1FTMSwift EVENT_UNION_CAVE_B1F_TM_SWIFT
UNIONCAVEB1F_BOULDER SPRITE_BOULDER 7 10 SPRITEMOVEDATA_STRENGTH_BOULDER OBJECTTYPE_SCRIPT UnionCaveB1FBoulder -1
UNIONCAVEB1F_POKE_BALL2 SPRITE_POKE_BALL 17 23 SPRITEMOVEDATA_STILL OBJECTTYPE_ITEMBALL UnionCaveB1FXDefend EVENT_UNION_CAVE_B1F_X_DEFEND

Scripts of interest

  • UnionCaveB1F_MapScripts - both def_scene_scripts and def_callbacks are empty. Nothing on B1F is gated.
  • TrainerPokemaniacAndrew (44:5417) - trainer POKEMANIAC, ANDREW, EVENT_BEAT_POKEMANIAC_ANDREW, PokemaniacAndrewSeenText, PokemaniacAndrewBeatenText, 0, .Script. .Script is the boilerplate after-battle path: endifjustbattled / opentext / writetext PokemaniacAndrewAfterBattleText / waitbutton / closetext / end. No flags beyond the beaten flag, no items, no warps.
  • TrainerPokemaniacCalvin (44:542b) - identical shape with EVENT_BEAT_POKEMANIAC_CALVIN and PokemaniacCalvinAfterBattleText ("You demonstrated on me!" is PokemaniacCalvinBeatenText, matching the walkthrough quote).
  • UnionCaveB1FBoulder - jumpstd StrengthBoulderScript. Not on the walkthrough's path to Lapras (it sits at 7, 10, up near the Ruins of Alph ladders), but it is the one Strength-gated object on the floor.

Flags and events

constant defined in read/written by meaning for a bot
EVENT_BEAT_POKEMANIAC_ANDREW constants/event_flags.asm:751 TrainerPokemaniacAndrew set after the win; suppresses the eyesight trigger
EVENT_BEAT_POKEMANIAC_CALVIN constants/event_flags.asm:752 TrainerPokemaniacCalvin same
EVENT_BEAT_HIKER_PHILLIP, EVENT_BEAT_HIKER_LEONARD constants/event_flags.asm the two Hiker trainer rows not on the Lapras path
EVENT_UNION_CAVE_B1F_TM_SWIFT constants/event_flags.asm itemball object row one-time TM_SWIFT
EVENT_UNION_CAVE_B1F_X_DEFEND constants/event_flags.asm itemball object row one-time X_DEFEND

Items

item how obtained source (label / bg_event / hidden) one-time flag
TM_SWIFT item ball at 2,16 UnionCaveB1FTMSwift EVENT_UNION_CAVE_B1F_TM_SWIFT
X_DEFEND item ball at 17,23 UnionCaveB1FXDefend EVENT_UNION_CAVE_B1F_X_DEFEND

Neither is mentioned by the walkthrough; both are on the way to the B2F ladder.

Trainers

const class id party (data/trainers/parties.asm label) script label rematch/phone
POKEMANIAC, ANDREW POKEMANIAC ($1e, constants/trainer_constants.asm:245) ANDREW (id 2) "ANDREW@", TRAINERTYPE_NORMAL at data/trainers/parties.asm:1086: L24 MAROWAK, L24 MAROWAK TrainerPokemaniacAndrew no phone (map header phone flag TRUE)
POKEMANIAC, CALVIN POKEMANIAC CALVIN (id 3) "CALVIN@", TRAINERTYPE_NORMAL at data/trainers/parties.asm:1092: L26 KANGASKHAN TrainerPokemaniacCalvin no phone
HIKER, PHILLIP HIKER PHILLIP "PHILLIP@" TrainerHikerPhillip off-path
HIKER, LEONARD HIKER LEONARD "LEONARD@" TrainerHikerLeonard off-path

TRAINERTYPE_NORMAL means level+species only: no custom moves, no held items.

Prize money: POKEMANIAC base reward is db 15 (data/trainers/attributes.asm:179 ; Pokemaniac). ComputeTrainerReward (engine/battle/read_trainer_party.asm:300) computes base * wCurPartyLevel where the level is the last party row, and WinTrainerBattle (engine/battle/core.asm:2292) pays that out four times (ld c, 4 at the .okay loop). So Andrew = 15244 = 1440, Calvin = 15264 = 1560 - both match the walkthrough exactly.

Wild encounters

Grass/cave, data/wild/johto_grass.asm:499 def_grass_wildmons UNION_CAVE_B1F, rate 6 percent morn/day/nite. Gold list (same in all three slots):

slot level species
1 8 GEODUDE
2 8 SANDSHREW (Silver: RATTATA)
3 7 ZUBAT
4 8 ONIX
5 9 ZUBAT
6 6 RATTATA
7 6 RATTATA

Water, data/wild/johto_water.asm:19, rate 2 percent: 15 WOOPER, 20 QUAGSIRE, 15 QUAGSIRE.

MAP_UNION_CAVE_B2F

This is the section's real content.

  • Script: maps/UnionCaveB2F.asm
  • Blocks: maps/UnionCaveB2F.blk (2b:466d UnionCaveB2F_Blocks)
  • Header: data/maps/maps.asm:109 -> map UnionCaveB2F, TILESET_CAVE, CAVE, LANDMARK_UNION_CAVE, MUSIC_UNION_CAVE, TRUE, PALETTE_NITE, FISHGROUP_SHORE (note: FISHGROUP_SHORE here, not FISHGROUP_LAKE like the two floors above)
  • Dimensions: constants/map_constants.asm:96 -> map_const UNION_CAVE_B2F, 10, 18 (20x36 walk cells)
  • Attributes: data/maps/attributes.asm:428 -> map_attributes UnionCaveB2F, UNION_CAVE_B2F, $09
  • Connections: none
  • Symbols: 44:5759 UnionCaveB2F_MapScripts, 44:575e UnionCaveB2FLaprasCallback, 44:5770 UnionCaveLapras, 44:59cb UnionCaveB2F_MapEvents, 25:595d UnionCaveB2F_MapAttributes

Warps (def_warp_events)

idx x y destination map dest warp
1 5 3 UNION_CAVE_B1F 5

One warp. There is no second exit: leaving is either back up this ladder, or Escape Rope / Dig (the walkthrough's suggestion).

Coord events (def_coord_events)

None - the block is empty. Nothing on B2F is a trip-wire; every event is an object you walk into the sight line of or press A on.

BG events (def_bg_events)

None.

Object events (def_object_events)

const sprite x y movement type script label event flag
UNIONCAVEB2F_ROCKER SPRITE_ROCKER 17 23 SPRITEMOVEDATA_STANDING_LEFT OBJECTTYPE_TRAINER, sight 5, PAL_NPC_RED TrainerCooltrainermNick -1
UNIONCAVEB2F_COOLTRAINER_F1 SPRITE_COOLTRAINER_F 5 13 SPRITEMOVEDATA_SPINRANDOM_FAST OBJECTTYPE_TRAINER, sight 1, PAL_NPC_RED TrainerCooltrainerfGwen -1
UNIONCAVEB2F_COOLTRAINER_F2 SPRITE_COOLTRAINER_F 3 28 SPRITEMOVEDATA_STANDING_DOWN OBJECTTYPE_TRAINER, sight 2, PAL_NPC_RED TrainerCooltrainerfEmma -1
UNIONCAVEB2F_POKE_BALL1 SPRITE_POKE_BALL 16 2 SPRITEMOVEDATA_STILL OBJECTTYPE_ITEMBALL UnionCaveB2FElixer EVENT_UNION_CAVE_B2F_ELIXER
UNIONCAVEB2F_POKE_BALL2 SPRITE_POKE_BALL 12 19 SPRITEMOVEDATA_STILL OBJECTTYPE_ITEMBALL UnionCaveB2FHyperPotion EVENT_UNION_CAVE_B2F_HYPER_POTION
UNIONCAVEB2F_LAPRAS SPRITE_SURF 11 31 SPRITEMOVEDATA_SWIM_WANDER, radius x=1 y=1, PAL_NPC_BLUE OBJECTTYPE_SCRIPT UnionCaveLapras EVENT_UNION_CAVE_B2F_LAPRAS

Notes a bot needs:

  • Lapras uses SPRITE_SURF (constants/sprite_constants.asm:87, id 53) - the same overworld sprite the player rides. It is not a Lapras sprite.
  • SPRITEMOVEDATA_SWIM_WANDER with radius 1,1 means the object drifts within one cell of 11, 31. A driver must not hard-code an exact tile to face; it should walk to the neighbourhood and press A at whichever adjacent water cell the sprite is occupying.
  • Its event flag is EVENT_UNION_CAVE_B2F_LAPRAS. CheckObjectFlag (engine/overworld/map_objects_2.asm:31) masks an object when the flag is set, so "Lapras present" = flag clear.

Scripts of interest

UnionCaveB2F_MapScripts (44:5759)

	def_scene_scripts          ; empty
	def_callbacks
	callback MAPCALLBACK_OBJECTS, UnionCaveB2FLaprasCallback

UnionCaveB2FLaprasCallback (44:575e) - the whole Friday rule, six opcodes:

	checkflag ENGINE_UNION_CAVE_LAPRAS
	iftrue .NoAppear
	readvar VAR_WEEKDAY
	ifequal FRIDAY, .Appear
.NoAppear:
	disappear UNIONCAVEB2F_LAPRAS
	endcallback
.Appear:
	appear UNIONCAVEB2F_LAPRAS
	endcallback

Control flow, spelled out:

  1. checkflag ENGINE_UNION_CAVE_LAPRAS - a daily engine flag (constants/engine_flags.asm:107, in the wDailyFlags2 block; bit DAILYFLAGS2_UNION_CAVE_LAPRAS_F = 1, constants/ram_constants.asm:315). Set means "already caught/fought it today" -> .NoAppear.
  2. readvar VAR_WEEKDAY (constants/script_constants.asm:59, id $0b) then ifequal FRIDAY (constants/ram_constants.asm:218, FRIDAY = 5, Sunday-based). Only Friday reaches .Appear.
  3. appear clears the object's event flag (Script_appear, engine/overworld/scripting.asm:879, ld b, 0 ; clear); disappear sets it (Script_disappear, :887, ld b, 1 ; set).

Because the callback is MAPCALLBACK_OBJECTS, it re-runs on every map load of B2F (engine/overworld/map_setup.asm:81 farcall LoadObjectMasks follows it). So a bot that walks out and back in on a non-Friday will find Lapras gone.

UnionCaveLapras (44:5770) - the encounter itself:

	faceplayer
	cry LAPRAS
	loadwildmon LAPRAS, 20
	startbattle
	disappear UNIONCAVEB2F_LAPRAS
	setflag ENGINE_UNION_CAVE_LAPRAS
	reloadmapafterbattle
	end

Load-bearing details:

  • loadwildmon LAPRAS, 20 - a level 20 wild Lapras, matching the walkthrough. There is no writevar VAR_BATTLETYPE here, so unlike Ho-Oh / Lugia / the Red Gyarados this is an ordinary wild battle: no forced item, no forced shiny, no BATTLETYPE_CANLOSE mercy.
  • disappear and setflag run unconditionally after the battle, with no iffalse / checkflag guard on the outcome. So Lapras is consumed for the day whether you caught it, KO'd it, ran, or blacked out mid-battle. That is exactly why the walkthrough says to save first - a soft reset is the only recovery.
  • setflag ENGINE_UNION_CAVE_LAPRAS is the daily flag, not a permanent event. CheckDailyResetTimer (engine/overworld/time.asm:88) zeroes wDailyFlags1 and wDailyFlags2 whole once a day has elapsed, so it comes back next Friday.

TrainerCooltrainermNick (44:577f), TrainerCooltrainerfGwen (44:5793), TrainerCooltrainerfEmma (44:57a7) - all three are the plain trainer <class>, <id>, EVENT_BEAT_*, <seen>, <beaten>, 0, .Script shape with an endifjustbattled / opentext / writetext ...AfterBattleText / waitbutton / closetext / end follow-up. No items, no warps, no flags beyond their own EVENT_BEAT_*.

CooltrainerfEmmaAfterBattleText is the in-map hint for the whole section: "Just once a week, a #MON comes to the water's edge. / I wanted to see that #MON..."

UnionCaveB2FElixer = itemball ELIXER; UnionCaveB2FHyperPotion = itemball HYPER_POTION (macros/scripts/maps.asm:155, two raw bytes db item, quantity, not bytecode).

Flags and events

constant defined in read/written by meaning for a bot
ENGINE_UNION_CAVE_LAPRAS constants/engine_flags.asm:107 (backed by data/events/engine_flags.asm:117 -> wDailyFlags2 bit DAILYFLAGS2_UNION_CAVE_LAPRAS_F) read by UnionCaveB2FLaprasCallback, set by UnionCaveLapras "Lapras already used up today". Cleared wholesale by CheckDailyResetTimer (engine/overworld/time.asm:88-97)
EVENT_UNION_CAVE_B2F_LAPRAS constants/event_flags.asm:1282 written by the callback's appear/disappear object visibility mask. Clear = visible (engine/overworld/map_objects_2.asm:31 CheckObjectFlag)
VAR_WEEKDAY constants/script_constants.asm:59 ($0b) readvar in the callback wCurDay, 0 = SUNDAY .. 6 = SATURDAY; FRIDAY = 5 (constants/ram_constants.asm:218)
EVENT_BEAT_COOLTRAINERM_NICK constants/event_flags.asm:857 TrainerCooltrainermNick beaten flag
EVENT_BEAT_COOLTRAINERF_GWEN constants/event_flags.asm:877 TrainerCooltrainerfGwen beaten flag
EVENT_BEAT_COOLTRAINERF_EMMA constants/event_flags.asm:891 TrainerCooltrainerfEmma beaten flag
EVENT_UNION_CAVE_B2F_ELIXER constants/event_flags.asm:1025 itemball object row one-time
EVENT_UNION_CAVE_B2F_HYPER_POTION constants/event_flags.asm:1026 itemball object row one-time

There are no SCENE_* values on this map: def_scene_scripts is empty, so wMapScene is never consulted here.

Items

item how obtained source (label / bg_event / hidden) one-time flag
ELIXER item ball at 16,2 UnionCaveB2FElixer (itemball ELIXER) EVENT_UNION_CAVE_B2F_ELIXER
HYPER_POTION item ball at 12,19 UnionCaveB2FHyperPotion (itemball HYPER_POTION) EVENT_UNION_CAVE_B2F_HYPER_POTION

The walkthrough names only the Hyper Potion ("Land on the right side to grab a Hyper Potion"). The Elixer at 16, 2 sits near the arrival ladder at 5, 3 and is never mentioned - free pickup for a bot on the way in.

Trainers

const class id party (data/trainers/parties.asm label) script label rematch/phone
COOLTRAINERM, NICK COOLTRAINERM ($1b, constants/trainer_constants.asm:184) NICK (id 1) "NICK@", TRAINERTYPE_MOVES at data/trainers/parties.asm:742 TrainerCooltrainermNick no phone (map phone flag TRUE)
COOLTRAINERF, GWEN COOLTRAINERF ($1c, :205) GWEN (id 1) "GWEN@", TRAINERTYPE_NORMAL at :867 TrainerCooltrainerfGwen no phone
COOLTRAINERF, EMMA COOLTRAINERF EMMA (id 15) "EMMA@", TRAINERTYPE_NORMAL at :954 TrainerCooltrainerfEmma no phone

Parties verbatim:

NICK (TRAINERTYPE_MOVES - custom movesets, no items):

# level species moves
1 26 CHARMANDER EMBER, SMOKESCREEN, RAGE, SCARY_FACE
2 26 SQUIRTLE WITHDRAW, WATER_GUN, BITE, CURSE
3 26 BULBASAUR LEECH_SEED, POISONPOWDER, SLEEP_POWDER, RAZOR_LEAF

GWEN (TRAINERTYPE_NORMAL):

# level species
1 26 EEVEE
2 22 FLAREON
3 22 VAPOREON
4 22 JOLTEON

EMMA (TRAINERTYPE_NORMAL): L28 POLIWHIRL, single mon.

Prize money (base * last-party-level * 4, see the B1F note): COOLTRAINERM and COOLTRAINERF both have db 12 ; base reward (data/trainers/attributes.asm:161 ; Cooltrainerm, :167 ; Cooltrainerf). Nick = 12264 = 1248, Gwen = 12224 = 1056 (last row is the L22 Jolteon), Emma = 12284 = 1344. All three match the walkthrough.

AI: both Cooltrainer classes carry the full AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY set and SWITCH_SOMETIMES; Pokemaniac is the weaker AI_BASIC | AI_SETUP | AI_OFFENSIVE | AI_AGGRESSIVE | AI_STATUS. None of the five hold items (db NO_ITEM, NO_ITEM).

Wild encounters

Grass/cave, data/wild/johto_grass.asm:554 def_grass_wildmons UNION_CAVE_B2F, rate 4 percent. This table is not split by version, and all three time-of-day slots are identical:

slot level species
1 22 ZUBAT
2 22 RATICATE
3 22 GOLBAT
4 21 GEODUDE
5 20 RATTATA
6 23 ONIX
7 23 ONIX

Water (the surf legs the walkthrough spends most of its time on), data/wild/johto_water.asm:26 def_water_wildmons UNION_CAVE_B2F, rate 4 percent (double the 1F/B1F water rate):

slot level species
1 15 TENTACOOL
2 20 QUAGSIRE
3 20 TENTACRUEL

Fishing group is FISHGROUP_SHORE (data/maps/maps.asm:109). No headbutt or rock-smash tables apply - it is a cave with no trees.

The walkthrough's Max Repel advice: REPEL_STEPS in the port mirrors RepelEffect / SuperRepelEffect / MaxRepelEffect; MAX_REPEL is 250 steps. With the B2F encounters capped at level 23 and the lead mon typically above that, Repel suppresses essentially all of the grass rolls.

3. Blockers and gates

Gate Enforced by (file:label) Requirement Unlock condition
Cannot cross any of the water on 1F / B1F / B2F engine/events/overworld.asm:322 SurfFunction.TrySurf (ld de, ENGINE_FOGBADGE / call CheckBadge / jr c, .nofogbadge), and the walk-into-water path TrySurfOW at :469 Fog Badge plus a party mon that knows SURF beat Morty in Ecruteak Gym (section 07)
Lapras object is absent 6 days out of 7 maps/UnionCaveB2F.asm:15 UnionCaveB2FLaprasCallback -> readvar VAR_WEEKDAY / ifequal FRIDAY in-game weekday must equal FRIDAY (constants/ram_constants.asm:218, value 5) set the GB clock / wait for Friday. Re-checked on every B2F map load (MAPCALLBACK_OBJECTS)
Lapras is absent again after one encounter same callback, first two opcodes: checkflag ENGINE_UNION_CAVE_LAPRAS / iftrue .NoAppear; the flag is set by UnionCaveLapras flag must be clear CheckDailyResetTimer (engine/overworld/time.asm:88) zeroes wDailyFlags1+wDailyFlags2 once a real day has passed
Lapras is consumed even on a failed attempt UnionCaveLapras: startbattle is followed unconditionally by disappear + setflag - no iffalse on wBattleResult none - this is the failure mode, not a gate save before pressing A; soft-reset on a bad outcome
Only one way out of B2F maps/UnionCaveB2F.asm def_warp_events has exactly one row (5, 3 -> UNION_CAVE_B1F, 5) - Escape Rope / Dig short-circuits the return walk (B2F's tileset is TILESET_CAVE)
Boulder at B1F 7, 10 UnionCaveB1FBoulder -> jumpstd StrengthBoulderScript Strength / Plain Badge not on the Lapras path; noted for completeness

Nothing in this section checks a badge count, a key item, or an EVENT_* story flag. The only two real gates are Surf and Friday.

4. Bot checklist

Preconditions for the whole section: Fog Badge, a party mon with SURF, the in-game weekday equal to FRIDAY, ENGINE_UNION_CAVE_LAPRAS clear, several Ultra Balls, a status move (Sleep/Paralysis) and a save written immediately before the Lapras press.

# Map Target Input intent Precondition Postcondition
1 MAP_ROUTE_32 warp at 6, 79 walk onto the warp on Route 32 now in MAP_UNION_CAVE_1F at 17, 3 (warp 4)
2 MAP_UNION_CAVE_1F - use MAX_REPEL from the pack have one 250 steps of encounter suppression
3 MAP_UNION_CAVE_1F avoid sight lines of UNIONCAVE1F_POKEFAN_M2 (15, 8, sight 3, facing left) and UNIONCAVE1F_FISHER2 (15, 15, sight 2, spinner) walk south-west - EVENT_BEAT_* untouched if avoided; the spinner is not reliably avoidable, budget for the battle
4 MAP_UNION_CAVE_1F south-west water press A facing water, or walk into it (TrySurfOW) Fog Badge + SURF wPlayerState = PLAYER_SURF
5 MAP_UNION_CAVE_1F warp 2 at 3, 33 step onto it - now in MAP_UNION_CAVE_B1F at 3, 33 (warp 4)
6 MAP_UNION_CAVE_B1F UNIONCAVEB1F_SUPER_NERD1 at 5, 32 (facing left, sight 3) walk into the sight line / talk - battle Pokemaniac Andrew (2x L24 Marowak); sets EVENT_BEAT_POKEMANIAC_ANDREW, +1440
7 MAP_UNION_CAVE_B1F surf east along the y~30 water walk east on water surfing reach the eastern shelf
8 MAP_UNION_CAVE_B1F UNIONCAVEB1F_SUPER_NERD2 at 17, 30 (facing left, sight 3) walk into the sight line - battle Pokemaniac Calvin (L26 Kangaskhan); sets EVENT_BEAT_POKEMANIAC_CALVIN, +1560
9 MAP_UNION_CAVE_B1F optional item ball 17, 23 press A EVENT_UNION_CAVE_B1F_X_DEFEND clear X_DEFEND in bag, flag set
10 MAP_UNION_CAVE_B1F warp 5 at 17, 31 step onto it - now in MAP_UNION_CAVE_B2F at 5, 3; MAPCALLBACK_OBJECTS runs UnionCaveB2FLaprasCallback
11 MAP_UNION_CAVE_B2F assert UNIONCAVEB2F_LAPRAS is present read the object list weekday == FRIDAY and ENGINE_UNION_CAVE_LAPRAS clear if absent, abort the run - nothing later will fix it
12 MAP_UNION_CAVE_B2F item ball 16, 2 press A EVENT_UNION_CAVE_B2F_ELIXER clear ELIXER in bag (walkthrough omits this one)
13 MAP_UNION_CAVE_B2F surf south from the entrance shelf walk south on water surfing approach the y~19-23 shelves
14 MAP_UNION_CAVE_B2F item ball 12, 19 (right-hand shore) land, press A EVENT_UNION_CAVE_B2F_HYPER_POTION clear HYPER_POTION in bag
15 MAP_UNION_CAVE_B2F UNIONCAVEB2F_ROCKER at 17, 23 (facing left, sight 5 - the longest on the floor) walk into the sight line - battle Cooltrainer Nick (L26 Charmander/Squirtle/Bulbasaur, custom moves); sets EVENT_BEAT_COOLTRAINERM_NICK, +1248
16 MAP_UNION_CAVE_B2F cross to the left shore, head north to UNIONCAVEB2F_COOLTRAINER_F1 at 5, 13 (spinner, sight 1) walk adjacent - battle Cooltrainer Gwen (L26 Eevee, L22 Flareon/Vaporeon/Jolteon); sets EVENT_BEAT_COOLTRAINERF_GWEN, +1056
17 MAP_UNION_CAVE_B2F walk the left path south to UNIONCAVEB2F_COOLTRAINER_F2 at 3, 28 (facing down, sight 2) walk into the sight line - battle Cooltrainer Emma (L28 Poliwhirl); sets EVENT_BEAT_COOLTRAINERF_EMMA, +1344
18 MAP_UNION_CAVE_B2F save open menu -> SAVE Lapras still present recoverable checkpoint
19 MAP_UNION_CAVE_B2F surf east/south-east from 3, 28 toward 11, 31 walk on water surfing the Lapras object drifts within +/-1 cell of 11, 31 (SWIM_WANDER, radius 1,1) - re-scan its live position each step
20 MAP_UNION_CAVE_B2F press A facing UNIONCAVEB2F_LAPRAS talk object present UnionCaveLapras runs: cry LAPRAS, loadwildmon LAPRAS, 20, startbattle
21 battle L20 wild Lapras inflict sleep/paralysis, chip to yellow/red, throw ULTRA_BALLs - on any exit: disappear + setflag ENGINE_UNION_CAVE_LAPRAS, then reloadmapafterbattle
22 - verify Lapras in party/box read party - if not caught, reload the step-18 save; do not walk out and back in, the callback will not re-place it
23 MAP_UNION_CAVE_B2F use ESCAPE_ROPE pack -> use in a cave tileset back at the last Pokemon Center door

Sight-line arithmetic for steps 6/8/15/16/17: Trainers.sees in the port (src/world/gen2/Trainers.lua:98) is the transcription of home/trainers.asm - the NPC must be facing the player's direction and the Chebyshev-free straight distance must be <= sight.

5. Port coverage

Beat Port file Status
Union Cave 1F/B1F/B2F map data (headers, blocks, warps, objects) src/import/RomExtractorGen2.lua (generic map/manifest extraction; per-map entries land in the private data/generated/ cache) implemented - data-driven, no per-map code
MAPCALLBACK_OBJECTS dispatch on map load src/world/gen2/World.lua:5700 (self:runMapCallback("MAPCALLBACK_OBJECTS"), deliberately before the sprite rebuild) implemented
readvar VAR_WEEKDAY src/world/gen2/World.lua:101 (VAR_WEEKDAY = 0x0b) and :1222 (if varId == VAR_WEEKDAY then return self:weekday() end) implemented
checkflag / setflag on ENGINE_* src/script/gen2/Vm.lua:195 / :208 implemented
ENGINE_UNION_CAVE_LAPRAS as a daily flag src/core/gen2/Apricorns.lua:84 ({ id = 88, name = "ENGINE_UNION_CAVE_LAPRAS" } in the wDailyFlags1/2 id list) and :344 (save.dailyFlags = {} on the daily wipe) implemented
appear / disappear object masking src/script/gen2/Opcodes.lua:115-116, consumed by the World object rebuild implemented
cry src/script/gen2/Vm.lua:621 implemented
loadwildmon + startbattle + reloadmapafterbattle (the whole static-encounter shape) src/script/gen2/Vm.lua:836, :817, :886 implemented
Wild catch rate / Ultra Ball / status bonus src/battle/gen2/Catching.lua (transcribes PokeBallEffect, including the two cart bugs) implemented
Water encounter tables while surfing src/world/gen2/FieldMoves.lua:175 ("standing in a cave rolls the grass list; surfing rolls the water" ...) + src/battle/gen2/Encounter.lua implemented
Surf gate on ENGINE_FOGBADGE src/world/gen2/FieldMoves.lua:106 (SURF = "FOG") and FieldMoves.surfFromMenu at :481 implemented
Trainer eyesight approach + battle src/world/gen2/Trainers.lua:98 (Trainers.sees), src/world/gen2/World.lua:5212 / :5227 / :5242 implemented
Trainer prize money (base x level x 4, Mom's split) src/battle/gen2/Prize.lua:82 (Prize.reward), :92 (Prize.rewardLevel) implemented - and it already documents the ld c, 4 that makes the walkthrough's figures right
Repel step counting (the Max Repel advice) src/world/gen2/World.lua:317 (REPEL_STEPS = { REPEL = 100, SUPER_REPEL = 200, MAX_REPEL = 250 }), :3366 implemented
Item ball pickup (OBJECTTYPE_ITEMBALL, i.e. the B2F Elixer / Hyper Potion and every ball on 1F/B1F) extractor stores the item at src/import/RomExtractorGen2.lua:2969 (obj.itemball = readItemBall(...)) and deliberately assigns no scriptKey; World:talk only dispatches on npc.def.scriptKey (src/world/gen2/World.lua:5276) missing - no consumer of obj.itemball exists anywhere under src/. Pressing A on a Poke Ball object currently does nothing
Escape Rope from a cave src/ui/PartyMenu.lua:634 is the Gen 1 DIG/ESCAPE_ROPE path; no Gen 2 equivalent found under src/core/gen2/ or src/world/gen2/ unverified / likely missing for Gen 2 - a driver should warp out manually
A driver exercising the weekday-gated MAPCALLBACK_OBJECTS shape tests/drivers/gold_map_callbacks.lua:117-156 covers the same pattern on Route 29 (Tuscany, Tuesday) implemented for the pattern, not for Union Cave B2F specifically - no tests/drivers/gold_* driver mentions Union Cave or Lapras

Honest summary: everything the Lapras encounter itself needs is in place (callback dispatch, weekday var, daily flags, loadwildmon/startbattle, catching). The two gaps that would bite a bot on this route are item balls are not pickable and no Gen 2 Escape Rope, neither of which blocks Lapras.

6. Unresolved / verify by hand

  • "Lapras only appears on Friday" vs. the 1F NPC hints. The code checks exactly FRIDAY (UnionCaveB2FLaprasCallback). But FirebreatherBillAfterBattleText on 1F says "On weekends, you can hear strange roars from deep in the cave" while PokemaniacLarryAfterBattleText says "Every Friday". Larry is right, Bill's line is flavour that contradicts the callback. The walkthrough follows Larry and is correct.
  • "once you've gotten HM Surf". The asm imposes no HM/badge check on the Lapras callback or on UnionCaveLapras; Surf is a reachability requirement (B2F's Lapras sits at 11, 31 surrounded by water and the approach legs on 1F/B1F cross water), enforced only by SurfFunction.TrySurf's ENGINE_FOGBADGE check. The walkthrough's phrasing implies a scripted gate that does not exist.
  • EXP figures. The walkthrough quotes per-mon EXP (637 for a L24 Marowak, 975 for a L26 Kangaskhan, 355/511/924/928/933/786, and two "?" entries). EXP is computed at runtime from base EXP x level, not stored per trainer, so none of these could be pinned to a table row. They are plausible but unverified here; data/pokemon/base_stats/*.asm would be the place to check.
  • Party ordering. The walkthrough lists Nick as Charmander / Bulbasaur / Squirtle and Gwen as Eevee / Vaporeon / Jolteon / Flareon. parties.asm has Nick as Charmander / Squirtle / Bulbasaur (:742) and Gwen as Eevee / Flareon / Vaporeon / Jolteon (:867). The sets match; the order does not. A bot planning lead matchups should use the asm order (it is also what decides the prize level: Gwen's last row is Jolteon L22).
  • "Head almost all the way through till you see the guy on the cliff still spinning around, which is the third trainer that you spot." Read against the 1F object table this is UNIONCAVE1F_FISHER2 (Firebreather Bill, 15, 15, SPRITEMOVEDATA_SPINRANDOM_FAST), with Hiker Daniel (4, 4) and Hiker Russell (15, 8) as the first two. That reading is an inference from coordinates and movement data, not something the asm states; the exact walking order depends on the .blk layout, which was not decoded here.
  • Which 1F ladder the walkthrough means. "Down and to the left is a lake. Surf past it and head down the ladder" is read as 1F warp 2 (3, 33 -> UNION_CAVE_B1F warp 4 at 3, 33), because Andrew stands at B1F 5, 32, immediately beside that arrival point. 1F warp 1 (5, 19 -> B1F warp 3 at 7, 19) would land nowhere near him. Verify against the block layout if a driver's pathing disagrees.
  • B2F "right side" / "left side" landings. The prose distinguishes a right and a left shore; the object coordinates (12, 19 Hyper Potion and 17, 23 Nick on the right, 5, 13 Gwen and 3, 28 Emma on the left) are consistent with that, but the actual water/land split comes from UnionCaveB2F.blk, which was not decoded.
  • Elixer at B2F 16, 2. Present in the asm, absent from the walkthrough. Not a contradiction, just an omission worth picking up.