# From Warp State to Loaded Map

Lecture ID: lecture_w04_d02
Week 4, Day 2

## Objectives
- Use Pokemon Showdown as a secondary comparison for battle data hooks, Dex/mods, simulator turns, and regression testing without replacing FireRed as the main architecture source.
- Connect week 4 material to the overall course plan.

## Transcript
All right, let’s pick up exactly where we left off last time. We are not starting the overworld from scratch today. You already have the startup shell in your head: AgbMain initializes the program, the main callback slots decide which runtime owns the frame, tasks provide per-frame dispatch when the active runtime wants many small behaviors, and SetMainCallback2 is the handoff point we used earlier in the course. We are also not going to re-teach map headers, layout references, or object events. Week 3 already made you responsible for recognizing those records. Today the question is narrower and more architectural: once the game has decided that the player is going somewhere, how does that destination state become a loaded, running map?

So the phrase for today is “from warp state to loaded map.” Not from tile data to map. Not from JSON to runtime. Not from startup to title screen. From a resolved destination, held in engine state, through the map loading path, into CB2_Overworld owning the frame again.

Let me put the first diagram on the board. Imagine three boxes from left to right. The first box says, “source map event.” The second says, “warp destination state.” The third says, “loaded map runtime.” Under the third box, I’m writing CB2_Overworld, because the loaded map is not just data sitting somewhere. It is data plus a frame callback that knows how to run the field system. The source focus for that callback in our packet is pokefirered/src/overworld.c, chunk_7ca71901368c46f7, and we are treating that as the runtime owner of normal map play.

Now, the reason I am drawing these as separate boxes is that the engine does not conceptually teleport straight from a tile under the player’s feet to a complete new world. It stages the transition. A warp event, or a script that causes a warp, or a door transition, or some other field action, eventually produces destination information. That information is not the final map. It is a compact promise about what should be loaded next. In FireRed terms, our source packet points us to pokefirered/src/overworld.c, chunk_9b2a7fa129935fc9, for warp destination state, and pokefirered/src/overworld.c, chunk_7ea6afd1253d6817, for LoadMapFromWarp. Those two chunks matter because they separate “where are we going?” from “perform all of the loading required to make that place playable.”

That distinction is one of the main architectural habits I want you to build. In a creature RPG, a map transition looks simple to the player. They step onto a door tile, the screen fades, and they appear somewhere else. But internally, this is a chain of contracts. The source map event contract says something like, “this coordinate has a warp behavior and names a destination.” The destination state contract says, “the next map load should use this map group, this map number, this warp identifier, or these coordinates depending on the case.” The loading contract says, “given that destination, install the proper map header, layout, events, scripts, tilesets, player position, and callbacks.” The runtime contract says, “now that the data is loaded, CB2_Overworld can advance the frame.”

Pause there. Check your understanding with a small question. If I tell you that the player just entered a house, is the house “loaded” at the instant the source warp event is read? No. The source event is evidence. It is not the loaded runtime. There is at least one intermediate representation of intention, and that is the warp destination state.

Now let’s make that concrete with Pallet Town, because Pallet Town is small enough to reason about without drowning in exceptions. The packet identifies pokefirered/data/maps/PalletTown/map.json, chunk_05643f6bfaa3b9bf, as our small reference case. Remember the warning from Week 2: “map.json” here is not runtime JSON being parsed by the Game Boy Advance while you play. We already covered generated data versus runtime data, so I’m only invoking that idea as a foundation. For us today, Pallet Town’s map JSON is a readable authoring-side reference for map data that becomes compiled data. Architecturally, it shows us the kind of map record whose identity can be targeted by a warp. Runtime loading is not opening that JSON file. Runtime loading is following compiled tables and global state that resulted from the build pipeline.

Suppose the player exits a building into Pallet Town. The exact source building is not important for the first pass. What matters is that the source map’s warp event does not need to carry the whole Pallet Town layout. It needs to carry a reference. That reference eventually becomes destination state. The destination might specify the map group and map number for Pallet Town and then a warp ID on the destination map. Why a destination warp ID? Because many maps have multiple entrances. If I say “load Pallet Town,” that is not always enough. Do I appear outside the player’s house, outside the rival’s house, near the lab, or at some script-controlled coordinate? The warp ID is a way to say, “load this map, then use this destination entrance record to determine placement.”

And this is where advanced learners sometimes make a subtle mistake. They treat coordinates as the primary identity of a transition. Coordinates are important, yes, but they are not always the first-class identity. In a data-driven map system, the stable link is often “destination map plus destination warp slot,” and the coordinates are looked up or adjusted during load. That gives designers and data authors some room to move entrance positions in the destination map without rewriting every source warp that targets that entrance. The architecture is saying, “link to an entrance concept,” not always “hard-code x equals 7 and y equals 12 everywhere.” FireRed has variations and special cases, of course, but the design pressure is clear.

Now I’m adding a second line to the board under the three boxes. It says: “resolve identity, load data, place avatar, hand off callback.” That is the spine of today’s lecture.

Let’s walk slowly through that spine.

First, resolve identity. A warp has to become a specific map identity. In FireRed’s organization, maps are not just filenames. They are indexed through map groups and map numbers, and those indices connect to the map header and associated data. We covered map group and map number in the map record lectures, so today I’m only using them. The important part is that the warp destination state is small. It does not carry the whole map. It carries enough identity to find the map.

Second, load data. This is where pokefirered/src/overworld.c, chunk_f00e575dedd069ed, comes in from the packet as the map data load sequence. A load sequence is not one assignment. A map header points to other structures. The layout has dimensions and block data. The map has events. The engine has to make the current map global state coherent before ordinary overworld logic runs again. If you are tracing this for the lab, do not write “load map” as one magic step and move on. Split it into the pieces that the rest of the engine expects to already be valid. The field system after the load should be able to ask, “what is the current map header?” “what events exist here?” “where is the player avatar?” “what callbacks or scripts should begin?” and receive answers that agree with each other.

Third, place the avatar. This is the part players notice immediately if it is wrong. If the destination map loads but the player appears inside a wall, you have a placement bug. If the player appears at the correct coordinate but faces the wrong way after a door transition, you have a presentation or state restoration bug. If the player arrives correctly but NPCs are missing, the map identity may be right while the event object loading is wrong. This is why today’s trace should not stop at the header. The loaded map is a coherent bundle.

Fourth, hand off to the callback. This is why CB2_Overworld matters. In previous weeks, we talked about SetMainCallback2 as a general runtime switch, and last lecture introduced CB2_Overworld as the map runtime callback. Today you should see it as the final owner after the warp load is complete. The map is not alive merely because its data structures exist. It is alive when the per-frame system is pointed at the overworld runtime and that runtime can update input, scripts, movement, tasks, and rendering effects in the expected order.

Now let’s compare this, carefully, to Pokemon Showdown, because the comparison is useful only if we keep FireRed as the main object. Showdown does not teach us how FireRed loads Pallet Town. It does not have GBA map groups, field callbacks, or tile-based warp events in the same sense. But Showdown is useful as a secondary architecture comparison because it makes battle data hooks and regression testing more explicit.

Look at the Showdown example from pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f. The excerpt in the packet shows damage logic checking things like immunity, one-hit KO behavior, damage callbacks, fixed damage, and level-based damage. That is not overworld loading. But it is an excellent comparison for data plus hooks. In Showdown, a move is not just a number in a table. A move can carry behavior that the simulator invokes at the correct point in the battle turn. So when we later discuss battle architecture, we will use Showdown’s battle hooks as a contrast with FireRed’s battle engine. Today, the analogy is limited: FireRed’s warp destination state is not a move callback, but both systems rely on small pieces of structured data being interpreted by a runtime that knows when to invoke them.

Let me say that more plainly. FireRed’s map warp says, “when this field transition occurs, resolve this destination through the overworld load path.” Showdown’s move data can say, “when damage is being calculated, run this hook or apply this special rule.” In both cases, the data alone is inert. The runtime gives it meaning. In FireRed, that runtime path runs through overworld loading and CB2_Overworld. In Showdown, that path runs through the simulator’s battle turn machinery and Dex or mod-specific data. The comparison is useful because it teaches the same architectural habit: ask not only “where is the data?” but “which runtime phase consumes this data, and what state must already be valid when it does?”

That phrase belongs in your notes: which runtime phase consumes this data?

If you are tracing a FireRed warp, the consuming phase is not battle damage calculation. It is the field transition and map loading phase. If you are tracing a Showdown move hook, the consuming phase is not overworld map load. It is a point inside a deterministic battle turn. Mixing those together produces vague architecture. Comparing them carefully produces skill.

Now, let’s return to the board and put names on the FireRed flow. I’m writing: source warp event, set or use warp destination, LoadMapFromWarp, map data load sequence, CB2_Overworld. The packet’s source anchors for these are pokefirered/src/overworld.c, chunk_9b2a7fa129935fc9 for destination state, chunk_7ea6afd1253d6817 for LoadMapFromWarp, chunk_f00e575dedd069ed for the map data load sequence, and chunk_7ca71901368c46f7 for CB2_Overworld. Those are the chunks you should cite in your week 4 homework if you are making claims about this path.

Now here is a concrete trace. The player stands on a warp tile in a source map. The field control code recognizes that the player’s movement has produced a map transition. The source event provides a destination reference. That reference updates the engine’s warp destination state. Then LoadMapFromWarp consumes that state. It identifies the destination map, loads the destination map data, resolves where the player should appear, and prepares the overworld runtime to continue. Once the transition finishes, CB2_Overworld is again the active map runtime callback, and normal per-frame overworld behavior resumes.

Notice the verb “consumes.” That is deliberate. A common bug in your mental model is to imagine the warp destination as permanent truth. It is more like a pending instruction. Once the load has used it, the current map state becomes the truth. The player is now in Pallet Town, or in the destination house, or wherever the transition resolved. If another system later asks where the player is, it should not need to replay the source warp. It should inspect the current map state.

Here is a check for understanding. Imagine I give you a crash report: after using a door, the screen fades correctly, the destination music starts, but the player cannot move and no NPCs appear. Which box on the board is most suspicious? Not necessarily the source warp event. The source event was probably good enough to trigger the transition. The problem sounds later: map data load sequence, event object initialization, callback handoff, or a state flag that prevents normal overworld control from resuming. If, instead, the player appears on the wrong destination map entirely, then the source event or warp destination identity is more suspicious. If the right map loads but the player appears at the wrong entrance, then the map identity may be correct but the destination warp ID or coordinate resolution is suspect.

