# Cable Club Maps as Multiplayer Entry Points

Lecture ID: lecture_w21_d02
Week 21, Day 2

## Objectives
- Trace FireRed gameplay from data records through globals, callbacks, tasks, scripts, structs, and concrete source functions.
- Connect week 21 material to the overall course plan.

## Transcript
All right, let’s pick up where yesterday’s link-manager lecture left us. We are not going to re-explain the whole callback system today, because by week 21 that should feel like old machinery to you: main callback slots, tasks, RunTasks as the per-frame dispatcher, and scripts as a higher-level event language sitting over C state. Today we are going to use that machinery to answer a more architectural question: how does FireRed turn a normal-looking room, the Cable Club, into the entry point for multiplayer?

So, if I put the title on the board, I would write: Cable Club map data is not “just scenery.” It is an entry boundary between the overworld state machine and the link-session state machine.

That is the core idea for the first part of today. The player experiences this as, “I walk into the Cable Club, talk to an attendant, choose trade or battle, and eventually I appear in a linked room.” But the engine experiences it as a chain of handoffs: static map records identify the space, scripts and warps route the player, globals record link intent and link state, callbacks switch control loops, tasks poll and advance negotiation, and the RFU or cable-link layer reports status through message constants. That is the same tracing discipline we have used all semester: data record, global, callback, task or script, struct, concrete function, visible behavior.

Now, quick recall from yesterday. We looked at `pokefirered/include/AgbRfu_LinkManager.h`, especially `chunk_8df030184c481b3b`, which gives us RFU mode constants. Those constants are not gameplay features by themselves. They are vocabulary for the link manager. They let the engine say, “I am in this kind of wireless-link posture now,” or, “I am negotiating as this side of the relationship.” Then we looked at the callback-message taxonomy in the same header, `pokefirered/include/AgbRfu_LinkManager.h` `chunk_b75d95e146826302`. That chunk matters because it separates link events into reported messages. A callback does not need to read the player’s mind. It receives link-manager messages and reacts.

And then, also from yesterday, `pokefirered/include/AgbRfu_LinkManager.h` `chunk_fc7074757ec1be96` gives us link-state constants and initialization parameters, while `chunk_f549b5be90786ef0` gives us the `LINK_MANAGER` state shape. Keep that in mind: constants define the legal states and messages; the struct gives those states somewhere to live. That distinction is a big deal. Constants are the alphabet; the state struct is the current sentence.

Now today we add maps.

Let me say that slowly, because it sounds almost too mundane. We add maps. Not battle logic yet, not trade packets yet, not serial timing yet. Maps. Why? Because the multiplayer feature begins as an overworld routing problem before it becomes a communication problem. The game has to put the player somewhere where the normal single-player assumptions are already constrained. In a route or a city, the map engine can assume one protagonist, local NPCs, and local event scripts. In a Cable Club link room, the map engine is being used as a staging area for an interaction whose real authority is going to move outside normal local overworld rules.

On the slide, imagine I have three boxes. First box: map group data. Second box: map header and warps. Third box: link handoff scripts and callbacks. The first two boxes are data-driven. The third box becomes procedural. That boundary is exactly the kind of architecture seam you should be able to identify in your week 21 homework.

For source grounding, look at `pokefirered/data/maps/map_groups.json` `chunk_f22eb0b9c9a5d2a4`. That chunk is important because the link maps are not magical names floating outside the world. They are included in the map group structure. FireRed’s maps are registered, grouped, and addressable through generated data. The Cable Club and battle rooms participate in that same map registry system. That means the engine can warp to them using the same broad map identity mechanisms it uses elsewhere, even though what happens after arrival is specialized.

This is one of the design patterns I want you to notice: FireRed often makes special features look ordinary at the outer boundary. A link room is still a map. A trade screen is still entered through callbacks. A battle still uses battle setup and battle globals. Specialization happens after the initial representation has been normalized into the engine’s existing data structures.

Now, let’s take the concrete example for today: the four-player Battle Colosseum. In `pokefirered/data/maps/BattleColosseum_4P/map.json` `chunk_f18829d873bc1246`, we have the 4-player Colosseum map header and warps. I want you to think of that JSON record as the map’s identity card. It tells the build pipeline, and eventually the runtime, what this map is: its layout reference, event content, connections or lack of ordinary connections, and warp definitions. The exact visible room the player walks into is not hand-authored as C code. It comes through map data.

Now, board cue here: I’m going to write the trace in a vertical column.

Map group entry. Map header. Warp. Script trigger. Link intent. Link manager state. Callback transition. Battle or trade subsystem.

That is the path we care about. Not every step is literally adjacent in one file, and not every link room uses the same exact visible script sequence, but architecturally this is the path. The room is not the multiplayer implementation. The room is the controlled entry point.

Let’s unpack that phrase, “controlled entry point.”

In a normal single-player map, almost anything can happen concurrently from a design point of view. The player can open the menu, collide with objects, trigger scripts, step on warps, start a trainer battle, and update field effects. The engine manages this with callbacks and tasks, yes, but the world remains fundamentally local. In a link room, FireRed needs tighter control. It needs to make sure every participating Game Boy agrees on the mode. It needs to know whether the local player is acting as parent or child in the session. It needs to prevent the player from wandering into arbitrary scripts while negotiation is half complete. It needs to transfer control from overworld interaction into link battle or trade code only when the communication layer says the session is ready.

This is where the link manager material from yesterday becomes practical. The RFU constants in `AgbRfu_LinkManager.h` `chunk_8df030184c481b3b` describe wireless modes. The callback messages in `chunk_b75d95e146826302` describe what the link manager can report. The state constants and init parameters in `chunk_fc7074757ec1be96` constrain the lifecycle. And the `LINK_MANAGER` state in `chunk_f549b5be90786ef0` gives the runtime a central place to accumulate progress, errors, identity, timing, and negotiated state.

So, here is the first advanced point: the Cable Club is not “where multiplayer happens.” The Cable Club is where the single-player overworld agrees to stop being sovereign.

That is a useful sentence. Write it in your notes. The overworld gives up control gradually. It does not vanish all at once. The player still sees map tiles. The player still moves through doors and warps. A script still runs when talking to an attendant. But the authority for the next major transition is no longer just, “Did the player choose yes in a menu?” It becomes, “Did the link manager reach a state that permits handoff?”

Let’s compare that briefly to something from earlier in the course, without re-teaching it. In the move relearner, which we studied from `pokefirered/src/learn_move.c` `chunk_af3598df6f092723`, a menu state machine eventually writes a selected move into party data. That was local authority. The UI asks, the player confirms, the code mutates local Pokémon data. In link entry, a similar-looking interaction, a prompt, a confirmation, a fade, has a stricter dependency: local confirmation is necessary, but not sufficient. The other device has to align. The communication state has to align. The mode has to align. That is why the link manager’s message vocabulary matters so much.

Check for understanding. If I ask, “Why can’t the Battle Colosseum just be a normal map with a script that calls StartBattle?” what should you say? You should say: because a linked battle is not merely a local battle with a different background. It requires synchronized participants, negotiated roles, link state, and a handoff into battle code that expects remote inputs and shared timing. A normal trainer battle setup can rely on local data and local AI. A link battle cannot.

Now, the source packet gives us a useful battle-side reminder. `pokefirered/src/battle_setup.c` `chunk_494d132a59422c85` includes the kind of boundary code where overworld conditions, scripts, battle constants, transitions, and battle loading meet. We are not going to dive into every function there today, but the file itself is evidence of the system boundary. Battle entry is not one monolithic “go battle now” call from a map. It is mediated by setup code, constants, transition helpers, script state, and overworld state. For linked play, that mediation becomes even more sensitive, because the battle subsystem must be entered with the correct link assumptions already established.

Let’s return to map data.

When `map_groups.json` includes link maps, as in `pokefirered/data/maps/map_groups.json` `chunk_f22eb0b9c9a5d2a4`, it means those maps can be referred to by group and number, included in generated tables, and targeted by warp definitions. When `BattleColosseum_4P/map.json` `chunk_f18829d873bc1246` defines its header and warps, it means the 4-player Colosseum is a real map in the same structural sense as a town interior. That is the trick. The map system gives the multiplayer feature a physical staging vocabulary: rooms, doors, positions, warps, object events. The link system gives it communication vocabulary: mode, role, state, callback message. The battle or trade system gives it gameplay vocabulary: participants, parties, choices, exchange, results.

Advanced architecture is often about noticing that no one system owns the full feature. The player says, “I battled my friend.” The engine says, “Map data routed the avatars, scripts collected intent, link manager negotiated a session, callbacks transferred control, battle controllers consumed local and remote decisions, and later the overworld resumed.”

Now, suppose we are modeling the lab version. The workshop asks you to model a simplified link-session state machine that starts from a Cable Club map trigger, negotiates parent-child state, enters linked battle mode, and returns to overworld. If you implement that as one giant function called `DoMultiplayer`, you have missed the lesson. A better model has at least these conceptual states: idle in overworld, map trigger activated, selected mode, waiting for partner, role established, session ready, handoff to battle or trade, returning from session, restoring overworld callback.

Notice what I did not say. I did not say you need to emulate the RFU hardware. For this week’s lab, hardware fidelity is not the goal. Architectural fidelity is. Your simplified model should preserve the handoff boundaries. It should know the difference between “the player wants to battle” and “the link session is ready to enter battle.” Those are not the same state.

Let me give a concrete example.

Imagine the player enters the Cable Club and selects a four-player battle. At the data level, their location is a map from the link map group. The target battle room is represented by map data, including the `BattleColosseum_4P` map JSON we just referenced from `chunk_f18829d873bc1246`. At the script level, an attendant or warp event starts the interaction. At the global-state level, the game records that the requested mode is a link battle, not a trade. At the link-manager level, the code initializes or advances a session using constants and state defined in `AgbRfu_LinkManager.h`, including the mode constants from `chunk_8df030184c481b3b` and link state definitions from `chunk_fc7074757ec1be96`. At the message level, the manager reports status using the taxonomy from `chunk_b75d95e146826302`. At the runtime-control level, tasks or callbacks keep polling, displaying waiting messages, responding to success or failure, and eventually transferring into the battle subsystem.

That is the vertical trace. Data records through globals, callbacks, tasks, scripts, structs, and concrete source functions. This is exactly the tracing objective for today, and it is the same objective that has appeared in your recent homework and quizzes. At this point in the course, I am no longer asking, “Can you name a file?” I am asking, “Can you explain why this file sits at this boundary, and what state crosses that boundary?”

Let’s pause there. If you are feeling tempted to collapse this into “the Cable Club calls the link manager,” slow down. That statement is not wrong in spirit, but it is too flat. The Cable Club is map data plus script behavior plus field control. The link manager is constants plus state plus callbacks plus timing. The handoff is not a direct line from one noun to another. It is a staged transition across engine layers.

Now, on the board, I’ll draw two horizontal lanes. Top lane: player-visible sequence. Bottom lane: engine sequence.

Player-visible: enter room, talk to attendant, choose battle, wait, enter Colosseum, battle starts.