That style of diagnosis is what I expect at this point in the course. Week 1 decomposition was broad. Week 2 made you trace data records through globals and callbacks. Week 3 made you identify files, structs, functions, constants, data tables, and state variables. Week 4 now asks you to reason through a state transition across multiple systems without flattening it into “the game loads a map.” That is the step from reading code to understanding architecture.

Let’s spend another minute on Pallet Town as a reference case. When we cite pokefirered/data/maps/PalletTown/map.json, chunk_05643f6bfaa3b9bf, we are not saying Pallet Town is special because it has every edge case. It is useful because it is small and recognizable. A small map lets you verify the relationship between a map identity and the data that identity selects. If your trace says the destination is Pallet Town, you should be able to connect that to the map data record. Then, from the loaded record, you should be able to explain why the engine can draw the correct layout and instantiate the correct field behavior. Pallet Town is our microscope slide. We use it not because the whole organism is that simple, but because a clean specimen lets us see the mechanism.

Now, one more comparison to Showdown before we move forward. Showdown’s Dex and mods architecture is attractive because it makes versioned battle data very visible. A file path like pokemon-showdown/data/mods/gen2stadium2/scripts.ts already tells you that some behavior is mod-specific. Again, chunk_3480e574205d5d0f shows damage logic in that mod context. FireRed’s map loading does not use that same mod system, but FireRed does have its own version of “runtime chooses data by structured identity.” The map group and map number select a map. A layout reference selects layout data. Warp IDs select entrance semantics. The similarity is not in implementation language or domain. The similarity is in architecture: structured identity lets the runtime choose the correct data without hard-coding every behavior in one giant branch.

For regression testing, Showdown will be our cleaner comparison later because deterministic simulator turns can be tested by constructing battle states and checking outcomes under known data. FireRed’s overworld path is harder to test in the same direct style because it is embedded in GBA runtime state, callbacks, graphics, input, and field effects. But the mental testing technique is similar. You define the precondition, execute the transition, and assert the postcondition. For today’s lab, the precondition is a source warp with a known destination. The transition is the warp resolution and LoadMapFromWarp path. The postcondition is current map identity, player coordinates, and callback handoff into the overworld runtime.

So if I were writing pseudocode on the board for the workshop, I would not write a full engine. I would write something like this in speech form: begin with a source warp event; read destination map group, destination map number, and destination warp ID; store or pass that into warp destination state; call the load-from-warp routine; inside that routine, resolve the map header from group and number; load the layout and associated map data; resolve player coordinates from the destination warp or explicit coordinates; initialize current map runtime state; then hand control to the overworld callback. That is the skeleton. Your job in lab is to annotate that skeleton with the actual FireRed source anchors and any state variables you can identify.

And now, before we move into the finer control flow, I want to make sure the key distinction is stable. A map record describes a place. A warp event describes a transition possibility. Warp destination state describes a pending target. LoadMapFromWarp performs the transition into loaded state. CB2_Overworld runs the loaded map frame after the transition. If those five things are blurred together, the code will feel like a pile of names. If you keep them separate, the code becomes a pipeline.

Good. In the next part, we are going to slow down inside that pipeline and ask what has to be true before and after LoadMapFromWarp runs. We will keep Pallet Town as the reference case, and we will use the Showdown comparison only when it sharpens our understanding of data being consumed by a runtime phase.

Good. So let us put that pipeline on the board as a set of obligations, not just a set of function names. I am writing five boxes. First box: source map event. Second box: warp destination state. Third box: LoadMapFromWarp. Fourth box: map data load. Fifth box: CB2_Overworld frame loop.

Now, listen carefully to the word “obligations.” In an architecture course, I do not want you to memorize that one function calls another function. That is useful, but it is fragile knowledge. I want you to ask, at each stage, what contract has to be satisfied before the next stage can run. That is the difference between reading code and understanding a runtime.

So, before LoadMapFromWarp runs, the game cannot merely have “a map somewhere.” It must have a resolved destination. That destination has to include enough information to answer: which map group, which map number, which warp entry if we are entering through a defined warp point, and what coordinate or fallback position the player should occupy. That is why the warp destination state matters as its own layer. In pokefirered/src/overworld.c, chunk_9b2a7fa129935fc9 is our anchor for that idea: the destination is represented as state before the new map is fully loaded. It is not just immediately drawn to the screen.

And that is the first subtle point for today. A warp is not a teleport command in the simple sense. It is a request to replace the active world context. That replacement has to be staged.

Now, if you look at Pallet Town as our small reference case, from pokefirered/data/maps/PalletTown/map.json, chunk_05643f6bfaa3b9bf, the map data gives us a manageable example because it is not an overwhelming dungeon or a multi-floor cave. It has the pieces we need: a map identity, layout association, events, and the ordinary static information that says, “this place exists in the world.” But Pallet Town’s map file does not, by itself, run the map. It is inert data until the overworld runtime consumes it. And this is where week 2 and week 3 are paying us back. We already established that generated or structured data is not the same thing as runtime behavior. Today we are watching the handoff.

Let me say that again in a slightly different way. The Pallet Town map record is a source of truth for what Pallet Town is. It is not the execution of arriving in Pallet Town. Arrival is a runtime process.

So, what does LoadMapFromWarp have to do? In pokefirered/src/overworld.c, chunk_7ea6afd1253d6817, the source focus is exactly that control flow. Architecturally, you should read it as the transition function between “we have a pending destination” and “the world state now points at the loaded destination.” It is the hinge. It resolves the pending warp into loaded map state, and then the later per-frame overworld callback can safely operate on that loaded state.

Pause here and check your own mental model. If I ask, “Where should collision come from after a warp?” the answer is not “from the warp.” The warp got us to the destination. Collision comes from the loaded destination map’s layout and metatile behavior data once that map is active. If I ask, “Where should NPC object events come from after a warp?” again, not from the warp itself. The warp may determine entry, but the destination map record determines the local event objects. That is the separation we need.

Now let’s trace a concrete example. Imagine the player leaves their house and enters Pallet Town. I am intentionally using a boring example because boring examples are where architecture becomes visible. There is no battle cutscene, no complicated script spectacle, no special effect that distracts us. The player steps on or activates a map transition from one map context to another. The source map event identifies a destination. The engine stores that target as warp destination state. Then LoadMapFromWarp consumes that state and begins the destination load. The map data load sequence, anchored for us in pokefirered/src/overworld.c, chunk_f00e575dedd069ed, brings the destination’s map header, layout reference, events, and related runtime structures into the active world context. After that, CB2_Overworld, from pokefirered/src/overworld.c, chunk_7ca71901368c46f7, can run the map as a per-frame runtime.

And notice the order. The callback loop is not the thing that decides what Pallet Town is from scratch every frame. That would be absurdly expensive and conceptually messy. The callback loop assumes the relevant world state has been established, and then it advances the frame: input, tasks, scripts, object movement, camera, field effects, and so on. We are not re-teaching callback slots today, but this is exactly why SetMainCallback2 mattered earlier. The main loop needs to know which runtime phase owns the frame.

Let me make a board note here: “Loaded map” does not mean “visible map only.” It means the game has enough active state for the overworld runtime to behave correctly. If your mental image is only background tiles appearing on the screen, you are missing half the architecture. Loaded map means data references, local events, player position, object state initialization, camera context, script context where relevant, and the callback handoff into ongoing overworld execution.

Now, I want to bring in Pokemon Showdown for a comparison, but carefully. FireRed is still our main architecture source. We are not replacing the Game Boy Advance runtime with a web simulator model. The comparison is useful because Showdown makes some runtime consumption patterns very explicit.

In Pokemon Showdown, battle data is organized so that the simulator can ask the Dex for structured information and then run turns over it. The packet points us to pokemon-showdown/data and pokemon-showdown/sim as recurring modules, especially around Dex, mods, moves, and battle simulation. A concrete example in the packet is pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, where damage calculation checks move properties and hooks such as damage callbacks. That is not an overworld warp, obviously. But architecturally it shows a similar pattern: data and hooks are made available to a runtime phase, and the runtime phase consumes them according to a staged control flow.

So, in FireRed today, our runtime phase is overworld map execution. In Showdown, when we later study battle, the runtime phase is simulator turn execution. FireRed’s LoadMapFromWarp is not analogous to Showdown’s damage formula line by line. Do not force a false equivalence. The analogy is at the architecture level. In both systems, the interesting question is: what data has been selected, what state has been staged, and which runtime loop is now allowed to consume it?

That is also why Showdown’s mods concept will be valuable later. A modded Dex lets the simulator load variant data for a particular generation or ruleset. FireRed’s map loading is not a modded Dex, but both systems teach us to separate data selection from runtime behavior. Data selection answers, “Which records are we using?” Runtime behavior answers, “What do we now do with those records?”

Check for understanding. Suppose I say, “The player appears in Pallet Town because Pallet Town’s JSON file contains Pallet Town.” Is that complete? No. It is not wrong as a data statement, but it is incomplete as a runtime statement. The complete answer needs the warp destination state, LoadMapFromWarp, the map data load sequence, and CB2_Overworld taking over after the load. The JSON tells us what can be loaded. The runtime path explains how it becomes the active world.

Now, let’s slow down inside the before-and-after boundary of LoadMapFromWarp. Before it runs, the system should be able to answer “where are we going?” but the destination should not yet be assumed to be fully active. After it runs, the system should be on the way to answering “what is the active map context?” That after-state is what allows the rest of the overworld machinery to proceed without every subsystem asking, “Are we still warping? Are we still resolving? Is the destination real yet?” Good architecture reduces how many parts of the program need to know about the transitional mess.

This is one of the reasons old handheld RPG code can still teach us modern lessons. The hardware is constrained, the language is C, and a lot of state is global or near-global. But the architecture still stages transitions. If you are designing a modern creature RPG, you might use ECS, scene graphs, asset bundles, async loading, or a scripting VM with coroutines. Fine. But the same conceptual problem remains. You still need a pending destination. You still need a load boundary. You still need a moment when the new world becomes authoritative. You still need an update loop that assumes the transition has completed enough to run.

Let me put a small pseudocode shape on the board, not as FireRed source, but as a mental model:

resolve source warp, store destination, load destination map, place player, initialize local runtime, hand off to overworld callback.

That is the lab shape for this week. In the workshop, when you trace a warp from source map event to destination map load, I want your pseudocode to preserve those stages. If your pseudocode collapses everything into “goToMap(destination),” it may be acceptable for a toy engine, but it will hide the exact architectural seams we are studying. For this course, the point is not to write the shortest pseudocode. The point is to write pseudocode that preserves the responsibilities.

Now, there is a tempting mistake here. Students often want to make the warp event responsible for too much. They imagine the warp event as if it personally loads the destination map, creates all NPCs, positions the camera, starts the music, and resumes gameplay. That is too much authority for a map event. A warp event should identify or parameterize a transition. The transition machinery should do the loading. The overworld runtime should run the result. This division keeps maps data-driven without making data pretend to be an engine.