Engine sequence: load map record, run map script or object script, set link mode intent, initialize link manager state, receive link callback messages, advance task state, switch callback into battle setup, run linked battle, restore overworld.

The mismatch between those two lanes is where most architecture bugs live. If the player cancels but the link intent global is not reset, you have a stale-state bug. If the link manager reports disconnection but the map script continues as if session-ready occurred, you have a handoff bug. If the battle subsystem starts before all participants agree on role or party data, you have a synchronization bug. If the overworld resumes without restoring the correct callback or map position, you have a return-path bug.

This is also why the VBlank and timing material from yesterday was not trivia. Link state is not advanced only when the player presses A. It is monitored over frames. The runtime shell we studied in earlier weeks, with `SetMainCallback2` in `pokefirered/src/main.c` `chunk_6cfd90f6d0136799` and task dispatch through `RunTasks` in `pokefirered/src/task.c` `chunk_3ffebc0341225936`, gives FireRed the frame-by-frame machinery needed to poll, wait, animate, and respond. I am mentioning those prior chunks only as foundation; we are not re-opening the callback lecture. The point is that multiplayer entry is not a blocking script in the abstract. It is embedded in a per-frame engine.

Here is a useful design rule: when you see a map-triggered feature that depends on external readiness, expect a task or callback state machine somewhere nearby. Scripts are good at sequencing visible interactions. They are not, by themselves, the right abstraction for uncertain asynchronous communication. The link manager may say, effectively, still waiting. It may say, partner found. It may say, disconnected. It may say, incompatible or failed. Those statuses correspond to the callback-message vocabulary in `AgbRfu_LinkManager.h` `chunk_b75d95e146826302`. A script that assumes immediate success would be too brittle.

Now another check for understanding. Suppose your simplified lab model has a state called `ENTER_BATTLE` immediately after `PLAYER_SELECTED_BATTLE`. What state are you missing? You are missing negotiation. More precisely, you are missing the waiting and readiness states where parent-child role, participant count, mode, and link availability are resolved. In a local menu state machine, confirm can lead to mutation. In a link state machine, confirm leads to negotiation.

Let’s connect this to the course plan. Weeks 20 and 21 are our system-integration closeout for persistence, link, and runtime boundaries. Save data taught us what must survive power loss and how ownership of persistent structures matters. Link play teaches us what must be agreed across devices and how ownership of temporary session state matters. Those are sibling problems. In saving, the question is, “Which state is authoritative enough to commit to flash?” In linking, the question is, “Which state is authoritative enough to let multiple machines enter the same gameplay mode?” Different hardware boundary, same architectural habit: identify the owner, validate the transition, and control the handoff.

That is why this lecture is placed now, not in week 4 when we first saw maps. Back then, a map was mostly layout, object events, warps, and scripts. Today a map can be an integration surface. The Battle Colosseum 4P map is not important because its room geometry is complicated. It is important because it demonstrates how a data-driven overworld object can be the entry point to a cross-device feature.

So, to recap the first segment before we move deeper into the source path: Cable Club maps are ordinary enough to live in the map group data, as shown by `pokefirered/data/maps/map_groups.json` `chunk_f22eb0b9c9a5d2a4`. Specific multiplayer rooms, like the four-player Colosseum, are ordinary enough to have map JSON records and warps, as shown by `pokefirered/data/maps/BattleColosseum_4P/map.json` `chunk_f18829d873bc1246`. But the behavior attached to those rooms is not ordinary. The room constrains the player into a staging context where link intent can be collected, link-manager state can be advanced using the RFU constants and state contracts in `pokefirered/include/AgbRfu_LinkManager.h` chunks `chunk_8df030184c481b3b`, `chunk_fc7074757ec1be96`, and `chunk_f549b5be90786ef0`, and link status can be interpreted through the callback messages in `chunk_b75d95e146826302`.

In the next part of the lecture, we are going to follow that handoff more tightly. We will ask: once the map has done its job as an entry point, where does responsibility move? And we will be very precise about the boundary between “I am standing in a multiplayer room” and “the engine has actually entered linked battle or trade mode.”

So let us put that boundary on the board. I am drawing a vertical line. On the left side I will write, “map and field context.” On the right side I will write, “link activity context.” That line is the architectural point of today’s lecture. The player does not enter linked battle merely by walking into a special-looking room. The player enters a map that is represented in the same broad data machinery as other maps, and then, through scripts, callbacks, globals, and link-manager state, the engine crosses into a different mode of play.

That sounds simple, but it is one of the places where FireRed is very instructive. A less disciplined architecture might make the map itself be the battle. It might say, “This map is a multiplayer battle map, therefore all multiplayer battle behavior lives here.” FireRed does something more layered. The map is an entry point. It gives you a location, a layout, warps, object positions, and the conditions under which interaction can begin. The link system is a communication state machine. The battle or trade system is the specialized activity that ultimately consumes the established link relationship.

Now, remember the formulation we have been using all semester: data record, global state, callback or task, concrete behavior. We are not abandoning that here. We are applying it to one of the most cross-cutting features in the game. In the four-player Colosseum example, the data record is visible in pokefirered/data/maps/BattleColosseum_4P/map.json chunk_f18829d873bc1246. The fact that the Cable Club maps sit in the map-group registry is visible through pokefirered/data/maps/map_groups.json chunk_f22eb0b9c9a5d2a4. Those are not the whole feature. They are the beginning of the trace.

Now pause there. If I ask you, “What does the map JSON know?” the answer should be fairly restrained. It knows map identity. It knows structural map information. It knows warps and map header relationships. It can point the engine toward scripts and events. But it does not, by itself, solve radio negotiation, parent-child assignment, message classification, synchronization, battle controller selection, or trade screen transition. Those responsibilities have to move elsewhere.

Let us make the example concrete. Imagine the player walks into the four-player Colosseum room. From the player’s perspective, that feels like entering a multiplayer space. From the engine’s perspective, the first several steps are still overworld work. The map loader has placed the player in a map from the link map group. The overworld is still the visible shell. Field scripts and object interactions are still the player-facing mechanism. The main loop is still dispatching through the same callback-and-task machinery we studied much earlier, with AgbMain and callback assignment grounded in pokefirered/src/main.c chunks chunk_b35676322b169f85 and chunk_6cfd90f6d0136799, and with task allocation and per-frame task dispatch grounded in pokefirered/src/task.c chunks chunk_cb0bb91a147bfb59 and chunk_3ffebc0341225936. I am not re-teaching that system now; I am reminding you that the link feature is not exempt from it.

The important design move is that the room collects intent before the game commits to link activity. Intent means: are we trading, battling, entering a particular multiplayer format, waiting for peers, or backing out? That intent has to be converted into link-manager state. In the RFU path, the vocabulary for that state lives in pokefirered/include/AgbRfu_LinkManager.h. The RFU mode constants in chunk_8df030184c481b3b give named modes to what would otherwise be magic numbers. The link state constants and initialization parameters in chunk_fc7074757ec1be96 define the state vocabulary and setup contract. The LINK_MANAGER state in chunk_f549b5be90786ef0 tells us that this is not just a casual helper function; there is an explicit state-bearing manager. And the callback message taxonomy in chunk_b75d95e146826302 tells us how the rest of the game can interpret what the communication layer is reporting.

Here is the check for understanding. If a Cable Club room script asks the player to begin a link battle, and the wireless or cable negotiation has not completed yet, should the battle engine own that waiting period? No. The battle engine should not be responsible for turning “standing near the attendant” into “connected peers are ready.” That is exactly the kind of boundary violation that makes a system brittle. The waiting period belongs to the field script, tasks, and link manager. The battle engine should be entered when the battle mode is actually being established.

Now I will add another line on the board underneath the first one. “Entry map does not equal activity engine.” Say that back to yourself, because it is the whole point of this lecture. A Safari Zone gate is not the Safari system itself. A Pokémon Center Cable Club room is not the link manager itself. A move relearner menu is not the party data model itself. We have seen this pattern repeatedly. In the move relearner, pokefirered/src/learn_move.c chunk_af3598df6f092723 shows a menu-oriented state machine coordinating selection and confirmation around party data, but the menu is not the creature model. In saving, the save UI and the save block ownership are not the same thing. Here, the map is not the multiplayer protocol.

That pattern is especially important in multiplayer because the cost of being wrong is higher. In a local single-player menu, if the state machine has a bug, maybe you draw the wrong text or return to the wrong screen. In a link session, if the boundary is sloppy, one device may believe it is waiting, another may believe it is entering battle, and a third may still be in field mode. That is why callback messages matter. The callback message taxonomy in pokefirered/include/AgbRfu_LinkManager.h chunk_b75d95e146826302 is not just an enum list for convenience. It is a shared interpretation layer. It lets the higher-level game code respond to link events without treating every low-level radio condition as an ad hoc special case.

Let us walk the handoff more slowly. First, the player occupies a Cable Club map. We know the map exists in the map data, and the four-player Colosseum has a concrete map record in pokefirered/data/maps/BattleColosseum_4P/map.json chunk_f18829d873bc1246. Second, a field interaction or scripted trigger collects the player’s intention. Third, the engine begins link setup, using the constants and manager state described in pokefirered/include/AgbRfu_LinkManager.h chunks chunk_8df030184c481b3b, chunk_fc7074757ec1be96, and chunk_f549b5be90786ef0. Fourth, the game receives and interprets messages through the callback taxonomy in chunk_b75d95e146826302. Fifth, once enough conditions are satisfied, responsibility transfers to the activity-specific system: battle for a linked battle, trade for a linked trade.

Notice what I did not put in that sequence. I did not say, “The map calls the battle loop directly and everything is done.” That would skip the communication contract. In FireRed, the path is more mediated. It has to be, because multiplayer is both a gameplay mode and a hardware coordination problem.

Now let us bring in battle setup. The source packet points us to pokefirered/src/battle_setup.c chunk_494d132a59422c85, and the include list alone is a useful architectural signal. That file touches task, overworld, sound, Pokémon data, load/save, scripts, event objects, field control, battle, battle transition, and battle communication concerns. The lesson is not that every include is equally important. The lesson is that battle entry is a boundary module. It sits between the world the player was occupying and the battle mode the player is about to enter. For ordinary wild battles or trainer battles, that boundary absorbs things like transition effects, script continuation, battle type, and post-battle return. For linked battles, that boundary has to respect the fact that another machine is participating in the same transition.

A useful mental model is an airlock. I do not mean that as decoration; I mean it as a state-machine pattern. The player starts in the overworld room. The game enters a staging state. The link manager confirms who is present and what role each participant has. Then battle setup can configure battle mode. Only after that should the battle loop behave as if the linked participants are part of the battle runtime. If you skip the staging state, you are asking the battle system to solve communication readiness and field exit at the same time. That is poor separation of responsibility.

Let me ask a pointed question. Where would you store the fact that this device is acting as a parent or a child in the RFU session? Would you store it in the map record? Obviously not. The map record is static data. It cannot represent a runtime role negotiated between devices. Would you store it permanently in save data? Also no, because parent-child role is session-specific. It belongs to runtime link state. That is why the LINK_MANAGER state from pokefirered/include/AgbRfu_LinkManager.h chunk_f549b5be90786ef0 matters. It gives us a place to reason about session-specific communication status.

Now, the “parent” and “child” language is worth being precise about. In a wireless arrangement, one participant often acts as the coordinating side, and others join. The game logic above that layer should not care about the raw details more than it has to. It should care that the link session is in a usable state, that the participants are known, and that the selected activity can begin. That is the value of constants and callback messages. Constants like those in chunk_8df030184c481b3b and chunk_fc7074757ec1be96 make code readable across modules. Callback messages like those in chunk_b75d95e146826302 form the reporting surface.

Now, if you are designing the simplified link-session state machine for this week’s lab, this is where your model should become sharper. Do not begin your model with “start battle.” Begin with “entered Cable Club map trigger” or “interacted with attendant.” Then pass through “choose or confirm activity.” Then pass through “initialize link manager.” Then pass through “negotiate role and peers.” Then pass through “receive ready message.” Only then do you enter “linked battle mode.” And after battle, you need a return edge back to overworld context. That return edge is not decorative. It is what makes the whole feature usable more than once.

This connects directly to our course-level architecture. In week one, we decomposed the creature RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. At the time, that list may have looked like a taxonomy. By week twenty-one, it should look like a set of negotiated boundaries. The Cable Club touches nearly all of those boundaries. It starts in overworld maps. It uses scripts and tasks. It consults party and battle eligibility. It may pass into trade. It may pass into battle. It must not corrupt save state. It must return cleanly.

Let us look at trade briefly, not to re-teach trade, but to compare the boundary. The research packet points to pokefirered/src/daycare.c chunk_119931105f546346, and that excerpt includes trade.h among its dependencies. That is a useful reminder that trade functionality is not isolated to a single visible room in the player’s mental model. Trade is a system with headers and data interactions that other modules may reference. Similarly, pokefirered/src/pokemon_summary_screen.c chunk_e8e17a42bbf17d46 includes link.h, trade.h, and battle_main.h. The summary screen can exist in contexts where link, trade, and battle constraints matter. Again, visible screens are not the same thing as system ownership.

So if the player is in a Cable Club trade room, the map still is not “the trade system.” It is the staging environment. Once both sides are ready, the trade system takes ownership of the trade interaction. If the player is in a Cable Club battle room, the map is not “the battle system.” It is the staging environment. Once the link has been negotiated and battle setup has been configured, the battle system takes ownership.

Now I want to connect this to battle runtime for a moment. The research packet points us to pokefirered/src/battle_util.c chunk_9335e5727aa0657d, where the include list includes link.h along with battle headers, move constants, items, abilities, weather, and battle controllers. That tells you something important: link is not only an entry concern. Once you are in linked battle, battle utility code may still need link-aware behavior. But that does not erase the boundary. There is a difference between “battle runtime uses link services” and “field map owns linked battle.” The former is necessary. The latter would be a design mistake.

Check yourself on this distinction. Suppose you see link.h included in a battle file. Does that prove the link manager owns battle? No. It proves battle may depend on link communication or link state for some behavior. Ownership is about which system drives the mode and which system is being used as a service. In linked battle, the battle engine drives battle turns, move resolution, controllers, and battle-specific behavior. The link layer provides communication state and synchronization support. The map layer has already done its entry job.

Now, imagine we are tracing one player action all the way through. I will narrate it as if we are writing a debugger story. The player steps through a warp into the four-player Colosseum. The warp is represented in the map data. The map belongs to the link map group, supported by pokefirered/data/maps/map_groups.json chunk_f22eb0b9c9a5d2a4, and the four-player room itself is represented by pokefirered/data/maps/BattleColosseum_4P/map.json chunk_f18829d873bc1246. The overworld callback continues to run. Tasks continue to update. The player interacts with a scripted object or trigger. The script does not magically become a radio protocol; it asks the runtime to begin the proper link flow. The link manager moves through named modes and states, described in pokefirered/include/AgbRfu_LinkManager.h chunks chunk_8df030184c481b3b, chunk_fc7074757ec1be96, and chunk_f549b5be90786ef0. Callback messages from chunk_b75d95e146826302 report what is happening. When the session is ready, battle setup takes the game across the boundary into battle mode, with pokefirered/src/battle_setup.c chunk_494d132a59422c85 acting as one of our architectural anchors for battle entry.

That is the trace. Data record to runtime state to script or task to manager state to callback message to activity handoff.

Now, why does this matter for clone design? Because when you build a simplified version, you will be tempted to compress all of this into one function called EnterMultiplayerBattle. For a tiny prototype, that may be acceptable for a day. But architecturally, you should still preserve the phases. Even if your implementation is small, write it as phases: map trigger, session request, connection negotiation, ready confirmation, activity launch, activity return. That makes your simplified model faithful to the source architecture without requiring you to reproduce every hardware detail.

Here is a concrete simplified state machine I would accept in the lab. The initial state is InCableClubRoom. The player activates a battle attendant, moving to SelectedBattleMode. The game calls a link-session initializer, moving to LinkInitializing. Once a local role is chosen, it moves to RoleAssigned. Once peers are visible, it moves to PeersDiscovered. Once all players confirm, it moves to SessionReady. Then it enters LinkedBattle. When battle ends, it moves to ReturningToCableClub, then back to InCableClubRoom. If you include timeout or cancel states, even better. But do not let cancel jump into an undefined void. Cancel should return to a known overworld or room state.

Let me underline the advanced part of this. The hard problem is not naming the states. The hard problem is deciding what information is legal in each state. In InCableClubRoom, it is legal to know the map id and player position. It is not legal to assume remote participants exist. In LinkInitializing, it is legal to know the requested mode. It may not yet be legal to know the final peer set. In SessionReady, it is legal to bind the selected activity to the participants. In LinkedBattle, it is legal for battle code to assume link synchronization is part of the battle runtime. If your code allows LinkedBattle assumptions while still in InCableClubRoom, you have crossed the boundary too early.

That is the architectural discipline I want you to take from FireRed. Not everything that appears together on screen belongs in the same module. Not everything that happens in sequence should be collapsed into one state. The player’s experience is continuous, but the engine’s responsibilities are staged.

And this also explains why returning from linked activity is a first-class concern. If a battle ends, the game must restore a coherent overworld context. It must not leave the player in a half-link state. It must not leave callbacks pointing at a stale activity. It must not leave tasks waiting for messages that will never arrive. We studied similar cleanup pressure in menu and save systems, but link raises the stakes because another device may have made corresponding assumptions. A clean return is part of correctness, not polish.

So, as we move forward in this lecture, keep that board diagram in mind: map and field context on the left, link activity context on the right, and a carefully managed boundary between them. The Cable Club map is the doorway. The RFU link manager is the negotiator. The callback messages are the reporting language. Battle setup or trade setup is the handoff into the specialized activity. And the overworld return path is the proof that the handoff was properly contained.

Now, let’s make that doorway less metaphorical. When I say “Cable Club map,” I do not mean merely a room with a nice tile layout and a nurse NPC. I mean a data-backed entry point whose map header, warps, scripts, and special connection behavior put the engine into a different contract. In ordinary overworld play, stepping through a warp changes location and continues the same basic field loop. In the Cable Club path, stepping into the multiplayer room is still map machinery, yes, but it is map machinery that exists to prepare a handoff.

Put that on the board: ordinary warp changes place; Cable Club warp changes place and prepares protocol. That distinction is small in the player’s eyes and very large in architecture.

The source evidence begins in the map registry. In pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, the link-related maps are not mysterious runtime inventions. They are listed as map data, inside the same broad map grouping system that the rest of the overworld uses. That matters because FireRed does not build multiplayer as an isolated separate executable mode from the first frame. It starts with the same design principle we have seen all course long: data records are registered, compiled, and then runtime code interprets them through known systems. The Cable Club is not “outside” the map system. It is a special use of it.

Now, slide your attention from the registry to a specific room. The four-player Colosseum map header and warp data are in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246. This is a useful example because four-player interaction makes the dependency on coordination obvious. A two-player trade can almost trick us into thinking of link as a direct pairwise menu. Four-player Colosseum behavior forces us to see the room as a staging environment. The map is not only scenery. The map encodes where players can appear, where exits are, and what transitions are legal. Those warps are part of the multiplayer boundary.

So here is the trace I want you to be able to perform on homework_w21. Start with the data record. The map group data names the link map. The individual map JSON describes the room, including warps. The build process turns that data into compiled tables. At runtime, the overworld system loads the map, and then player movement or script interaction reaches the trigger point. From there, the system must decide whether this is ordinary field navigation or a transition toward a link activity. That decision then pulls in globals and callbacks that the map data alone does not own.

Let me pause and check something. If I ask, “Where does multiplayer begin?” the answer is not “in the battle engine.” That is too late. It is also not “in the RFU manager” by itself. That is too low-level. The better answer is: multiplayer begins at the boundary where an overworld map or script commits the player to a linked activity and starts relying on link state. The Cable Club room makes that boundary visible.

Now, we bring in last lecture’s material, but briefly. In pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b, the RFU mode constants give names to the broad communication roles and modes. Those constants are not UI concepts. They are not map concepts. They are protocol-level labels that upper systems can select or observe. Then, in the same header, chunk_b75d95e146826302 gives us the callback message taxonomy. That taxonomy is the reporting language I mentioned earlier. It is how lower-level link progress becomes something the gameplay layer can react to without every map script manually inspecting raw wireless state.

And this is where advanced architecture starts to get interesting. A beginner says, “The player talks to the Cable Club attendant, and then the game connects.” An intermediate engineer says, “A script calls some link setup function.” An advanced engineer asks, “What contract is created between map script, task scheduler, main callback, link manager state, and the destination activity?” That is the level we are working at now.

Let’s do a concrete run. Imagine the player has entered the Cable Club and is being routed toward a four-player Colosseum session. The visible behavior is simple: the player walks into a room, maybe confirms a mode, waits for partners, and then enters a battle. Architecturally, though, several state machines are now nested.

The outer state machine is still the main engine shell. We have seen this before in pokefirered/src/main.c, where SetMainCallback2 appears in chunk_6cfd90f6d0136799. I am not going to reteach main callbacks today, but remember the key point: callback slots decide which high-level loop owns the frame. If the game is in overworld, the field callback owns the frame. If the game is in battle, battle owns the frame. The link handoff has to switch ownership without losing the return address, so to speak.

Inside that, tasks continue to act as per-frame workers. In pokefirered/src/task.c, CreateTask in chunk_cb0bb91a147bfb59 and RunTasks in chunk_3ffebc0341225936 are the familiar pieces. Again, not a full review, just a reminder: tasks let code represent waiting, polling, fade transitions, input pauses, and staged setup without blocking the whole machine. Link setup loves tasks because communication is not instantaneous. You ask for a connection state, wait for messages, respond to success or failure, and only then enter the next activity.