And this gives us a useful connection to battle data hooks. In Showdown, if a move has a callback, that callback is not the entire simulator. The callback participates in a larger simulator turn framework. Again, pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, is useful because the excerpt shows the runtime checking immunities, OHKO behavior, fixed damage, callback damage, and then continuing through a calculation path. A hook is not the world. A hook is a controlled extension point inside a runtime.

Carry that sentence back to FireRed. A warp event is not the world. It is a controlled data point inside the overworld transition system.

Now let’s connect this to the broader course plan. Weeks 3 through 5 are our overworld map runtime block. Week 3 gave us map anatomy: map headers, layouts, object events, warp events, and the distinction between layout data and gameplay data. Week 4 is about the runtime flow that consumes those records. Week 5 will push further into event scripting and field behavior, where the loaded map is no longer just entered, but interacted with. So today is the bridge. We are moving from “what is in the map record?” to “how does the engine enter and run that map?”

That bridge matters for the whole course because every later system uses the same discipline. Creature data is not the party menu. Move data is not the battle engine. Item data is not the bag UI. Save data is not the save/load flow. The recurring question is always: which static records exist, which runtime state selects them, and which callback, task, script, or simulator loop consumes them?

Let me ask another check. If the player warps into Pallet Town and an NPC is visible, which of our five boxes owns the NPC’s existence? Think it through. The map record describes that local object event. The map data load sequence brings relevant local data into active state. CB2_Overworld and its associated systems update and draw the active overworld. LoadMapFromWarp is part of getting there, but it is not where we should conceptually store “Pallet Town has this NPC.” Good. That distinction will save you from writing tangled engines.

Now, we also have to be precise about “loaded.” In many engines, loading is not one action. It is a sequence of partial preparations. Some data can be selected immediately. Some visual data may need decompression or copying. Some runtime objects may need initialization. Some callbacks may be swapped before or after fades. FireRed’s source focus for map data load in pokefirered/src/overworld.c, chunk_f00e575dedd069ed, is important because it reminds us that loading a map is not merely assigning an integer map id. It is a coordinated preparation step.

So if you are taking notes, do not write, “LoadMapFromWarp equals set map.” Write, “LoadMapFromWarp consumes warp destination state and initiates or performs the transition into the destination map context.” That wording is less catchy, but it is much more accurate.

Let us return to Pallet Town. Why is it such a good reference case? Because it is semantically familiar and structurally small. The town has exits, houses, signs, NPCs, and normal map behavior, but it does not require us to understand an entire special-case system. When you inspect pokefirered/data/maps/PalletTown/map.json, chunk_05643f6bfaa3b9bf, you are looking at a record that participates in the same architecture as more complex maps. The smallness is pedagogical, not architectural. The same pipeline generalizes.

And that is a principle you should carry into your own engine work. Test your load pipeline with a small map first. Not because small maps are special, but because they let you see the pipeline without noise. Then, once the pipeline is stable, you can load complex maps, scripted maps, battle-triggering maps, and maps with unusual transitions.

This is also where regression testing enters the conversation. FireRed itself, as a decompilation target, gives us source structure to study, but Pokemon Showdown gives us a living example of how deterministic simulation can be tested aggressively. The research packet flags pokemon-showdown/test as a high-signal area for later course use, especially when we discuss simulator turns and regression testing. Today, I only want the concept: when a runtime consumes staged data, regressions often happen at the boundary. Wrong selected data. Wrong state carried forward. Wrong callback invoked. Wrong order of operations.

For a warp system, a regression test might ask: given this source warp, do we resolve the expected map group and map number? Given a destination warp id, do we place the player on the correct coordinates? Given entry into Pallet Town, does the active map context match the intended destination before the overworld frame loop resumes? Those are architecture tests. They are not visual polish tests. They protect the pipeline.

And later, when we study Showdown more directly, the same testing logic will apply to battle turns. Given this Dex data, this mod, this move, this ability, and this turn order, does the simulator produce the expected result? The details differ. The discipline is the same.

Now, I want to catch one more misconception before it hardens. Data-driven architecture does not mean data is allowed to be vague. A data-driven engine usually requires stricter contracts, not looser ones. A warp record must identify a destination in a form the engine can resolve. A map record must point to layout and event data in forms the loader understands. A battle move hook must match what the simulator expects. Data-driven systems are powerful because the runtime can consume many records through common machinery. But that only works if the records are shaped correctly.

So, in your lab pseudocode, I want you to include failure thinking. Not elaborate error handling, because FireRed’s original runtime context is not the same as a modern editor. But architecturally, ask: what would be invalid? A missing map group. A map number that does not correspond to a known header. A warp id that is out of range for the destination map’s warp events. Coordinates that place the player into an invalid metatile. A callback handoff before the map state is ready. Even if the original code assumes valid compiled data, your architectural explanation should know where the assumptions are.

That is an advanced learner move. Beginners say, “The data is valid because the game shipped.” Advanced engineers say, “The runtime is simpler because the toolchain and data conventions guarantee validity before execution.” That is the connection back to our earlier work on generated data and headers as contracts. We are not re-opening that whole topic, but we are using it. The map loader can be direct because the project’s data pipeline has already constrained what the data can look like.

All right, quick recap before we continue into the finer control flow. A map record describes the place. Pallet Town’s map data, in pokefirered/data/maps/PalletTown/map.json, chunk_05643f6bfaa3b9bf, is our small concrete place record. Warp destination state, in the overworld source focus around pokefirered/src/overworld.c, chunk_9b2a7fa129935fc9, represents the pending target. LoadMapFromWarp, pokefirered/src/overworld.c, chunk_7ea6afd1253d6817, is the transition hinge. The map data load sequence, pokefirered/src/overworld.c, chunk_f00e575dedd069ed, prepares the destination context. CB2_Overworld, pokefirered/src/overworld.c, chunk_7ca71901368c46f7, is the runtime callback that runs the loaded overworld frame after that handoff.

And the comparison to Showdown is this: in Showdown, battle data, Dex/mod selection, and move hooks are consumed by a simulator turn runtime; in FireRed today, map records and warp destination state are consumed by an overworld load runtime. The shared lesson is staged data consumption, not identical code structure.

Now, in the next stretch, we are going to zoom one level deeper into player placement. Because resolving the destination map is not enough. Once the destination exists, the player has to stand somewhere coherent, face some direction or preserve some entry behavior, and the camera has to make the loaded world feel continuous rather than arbitrary. That is where the warp id and coordinates stop being metadata and become embodied gameplay state.

So let’s put a little dot on the board. Imagine I draw a rectangle labeled destination map, and inside it I draw a single tile, maybe near the bottom edge of Pallet Town, maybe at the door to the player’s house. The beginner’s mistake is to think the rectangle is the hard part. “I loaded Pallet Town, therefore the warp is done.” But the game does not experience a map as a file. The game experiences it as a loaded coordinate space with a camera, avatar object, metatile behaviors, scripts that may run on entry, and enough visual state that the first frame after the fade does not look broken.

That is why the warp destination state matters as a little bundle, not as a single map name. In the FireRed source focus for today, the warp destination material in pokefirered/src/overworld.c, chunk_9b2a7fa129935fc9, is important because it holds the intermediate answer to the question, “Where are we going?” But that question has several sub-questions hiding inside it. Which map group? Which map number? Which warp event inside that destination map, if we are entering through a named warp? Or, if the engine has already resolved exact coordinates, which x and y should be used directly? And then, once that state has been interpreted, which callback or continuation should run next?

Now, pause there. Check your understanding. If I say “warp to Pallet Town,” is that a complete runtime instruction? No. It is a human-readable description. The runtime needs a map group and map number, and often a warp id or coordinate pair. It needs that because the map is not just scenery. It is an addressable structure. The player has to be placed at an address inside it.

Let’s use Pallet Town as our small reference case again, but carefully. I am not going to re-teach the map header fields from last week, because you already had that in the map record lectures and in the week 3 lab. What I want you to notice today is how a small map makes the load chain visible. In pokefirered/data/maps/PalletTown/map.json, chunk_05643f6bfaa3b9bf, Pallet Town is not magical because it is the starting town. Architecturally, it is useful because it is compact enough that you can reason about the relationship between map data, warp events, object events, and the player’s eventual visible position without drowning in edge cases. When the destination says, in effect, “load this map and enter through this warp,” the engine must connect that symbolic warp reference to physical coordinates in the destination map’s event data.

And this is one of those moments where you should hear the earlier weeks quietly in the background. Week 1 gave us the runtime shell: main callback slots, per-frame execution, task dispatch. Week 2 gave us the distinction between data that exists as authored source or generated C assets and data that is actually consumed at runtime. Week 3 gave us the map record as a structured contract: layout references, events, warps, scripts, and object definitions. Today, in week 4, all of that becomes motion. A player steps on a door tile, and the architecture turns authored map structure into a new live overworld.

I’ll write this as a chain on the board: source event, destination state, map load, player placement, callback handoff, first playable frame. That is the mental model I want you to keep.

Now, let’s talk about why the warp id is such a good example of deferred meaning. On the source map, a warp event can point at a destination map and a destination warp id. That id is not the same as coordinates yet. It is more like saying, “When you get to that map, use its entrance number two.” So, the meaning of the id depends on the destination map’s own event table. If the destination map changes its warp ordering, or if a modder adds a new warp and shifts identifiers carelessly, the same source map may now place the player incorrectly. That is not a rendering bug. That is a data contract bug.

Advanced systems work often looks like this. The bug presents visually, but the cause is a broken contract between two pieces of data and the runtime that interprets them.

So if you were debugging a bad door in FireRed, I would not start by staring at sprites. I would trace the address chain. What source warp fired? What destination group and number were written? What destination warp id was stored? Did LoadMapFromWarp interpret that state as expected? Did the map data load sequence populate the destination event data before the player placement logic tried to use it? Those questions are directly aligned with pokefirered/src/overworld.c, chunk_7ea6afd1253d6817, for LoadMapFromWarp, and chunk_f00e575dedd069ed, for the map data load sequence. The order is the architecture.

Let me slow that down with a concrete example. Suppose we have a house interior, and its exit warp points to Pallet Town, warp id zero. When the player steps on the exit tile, the game does not need to keep the whole source interior in its head forever. It needs to preserve the destination facts. “Pallet Town, warp zero.” During the load, Pallet Town’s map data becomes current. Then warp zero in Pallet Town is resolved to a coordinate, usually adjacent to the building’s doorway, with the player placed so that exiting the house feels physically continuous. If that warp id accidentally points to the wrong entrance, you might exit your house and appear near the lab, or on a different edge of town, or on a tile that looks legal but feels nonsensical. The engine did what it was told. The data contract lied.