Now add the link manager itself. In AgbRfu_LinkManager.h, chunk_fc7074757ec1be96 covers link state constants and initialization parameters, while chunk_f549b5be90786ef0 exposes the LINK_MANAGER state shape. That is the lower-level state machine beneath the room. It is not enough for the map to say, “This is a Colosseum.” The link manager has to represent whether this device is initialized, whether it is acting as parent or child, whether partners have been found, whether communication is stable, and whether a message has arrived that should advance or abort the activity.

Watch the layering here. Map data answers, “Where are we, and what can the player touch?” Script and field logic answer, “What interaction did the player commit to?” The RFU manager answers, “What is the communication status?” Tasks answer, “What step of the staged operation are we on this frame?” Main callbacks answer, “Which major gameplay loop owns rendering, input, and simulation right now?” Battle or trade setup answers, “How do we enter the specialized multiplayer activity?”

That is the chain. Data record, globals, callback, task, script, struct, concrete source function. That is the course objective in one trace.

Let me put a small example on the board. Suppose we label four columns: map, script, link, activity. In the map column, write BattleColosseum_4P map JSON, pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246. In the script column, write player-facing trigger or attendant interaction. In the link column, write RFU mode constants and callback messages from AgbRfu_LinkManager.h, chunks chunk_8df030184c481b3b and chunk_b75d95e146826302. In the activity column, write battle setup boundary, and for source grounding you can connect that to pokefirered/src/battle_setup.c, chunk_494d132a59422c85, which pulls together battle, overworld, script, event data, transition, and related headers.

Notice what that last citation tells us. battle_setup.c is not a pure mathematical battle module. Its include surface in chunk_494d132a59422c85 shows it sits at a crossing point: task, overworld, sound, pokemon, load_save, script, event data, battle transition, battle, and more. That is exactly what a handoff module looks like. It has to know enough about the field side to leave it correctly and enough about the battle side to enter it correctly. For linked battle, that boundary becomes even more delicate because remote peers must cross equivalent boundaries in compatible order.

Here is a check for understanding. Why can’t the map JSON simply say, “start four-player battle,” and be done? Take a second. The answer is that the JSON is declarative world data. It can identify the place and its warps, but it cannot by itself complete a temporal protocol. Multiplayer setup is temporal. It unfolds across frames. It depends on peer availability, role negotiation, callback messages, timers, and possible failure. That is why tasks and link manager state are needed. The map can provide the doorway, but it cannot be the negotiator.

This also explains why VBlank and timing matter in the week’s source focus. We are not treating VBlank as a graphics trivia footnote. In a link session, stable per-frame timing provides the heartbeat for polling, message movement, and timeout behavior. If the field activity is fading out, if the link manager is counting frames, if tasks are waiting for a callback message, those systems must agree on when “next” happens. You saw this already at a smaller scale with menu state machines and fade transitions. In link mode, the same pattern becomes distributed. One device’s waiting state corresponds to another device’s sending or readiness state.

Now, a common mistake in student designs is to put too much intelligence in the room. They build a “CableClubRoom” object that owns connection negotiation, player placement, battle start, failure recovery, and return. That works for a toy clone for about fifteen minutes, and then it collapses. FireRed’s architecture pushes those responsibilities into separate systems. The map remains a map. Scripts sequence interactions. Link manager tracks communication. Main callbacks swap high-level loops. Tasks handle staged per-frame work. The battle setup boundary moves into battle. The return path goes back through overworld restoration.

For your lab model, I want you to preserve that separation even if your implementation is tiny. You can have simplified enums. You can have fake network messages. But do not make one giant “do multiplayer” function. Model a Cable Club trigger that starts a link-session state machine. Model parent and child negotiation. Model a callback message such as “partner found,” “ready,” or “error.” Model a transition into linked battle mode. Then model a return to overworld. If your return path is an afterthought, your model is architecturally incomplete.

Let’s connect this to the course plan. Back in week 1, we decomposed the game into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. At the time, that decomposition may have felt like a syllabus map. Now it should feel like an execution trace. Cable Club maps are where several boxes on that diagram touch at once. The overworld map box supplies the entry point. The script box supplies the user-facing decision path. The runtime shell supplies callback ownership. The task system supplies staged progress. The link system supplies remote coordination. The battle or trade system supplies the specialized activity. Save and persistence sit nearby because some outcomes may need durable updates, but, as we covered in the save lectures, persistence has its own ownership and sector discipline. We do not need to reteach that today; we just need to remember that multiplayer must not casually scribble durable state outside the proper save boundaries.

Let’s be precise about parent and child roles. In RFU-style communication, one device may behave as a parent and others as children. The exact names and constants belong to the RFU link manager header, especially the mode constants in pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b, and the state definitions around chunk_fc7074757ec1be96 and chunk_f549b5be90786ef0. Architecturally, those roles are not merely labels. They affect who advertises, who joins, who waits for whom, and how the session stabilizes before gameplay begins. If you design a clone and treat all peers as perfectly symmetrical from the first frame, you are skipping one of the hardest parts of handheld multiplayer design.

Now imagine a failure. Player A enters the room and waits. Player B disconnects before battle begins. What should happen? The wrong answer is, “Return to the title screen,” unless the game truly cannot recover. Another wrong answer is, “Start battle anyway.” The correct architecture needs an abort path that unwinds the staged operation. Tasks waiting on link messages should terminate. The callback should return to the appropriate field context. The map state should still be valid. Player position and script variables should not imply a battle occurred if it did not. If there was a fade, it should resolve. If there was a message box, it should be dismissed or replaced with an error message through the normal UI path.

This is why I keep saying that cleanup is correctness. A multiplayer handoff is not correct merely because the happy path starts a battle. It is correct when success and failure both leave the engine in coherent states.

Let’s look again at the relationship between source files. The map group source, pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, proves the Cable Club rooms participate in the data-driven map registry. The BattleColosseum_4P map source, chunk_f18829d873bc1246, gives us a concrete map data record with header and warps. AgbRfu_LinkManager.h, chunks chunk_8df030184c481b3b, chunk_b75d95e146826302, chunk_fc7074757ec1be96, and chunk_f549b5be90786ef0, gives us the link vocabulary: modes, messages, states, initialization, and manager state. main.c chunk_6cfd90f6d0136799 and task.c chunks chunk_cb0bb91a147bfb59 and chunk_3ffebc0341225936 give us the runtime scheduling frame. battle_setup.c chunk_494d132a59422c85 shows the boundary code’s dependency surface. Together, they support the claim that Cable Club multiplayer is not a single subsystem. It is a coordinated passage across subsystems.

Now, one more check. If I give you a bug report that says, “After a failed Cable Club connection, the player is stuck and input no longer works,” where do you look first? Do not answer “the map” only. You would inspect the task that was waiting for link progress. You would inspect whether the main callback was restored. You would inspect whether the script context was released or still locked. You would inspect the link manager state and callback message that represented the failure. Then you would inspect the map only as part of restoring the field context. The symptom appears in the room, but the bug may live at the handoff boundary.

And if the report says, “The game enters battle, but the wrong number of players are present,” then the map is again only one part of the investigation. The four-player room data establishes the intended setting, yes, but player count belongs to link negotiation and activity setup. You would ask whether the RFU role and mode constants selected the right session type. You would ask whether callback messages indicated all peers were ready. You would ask whether the battle setup boundary received the correct linked battle configuration. This is the advanced diagnostic habit: locate the contract that was violated, not merely the screen where the player noticed the violation.

For the next part of the lecture, we are going to follow the handoff more tightly into linked battle and trade boundaries. We will keep the Cable Club map on the left side of the board, because I do not want you to lose the entry point. But we are going to shift the center of gravity from “how does the room initiate multiplayer?” to “how does the engine decide it is safe to leave the room?” That is where the callback messages and link state constants become more than definitions. They become gates. A gate is not just a condition. It is a promise that the next subsystem may now assume something stronger than the previous subsystem could assume.

So let us make that word “promise” concrete. Suppose the player walks into the Cable Club, talks to the attendant, chooses a link battle, and enters the four-player Colosseum. At the map level, this still looks like ordinary overworld behavior. There is a map header. There are warps. There are object events. There are scripts. The map group table names the available maps, and the link maps live in that data universe just like Pallet Town or a Pokémon Center does. That is why the source focus includes pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, and the specific four-player room header in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246. Those records are not themselves “multiplayer logic.” They are entry points. They place the player into a controlled spatial and scripting context where multiplayer logic is allowed to take over.

Now, I want you to imagine the board divided into three vertical lanes. On the left, write “map and script world.” In the middle, write “link manager and RFU world.” On the right, write “battle or trade world.” The mistake I see advanced students make is drawing one arrow from left to right and labeling it “start link battle.” That arrow is too magical. FireRed does not simply jump from room to battle. It incrementally strengthens the assumptions available to the next layer.

In the map and script world, the game knows where you are. It knows which NPC you talked to. It knows which warp brought you into the room. It knows which script variable or special function is currently executing. But it does not yet have the right to assume that remote machines agree with you. In the link manager world, the system starts reasoning about parent and child roles, RFU mode, connection state, callback messages, timers, and readiness. In the battle or trade world, the system wants much stronger assumptions: that the participants exist, that the local machine’s role is meaningful, that the link channel can carry battle or trade protocol data, and that the overworld should no longer be treated as the active gameplay loop.

That last part matters. The battle engine does not want to be half-owned by the Cable Club script. Trade does not want the attendant’s dialogue box to still be conceptually active. Once the handoff happens, the destination subsystem needs ownership. That is the same architectural idea we saw in earlier weeks with main callbacks and task states, but we are applying it at a larger boundary now. Previously, when we talked about SetMainCallback2 in pokefirered/src/main.c, chunk_6cfd90f6d0136799, the important point was that the engine can redirect the per-frame driver toward a new mode. We are not re-teaching that callback shell today. We are using it. The Cable Club is one of the places where that old lesson becomes operationally expensive: if you switch too early, you enter battle without a stable link; if you switch too late, the map remains active after the multiplayer system expects exclusivity.

Look back at the RFU constants from pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b. Those constants are easy to underestimate because they look like names. Advanced readers must train themselves to ask, “Who is allowed to interpret this name, and what stronger condition does it imply?” RFU mode constants are not decorative vocabulary. They let the code distinguish what kind of communication session it believes it is running. That matters because a room that initiates a linked battle and a room that initiates a trade do not merely differ in later UI. They differ in what the handoff must prepare. Battle needs synchronized battle participants and battle controller expectations. Trade needs party inspection, exchange sequencing, and eventual party mutation. Both rely on link infrastructure, but the contract they require from that infrastructure is not identical.

Now, under that, write “callback message taxonomy.” That is pokefirered/include/AgbRfu_LinkManager.h, chunk_b75d95e146826302. Here is the diagnostic move. A callback message is not just “something happened.” It is the link manager’s way of telling the game which phase boundary has been reached, or which boundary has failed. If a message says, in effect, that connection establishment has progressed, then the script or task layer may move forward. If a message says a partner disappeared, then the room must not continue pretending that the next subsystem can safely assume multiplayer ownership. So when you debug a Cable Club failure, you should not begin with the visible symptom, such as “the player is stuck in the room” or “the battle never starts.” Begin with the last promise that was successfully made. Did the map script finish its part? Did the RFU mode get selected? Did the link state become strong enough? Did the callback message that normally opens the gate arrive? Did a task consume that message and advance, or did it arrive but get ignored because some global was in the wrong state?

Let me give you a concrete example. Suppose we are modeling the lab exercise for this week: a simplified link-session state machine that starts from a Cable Club map trigger, negotiates parent and child state, enters linked battle mode, and returns to overworld. In your clone, the player steps onto a tile or speaks to an attendant. That map trigger starts a script. The script sets the desired session type to link battle and creates or activates a task. The task begins in a “prepare room” state. It may freeze player movement, clear pending text, or wait for a fade. Then it enters a “negotiate link” state. Here, it does not start battle. It calls into the link manager or polls a shared link status. If this local machine is the host, parent, or initiating side, it advertises or waits differently from the child side. Once both sides report readiness, the task advances to “handoff to battle.” Only there does it set the battle flags, configure participants, and transition to the battle callback.

Notice the difference between a state and a gate. “Negotiate link” is a state. “All participants are ready and the local role is known” is a gate. The state is where you wait. The gate is the promise that lets you leave.

Check for understanding here. If the map script says, “The player chose link battle,” is that enough to enter battle? No. It is enough to request a link battle. It is not enough to assume the link session exists. If the link manager says, “A wireless parent or child role has been established,” is that enough to mutate party data as though a trade completed? No. That is a communication promise, not a trade-completion promise. You have to keep the promises separated, or your architecture will collapse under rare timing failures.

Let us connect that to the constants in pokefirered/include/AgbRfu_LinkManager.h, chunk_fc7074757ec1be96, the chunk called out for link state constants and initialization parameters. The initialization side is especially important because multiplayer bugs often look like runtime bugs when they are actually bad initial conditions. If the link manager is initialized with the wrong mode, wrong role expectations, or wrong timing assumptions, later code may appear to stall in a reasonable state. It is waiting for a message that will never be produced under the current configuration. That is one of the nastiest classes of state-machine bugs: every individual state looks defensible, but the graph is impossible to complete.

Now write “VBlank timers” underneath the middle lane. We covered this yesterday from the link-manager side, so I will not restart the lesson. The point we need today is narrower. Link communication on the Game Boy Advance has to coexist with the frame loop. VBlank gives the engine a regular timing boundary. The link state cannot just be a blocking while-loop that monopolizes the CPU until the other player appears. The game still needs to update relevant tasks, process callbacks, and keep the machine alive. So the Cable Club handoff is not a single blocking function call. It is a negotiated progression over frames.

This is where pokefirered/src/task.c, chunk_cb0bb91a147bfb59 for CreateTask and chunk_3ffebc0341225936 for RunTasks, remains part of the story. We are not studying menu tasks today, and we are not going back through the move relearner. But the pattern is the same: create a small per-frame state machine, let RunTasks dispatch it, and advance only when the data says the next phase is legal. In week 17, you saw task states around move replacement. In week 21, that same design style is now guarding an inter-device protocol boundary. Same architectural shape, higher stakes.

Now shift your eyes to the right lane: battle and trade world. The battle setup code in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, is useful evidence here because it sits near the border between overworld conditions and battle entry. The includes alone tell a story: task, overworld, script, event data, field control, battle, battle transition, and related systems are all in the neighborhood. I am not claiming that a single include list proves behavior by itself. But for architecture reading, it shows you that battle setup is a boundary module. It has to understand field context and battle context. It is precisely the sort of place where a Cable Club handoff must eventually land, or at least prepare the assumptions that battle setup expects.

Trade has a similar boundary flavor. The daycare excerpt in pokefirered/src/daycare.c, chunk_119931105f546346, includes trade.h alongside party, menu, overworld, script, naming, and text systems. That does not make daycare our trade-entry module for today. But it reminds us that trade is never “just link bytes.” In this codebase, trading is connected to party data, UI, scripts, and overworld return. When a linked trade finishes, the local save-relevant state may have changed. That brings forward our week 20 persistence lessons without re-teaching them: save block ownership and sector validation matter because link features can produce real game-state mutations. A failed or half-completed handoff must not leave the party model in a state that neither the overworld nor trade code owns.

Let me pause and draw a small state machine on the board. First box: “Cable Club map loaded.” Under it, data source: map_groups.json and BattleColosseum_4P map.json. Second box: “script or object interaction requests session.” Third box: “link manager initialized.” Under that, RFU mode constants and link state constants from AgbRfu_LinkManager.h. Fourth box: “callback message confirms readiness.” Fifth box: “task commits handoff.” Sixth box: “battle or trade callback owns frame loop.” Seventh box: “return path restores overworld.”

Now, what is missing if I only draw those boxes? The globals. FireRed is a C codebase with global state carrying meaning across subsystem boundaries. The header pokefirered/include/AgbRfu_LinkManager.h, chunk_f549b5be90786ef0, is specifically important because it exposes link-manager state as a contract, not just internal machinery. When a header describes shared state, advanced readers should ask which modules are supposed to read it, which modules are supposed to mutate it, and which transitions are supposed to be one-way. A variable that means “we are negotiating” cannot casually be repurposed to mean “we are in battle.” If the same flag is overloaded too aggressively, the code becomes impossible to reason about during disconnects, cancellations, or returns.

Here is a concrete failure scenario for your clone. You implement one global called linkReady. The Cable Club task sets linkReady to true once two players are detected. The battle system sees linkReady and starts the battle. Later, during battle, a player disconnects. You set linkReady to false. The overworld return code checks linkReady and decides the room was never properly entered, so it runs the wrong cleanup path. That bug came from a weak contract. You used one boolean for at least three meanings: partners detected, battle handoff committed, and active link still healthy. FireRed’s more elaborate constants and callback messages may feel verbose, but they exist because multiplayer has more than two states.

A better clone design would split the meanings. You might have desiredSessionType, localLinkRole, connectionPhase, handoffCommitted, and destinationMode. Then “partners detected” advances connectionPhase. “Battle ownership taken” sets handoffCommitted. “Disconnect received” becomes an event that the current owner interprets. The room interprets disconnects before handoff. The battle system interprets disconnects after handoff. Same physical event, different owning subsystem. That is the deeper architecture lesson.

Let me ask a check question. If a disconnect happens before the battle callback owns the frame loop, who should clean up the player’s map state? The Cable Club or overworld-side session task. If a disconnect happens after the battle callback owns the frame loop, who should decide the battle outcome or emergency exit path? The battle or link-battle owner. If your answer is “the link manager,” be careful. The link manager reports and maintains communication state. It should not be responsible for every gameplay consequence of communication loss. That distinction is how you avoid turning infrastructure into a god object.

This also explains why the Cable Club maps are so controlled. The four-player Colosseum map is not just a decorative room. A constrained map simplifies the legal transitions. The warps in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, define how the player can enter and exit that space. The map-group record in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, places the map in the world’s data registry. But the room’s purpose is architectural: limit the player’s available actions while the engine is preparing a fragile cross-system transition. You do not want the player freely changing maps, opening arbitrary menus, triggering unrelated scripts, and rearranging party state while the link manager is negotiating battle readiness.

This is the same design instinct we saw in earlier UI-heavy systems. The move relearner in pokefirered/src/learn_move.c, chunk_af3598df6f092723, is a state machine that constrains the player into a narrow interaction until the move replacement is either committed or canceled. We are not re-teaching move replacement. I am using it as an analogy. The Cable Club is a spatial state machine. Instead of a menu list and confirm window, it uses a room, attendants, warps, and link status gates. In both cases, constrained interaction protects a sensitive mutation or handoff.

Now, slide cue: imagine a timeline across the top of the screen. Frame zero: player confirms. Frames one through many: task waits for fade, scripts complete, link manager initializes. Later frames: callback messages arrive. Later still: handoff commits. What should not happen on frame zero? Party mutation. Battle controller startup. Trade exchange. Why? Because the player’s intention is not yet the same as system readiness. This is an advanced but very practical distinction: intention is input; readiness is state; commitment is ownership transfer.

The battle side gives us another useful comparison. In pokefirered/src/battle_util.c, chunk_9335e5727aa0657d, the surrounding module includes link, random, Pokémon data, battle scripts, battle controllers, abilities, items, moves, and weather constants. Again, the point is not that this one excerpt explains Cable Club. The point is that battle execution assumes a rich environment has already been assembled. Once you are in battle utility code, you are no longer asking, “Did the player just talk to the attendant?” That question belongs to the entry side. Battle logic asks battle questions: What move is being resolved? Which controller owns which battler? Which item or ability modifies the outcome? A clean handoff means each subsystem gets to ask its native questions.

Let us recap the chain in spoken form, because this is exactly the kind of answer I expect in homework_w21. The Cable Club map data gives us a controlled physical entry point. The script and task layer converts player interaction into a requested multiplayer session. The RFU link manager, governed by constants and state definitions in pokefirered/include/AgbRfu_LinkManager.h, chunks chunk_8df030184c481b3b, chunk_b75d95e146826302, chunk_fc7074757ec1be96, and chunk_f549b5be90786ef0, negotiates communication role and readiness over time. Callback messages act as gates, not merely notifications. Once the gate is strong enough, a task commits the handoff to battle or trade. From that moment, the destination subsystem owns the active gameplay loop and interprets later link events according to its own contract. On return, the engine must restore overworld ownership without pretending the Cable Club script was continuously in charge the whole time.

That last sentence is the one I want you to keep. The room starts the process, but it does not own the entire multiplayer experience. The link manager enables the process, but it does not own every gameplay consequence. The battle or trade system consumes the process, but it should not know about every attendant script or warp that led there. Good architecture is not the absence of coupling; a creature RPG is full of coupling. Good architecture is knowing which coupling is a contract and which coupling is leakage.

So, as we move into the next segment, keep the three-lane board in your notes. Map and script world on the left, link manager in the middle, battle and trade on the right. The question we will ask next is not merely “where does the code jump?” It is, “what does each side believe has become true at the moment of the jump?” That belief is where most serious multiplayer bugs either are prevented or are born.

Now, let’s make that belief question concrete.

Imagine the player has walked into the Cable Club, talked to the attendant, selected a linked battle, and passed the social confirmation screens. From the player’s point of view, this is one smooth interaction. They are in a Pokémon Center, then they are in a link room, then they are in a battle. But the engine does not experience that as one smooth idea. The engine experiences it as a sequence of handoffs between subsystems that only partially understand each other.

So, on the board, I’m going to draw three boxes again. Left box: map and script. Middle box: RFU and link manager. Right box: battle or trade. Now I’m going to add a fourth label underneath all three boxes: global state. That is where the handoff becomes dangerous, because globals are the shared memory of the fiction. They are how one subsystem says, “I believe this is a link battle,” and another subsystem later says, “All right, then I will initialize a link battle controller instead of a wild battle controller.”