Now, this is where Pokemon Showdown gives us a useful comparison, but not a replacement model. In Showdown, the central runtime is not an overworld loader. It is a simulator consuming battle data. Its Dex and mod system decide which version of species, moves, abilities, and scripts are active, and then the simulator turn engine invokes hooks at specific points in battle resolution. The research packet points us toward pokemon-showdown/data and pokemon-showdown/sim as recurring modules for that reason. A concrete hook example appears in pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, where damage calculation can flow through move properties such as damage callbacks, fixed damage, level-based damage, immunity checks, and base power handling. That is battle logic, not map loading. But architecturally, notice the similarity: authored or versioned data is not enough by itself. It becomes gameplay only when a runtime consumes it at the correct stage.

In FireRed today, the stage is map transition. In Showdown, the stage might be damage calculation during a turn. FireRed says, “I have destination warp state; now resolve it into loaded map and player position.” Showdown says, “I have a move object under a particular Dex mod; now resolve its hooks and properties into battle effects.” Same broad architectural lesson: data has meaning only inside a runtime phase that knows how to interpret it.

Do not flatten those systems into each other, though. FireRed is our primary architecture source. The Game Boy Advance constraints matter. The callback shell matters. The overworld runtime callback, CB2_Overworld in pokefirered/src/overworld.c, chunk_7ca71901368c46f7, matters because it tells us what the loaded map becomes after the transition. Showdown’s simulator can help you name patterns like hooks, mods, and deterministic regression tests, but it should not seduce you into thinking FireRed is a TypeScript simulator with an overworld skin. It is not. It is a C game organized around callbacks, globals, tasks, scripts, and data tables.

Let’s do another check for understanding. If I ask, “Where is the equivalent of Showdown’s move damage callback in today’s FireRed warp lecture?” the answer is not “there is an identical callback.” The answer is, “The comparable idea is staged interpretation.” In Showdown, a move’s callback is interpreted during a simulator damage phase. In FireRed, a warp destination record is interpreted during map load and player placement. We compare them to sharpen our architecture vocabulary, not to claim the systems are isomorphic.

Now, look again at the board chain. Source event, destination state, map load, player placement, callback handoff, first playable frame. The most subtle step for many students is the handoff. You can write code that loads map data and places the player, and still not have returned to the normal overworld runtime. The engine must end in the right callback state. Otherwise, you have a loaded map that is not being advanced by the usual per-frame overworld machinery.

That is why CB2_Overworld is not just trivia from Monday’s lecture. It is the runtime home of the loaded overworld. Once LoadMapFromWarp and the surrounding load sequence finish their transition work, the game must arrive at a state where the map can animate, scripts can progress, input can be interpreted, tasks can run, and the player can regain control when appropriate. In other words, loading the map is a preparation phase. CB2_Overworld is part of the continuing life of that map.

Let me say that in a form you can use in the lab: LoadMapFromWarp is not the destination; it is a bridge. The destination is a coherent overworld runtime.

In your pseudocode this week, I want you to represent that distinction. Do not write a function called warpPlayer that simply assigns map equals Pallet Town and x equals ten and y equals six. That will be too flat. Write it in phases. First, capture or receive the destination. Second, resolve the destination map identity. Third, load the map data. Fourth, resolve the warp id into coordinates if needed. Fifth, place the player and camera. Sixth, choose the continuation callback or field transition behavior. The exact names may vary, but the phases should be visible.

And yes, I can hear the question forming: “Professor, if the engine eventually resolves everything to coordinates, why not store only coordinates in every source warp?” Good. That is exactly the kind of systems question I want at this point in the course.

There are tradeoffs. Direct coordinates are simple when you have a special scripted movement or a one-off placement. But warp ids create a level of indirection that can make map-to-map entrances easier to author and maintain. If the destination doorway shifts because the map layout changes, the destination map can adjust its own warp coordinate, and source maps that point to that entrance conceptually still point to the entrance. The indirection says, “I am entering through that map’s warp slot,” not “I know the exact tile in that other map forever.” But the cost is that the warp id contract must remain consistent. If ids are reordered without care, the indirection betrays you.

This is very similar to the reason stable identifiers matter in other parts of the course. We saw constants and generated tables earlier. We saw headers as contracts. We saw layout registry references. I am not going to re-open all of that, but the same theme is back: data-driven architecture does not free you from contracts. It multiplies them. The runtime can only be flexible if the identifiers are stable enough to mean something.

Now let’s place regression testing into the conversation, using Showdown carefully. Pokemon Showdown is unusually valuable as a comparison point because simulator behavior can be tested turn by turn. Its architecture makes it natural to say, “Given this team, this mod, this move, this battle state, the simulator should produce this result.” That is one reason the research packet calls out pokemon-showdown/test as a high-signal area. In FireRed overworld work, you do not usually have the same simple pure turn transcript, but you can still borrow the testing instinct. You can create a trace: given source map A, warp event B, destination map C, destination warp id D, after LoadMapFromWarp and map data load, the current map should be C and the player should be at the coordinates represented by D.

That is a regression test idea, even if the harness is different. You are asserting that a data contract still resolves to the same embodied state.

Let’s imagine you are building your own creature RPG engine after this course. A FireRed-inspired architecture might have a map loader, a player spawn resolver, an event script VM, and a battle engine. A Showdown-inspired testing discipline might say, “Every time I alter data formats or map records, I should have fixtures that prove important transitions still resolve.” You might have a test named something plain, like enter_player_house_exit_to_town. It would load the source map, trigger the exit warp, and assert the destination map id, spawn x and y, facing direction, and post-load callback state. That is not FireRed’s exact tooling, but it is the professional habit you should extract from comparing these systems.

Now, I want to return to camera placement, because if you only think about the avatar, you will miss part of the player experience. On a tile-based map, the avatar coordinate and the camera coordinate are related but not identical. The player might stand at a specific tile, while the camera must decide which portion of the map is visible. On small maps like Pallet Town, the camera may be constrained by map edges. On larger maps, it can center or nearly center around the player. The warp load must make the first visible frame plausible. If the camera is one frame late, you may see a snap. If the map data is not ready, you may see garbage or an incorrect tile region. If scripts run before placement is coherent, entry events can misfire.

So, when we say “from warp state to loaded map,” we do not merely mean memory contains a new map header. We mean the player’s embodied context is rebuilt: map, coordinates, camera, objects, scripts, visual transition, and runtime callback.

Let’s do a small board-work cue. I’ll draw two columns. Left column: FireRed. Right column: Showdown. Under FireRed, I write warp destination state, map data load, player spawn, CB2_Overworld. Under Showdown, I write Dex mod, battle data, move hook, simulator turn. Now, across the middle I write staged interpretation. That is the comparison. Then I cross out identical implementation, because that is not the comparison.

This matters for the overall course plan. Weeks 3 through 5 are the overworld map runtime block. Week 3 asked, “What is a map record?” Week 4 asks, “How does map data become a running map?” Week 5 will push us farther into scripts, events, and the way local map behavior becomes progression. After that, when we move into creature data and battle, the same mental habit will pay off. You will not ask only, “Where is the move table?” You will ask, “At what runtime phase is this move table consumed, through which globals or structs, under which callback or state machine, and how does the player see the result?” That is the course through-line.

You can already see the bridge into battle in FireRed itself. The battle setup source, pokefirered/src/battle_setup.c, chunk_494d132a59422c85, pulls together overworld, task, script, field, transition, and battle-related includes. I am not using that today to teach battle setup in detail. That lecture comes later. I am pointing out that FireRed does not have a clean wall where overworld ends and battle begins. It has controlled transitions. The same engineering discipline you apply to warp loading will apply to wild encounters, trainer battles, and special scripted battles: preserve the right state, enter the right runtime, and hand control to the right callback or task sequence.

That should also sharpen how you approach the week 4 homework and quiz. At this point in the course, I expect you to be more precise than “the game loads the next map.” That sentence would have been acceptable as a casual answer in week 1. It is not enough now. A strong answer names destination state, map group and map number, destination warp id or coordinates, map data load order, player placement, and the eventual overworld callback. A very strong answer also says what can go wrong if any one of those contracts is broken.

Let me give you a failure catalog, because engineers remember systems better when they can name failure modes. If the destination map group or number is wrong, you load the wrong map. If the warp id is wrong, you load the right map but stand in the wrong place. If the map data load sequence is incomplete, you may resolve placement against stale or unavailable event data. If player placement occurs before the destination warp events are available, you have an ordering bug. If the callback handoff is wrong, you may see a loaded map that does not behave like a normal overworld. If camera initialization is wrong, the player may be technically placed correctly but visually presented incorrectly. Each of these bugs belongs to a different link in the chain.

Notice how much more diagnostic that is than “warp broken.” “Warp broken” is not an engineering diagnosis. It is a symptom report.

Now, one more check for understanding before we move forward. Suppose a student says, “I changed Pallet Town’s map data, and now exiting a house puts me two tiles to the left of the door. I should fix LoadMapFromWarp.” What is the first question you should ask? You should ask whether the destination warp coordinates or warp id mapping in the Pallet Town data changed. The runtime may be fine. The authored map data may now be inconsistent with the source warp’s expectation. Only after you inspect that contract should you accuse the loader.

That is also why the lab asks you to trace from source map event to destination map load. The trace protects you from guessing. You start with the event that fired. You record the destination state. You follow LoadMapFromWarp in pokefirered/src/overworld.c, chunk_7ea6afd1253d6817. You connect it to the map data load sequence in chunk_f00e575dedd069ed. You then identify where the normal overworld runtime resumes through CB2_Overworld in chunk_7ca71901368c46f7. The result is not just a diagram. It is a debugging instrument.

Let’s close this segment by synthesizing where we are, but not the whole lecture yet, because we still have more machinery to examine after the break. A warp is not a teleport spell implemented as one assignment. It is a staged transition from one coherent runtime context to another. FireRed expresses that through destination state, map loading, coordinate resolution, player and camera placement, and callback handoff. Pokemon Showdown, used only as a secondary comparison, helps us name the broader pattern: data and hooks become behavior when a runtime phase consumes them under a selected ruleset or context. In Showdown that might be a Dex mod and a simulator turn. In FireRed today, it is a destination map and the overworld loader. Keep those systems distinct, but keep the shared discipline: trace the data to the runtime phase, then trace the runtime phase to visible behavior.