The map data gives us the physical entry point. In today’s source focus, the link map group in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, is important because it tells us that the multiplayer locations are not magical free-floating scenes. They are map records in the same larger map registry system as ordinary towns, houses, routes, and Pokémon Center interiors. And the 4-player Colosseum header and warp records in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, matter because they show the same architectural principle at a more specific level: linked multiplayer has a physical staging map with warps, coordinates, events, and layout references. The multiplayer entry point is not simply a call to “start network mode.” It is a place.

And that is a very FireRed idea. We saw it earlier in the semester with ordinary map loading: data records name layouts, events, scripts, connections, and music; the overworld runtime interprets those records; then callbacks and tasks animate the result frame by frame. We are not going to re-teach that system today, but I want you to recognize the recurrence. The Cable Club is using the same data-to-runtime pattern, but the consequence is more severe because the map script eventually crosses into synchronized multiplayer.

Now, pause on that phrase: synchronized multiplayer. In a single-player cutscene, if the local script sets a flag too early, maybe the wrong NPC disappears. That is a bug, certainly. But in linked play, if one machine believes the session is in one mode and another machine believes it is in another mode, the two Game Boys can diverge. And divergence is not just cosmetic. It can mean one side is waiting for battle data while the other side is still waiting for a handshake message. That is the architectural pressure behind the RFU constants and callback messages we studied in the previous lecture.

Let me point to the header again. pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b, gives us RFU mode constants. That source focus matters because mode constants are not just names for convenience. They are the vocabulary by which the rest of the program can say, “We are acting as a parent,” or “We are acting as a child,” or “We are entering a particular wireless communication role.” Then chunk_b75d95e146826302 gives the callback message taxonomy, which is the vocabulary for events emitted by the link manager. And chunk_fc7074757ec1be96 gives link state constants and initialization parameters, which is where you see that the system is careful about not treating “RFU exists,” “RFU is initialized,” “players are connected,” and “gameplay can begin” as the same condition.

That distinction is the whole lecture in miniature.

If I write on the board, “connected equals ready,” cross that out. Connected does not equal ready. A link can be physically or wirelessly present while the gameplay layer is not safe to consume yet. The link manager might have established communication, but the battle subsystem may not yet have received the exact participant count, player role, trainer identifiers, party data boundary, or mode flag it expects. Similarly, the map script may have moved the player into a Colosseum room, but that does not mean the battle engine should already assume all battlers are configured.

So we need to separate four words: present, connected, negotiated, committed. Present means there is a possible peer or signal. Connected means the link manager has a communication relationship. Negotiated means the parties agree on role and session shape. Committed means the gameplay system has crossed the boundary and is now allowed to consume the session as a battle or trade.

Check for understanding here. If the player is standing on a Cable Club map, are they committed to a battle? No. If the RFU manager has reported a connection callback message, are they committed to a battle? Still no. If the attendant script has finished the selected battle dialogue, are they necessarily committed? Not by itself. Commitment happens when the script, globals, link state, and destination subsystem line up around a specific handoff.

This is why I keep calling the Cable Club a multiplayer entry point rather than the multiplayer system itself. It is an entry point because it coordinates the transition. It does not replace the link manager. It does not replace the battle engine. It creates the conditions under which the transition is legal.

Let’s walk one concrete example.

Suppose we are in a 4-player Battle Colosseum flow. The player begins in an overworld map, talks to an attendant, and eventually warps into a multiplayer room represented by the BattleColosseum_4P map data. That map data, again, is represented in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246. From an architecture perspective, the map record gives us the staging environment. The script layer decides when the player can enter it. The link manager tracks whether the communication side has reached a usable state. The battle setup layer later prepares the battle transition.

Now connect that to pokefirered/src/battle_setup.c, chunk_494d132a59422c85. The excerpt in our packet shows that battle setup pulls together task control, overworld, script, event data, field movement, metatile behavior, player avatar control, battle transition, battle constants, and related systems. That include list is not trivia. It tells you battle setup is a boundary module. It lives where overworld fiction becomes battle runtime. So for link battles, the design question is not, “Does battle_setup know networking exists?” It must, at least indirectly. The better question is, “Does battle_setup only depend on the link contract it needs, or does it leak Cable Club script details into battle logic?”

And this is where advanced architecture reading becomes different from beginner code reading. A beginner asks, “What function starts the battle?” That is a valid question, but too small. An advanced reader asks, “Which state variables had to be true before that function could safely run, and who was responsible for making them true?” In FireRed, because the shell is callback-and-task driven, the answer usually includes more than one layer. AgbMain initializes the runtime shell, SetMainCallback2 changes the main execution mode, RunTasks dispatches per-frame task logic, scripts drive field interactions, and specialized modules consume globals or struct fields at the boundary. We covered AgbMain, SetMainCallback2, CreateTask, and RunTasks earlier from pokefirered/src/main.c and pokefirered/src/task.c in the course packet, so today I only want you to reuse that as a mental model: the link handoff is not a giant blocking function call. It is a staged change in what the frame loop is allowed to do.

Let me say that another way. The player experiences waiting. The engine experiences repeated frames. Each frame can run VBlank work, input work, task dispatch, link polling, script continuation, animation, and callbacks. That is why VBlank timers and link state belong in this week’s material. Link communication is time-sensitive, and the engine cannot simply stop the world in a naive while-loop until everyone is ready. It has to remain aligned with the hardware frame rhythm.

Now, in pokefirered/include/AgbRfu_LinkManager.h, chunk_f549b5be90786ef0, the LINK_MANAGER state is part of the source focus. When you see a named manager state in a header, treat it as a shared contract. It means the C files using this header are not supposed to invent their own private interpretation of the connection lifecycle. They are supposed to ask or update the manager according to the defined vocabulary. That is exactly the same architectural pattern we have seen all semester with constants for battle types, item pockets, map groups, party menu actions, and script commands. Constants are not decoration. Constants are the public language of a subsystem.

Here is a useful board exercise. I’m going to write five rows: data record, script decision, global/session state, manager callback, gameplay consumer. Now let’s fill them in for a Cable Club battle.

Data record: the Cable Club and Colosseum maps exist in the map group and map JSON records, including the 4-player Colosseum map header and warps. Script decision: an attendant or map trigger checks the player’s chosen activity and moves the player through the entry flow. Global/session state: the engine records that the current multiplayer session is a link battle of a particular shape. Manager callback: the RFU layer reports role, connection, child or parent progress, readiness, or failure through the message taxonomy in AgbRfu_LinkManager.h, chunk_b75d95e146826302. Gameplay consumer: battle setup and battle runtime consume the now-committed link battle state.

Notice the asymmetry. The data record does not know the callback message. The callback message does not know the exact map warp. The battle engine does not need to know which attendant line the player read. That is separation of concerns. But they are still coupled through the contract of session state. If the script sets the wrong session type, the battle side will behave incorrectly. If the link manager never reports the readiness message the script is waiting for, the map flow stalls. If the map warp sends the wrong number of players into the wrong room, the social fiction and the communication topology stop matching.

That is the kind of bug you should be able to predict before you run the program.

Let’s compare this briefly to prior systems without re-opening them. In the move relearner lecture, pokefirered/src/learn_move.c, chunk_af3598df6f092723, gave us a menu-driven state machine that eventually wrote a move into party data. That was local, UI-heavy, and party-data-sensitive. In the save lectures, the load-save and sector-validation material taught us that persistence boundaries require careful ownership and verification. The Cable Club combines both kinds of pressure. Like the move relearner, it is a user-facing sequence with choices and state transitions. Like saving, it has a hard correctness boundary where bad assumptions can corrupt the player’s experience. But unlike both of those, it has another machine on the other side. So now the state machine is not only internal; it is cooperative.

That cooperative state machine is what your week 21 lab is asking you to model. The lab says: start from a Cable Club map trigger, negotiate parent and child state, enter linked battle mode, and return to overworld. That is not busywork. That is the small version of the real architecture. If your simplified model has a direct call from “talk to attendant” to “start battle,” it is missing the important part. You need intermediate states. You need a waiting state. You need a failure or cancellation path. You need a committed state. And you need a return path that restores overworld control without pretending the battle was just an ordinary local scene.

Let’s make the return path explicit, because students often under-model it.

When a linked battle ends, the player does not return to an arbitrary overworld state. The engine has to restore a state consistent with the map and script world. Which map? Which coordinates? Which music? Which NPCs or attendants are active? Is the link manager still active? Are we dissolving the session or returning to a room where the players may continue? These are not all answered in one file, but the architecture forces the questions. The battle system can report a result. The overworld can resume. The link manager can disconnect or remain in an expected state. The script may need to continue after the special event. Again: three lanes, shared belief.

So if I ask, “Who owns the result of a link battle?” the answer is layered. The battle engine owns battle resolution. The link system owns communication status. The overworld and script layer own what happens next in the field. And global state or event variables may carry the bridge between them. It is tempting to find one master owner, but FireRed’s architecture is more distributed than that. The discipline is in the contracts.

Now let’s talk about parent and child, because this is a place where the RFU constants can feel too hardware-specific if you read them in isolation. In a wireless session, one machine often has to coordinate discovery or session creation, while others join. In the source focus, the RFU mode constants in pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b, give names to those roles. The gameplay layer should not treat role as a personality trait of the player. It is a communication role. The player who is “leader” in the room may correspond to a parent-like role in the communication setup, but the important architectural point is that the role affects synchronization responsibilities.

In a simplified model, parent might mean, “I announce the session and wait for children.” Child might mean, “I search for and join a session.” Then both move into a negotiated state once the expected participant count and session mode agree. But do not skip the agreement step. If one side thinks the session is a trade and another thinks it is a battle, both sides may be connected, but they are not negotiated. If one side thinks it is a two-player battle and another side is waiting for four players, connected still does not mean ready.

Let’s do a quick check. What is the architectural difference between a link manager callback message and a map script command? Think it through. A script command is authored as part of field behavior. It says, in effect, “when this event runs, perform this field/gameplay action.” A link manager callback message is communication feedback. It says, “the link layer has observed or reached this condition.” The script may respond to that condition, and tasks may poll or wait around it, but the callback message is not itself a map event. If your mental model collapses those two, you will accidentally make the map own the network.

And the map should not own the network. The map owns entry context.

Let’s return to source evidence. The packet’s high-value module list calls out pokefirered/include/AgbRfu_LinkManager.h as a recurring course module around defines, constants, externs, guards, and void declarations. That is the header side of the contract. It also calls out pokefirered/src/AgbRfu_LinkManager.c as the implementation side, though today’s named source chunks are in the header. The way to read that pairing is simple: the header tells other modules what concepts they are allowed to share; the C file implements the behavior behind those concepts. When we cite the RFU mode constants, callback messages, link state constants, and LINK_MANAGER state from chunks chunk_8df030184c481b3b, chunk_b75d95e146826302, chunk_fc7074757ec1be96, and chunk_f549b5be90786ef0, we are citing the shared language that makes the Cable Club handoff possible.

Now, let’s contrast the 4-player Colosseum with an ordinary interior map. An ordinary interior may have warps, object events, background events, and scripts. The 4-player Colosseum also has map structure, but its gameplay meaning depends on a synchronized external condition. You cannot fully understand that map by looking only at its tiles. You have to ask why the player is there, how many peers are expected, and what session mode is active. That is a more advanced version of a point we made weeks ago: map data is declarative context, not the whole behavior.

This is also where students sometimes misuse the phrase “data-driven.” They say, “The map is data-driven, so the map data drives the battle.” Not quite. The map data participates in a data-driven overworld system. It does not, by itself, drive the battle engine. The transition is mediated by scripts, callbacks, tasks, global variables, and subsystem contracts. Data-driven does not mean data-only. In FireRed, data becomes behavior through runtime interpreters and state machines.

Let me give you a small failure scenario, because failure scenarios are often clearer than the happy path.

Suppose the script warps the player into the 4-player Colosseum room before the link manager has confirmed the expected participant count. The player sees the correct room, so a superficial test might pass. But one machine is now visually committed to a session that the communication layer has not fully negotiated. If the connection fails, you now need to unwind from a more advanced field state. Do you warp the player back? Do you show an error? Do you reset the link manager? Do all players unwind the same way? That one early warp creates cleanup complexity.

Now suppose the opposite bug. The link manager reaches a ready state, but the script does not advance because it is waiting for the wrong callback message. The communication side is ready, but the field side is stuck. To the player, this looks like a freeze or endless wait. Architecturally, the bug is a mismatch between the callback taxonomy and the script/task state waiting on it. That is why chunk_b75d95e146826302, the callback message taxonomy, is such a serious artifact. It is the menu of possible link-layer facts that other systems can react to.

One more scenario. Suppose the battle setup sees a global indicating link battle, but the participant role has not been initialized consistently. The battle starts, but input routing or opponent assignment is wrong. This is the nastiest class of bug because the transition appears to succeed. The defect only emerges after the consumer starts interpreting incomplete state. This is why I want you to write “committed” as its own state in your lab model. Committed means the consumer has everything it needs, not merely that the previous layer is tired of waiting.

Let’s tie that back to the concrete C architecture. FireRed’s frame loop gives different subsystems chances to make progress over time. Task allocation and task IDs, which we covered earlier through pokefirered/src/task.c, are a general mechanism for per-frame state machines. Main callbacks, from pokefirered/src/main.c, shape the broad mode of the program. Scripts provide authored field sequences. The RFU link manager provides communication state and messages. Battle setup bridges overworld into battle. None of those alone is “the Cable Club.” The Cable Club experience is the composition.

That composition is why the course keeps asking you to trace from records through globals, callbacks, tasks, scripts, structs, and functions. If you only trace data records, you miss the runtime negotiation. If you only trace functions, you miss the authored entry points. If you only trace globals, you miss who is allowed to set them and when. The professional skill is moving across layers without flattening them.

Let me ask another check for understanding. In the three-lane diagram, where would you place pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4? Left lane, map and script world. Where would you place the RFU mode constants from AgbRfu_LinkManager.h, chunk_8df030184c481b3b? Middle lane, link manager contract. Where would you place pokefirered/src/battle_setup.c, chunk_494d132a59422c85? On the boundary between the left and right lanes, because it uses overworld and script context to enter battle runtime. And where would you place a variable that records “this is a link battle”? Underneath, in shared session or global state, because multiple lanes may interpret it.

Good.

Now, I want to refine our vocabulary one more step. We should distinguish staging maps from resolution systems. A staging map is where the player is organized, positioned, and socially prepared for the multiplayer activity. A resolution system is the engine that executes the activity: battle or trade. The Battle Colosseum map is staging. The battle engine is resolution. The trade room is staging. The trade system is resolution. Staging can contain scripts, warps, attendants, player positions, and readiness checks. Resolution contains the rules of the multiplayer activity.

Why does that distinction matter? Because staging is allowed to be user-facing and forgiving. It can show messages, cancel, retry, ask for confirmation, and move the player around. Resolution is stricter. Once a battle begins, both sides must run compatible battle state. Once a trade begins, both sides must agree on selected Pokémon, data transfer, confirmation, and final mutation of party or storage. The handoff from staging to resolution is therefore a point of no casual return.

You can see a related pressure in pokefirered/src/daycare.c, chunk_119931105f546346, which includes trade.h among many other systems in its excerpt. I am not saying daycare is the Cable Club. I am pointing out a recurring FireRed pattern: systems that mutate Pokémon or present special flows often include and coordinate with several modules. The include list tells you where boundaries are being crossed. For link trade, that boundary is even more sensitive because mutation may involve two cartridges agreeing on exchanged Pokémon data.

Similarly, pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, includes link.h, trade.h, battle_main.h, party menu headers, item headers, and constants. The summary screen can be entered from different contexts, including link-adjacent contexts, and must read party data appropriately. Again, we are not re-teaching summary screen behavior. We are using it as evidence for a broader pattern: advanced FireRed UI and gameplay modules often sit at crossroads, and the correctness comes from context flags and contracts.

Now, in the Cable Club, the context is not only UI context. It is networked session context. That is a higher bar.

For your own engine designs, this is where I would recommend making the session object explicit. FireRed, being a GBA C codebase with its own global style, often expresses this through globals, structs, constants, and manager state. In a modern clone, you might create a LinkSession struct with fields like mode, role, expectedPlayers, connectedPlayers, readiness, localPlayerId, remotePlayerIds, and committedActivity. But the underlying requirement is the same. Do not let “we are on the link room map” substitute for “we have a valid battle session.”

Let me put that as a rule: location is not authorization. Being in the room does not prove the session is valid. It only proves the map system placed the avatar there. Authorization comes from the negotiated link state and the gameplay contract.

That rule applies to other systems too. Being on a shop map does not mean the bag transaction is valid. Being on a move relearner screen does not mean a move should be written before confirmation. Being in a save menu does not mean flash has been safely written. Across the course, we keep finding the same architecture lesson in different clothing: visible context and committed mutation are different things.

Now, let’s follow the handoff one more time, slower.

The player activates an event. The map script begins. The script may call special functions or set variables that begin link setup. A task or callback continues link negotiation over multiple frames. The RFU link manager, defined through contracts in AgbRfu_LinkManager.h, reports state changes using its callback message vocabulary. The script or task layer observes that the required state has arrived. The player is placed into the correct multiplayer staging map from the link map group. The session becomes committed to battle. Battle setup consumes the session flags and enters battle runtime. When the battle resolves, control returns through a callback or scripted continuation to overworld state.

That is the trace you should be able to reproduce in your notes. Not every function name has to be memorized. But each kind of artifact must be accounted for: data record, script, global or struct, manager callback, task or callback loop, battle setup, return path.

And because this is an advanced course, I want to emphasize what not to over-abstract. Do not say, “There is an event bus.” FireRed is not built as a modern generic event bus architecture. It has callbacks, tasks, scripts, globals, constants, and specialized managers. Do not rename those into a design pattern that hides the source. We can compare it to modern patterns, but when reading FireRed, use FireRed’s actual mechanisms. That is why source paths and chunk IDs matter. They keep our architecture claims grounded.

So, when you write your lab solution, I expect your simplified state machine to have names that correspond to real responsibilities. “Idle in Cable Club.” “Prompting player.” “Initializing link manager.” “Waiting for parent or child negotiation.” “Waiting for all players.” “Warping to staging map.” “Committing to battle.” “In linked battle.” “Returning to overworld.” “Failure cleanup.” Those are not necessarily FireRed’s exact internal enum names, but they are architecture-faithful states. They preserve the separation between map staging, communication negotiation, and gameplay resolution.

And one last warning before we move on: avoid making failure cleanup an afterthought. Multiplayer entry points fail in ordinary ways. The other player cancels. The signal drops. The expected number of players never arrives. One side selects a different mode. A timeout expires. In a single-player RPG, cancellation usually returns control to a local menu or script branch. In a link flow, cancellation must also leave the communication manager and peer expectations in a sane state. That is why the callback message taxonomy and link state constants are not peripheral. They are how failure becomes representable.

Let’s pause there and do a quick recap before the next segment. We have treated Cable Club maps as physical staging records, not as the whole multiplayer system. We connected the link map group in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, and the 4-player Colosseum map data in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, to the left lane of our board. We connected RFU mode constants, callback messages, link state constants, initialization parameters, and LINK_MANAGER state from pokefirered/include/AgbRfu_LinkManager.h, chunks chunk_8df030184c481b3b, chunk_b75d95e146826302, chunk_fc7074757ec1be96, and chunk_f549b5be90786ef0, to the middle lane. And we connected battle setup, especially the boundary-heavy include context in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, to the right-lane handoff into battle runtime.

The key sentence is this: the multiplayer entry point is not where the link exists; it is where map context, negotiated communication state, and gameplay commitment become true at the same time. Keep that sentence available, because next we are going to use it to inspect the boundary between linked battle and linked trade, and we will ask why the same Cable Club architecture can stage both activities without letting either activity completely own the overworld.

So, let’s put linked battle and linked trade side by side, because this is where a lot of advanced students accidentally flatten the architecture. They say, “Both are multiplayer, so they must be two flavors of the same subsystem.” That is half true, and half dangerously misleading. They share the Cable Club entry discipline. They share link negotiation. They share the fact that the overworld must temporarily give up authority. But they do not share the same gameplay owner after commitment.

On the board, I’m drawing one bracket around the Cable Club map and script side, and then two arrows leaving it. One arrow goes to linked battle. The other goes to linked trade. The important part is not that the arrows begin in the same room. The important part is that the room does not become the battle engine, and it does not become the trade engine. It remains the staging site.

Think back to our phrase from a minute ago: the multiplayer entry point is where map context, negotiated communication state, and gameplay commitment become true at the same time. For linked battle, gameplay commitment means, “we are now allowed to construct a battle session whose participants are remote players.” For linked trade, gameplay commitment means, “we are now allowed to expose party data, offer selection, synchronize choices, and perform a persistent mutation if the protocol completes.” Those commitments are very different, even though the hallway that gets you there is architecturally similar.

Now, let me make this concrete. Suppose the player walks into the Cable Club and enters a four-player Colosseum room. The left lane of our diagram starts with data, not with code. The map group data in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, gives us the high-level organization that says these maps are part of the link-facing map set. Then the specific BattleColosseum_4P map header and warp data in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, gives us the concrete space: the room, its exits, and its relationship to other maps. That is not multiplayer logic yet. It is spatial eligibility. It says, in effect, “this is a place where the rest of the system may stage multiplayer behavior.”

Check your understanding here: if I delete or corrupt the RFU link manager constants, does the map stop being a map? No. It may stop being useful as a multiplayer staging area, but the map record is still map data. Conversely, if I have a perfectly initialized link manager but I am standing in a normal Viridian City interior with no relevant scripts or warps, do I automatically enter a linked battle? Also no. This is the architectural separation we care about.

Now let’s move to the middle lane. The link manager header, pokefirered/include/AgbRfu_LinkManager.h, is acting as a contract surface. In chunk_8df030184c481b3b we have RFU mode constants. In chunk_b75d95e146826302 we have the callback message taxonomy. In chunk_fc7074757ec1be96 we have link state constants and initialization parameters. In chunk_f549b5be90786ef0 we have the LINK_MANAGER state. I want you to hear the design implication in that list. The header is not just giving you magic numbers. It is defining what other modules are allowed to believe about the communication machine.