Now, let us use that comparison carefully, because this is where students often overgeneralize. When I say Showdown has a Dex mod and FireRed has a destination map, I am not saying those are the same feature. They are not. One is a battle rules and data context in a server-side simulator. The other is a world-space context in a Game Boy Advance RPG. What I am saying is that both systems avoid scattering “what should happen now” across random code. They collect state, choose a context, and then allow the active runtime phase to interpret that context.

So, on the board, I am going to draw two columns. Left column: FireRed overworld transition. Right column: Showdown battle execution. In the FireRed column, I am writing “warp destination state,” then “LoadMapFromWarp,” then “map data load,” then “CB2_Overworld.” Those names are grounded in our source focus for today: pokefirered/src/overworld.c chunk_9b2a7fa129935fc9 for warp destination state, pokefirered/src/overworld.c chunk_7ea6afd1253d6817 for LoadMapFromWarp, pokefirered/src/overworld.c chunk_f00e575dedd069ed for the map data load sequence, and pokefirered/src/overworld.c chunk_7ca71901368c46f7 for CB2_Overworld. In the Showdown column, I am writing “selected Dex or mod,” then “battle data hooks,” then “simulator turn,” then “testable battle log or result.” That second column is not our primary architecture source today, but it helps us name a habit of thought.

The habit is this: do not ask only, “Where is the data?” Ask, “Which runtime phase is allowed to consume this data, and under what assumptions?” In FireRed, a warp event record by itself does not move the player. It becomes motion only when the current map runtime detects the interaction, records or resolves the destination, and eventually lets the loading flow consume that destination. In Showdown, a move record by itself does not cause damage. It becomes damage only inside the simulator’s battle phase, where move hooks, immunity checks, base power logic, and damage callbacks are interpreted under a chosen ruleset. You can see that hook-heavy style in pokemon-showdown/data/mods/gen2stadium2/scripts.ts chunk_3480e574205d5d0f, where the damage calculation path checks immunities, OHKO behavior, damage callbacks, level-based damage, and fixed damage. The point is not that FireRed should have been written like Showdown. The point is that mature game systems separate authored facts from the phase that gives those facts operational meaning.

Now, back to FireRed. Suppose the player walks into Pallet Town from Route 1. I am choosing Pallet Town because it is small enough to fit in our heads, and because our source packet gives us pokefirered/data/maps/PalletTown/map.json chunk_05643f6bfaa3b9bf as the small reference case. At the data level, Pallet Town has map identity, layout reference, events, warps, connections, and the rest of the authored map record we have already studied. I am not going to re-teach map headers or layout registries here; that was week 3. But I do want you to notice how differently those same facts behave today. In week 3, we treated the map record as a static architectural object. Today, we treat it as a load target. Same data, different question.

Let me say that again, because it is a useful professional distinction. Static analysis asks, “What fields exist, and what do they refer to?” Runtime tracing asks, “At what moment are those fields read, copied, expanded, or used to create behavior?” A common advanced mistake is to be very good at static analysis and still lose the runtime. You can recite every field in a map JSON record and still be unable to explain why the player appears at a particular tile after entering a door. For this course, that is not enough. We are pushing past “I know the data shape” into “I can trace the loaded behavior.”

So, imagine we are stopped at the moment before the destination map appears. The screen is in transition. The source map is no longer the stable context the player is meant to inhabit. But the destination is not simply visible yet either. This middle region is where architecture matters. FireRed needs to know which map group and map number to load. It needs to know whether the target came from a warp ID, explicit coordinates, a special respawn point, or a scripted override. It needs to know where to place the avatar, where to place the camera, which map scripts may run, which objects should exist, which connection data applies, which tileset and layout must be available, and what callback should continue execution once loading is complete.

That is why the warp destination state in pokefirered/src/overworld.c chunk_9b2a7fa129935fc9 matters. It is not just a convenience variable. It is the bridge between one coherent runtime context and the next. If you only think of it as “the target map,” you understate it. It is closer to a small contract saying, “The next map load should resolve around these destination facts.” Then LoadMapFromWarp, in pokefirered/src/overworld.c chunk_7ea6afd1253d6817, is one of the places where that contract is consumed. It takes the stored destination information and turns it into a loaded map context.

Now let us do a check for understanding. If I ask, “Why not have every door script directly call a complete map-loading routine with all coordinates and all graphics setup?” what is the architectural objection? Take a few seconds.

Good. There are several answers, but the one I want to foreground is that it would mix authoring-level event intent with low-level runtime transition mechanics. A door event should identify or imply a destination. It should not have to know every detail of decompression, object-event instantiation, callback handoff, script state reset, camera placement, and map lifecycle. By routing through destination state and a common loader, FireRed gets consistency. Doors, caves, houses, stairs, and other transitions can share the same machinery even when the source of the destination differs.

This is also why bugs in this area are so revealing. If a warp sends you to the correct map but the wrong tile, your map identity path may be correct while your coordinate resolution path is wrong. If the player appears at the correct tile but objects are missing, the destination was resolved but the map data or object-event loading path may be suspect. If the map appears but controls do not resume, you have to inspect callback or task handoff. If music, scripts, or visual effects are wrong, you widen the trace to the associated subsystems. The architecture gives you a debugging map.

Let us connect that to the lab. In the workshop, you are asked to trace a warp from a source map event to destination map load, then implement pseudocode for resolving map group, map number, warp ID, player coordinates, and callback handoff. Notice the order of those nouns. Map group and map number establish destination identity. Warp ID or explicit position refines where in that destination the avatar should land. Player coordinates and camera placement turn identity into a visible starting position. Callback handoff returns the machine to a per-frame runtime that can accept input and animate the world. If your pseudocode jumps from “door touched” to “new map shown,” it will look clean, but it will be architecturally empty.

Now, let me bring in Showdown again for a narrow reason: testing. Showdown is valuable as a comparison because its simulator structure naturally supports deterministic regression tests. The research packet explicitly points us toward pokemon-showdown/test as a recurring module for regression testing, and toward pokemon-showdown/data and pokemon-showdown/data/mods as high-signal areas for ruleset data. In a battle simulator, you can often construct a battle, choose a format or mod, perform a sequence of turns, and assert on resulting state or logs. That is a beautiful testing surface. It does not mean every game system is equally easy to test, but it gives us vocabulary.

What would the FireRed equivalent be for today’s topic? It would not be “run a full game and eyeball whether the player arrives.” That is manual playtesting, and it has value, but it is not the whole testing strategy. A more surgical test, if we were building this architecture today in a modern harness, would set up source warp data, set destination state, invoke the warp resolution routine or a wrapper around it, and assert the resolved map group, map number, destination warp ID, x and y coordinates, facing direction if relevant, and the next callback. In other words, we would test the transition contract. We would not need a screen to prove every part of the logic.

FireRed itself, as a decompiled GBA project, is not organized around modern unit tests in the same way Showdown is. That is fine. We are not judging it by the wrong toolchain. But as architects, we can still ask, “Where are the boundaries that would be testable?” The boundary around destination state is testable. The boundary around map data loading is testable with fixtures. The boundary around callback handoff is testable if callbacks are represented as inspectable function pointers or state identifiers. The boundary around the entire rendered transition is harder and more integration-like. That is the same reasoning you will use later in the course for battles, inventory, save blocks, and link behavior.

Let me pause and ask another question. In Showdown, if a move’s damage behavior differs between two generations, where would you expect that difference to live: in the main simulator loop only, in the Dex or mod data and scripts, or scattered through individual tests?

Right, the best answer is that the ruleset-specific behavior should be represented in the selected data and hook layer, and the simulator loop should consume it consistently. Tests should verify it, not define it. That is exactly why pokemon-showdown/data/mods/gen2stadium2/scripts.ts chunk_3480e574205d5d0f is such a useful comparison point. It shows ruleset-sensitive battle behavior as executable hook logic. But now translate the principle back to FireRed. If a particular warp in Pallet Town leads somewhere, that destination should be authored in map/event data or derived through the established warp mechanisms, not hardcoded as a special case in the main overworld callback. CB2_Overworld should run the map runtime. It should not become a giant list of one-off doors.

This is the larger course plan showing through. Weeks 1 and 2 gave us the runtime shell: startup, callback slots, task dispatch, generated data, and the idea that tables become behavior only through runtime consumers. Week 3 gave us map anatomy: map headers, layouts, object events, warps, and the separation between layout data and gameplay data. Week 4 is where those two strands meet. We are watching a data-authored map transition pass through runtime state and arrive back at a stable callback-driven overworld. That is why this week sits in the middle of the overworld module rather than at the beginning. You needed the earlier machinery first.

And this will matter again in week 5. When we move further into field scripts, object interactions, and progression state, you will see a similar pattern. A script command is not a gameplay feature by itself. It becomes one when the script VM, event flags, variables, object state, and callback/task system cooperate. Later, when we reach battle setup, the same tracing discipline will keep us from treating “battle” as a monolith. The source packet already gives us a useful forward pointer in pokefirered/src/battle_setup.c chunk_494d132a59422c85, which includes links across overworld, scripts, field effects, battle transition, and battle headers. That file’s include surface is a clue: entering battle is also a transition between runtime contexts. We are not covering that system today, but you should already feel the rhyme. Overworld to map, overworld to battle, menu to summary screen, field to naming screen; these are all structured handoffs, not magic jumps.

A concrete example may help. Let us say a student implements a custom house in Pallet Town. They add a warp tile outside and a corresponding destination inside. The first build compiles. The door animates. The fade happens. But the player appears inside the house one tile too far left, partially on a wall. At the beginner level, the student says, “The warp is broken.” At the advanced level, you should ask, “Which layer is wrong?” Is the source warp pointing to the wrong destination warp ID? Is the destination warp placed at the wrong coordinate in the destination map data? Is the landing offset applied differently for door-style transitions? Is the player avatar being adjusted after load? Is the camera centered on the wrong coordinate, making a correct avatar position look suspicious? Is the wrong map bank or map number being loaded, perhaps to a similar-looking interior? Those are different hypotheses, and each maps to a different part of the transition chain.

Now I am going to write a small pseudocode shape on the board, verbally, not as final lab code. “Read current interaction. Identify warp event. Store destination map group, map number, warp ID or coordinates. Begin transition. Load destination map header and layout. Resolve destination coordinates. Place player. Initialize objects and scripts. Set overworld callback.” That is the spine. In real FireRed, the code has more branches and more subsystem calls, but this spine keeps you oriented. If you cannot place a function somewhere on that spine, you probably have not understood why it exists yet.

Notice also the direction of dependency. Map data does not call the runtime. The runtime reads map data. Destination state does not render the map. The loader consumes destination state and prepares the map. CB2_Overworld does not author Pallet Town. It runs the active overworld context after loading has made Pallet Town current. That direction matters. In architecture reviews, I often ask, “Who owns the next step?” In FireRed’s warp flow, ownership passes from event detection, to destination state, to loader, to map runtime. Each step has a narrower responsibility than “make the game continue.”

This is a good place to revisit the phrase “data-driven,” because by now it should sound less glamorous and more precise. Data-driven does not mean data does everything. It means authored data is positioned so that general runtime machinery can produce specific behavior from it. The door to a house is specific. The warp loader is general. Pallet Town is specific. The map-loading sequence is general. A Showdown generation mod is specific. The simulator’s turn machinery is general. When a system gets this balance right, adding content does not require rewriting the runtime every time. When it gets the balance wrong, every new feature feels like surgery.

Let us do one more check for understanding before we move to the next segment. Suppose I give you a bug report: “After taking a warp, the correct destination map appears, but the game immediately runs the wrong map script.” Which part of today’s chain would you inspect first?

I would not start with the tileset. I would not start with sprite graphics. The map identity is probably correct if the visible destination is correct, though I would still verify group and number. I would inspect the loaded map header and script pointer or script table association, then the state that determines which map script phase runs on entry. I would also check whether the transition path marked this as a normal warp, a special warp, a connection movement, or a returning state, because those can influence entry behavior. That is architectural debugging: the symptom tells you which layers are less likely and which layers deserve attention.

Now, a warning about the Showdown comparison. Do not import its abstractions back into FireRed carelessly. Showdown’s Dex and mod structure is a natural fit for a simulator that supports many generations and formats under one codebase. FireRed is a fixed commercial game targeting one hardware platform and one ruleset, with data compiled into the ROM. So if you find yourself saying, “FireRed should have a Dex mod layer for maps,” stop. That is not the lesson. The lesson is that both systems have selected contexts that govern interpretation. Showdown selects battle rules and data; FireRed selects a loaded world context. Similar architectural pattern, different implementation pressure.

That distinction will be important when you write your homework. I am not looking for a generic essay that says, “Everything is data-driven.” I am looking for a trace. Start from a concrete source record. Name the runtime state that carries it forward. Name the function or phase that consumes it. Name the visible behavior. For today’s lecture, a strong answer might begin with a warp event in a map record such as the Pallet Town map data in pokefirered/data/maps/PalletTown/map.json chunk_05643f6bfaa3b9bf, pass through warp destination state in pokefirered/src/overworld.c chunk_9b2a7fa129935fc9, identify LoadMapFromWarp in pokefirered/src/overworld.c chunk_7ea6afd1253d6817, and end with the overworld callback in pokefirered/src/overworld.c chunk_7ca71901368c46f7. That is a real chain. It is much stronger than saying, “The map loads from JSON,” because as we already established in week 2, the JSON is part of the build-time source pipeline, not a runtime file the GBA is parsing.

Let me tie this back to callbacks one more time. CB2_Overworld is not merely “the overworld function.” It is the callback that says the main loop is now spending its frame budget on overworld behavior. That includes tasks, input handling, object updates, scripts, field effects, camera behavior, and rendering coordination. The map loader’s job is to prepare a coherent context so that when CB2_Overworld is active, it is not operating over half-valid state. This is a subtle but critical point. Runtime callbacks assume invariants. Loading code establishes invariants. If you break that contract, you get unstable behavior: missing objects, stale coordinates, incorrect scripts, wrong visual state, or control lockups.

Professional game code often has this split. There is code that mutates the world while it is live, and code that constructs or swaps the world while it is not yet live. Mixing those two modes creates pain. If a function is meant to run every frame, it should not unexpectedly perform a full context rebuild. If a function is meant to rebuild context, it should leave the system in a shape the frame callback understands. FireRed’s callback architecture makes that visible because SetMainCallback2 and the callback slots from earlier weeks are explicit. We do not need to re-teach them now; we just need to remember that this is where they pay off.

So, as we go into the next section, keep three questions active. First, what destination facts are being carried? Second, which loading phase consumes them? Third, what invariants must hold before CB2_Overworld resumes control? Those three questions will let us read the rest of the map-loading flow without drowning in local details. And when we compare briefly to Showdown, we will keep the comparison disciplined: selected context, hook consumption, deterministic behavior, and regression testing. The architecture lesson is shared, but FireRed remains our main source of truth for today.

Now, let’s take those three questions and make them do real work. I am going to put them on the board, not because they are complicated, but because they are easy to forget when you are staring at a long C function. Destination facts. Loading phase. Overworld invariants. That is the small mental debugger I want you to carry into the lab.

When we say destination facts, we are not talking about a vague idea like, “the player is going to Pallet Town.” The engine needs exact, compact state. It needs a map group, a map number, a warp identifier, and eventually concrete player coordinates and facing behavior. That is why the warp destination state in pokefirered/src/overworld.c, chunk_9b2a7fa129935fc9, matters architecturally. It gives us a place to store the answer to the question, “Where should the next overworld instance be constructed?” Notice that I said constructed, not merely displayed. A loaded map is not a background image. It is a runtime environment with layouts, metatiles, collision, scripts, objects, music decisions, callbacks, and player avatar state.

So if I walk into the door of the player’s house, what is the important event? The visible event is that the screen fades and I appear inside another map. But the architectural event is that the current map runtime produces a destination record, hands control to the load path, and then the load path rebuilds enough global overworld state that CB2_Overworld can safely resume. That last phrase is doing a lot of work. CB2_Overworld, in pokefirered/src/overworld.c, chunk_7ca71901368c46f7, is not a magical “game runs now” button. It is a frame callback with expectations. It expects that the map header, layout references, object events, player position, and tasks or scripts that must run on entry are all coherent enough that one frame of simulation will not contradict the map we just loaded.

Here is the concrete example I want you to hold. Imagine a warp from a house doorway into Pallet Town. We are using Pallet Town as a small reference case because its map record is not trying to overwhelm us. The Pallet Town map data in pokefirered/data/maps/PalletTown/map.json, chunk_05643f6bfaa3b9bf, gives us a map-level data record that can be inspected without getting distracted by a huge dungeon or route. Earlier in the week we talked about map header fields, layouts, object events, and warps, and I am not going to reteach those. Today the question is narrower: once a warp says, “load this destination,” how does that destination become the live map the player can walk around in?

Let me draw this as a timeline. On the left side of the board, we have source map runtime. In the middle, we have warp destination state. On the right side, we have destination map runtime. Now, under the middle, write “not yet visible.” That is important. The warp destination is not the map itself. It is a promise that the loader will later redeem. Students often collapse these two ideas and say, “the warp loads the map.” That is acceptable casual language, but it hides the control-flow shape. More precisely, the warp records or resolves destination facts, and then LoadMapFromWarp consumes those facts as part of a staged transition.

LoadMapFromWarp in pokefirered/src/overworld.c, chunk_7ea6afd1253d6817, is where that promise becomes operational. It belongs to the control-flow side of the architecture. It is not merely a data lookup. It is the bridge between a selected destination and the map data load sequence. Then the map data load sequence, from pokefirered/src/overworld.c, chunk_f00e575dedd069ed, performs the job we care about today: use the destination to populate the runtime representation that the overworld callback will run.

Now pause there. Check for understanding. If I ask, “What is the consumer of the warp destination state?” the answer is not “the screen fade,” and it is not “the player.” The consumer is the load path, especially the LoadMapFromWarp path and the surrounding map data load sequence. If I ask, “What ultimately consumes the loaded map state frame by frame?” now the answer is CB2_Overworld. That distinction is going to save you a lot of confusion when you trace this in lab.

Let’s compare this to Pokemon Showdown, but very carefully. Showdown is not our map architecture source. It is a battle simulator. It does not tell us how FireRed’s overworld works. But it is useful as a secondary comparison because it makes certain architectural ideas more visible: selected context, hooks, deterministic turn processing, and tests. In Showdown, you often see battle behavior organized around data plus hooks. A move is not only a row of numbers; it can have callbacks that affect damage or immunity behavior. The research packet points us, for example, to pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, where damage calculation logic checks immunities, OHKO behavior, damage callbacks, level-based damage, fixed damage, and then continues into the regular calculation path. That is a battle-domain example, not an overworld-domain example. But architecturally it rhymes with what we are studying: select a context, run the right staged logic, and make sure the state handed to the next step satisfies that step’s assumptions.

In FireRed’s warp flow, the hook is not a JavaScript callback on a move. The hook is more like the callback handoff in the engine shell: after the loader has done its work, CB2_Overworld can resume as the active runtime callback. You should feel the similarity and the difference. Similarity: both systems separate data from the runtime procedure that consumes it. Difference: Showdown’s battle hooks are often domain-specific effect callbacks inside a simulator turn, while FireRed’s warp flow is a broader engine-state transition across map runtimes.

Let me say that again in plainer language. Showdown asks, “Given this battle state and this move or effect, what should happen in the simulator turn?” FireRed asks, “Given this warp destination and this current engine state, what map runtime should exist next?” Those are different questions. But both questions are answered by combining data, state, and staged control flow, not by one giant unstructured function that knows everything.

Now, on the board, I want to add a second column beside our FireRed timeline. Label it Showdown battle turn. Under it, write “Dex/mod selection,” “effect data,” “hook execution,” “battle state update,” and “test expectation.” Again, no bullet list in your notes is required; I am just visually aligning the concepts. Showdown’s Dex and mods system is a good comparison point because it makes versioned data explicit. A move, ability, species, or learnset can mean different things under different generations or mods. The context research packet identifies pokemon-showdown/data, pokemon-showdown/sim, and pokemon-showdown/test as recurring high-signal modules for this course comparison, especially around Dex, mods, simulator turns, and regression testing. We are not going to replace FireRed with Showdown, but we are going to borrow that lens.

Here is the lens: before you execute behavior, you must know which rule context you are in. In Showdown, that might be a generation mod. In FireRed overworld loading, that is the selected destination map and its associated data records. Pallet Town is not just “some outdoor map.” It has a particular map record, and that record points into the layout and events we discussed in week 3. When LoadMapFromWarp resolves the destination and the map data load sequence runs, the engine is selecting the concrete context in which future overworld frames will be interpreted.

That is why “loaded map” is a stronger phrase than “map chosen.” Chosen means you have an identifier. Loaded means the engine has materialized the runtime state around that identifier. If your pseudocode in lab stops after assigning mapGroup and mapNum, it is incomplete. You have selected the destination, but you have not shown how the player coordinates, warp ID, map header lookup, entry scripts, object state, and callback handoff fit together. I am not asking you to reproduce every line of FireRed. I am asking you to preserve the architecture: destination state first, loading phase second, callback-ready invariants third.