For example, a mode constant tells the game which kind of RFU behavior is being attempted. A callback message tells the game what kind of event has arrived from the communication layer. A link state constant tells the game what phase the link session believes it is in. The LINK_MANAGER state holds the ongoing reality that cannot be reconstructed from one frame of input. So if you are building a simplified version for the lab, and you only model “connected true or false,” your model is too weak. You need at least enough state to distinguish “trying to connect,” “negotiating role,” “ready but not committed,” “committed to activity,” and “tearing down or returning.”

Let’s pause there. Why does parent or child role matter? Not because one cartridge is morally more important. It matters because a multiplayer protocol needs a stable authority relationship during negotiation. If every Game Boy tries to make the same decision at the same time, you need a deterministic way to resolve who announces, who listens, who accepts, who joins, and who treats a received message as authoritative. In the RFU model, parent and child are communication roles. Then the gameplay layer interprets those roles as part of session setup. So in your lab state machine, do not name your states only after visible rooms, like “inside Cable Club” and “inside battle.” Name some states after protocol commitments, because that is where the architecture actually has to be correct.

Now I’m going to write a short trace verbally. Data record: BattleColosseum_4P map entry. Runtime context: the overworld has loaded a map from the link map group. Script or trigger: the player interacts with the Cable Club flow that asks the system to begin a multiplayer activity. Globals and link state: the RFU link manager moves through its modes, callback messages, and state fields. Main-loop integration: VBlank and per-frame callbacks keep the communication state alive while the visible game is waiting. Commitment: the game crosses into linked battle setup or linked trade setup. Return: after the activity completes, control returns to an overworld-compatible state.

Notice what I did not say. I did not say the map script directly runs the whole battle. I did not say the link manager owns party mutation. I did not say VBlank is a gameplay state machine. Each part does its own job, and the handoff is the thing we are studying.

Now, right lane. For linked battle, the most useful evidence packet we have today is pokefirered/src/battle_setup.c, chunk_494d132a59422c85. Even from the include context, we can see why this file is boundary-heavy: it pulls in task.h, overworld.h, item.h, sound.h, pokemon.h, load_save.h, quest_log.h, script.h, event_data.h, event object movement, metatile behavior, field control, field player avatar, screen effects, battle.h, battle_transition.h, and battle controllers. That does not mean every include is equally active in every path. It means battle setup sits at a crossing where field state, script state, party state, transition visuals, and battle runtime must agree.

So, when the Cable Club sends us toward a linked battle, the battle setup side is not merely asking, “Can I draw the battle screen?” It must ask, “What kind of battle is this? Which participants exist? What are their parties? Which flags or special conditions apply? What callbacks will resume after the battle? What field state must be preserved or suspended?” For an ordinary wild battle, some of those answers come from map encounters and random rolls. For a trainer battle, some come from trainer data and scripts. For a linked battle, some come from negotiated remote state. Same battle engine family, different source of authority.

Let me ask a check question. If a linked battle desynchronizes, is that primarily a map problem? Usually no. The map staged the encounter. The desynchronization is more likely in communication state, battle command synchronization, or inconsistent data assumptions. But could a bad map or warp strand the player after the battle? Yes. That would be a return-path problem. Advanced debugging means you separate those two failures instead of saying, “multiplayer is broken.”

Now trade. Trade is interesting because it is more obviously a persistent data mutation. Battle may change some data after completion, but trade’s central purpose is to exchange party creatures and write the result into owned save-backed structures. This is where our earlier persistence weeks matter, but we are not going to re-teach sector validation or save block ownership. We only need the consequence: once a trade commits, it is not just a UI animation. It is a mutation of player-owned creature data that must survive. That makes the boundary stricter.

The source packet reminds us that trade is not isolated from the rest of the game’s UI and data world. For instance, pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, includes link.h, overworld.h, trade.h, battle_main.h, party_menu.h, item constants, move constants, and region map material. I am not claiming the summary screen is the Cable Club trade implementation. I am using the include context as architectural evidence: player-facing creature inspection lives near link, trade, battle, and party concerns. Likewise, pokefirered/src/daycare.c, chunk_119931105f546346, includes trade.h alongside pokemon storage, event data, menu, strings, party menu, naming screen, and overworld-related headers. Again, not because daycare equals Cable Club, but because creature ownership changes are embedded in a broad ecosystem of UI, events, and persistence-facing structures.

So, for a linked trade handoff, the Cable Club map and RFU manager should get us to a committed session, but once we are choosing creatures and confirming exchange, the trade-specific system must own the rules. Can this Pokémon be traded? What data must be displayed? Which party slot is selected? What happens if communication fails before final confirmation? What happens after final confirmation? Which side’s received creature gets written where? What scripts or callbacks resume afterward? These are not map questions. They are trade workflow and data integrity questions.

This gives us a useful design rule for clone projects: never let the lobby object mutate final player-owned inventory or party data just because the lobby initiated the flow. The lobby can request a trade session. The link manager can negotiate peers. The trade system performs the trade. The save system persists the result. That division is what prevents “entering a room” from becoming an all-powerful god function.

Let’s bring in our prior UI state-machine work for one minute. When we studied move replacement and the move relearner, using pokefirered/src/learn_move.c, chunk_af3598df6f092723, we emphasized menu callbacks, task states, confirmation branches, and controlled writes to Pokémon data. Today’s trade boundary has the same discipline at a higher risk level. A move relearner changes one creature’s move slots under local control. A link trade changes ownership across two machines under communication constraints. The architectural pattern is familiar: staged selection, explicit confirmation, controlled mutation, return callback. The stakes are higher because there is another cartridge involved.

Now, imagine I am drawing the lab model on the board. We start with OverworldIdle. The player steps onto or activates a Cable Club trigger. We move to CableClubScript. The script requests LinkInit. LinkInit configures RFU mode and initialization parameters, echoing the contract ideas from pokefirered/include/AgbRfu_LinkManager.h, chunk_fc7074757ec1be96. Then we enter NegotiatingRole. Callback messages arrive, in the spirit of the taxonomy from chunk_b75d95e146826302. We branch into ParentReady or ChildReady, not because the visible UI must show those names, but because our internal model must know who is coordinating. Once both sides agree on the intended activity, we enter ActivityCommitted. From there we branch to LinkedBattleSetup or LinkedTradeSetup. After completion, we enter LinkTeardown, then ReturnToCableClubMap, then OverworldIdle.

Now, if you are thinking, “Professor, that sounds like more states than the player sees,” yes. Exactly. Good architecture often has more internal states than visible screens. The player sees “walk into room, talk to attendant, battle or trade, come back.” The engine sees “map load, script dispatch, RFU initialization, callback messages, role negotiation, activity selection, battle or trade setup, callback replacement, teardown, map restoration.” One of the major goals of this course has been to make you fluent in that hidden chain.

Let’s do a concrete failure example. A student clone stores a variable called currentMode, with values “overworld,” “link,” and “battle.” When the player enters the Cable Club, currentMode becomes “link.” When negotiation succeeds, currentMode becomes “battle.” The battle ends, and the clone sets currentMode back to “overworld.” That sounds fine until a disconnect happens after negotiation but before battle setup. What state is the game in? “Link” is too broad. Does it still have a parent? Does it have a selected activity? Has the room script already advanced? Is the player frozen? Is the return warp active? Should the UI show a message? Should the trade or battle system clean up anything? The variable does not know. So the clone either crashes, soft-locks, or teleports the player incorrectly.

FireRed’s architecture is more segmented than that. We have map data that can be loaded independently. We have global link state that can represent communication phases. We have callbacks and tasks that allow staged per-frame progress. We have battle setup as a boundary module. We have trade and party-facing code that sits near creature data and UI. This is why the course keeps returning to the same trace: data records, globals, callbacks, tasks, scripts, structs, concrete source functions. Not because repetition is fun, but because that trace is how the real engine avoids turning every feature into one giant conditional.

Now, a quick comparison to our secondary Showdown material, just to position it correctly. Pokemon Showdown is useful when we want deterministic battle data hooks, simulator turns, and regression testing patterns, especially in sources like pokemon-showdown/data learnset and mod files from the research packet. But for today’s question, Showdown is not the primary model, because the hard problem today is not just “how do battle rules resolve?” It is “how does a handheld RPG move from an overworld map into a communication-dependent local runtime and then back?” FireRed is the authority for that architecture. Showdown can inspire testing discipline later, but it does not replace the Cable Club, map, RFU, and callback structure we are studying.

Let me check the room. If I ask you, “Where is the multiplayer entry point?” the novice answer is “the link manager.” The better answer is “the Cable Club script.” The advanced answer is “the entry point is a boundary condition formed by map context, script progression, link manager state, and activity-specific handoff.” That is the answer I want you to carry into the homework.

Now, let’s recap the two handoffs. For linked battle, the communication system establishes that remote players exist and are ready. The battle setup side accepts the commitment and constructs a battle runtime with link-aware participant assumptions. It must coordinate with callbacks, transitions, party data, battle controllers, and return behavior. For linked trade, the communication system also establishes readiness, but the trade workflow then owns selection, confirmation, exchange, and persistent party mutation. The Cable Club does not become either of those systems. It is a carefully controlled threshold.

And this is the larger week 21 lesson. On Monday, when we treated the link manager as a communication state machine, we focused on RFU modes, callback messages, VBlank timers, and link state. Today, we anchored that abstract machinery in actual spaces: the link map group in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, and the four-player Colosseum map in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246. Then we followed the path into activity handoff, using pokefirered/src/battle_setup.c, chunk_494d132a59422c85, as our evidence that battle setup is a boundary between overworld, script, party, visual transition, and battle runtime concerns. The week is not “wireless trivia.” The week is system integration under communication constraints.

So, when you work on the lab model, do not start by drawing screens. Start by drawing authority. Who owns the room? Who owns negotiation? Who owns readiness? Who owns battle construction? Who owns trade mutation? Who owns return to the overworld? Once you have those owners, your states become much easier to name. Your failure cases become much easier to test. And your clone starts to resemble an architecture instead of a pile of event handlers.

Here is the closing synthesis for today. FireRed’s Cable Club architecture is powerful because it refuses to collapse place, protocol, and gameplay into a single concept. The map data says where multiplayer may be staged. The scripts and overworld machinery decide when the player has requested an activity. The RFU link manager records and reports communication reality through constants, states, parameters, and callback messages. The battle and trade systems accept control only after the session has crossed a commitment boundary. And after the activity, the game must return to the overworld without pretending the battle engine or trade workflow owns the map forever.

That is the design pattern I want you to recognize beyond FireRed. Any serious creature RPG with local or online multiplayer needs an equivalent threshold. It may not have a Cable Club. It may not have RFU. It may not have GBA callbacks. But it will still need staging context, negotiated session state, activity-specific ownership, failure handling, and return discipline. If you can trace that path from a data record to a global state field to a callback to a task or script to a concrete gameplay handoff, you are no longer just reading decompiled code. You are reading the architecture.

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