Let’s walk through an intentionally simplified version aloud. Suppose the player activates a warp tile. The current map runtime detects that the tile behavior or event context corresponds to a warp. It resolves the target map group, map number, and warp ID. Those facts are stored in the warp destination state. Then the engine initiates the transition. During the transition, LoadMapFromWarp consumes that state. It asks, effectively, “Which map data should become current?” The map data load sequence pulls in the destination map header and related layout and event data. Then the player’s destination position is resolved, often through the destination warp record, because the target warp ID is not merely decorative; it tells the engine where on the destination map the avatar should appear. Finally, once the runtime is coherent, control returns to the overworld frame callback.

Now, a careful student might ask, “Is every warp exactly this simple?” No. There are dynamic warps, special transitions, scripted movement, and edge cases around things like entering buildings, using doors, or returning from certain states. But the conceptual skeleton holds. You always want to ask which facts were selected, which phase consumed them, and what must be true before normal runtime resumes.

Let me test the room with a small scenario. Suppose you trace a bug where the screen transitions correctly, the destination background appears, but the player appears at the wrong doorway. Which part of our three-question debugger do you inspect first? Not CB2_Overworld in general. Not the entire renderer. You inspect the destination facts and how the warp ID is consumed to resolve player coordinates. If the map appears at all, then the map group and map number may be correct. The wrong doorway suggests that the destination warp ID or coordinate resolution is wrong. That does not prove the loader is innocent, but it narrows your search.

Second scenario. The player appears in the correct position, but object events are missing or scripts do not behave as expected. Now you suspect the map data load sequence and the event/object initialization part of the runtime state. You would look around pokefirered/src/overworld.c, chunk_f00e575dedd069ed, because that is the source focus for the map data load sequence. Again, you do not start by saying, “The overworld is broken.” You classify the broken invariant.

Third scenario. The destination seems loaded, but after the transition the game does not respond correctly frame by frame. Now you ask whether the state handed back to CB2_Overworld satisfies the callback’s assumptions. That sends you back to pokefirered/src/overworld.c, chunk_7ca71901368c46f7, and to our earlier week 1 and week 2 material about callbacks and tasks. We are using those earlier concepts now, not re-opening them from scratch.

This is also where regression testing enters the conversation. FireRed itself, as a GBA game codebase, is not organized like Showdown’s test-heavy simulator environment. But as architects studying it, we can still ask what a regression test would assert. Showdown’s ecosystem is helpful here because simulator turns are often made testable by controlling input battle state and comparing deterministic output. The battle hook example from pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, is a good reminder that if an effect hook can change damage, a test must pin down not only the data row but also the context and callback behavior. For FireRed warp loading, our equivalent test thought experiment is: given source warp interaction and destination warp data, assert the resulting map group, map number, warp ID, player coordinates, and active callback state.

In your lab, when you implement pseudocode, I want you to write it in a way that could be tested. That does not mean we are building a full harness today. It means your pseudocode should expose intermediate values. If your code says “doWarp()” and then “loadMap(),” I cannot inspect your reasoning. But if your pseudocode has named steps like resolveDestinationWarp, storeWarpDestination, loadMapHeaderFromDestination, resolvePlayerCoordsFromWarpId, initializeMapRuntime, and setOverworldCallback, then your architecture is visible. The labels do not have to match the C function names perfectly. The responsibilities have to match.

Let’s connect this to the course plan, because week 4 is a hinge week. In weeks 1 and 2, we emphasized the runtime shell: main callbacks, task dispatch, data-to-behavior tracing, and headers as contracts. In week 3, we moved into map records: map headers, layouts, object events, warp events, and dynamic warps. This week, we are joining those halves. A map record by itself is static. A callback by itself is just a function pointer in a loop. The creature RPG begins to feel like a world when the runtime shell can select a map record, load it into live state, and resume a frame-by-frame overworld loop.

That is why this lecture is titled “From Warp State to Loaded Map,” not “Warp Events,” and not “Map JSON.” We already did the static side. Now we are looking at the state transition. And this prepares us for the next part of the course, where event scripting, battles, party state, and UI will all use the same architectural move: store selected facts, run staged control flow, then hand off to the next runtime mode with the right invariants.

Let me give one more comparison before we return fully to FireRed. In Showdown, the Dex/mod layer gives the simulator a rule context before it interprets species, moves, or learnsets. If you accidentally use the wrong mod context, the simulator may still run, but it will run under the wrong rules. That is analogous to loading the wrong map group and map number in FireRed. The engine may still display something. It may not crash immediately. But the player-visible behavior is wrong because the selected context is wrong. On the other hand, if the context is right but a hook is skipped, you get a different kind of bug. In Showdown battle code, skipping a damage callback changes a move’s behavior. In FireRed overworld loading, skipping an initialization step changes how the destination map behaves after it appears.

So, when you write your lab trace, do not only draw arrows from file to file. Draw the contract at each arrow. Source warp event to destination state: the contract is destination identity. Destination state to LoadMapFromWarp: the contract is consumable map and warp identifiers. LoadMapFromWarp to map data load sequence: the contract is selected map data and entry context. Map data load to CB2_Overworld: the contract is callback-ready overworld state. This is the level of precision I expect now because the recent homework and labs have already checked basic system identification. At this point in the course, naming files is not enough. You need to say what responsibility crosses the boundary.

Now I’ll pause and ask a comprehension question out loud. Suppose I give you Pallet Town’s map JSON and the LoadMapFromWarp function, but I hide CB2_Overworld. Can you still explain how the destination map is selected? Yes. You can explain the source data and the loading path. But can you fully explain why the next frame behaves correctly? No, because that requires the callback-ready invariant. Conversely, suppose I give you CB2_Overworld and the map data load sequence, but I hide the warp destination state. Can you explain how the player got to this particular door? Not fully. You can explain how the loaded map runs, but not how this destination was chosen.

That separation is the whole point. Architecture is not just knowing where things are. It is knowing what each part is allowed to assume about the other parts.

For the next segment, we will zoom into the handoff more tightly. We will ask what “current map” means as runtime state, how entry-time setup differs from per-frame overworld behavior, and how to avoid mixing those two phases in your mental model. Keep your board diagram nearby: destination facts, loading phase, overworld invariants. That is our through-line from warp state to loaded map, and it is also the pattern we will reuse when the course moves from overworld maps into scripted events and then into battle entry.

Good. Now, let us tighten that distinction, because this is where advanced readers of the engine often make a very subtle mistake. They look at the loaded map and say, “Ah, the map is the state.” Not quite. The map is data plus selected runtime interpretation. The current map, as the engine experiences it, is a bundle of commitments: which map header is active, which layout has been selected, which objects are instantiated, which scripts are eligible, which callback owns the frame, which coordinates the avatar occupies, which transition effects have completed, and which temporary entry conditions are still being honored.

So, if I write on the board, “current map,” I do not want you to read that as one pointer. I want you to read it as a staged agreement among systems. The warp system contributes destination facts. The map loader resolves those facts into map data. The overworld callback then assumes the map is coherent enough to run frame by frame. That means each stage narrows uncertainty. Destination state says, “we intend to arrive at map group X, map number Y, warp Z, or coordinates A and B.” Loading says, “given that intent, here is the concrete map header, layout, events, tiles, connections, and initial camera/player placement.” Runtime says, “from this point forward, input, tasks, scripts, objects, and rendering may proceed under stable map invariants.”

This is why the code organization matters. In the FireRed source focus for today, the warp destination material in pokefirered/src/overworld.c, chunk_9b2a7fa129935fc9, is not merely a convenience layer. It is the point where destination facts become explicit enough to survive the transition between maps. Then LoadMapFromWarp, in pokefirered/src/overworld.c, chunk_7ea6afd1253d6817, uses those facts to drive the loading path. And then CB2_Overworld, in pokefirered/src/overworld.c, chunk_7ca71901368c46f7, is the callback that owns the ordinary map runtime once we are past the special entry work. Those are not interchangeable responsibilities.

Pause there and check your understanding. If I ask, “Where should the game decide which destination map to enter?” the answer is not “inside the per-frame overworld loop.” That loop should not be rediscovering the reason for arrival every frame. And if I ask, “Where should the game animate NPCs, accept directional input, and allow normal field scripts to keep running?” the answer is not “inside the warp destination resolver.” The resolver should not become a miniature overworld. This is the boundary discipline I want you to keep.

Let us use Pallet Town as our small reference case again, but from the runtime side rather than the data-authoring side. In pokefirered/data/maps/PalletTown/map.json, chunk_05643f6bfaa3b9bf, the map is described as authored data. We have already discussed map records, layouts, object events, and warp events in earlier lectures, so I am not going to re-teach those fields. Instead, ask a runtime question: when the engine arrives at Pallet Town through a warp, what must be true before the player can take one step?

First, the destination must have been resolved to Pallet Town’s map identity. Second, the loader must know whether the arrival is through a warp event, explicit coordinates, or a special transition mode. Third, the layout data must be available to collision, rendering, and camera systems. Fourth, the object event system must be able to instantiate the local objects in the right relationship to the player. Fifth, the callback slot must be set so the frame loop is now the overworld runtime rather than the previous screen, transition, or loading callback.

Notice that none of those is “draw Pallet Town because the JSON says Pallet Town exists.” The JSON, or more precisely the generated map data derived from the authored map source, is not the runtime. This is where our week two material comes forward. We already separated authored data from compiled runtime structures, and we already treated headers as cross-module contracts. Today, those ideas become operational. The map record is only useful because the loader and callback systems agree on how to interpret it.

Let me put a small flow on the board in words. Source map event, then destination state, then LoadMapFromWarp, then map data load sequence, then entry setup, then CB2_Overworld. I am deliberately not drawing this as a circle yet. The ordinary overworld runtime is cyclic, yes, because every frame repeats input, tasks, rendering, and script-related work. But the warp-to-load path is more like a one-way bridge. You cross it, and once you are across it, you should not keep carrying all the bridge machinery in your hands.

Here is a concrete example. Suppose the player exits a house into Pallet Town. The source interior contains a warp event. The player steps onto it or presses into the door transition, depending on the interaction. The engine does not need to ask Pallet Town, “Which house did the player exit from?” as a general question during every Pallet Town frame. Instead, the warp destination state carries enough information for the loading path to place the player at the correct entry point. Once placed, Pallet Town runs like Pallet Town. The fact that the player came from a house may matter briefly for entry animation or facing direction, but it is not the central identity of the map forever.

That is an architectural pattern you will see again when we enter battle. The overworld has to decide, “A battle should start, with these participants, this battle type, this transition, this background, this trainer or wild encounter context.” But once the battle engine is running, it should not query the entire overworld every turn to rediscover why the battle exists. The battle entry state carries the necessary setup facts, and then the battle runtime owns its turn loop. We can already see the connective tissue in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, where the included modules span task handling, overworld, script, event data, field control, battle, battle transition, and related systems. That chunk is useful evidence not because we are studying battle setup in full today, but because it shows the same architectural pressure: a gameplay transition crosses module boundaries, and the setup code must coordinate state without collapsing all systems into one undifferentiated loop.

Now, let us bring in Pokemon Showdown for comparison, carefully. FireRed remains our main architecture source. Showdown is not an overworld engine, and it is not a GBA runtime. But Showdown is a useful secondary comparison because it makes battle data hooks and deterministic simulator behavior very explicit. In Showdown, data can contain hooks and callbacks that affect battle computation. The research packet points us toward pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, where damage calculation logic checks things like immunities, OHKO moves, damage callbacks, fixed-damage moves, level-based damage, and then continues through the calculation path. That is a battle-side version of the same question: which phase owns which responsibility?

In FireRed overworld loading, the warp destination resolver should not become the map runtime. In Showdown battle simulation, a move data hook should not become the entire simulator. A damage callback can modify or supply damage under controlled circumstances, but it is invoked by a battle engine that owns the turn and calculation sequence. The callback is powerful, but it is powerful because the surrounding engine defines when it runs, with what arguments, and how its result is interpreted.

So, board cue: write “hook” under Showdown and “callback” under FireRed, but do not treat them as identical words. FireRed’s CB2_Overworld is a main callback, a frame-level owner of runtime behavior. Showdown’s battle data hooks are often effect-level functions invoked inside a simulator turn. Both are inversion-of-control tools, but they live at different scales. That distinction is advanced but important. A callback can own the world for a frame. A hook can contribute to a calculation. If you blur those levels, your engine becomes difficult to reason about.

Check for understanding here. If a Showdown move has a damage callback, does that mean the move object runs the whole battle? No. The simulator turn machinery calls into the move’s effect at a defined point. Similarly, if FireRed sets CB2_Overworld as the active callback, does that mean CB2_Overworld personally resolves every authored map field from scratch every frame? No. It relies on the load sequence having already established valid current-map state.

This comparison helps us name the deep pattern: staged authority. Early stages assemble facts. Middle stages validate and load. Runtime stages assume invariants and advance simulation. Regression tests, when we get to them more deeply, are useful because they detect when a later stage accidentally starts depending on an earlier stage’s temporary assumptions. Pokemon Showdown’s test culture is valuable here. The packet identifies pokemon-showdown/test as a recurring module, and for our purposes the lesson is not “FireRed should be rewritten like Showdown.” The lesson is that deterministic turn simulators are especially testable because input state, data hooks, turn order, and expected outcomes can be isolated. When we later design tests or trace exercises around FireRed, we should ask similar questions: what is the minimal state needed to reproduce this transition, and what invariant should hold afterward?

For today’s lab, that means your warp trace should not be a vague story. It should be a chain. Start at the source map event. Identify the destination facts. Follow the transition into LoadMapFromWarp in pokefirered/src/overworld.c, chunk_7ea6afd1253d6817. Then connect that to the map data load sequence in pokefirered/src/overworld.c, chunk_f00e575dedd069ed. Then stop when the map has become a stable overworld runtime under CB2_Overworld in chunk_7ca71901368c46f7. If your pseudocode jumps straight from “player touched warp” to “draw destination,” it is too compressed. If your pseudocode keeps checking the source warp forever after arrival, it is too entangled.

Let me say that another way. Good architecture is not only about adding layers. It is about knowing when a layer’s job is over. The warp destination state has a lifespan. It is crucial during transition. It is not the permanent identity of gameplay. The map loader has a lifespan. It prepares the runtime. It is not the runtime itself. CB2_Overworld has a lifespan too. It owns ordinary overworld frames, but it should not pretend to be the whole game, because other callbacks own menus, battles, naming screens, link modes, and other states.

You can see that broader course pattern if we step back. Week one gave us the runtime shell: AgbMain, callback slots, tasks, and the discipline of frame ownership. Week two gave us the data pipeline: authored records, generated code, headers as contracts, and the difference between source data and runtime representation. Week three gave us the map record anatomy: map headers, layout references, object events, warps, and the separation of layout data from gameplay data. Week four, this week, is where those strands meet. A warp is not just a map field. It is authored data that becomes runtime destination state, passes through load control flow, and lands under a frame callback that can make the destination playable.

That is why the homework and quiz for this week are more demanding than a file-identification exercise. You have already practiced tracing data records through globals, callbacks, tasks, scripts, structs, and concrete functions. You have already identified the major systems and their files. Now I expect you to reason about phase boundaries. When I read your trace, I should be able to tell whether you know which facts exist before loading, during loading, and after loading. Those are different moments.

Let us walk through a compact pseudocode version out loud. I will speak it as if I am writing it on the board.

Player activates warp event. Record intended destination into warp destination state. Determine whether the destination is map-group and map-number plus warp-id, or some special coordinate-based entry. Begin map transition. Call the load-from-warp path. Resolve map header and layout references. Load map data required by rendering, collision, events, and object setup. Place player according to destination warp or coordinates. Initialize entry scripts, camera, object events, and transition state as required. Hand control to CB2_Overworld for normal per-frame map behavior.

That pseudocode is intentionally not C. In the lab, you will make it more concrete by naming the state fields and functions from the chunks we have been using. But conceptually, it already shows the boundary. The source event does not load every tile itself. The destination state does not run the field forever. The callback does not author the map.

Now, let us connect this forward to scripts, because that is where many of you will be tempted to overgeneralize. Event scripts will look like they can do almost anything, and in some sense they are a powerful orchestration mechanism. But scripts still run in a context. A script that executes on map entry depends on the map already being sufficiently loaded to provide script variables, objects, and player state. A script that starts a battle depends on battle setup code to interpret its request. A script that gives an item depends on inventory code to enforce item behavior. So the pattern from warp state to loaded map becomes a template for later systems: script request, setup state, subsystem load or transition, runtime owner.

Showdown gives us another useful contrast here with Dex and mods. In Showdown, the Dex and mod system let the simulator access different generations, rules, moves, learnsets, and effect definitions. The research packet points to pokemon-showdown/data/learnsets.ts and several modded learnset files such as pokemon-showdown/data/mods/gen9dlc1/learnsets.ts, pokemon-showdown/data/mods/gen9predlc/learnsets.ts, and pokemon-showdown/data/mods/gen8dlc1/learnsets.ts as high-signal data sources. The exact topic of learnsets is not our main focus today, but the architecture comparison is useful. Showdown’s simulator can ask its Dex for data under a selected mod context. FireRed, by contrast, is a compiled game with a fixed data set for this ROM. So when FireRed loads Pallet Town, it is not choosing among arbitrary runtime mods. It is resolving into the compiled world. When Showdown simulates a battle, it may resolve data through a Dex abstraction that knows which generation or mod is active.

That difference matters. Do not import the wrong abstraction backward. If you design a FireRed-like GBA engine, you may not want a large runtime Dex/mod layer unless your goals include dynamic rulesets. But if you design a modern battle simulator, you probably do want data indirection, mod inheritance, and regression tests around behavior changes. Architecture follows constraints. FireRed’s constraints include cartridge memory, GBA frame timing, compiled assets, and a callback-centered runtime. Showdown’s constraints include server-side simulation, many generations, active ruleset variation, and testable competitive behavior.

So, when I say “use Showdown as a comparison,” I mean use it to sharpen your categories. Showdown teaches us how explicit battle data hooks can be. FireRed teaches us how a full RPG runtime coordinates maps, callbacks, scripts, rendering, save state, and battle entry on constrained hardware. They illuminate each other, but one does not replace the other.

Let us do one more understanding check. Imagine a student says, “I want to add a special warp that changes the destination based on the player’s party.” Where should that logic live? Think for a moment. It probably should not live in CB2_Overworld as a per-frame check after arrival. It also should not be hard-coded into Pallet Town’s ordinary map data if the behavior belongs to a particular source warp or script. A reasonable design is to let a script or warp-handling path compute the destination before LoadMapFromWarp consumes it. Then the ordinary loading sequence remains ordinary. The special case influences destination selection, not the entire map runtime.

Now imagine another student says, “I want every arrival in Pallet Town to run a one-time script that checks story progress.” That is different. That belongs closer to map entry behavior, because the condition is about entering Pallet Town, not about one particular source door. Same map, different phase. That distinction is exactly what your lab trace is training.

Let me recap the core model before we close this part. A warp is a transition request, not the destination runtime. Destination state is a temporary contract, not the whole map. LoadMapFromWarp is control flow that consumes destination facts and prepares current-map state. The map data load sequence materializes the authored world into runtime structures. CB2_Overworld is the ordinary owner of map frames after that preparation. Pallet Town is useful because it is small enough to reason about, but the same logic scales to more complicated maps, scripted entrances, battle transitions, and later course topics.

And the broader course plan should now feel less like a list of systems and more like a chain of handoffs. Runtime shell gives us callback ownership. Data generation gives us stable tables and contracts. Map anatomy gives us authored spatial and gameplay records. Overworld loading gives us transition control flow. Next, scripts will give us conditional behavior inside those loaded maps. Then battle entry will show us another major handoff, from field state into turn-based combat. Later, inventory, UI, save, and link systems will each add their own version of the same question: what facts are passed in, who owns the runtime, and what invariants must be true afterward?

So for your workshop, keep the board diagram simple but strict. Destination facts. Loading phase. Overworld invariants. When you annotate code, cite the concrete places: pokefirered/src/overworld.c chunk_9b2a7fa129935fc9 for warp destination state, chunk_7ea6afd1253d6817 for LoadMapFromWarp, chunk_f00e575dedd069ed for the map data load sequence, and chunk_7ca71901368c46f7 for CB2_Overworld. Use pokefirered/data/maps/PalletTown/map.json chunk_05643f6bfaa3b9bf as your small map reference. If you bring in Showdown in your notes, keep it secondary and specific: battle data hooks, Dex and mods, simulator turns, and regression testing. That comparison is useful only when it helps you see FireRed more clearly.

The synthesis for today is this: from warp state to loaded map is not a teleport in the codebase; it is a disciplined transfer of authority. The source map initiates. Destination state records intent. The loader resolves and prepares. The overworld callback runs. Once you can see that transfer, you are no longer just reading functions. You are reading the engine’s contract with itself.

## Source References
- pokefirered/src/battle_setup.c
- pokefirered/src/daycare.c
- pokemon-showdown/data/mods/gen2stadium2/scripts.ts
- pokefirered/src/learn_move.c
- pokefirered/src/pokemon_summary_screen.c
