# Link Manager as a Communication State Machine

Lecture ID: lecture_w21_d01
Week 21, Day 1

## Objectives
- Decompose a complete creature-collector RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems.
- Connect week 21 material to the overall course plan.

## Transcript
All right, let’s pick up from the systems integration side of the course and move into the link layer. Up to this point, you have seen FireRed as a collection of cooperating state machines: the runtime shell owns callbacks and tasks, the overworld owns maps and scripts, the creature model owns party data, the battle engine owns turn execution, the inventory and UI layers mediate player choices, and the save system decides what survives power-off. Today we add the awkward one: communication with another machine.

And I want to emphasize that word, awkward, in a technical sense. Linking two Game Boy Advance systems is not just “call a battle function with another player.” It is not even “open a socket,” because we are not in a modern operating system with buffered streams, threads, and a kernel networking stack. FireRed has to maintain a real-time local gameplay loop while hardware communication advances in small steps. So the link manager becomes a communication state machine, and the rest of the game has to treat it as a system boundary.

Let me put the first board diagram up. On the left, write “overworld map and script.” In the middle, write “link manager.” On the right, write “battle or trade runtime.” Now draw arrows from the overworld into the link manager, and from the link manager into battle or trade. But do not draw one direct arrow from the Cable Club NPC straight into battle. That would be the beginner’s architecture mistake. The Cable Club map may provide the player-facing doorway, but the link manager owns whether a connection exists, what role this machine plays, whether the session is stable, and what kind of multiplayer mode we are entering.

This is the same pattern we have seen before, only with higher stakes. When we studied menu callbacks and task-backed state machines, especially around the move relearner in pokefirered/src/learn_move.c, chunk_af3598df6f092723, the lesson was that visible behavior is often the final result of a task loop stepping through internal modes. We are not going to re-teach that today. Just reuse it. A menu state machine can assume it owns the local UI. A link state machine cannot make that assumption. It must coordinate with another console that is running its own frame loop, its own VBlank timing, its own local state, and possibly its own player input.

So our lecture question is this: how does FireRed turn a fragile physical communication process into something the rest of the game can reason about?

The answer is that it uses constants, callbacks, role state, timer state, and carefully chosen handoff boundaries. Each of those is important. Constants define the vocabulary. Callback messages classify events. Role state separates parent behavior from child behavior. Timers make the link system frame-aware. Handoff boundaries prevent battle, trade, overworld, and save from all grabbing the same responsibility.

Now, look at the source focus for today. The RFU mode constants are in pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b. The callback message taxonomy is in the same header, chunk_b75d95e146826302. Link state constants and init parameters are in chunk_fc7074757ec1be96, and the LINK_MANAGER state structure is in chunk_f549b5be90786ef0. Those chunks matter because they show us the contract. Not the implementation body yet, but the shared language that other modules can rely on.

This is a good place to pause and separate two ideas that are easy to merge incorrectly. A mode constant is not the same thing as a state constant. A mode describes what sort of link behavior we are attempting: maybe parent, maybe child, maybe a mode related to finding or joining peers. A state describes where we are in the process: initializing, waiting, connected, transferring, erroring, disconnecting, and so on. In a clean architecture, mode is the intent and state is the progress through that intent. FireRed’s RFU link manager has to carry both, because “I am trying to be the parent” and “I have successfully negotiated a child connection” are not the same fact.

If you are building your simplified model for this week’s lab, that distinction is one of the first things I will check. If your state machine has a single enum with entries like “parent,” “child,” “battle,” and “trade,” you have mixed role, protocol progress, and gameplay destination into one pile. That might work for a toy demo, but it is not the shape of FireRed. A better design has at least a role field, a connection state field, and a destination or requested activity field.

Let’s make this concrete. Suppose the player walks into the Cable Club and steps on the warp or talks to the attendant that leads toward a four-player Colosseum. The map data is not just flavor. The link maps are part of the architecture. The research packet points us to pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, for the link map group, and to pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, for the four-player Colosseum map header and warps. That means multiplayer is not implemented only as a hidden system menu. It has spatial entry points. The player enters a map whose layout, warps, and scripts create a controlled staging environment for the communication handoff.

Why is that useful? Because a creature-collector RPG already has an overworld model for player intent. Maps and scripts are how the game represents “you are in a place where this behavior is legal.” The Cable Club is not just a room. It is a gatekeeper. It narrows what the player can do, places them near attendants or warp tiles, and prepares the rest of the runtime to stop treating the player as a purely local avatar.

Now, let’s connect that to the complete decomposition objective for the course. Runtime shell: the main loop and callback slots keep frames moving. Overworld maps: the Cable Club and Colosseum data give the player a physical entry point. Event scripting: NPCs, warps, and script commands initiate the link attempt. Creature data: the party or selected Pokémon becomes the payload for battle or trade. Battle: a linked battle uses a different boundary than a wild or trainer battle. Inventory and UI: choices still exist, but they must be constrained by synchronization. Save: persistent data must not be half-written because a cable or RFU session glitched. Link system: the manager coordinates role, connection, messages, timing, and handoff.

That is the whole course stack in one feature. Week 21 is here because link play forces all the prior modules to negotiate ownership.

Let’s do a board-work pass on the state machine. I’m going to draw a simplified version, not the exact production enum. First state: idle in overworld. Second: player enters Cable Club context. Third: initialize RFU or link parameters. Fourth: advertise or search, depending on role. Fifth: negotiate parent and child relationship. Sixth: stabilize connection. Seventh: exchange session intent, such as battle or trade. Eighth: hand off to battle or trade runtime. Ninth: return, clean up link state, restore overworld.

Now, check your understanding here. Which of those states belongs to the map system? Only the early visible context and the return destination. Which belongs to the battle system? The linked battle runtime after handoff. Which belongs to the link manager? The middle: initialization, role negotiation, connection stability, callback messages, and session readiness. If your answer puts “negotiate parent and child” in the battle engine, that is the bug. Battle should not need to know how RFU parent discovery works. Battle should receive a synchronized multiplayer environment, not build one from raw communication hardware.

This is where the callback message taxonomy in pokefirered/include/AgbRfu_LinkManager.h, chunk_b75d95e146826302, becomes important. A communication layer cannot merely expose “connected true” or “connected false.” That is too crude. It needs to say what kind of thing has happened. A peer appeared. A peer disappeared. A transfer completed. A connection failed. A state changed. An error occurred. The exact message names are part of the header contract, but architecturally the point is this: callbacks let the low-level link progression report events upward without forcing every caller to poll every internal field.

Now, we have to be careful with the word callback because we have used it elsewhere in the course. Earlier, when we looked at SetMainCallback2 in pokefirered/src/main.c, chunk_6cfd90f6d0136799, we were talking about the main runtime callback slot, the function that owns the high-level per-frame mode. And when we studied RunTasks in pokefirered/src/task.c, chunk_3ffebc0341225936, we were talking about task dispatch inside that frame. Link manager callbacks are a related architectural idea, but not the same callback slot. They are messages from a communication subsystem to its owner or listener. Same pattern family, different layer.

Let me give you an example. Imagine the local player chooses “link battle,” and this machine is acting as the parent. The parent starts advertising or waiting. Another machine joins as a child. If the link manager only returned a boolean, the overworld would ask each frame, “Are we linked yet?” But linked for what? With whom? Stable for how many frames? Did the child disconnect and reconnect? Is the remote machine in the same mode? A callback message taxonomy allows the link manager to report events with more precision. The higher-level logic can then step from “waiting for partner” to “confirming session” to “starting battle handoff” without pretending the communication process is atomic.

And atomicity is the thing you should be suspicious of. Any time you hear yourself say, “When the player selects link battle, the game enters link battle,” slow down. In local code, that may look almost true. In link code, there is a negotiation gap. The state machine lives in that gap.

Now, VBlank timers. Why are we talking about VBlank in a link lecture? Because on the GBA, time is not an abstraction you can ignore. The game’s rendering and input rhythm is frame-based, and communication progress is coordinated around hardware and interrupt-driven timing. The source focus explicitly includes VBlank timers and link state, and the link state constants and init parameters in pokefirered/include/AgbRfu_LinkManager.h, chunk_fc7074757ec1be96, are our header-level evidence that initialization and state management are first-class, not incidental. A robust link manager needs to know not just what mode it is in, but how long it has been waiting, whether periodic maintenance has happened, and whether a timeout or transition condition should fire.

Think of it as a state machine with a pulse. A menu state machine can step when the player presses A or B. A battle state machine can step when animation, input, or script processing reaches a boundary. A link manager must also step because time itself has passed. If no partner appears for enough frames, that means something. If a child does not respond after a handshake phase, that means something. If data exchange cannot be confirmed within a window, that means something. So the VBlank timer is not decoration. It is part of the logic that converts raw waiting into protocol meaning.

Let’s pause for another check. If I ask, “Why not make the Cable Club script wait in a loop until the link is ready?” what should your answer be? The first answer is that scripts are not the right owner for low-level communication timing. The second is that blocking the game’s frame progression would interfere with all the per-frame systems that must continue to run. The third is that a script does not have the vocabulary to distinguish all the link manager’s internal events unless the link manager translates them into stable messages. That is the role of the communication state machine.

And this is also why the LINK_MANAGER state structure in pokefirered/include/AgbRfu_LinkManager.h, chunk_f549b5be90786ef0, deserves attention. Structures are architectural memory. They tell you what the system must remember between calls. If a subsystem were truly simple, it could be a function with arguments and a return value. But a link manager must remember current mode, current state, peers, timers, callbacks, buffers, and probably flags that represent transitional facts. The presence of a named LINK_MANAGER state is a clue that the game treats communication as an ongoing actor inside the runtime, not a one-shot utility.

I want you to compare that to save persistence from last week. In the save system, we cared about ownership, sector validation, and flash read/write boundaries. We are not redoing that material, but notice the similarity: the dangerous systems are the ones where an external reality can interrupt you. Flash memory can fail or be partially valid. A wireless or cable link can drop or desynchronize. So FireRed wraps those realities in stateful managers that present safer boundaries to the rest of the game.

Now, where does battle come in? A linked battle is still a battle, but it is not born the same way as a wild battle. In pokefirered/src/battle_setup.c, chunk_494d132a59422c85, the source packet identifies battle setup concerns around constants, transition, load, and event integration. That is the local battle-entry side of the architecture. For ordinary battles, the overworld or script can set battle parameters, run a transition, and hand control to battle callbacks. For link battles, the setup must be gated by communication readiness. The system cannot let one player enter the battle engine while the other is still choosing or while the parent-child relationship is unresolved.

So the handoff boundary should be phrased like this: the overworld and Cable Club establish intent; the link manager establishes synchronized session readiness; then the battle or trade system receives control. Do not phrase it as “the Cable Club starts battle.” That skips the most important system in the middle.

Let’s use a four-player Colosseum example. The map in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, gives us a concrete multiplayer staging space. Four players are not just four independent local avatars. The game must know which consoles are present, which one coordinates, and when the group is ready. If one player backs out, the state machine must not leave the other three in a battle-start transition. If the parent system changes state, the child systems need to respond coherently. The map header and warps define where this can occur in the world; the link manager defines whether the multiplayer session is actually valid.

You can see why this week’s lab asks you to model 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. That is not a random exercise. It is the smallest complete slice of the whole architecture. You must start from spatial data, step through event initiation, represent communication state, hand off to battle, and cleanly return.

Let me say that again as a recap, because it is the central thesis for today. Link play is not a feature bolted onto battle. It is a cross-system corridor. The player enters that corridor through map data and event scripts. The link manager owns the unstable middle. Battle or trade owns the synchronized activity after the connection is ready. Save and party data sit behind the activity, and they must only be mutated at safe points.

Now, an advanced point. The RFU mode constants in pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b, are not merely labels for hardware modes. They are a compression of design choices. When a game distinguishes RFU modes, it is deciding which behaviors are legal together. Searching is different from advertising. Parent is different from child. Waiting for data is different from delivering gameplay. The constants protect the rest of the code from making vague requests like “do wireless stuff.” Instead, the code can request or observe a particular mode.

In your own clone architecture, this is where you should resist overgeneralization. Do not create a LinkManager with one method named updateConnection and one field named status. Give it an explicit mode. Give it a session role. Give it a connection phase. Give it callback messages or event outputs. Your model does not need the full FireRed complexity, but it should preserve the separation of concepts.

Here is a concrete pseudo-flow, spoken rather than written as code. The player steps into the Cable Club battle entry. The map script asks the runtime to begin a link session for a battle. The link manager initializes using parameters appropriate to that mode. The local machine becomes a parent or searches for a parent as a child. During VBlank-paced updates, the manager receives lower-level communication events and translates them into callback messages. The overworld-facing task watches those messages. Once all required participants are present and the selected activity agrees across machines, the task freezes or exits the staging UI, prepares battle parameters, and transfers control to the linked battle runtime. When the battle ends, the result path returns through cleanup, not directly to arbitrary overworld movement.

Notice how many state boundaries that sentence crossed. That is why this material is week 21, not week 3.

One more check for understanding before we move deeper in the next portion. If I give you the phrase “Cable Club map group,” what system should come to mind first? The overworld map registry. That is supported by pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4. If I give you “RFU callback messages,” what should come to mind? The link manager’s event vocabulary, supported by pokefirered/include/AgbRfu_LinkManager.h, chunk_b75d95e146826302. If I give you “linked battle handoff,” what should come to mind? A boundary between synchronized communication readiness and battle setup, with battle setup concerns tied back to pokefirered/src/battle_setup.c, chunk_494d132a59422c85. Those are three different layers. Keeping them separate is how you avoid designing a multiplayer feature that works only when nothing goes wrong.

And of course, things do go wrong. Players cancel. Players walk away. Wireless discovery fails. Timing windows expire. The other machine may be in the wrong mode. A local UI may be ready before the remote side is ready. Advanced architecture is not about assuming the happy path is complicated; it is about making the unhappy paths representable. A communication state machine is how FireRed gives names to those in-between conditions.

So, as we continue, keep the board diagram in your mind: map and script on the left, link manager in the center, battle or trade on the right. The center is not passive plumbing. It is an active stateful manager with constants, callback messages, role negotiation, VBlank-aware timing, and a cleanup path. In the next stretch, we will zoom further into the message taxonomy and talk about how to design your simplified lab model so that parent and child machines can disagree temporarily without corrupting the session.

So let us make that disagreement concrete. Suppose the player walks into the Cable Club, chooses a link battle, and the local game has already committed the UI to “we are entering a multiplayer mode.” That is a local promise. It is not yet a global fact. The remote machine may still be selecting a menu option, or may have failed negotiation, or may be present but in a different RFU mode. If your architecture has only two states, “not linked” and “linked,” you have nowhere honest to put that period of uncertainty. You will either lie to the overworld and say the battle exists too early, or you will keep the UI frozen without any named explanation of what it is waiting for.

This is why, when we look at FireRed’s RFU link layer, the interesting thing is not merely that it can communicate. The interesting thing is that it gives communication phases names. In pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b, the RFU mode constants are part of that naming system. They are not graphics. They are not battle rules. They are not map layouts. They are a vocabulary for the communication shell. Then in the same header, chunk_b75d95e146826302 gives us the callback message taxonomy, and that is even more revealing architecturally, because messages are how the link manager reports partial progress, failure, role changes, and readiness to the code above it.

Let me write this on the board as a small trace. Not full code, just the runtime story. First, player enters Cable Club space. Second, script or field interaction requests link setup. Third, link manager enters a negotiation state. Fourth, callbacks report messages upward. Fifth, only after the required role and readiness conditions exist do we hand off to battle or trade. That fourth step is where many clone projects become brittle. They treat callbacks as little helper functions, as if the only point is to notify the UI. But here the callback message is a boundary object. It translates low-level link events into decisions that the rest of the engine can safely understand.

Now, check yourself on this distinction. If I say, “The local machine selected link battle,” is that enough to start a battle engine? No. It is enough to begin attempting the handoff. If I say, “The local and remote machines have negotiated roles and the link manager has reported the appropriate ready message,” now we are much closer. The difference is the difference between user intent and communication state. Advanced architecture keeps those separate.

This should sound familiar from our earlier menu work, but the risk profile is higher. In the move relearner and summary screen lectures, we saw UI state machines that wait for a player choice, then mutate party data through controlled branches. For example, pokefirered/src/learn_move.c, chunk_af3598df6f092723, was useful to us because it made the move relearner’s state machine visible as a sequence of named menu states and callbacks. But the move relearner is local. If the player cancels, you unwind a local screen. If the player confirms, you write local party data. In RFU link, you are coordinating two or more independent machines. Cancellation is not just “close the menu.” It is a communication event that must leave both sides able to recover.

That is why the callback message taxonomy matters. Imagine a simplified version for your lab. I do not want you to copy FireRed line for line. I want you to model the architectural shape. You might define messages like “search started,” “partner found,” “role assigned,” “session ready,” “remote cancelled,” “connection lost,” and “handoff complete.” Now notice something: not all of these are successes or failures. Some are intermediate facts. “Partner found” is not “battle ready.” “Role assigned” is not “map cleanup complete.” “Session ready” is not “battle engine owns the frame loop.” A good message taxonomy makes those distinctions cheap.

Let us connect this to the week’s other source focus. In pokefirered/include/AgbRfu_LinkManager.h, chunk_fc7074757ec1be96, the link state constants and initialization parameters show that link setup is parameterized, not just toggled. That is important. A link mode is not merely a Boolean switch. The manager needs to know what kind of link activity it is organizing, what assumptions to use during initialization, and which states count as valid next states. Then chunk_f549b5be90786ef0, also in AgbRfu_LinkManager.h, gives us the LINK_MANAGER state itself, which is the persistent runtime record of this subsystem. In course language, this is the same pattern we have used all semester: a data or constant vocabulary, a runtime state holder, and callback-driven behavior that makes player-visible transitions possible.

Now I want to pause and ask a diagnostic question. If you were implementing this from scratch, where would you store “I am currently the parent machine”? Would you put it in the battle engine? Raise your hand mentally if that was your first instinct. It is tempting because battle is the thing you are trying to reach. But no, that role belongs to the link setup layer until the handoff is complete. The battle engine may later need to know player ordering or controller assignment, but the negotiation role, parent or child, is a communication concern first. If you let the battle engine own it too early, you create a bad dependency: now battle startup must understand failed RFU negotiation. That is exactly the kind of cross-module leakage this course has been teaching you to avoid.

So on the board, I am going to draw a box labeled “Link Manager,” and inside it I am going to put three small fields: mode, role, and phase. These are conceptual fields, not a claim about exact struct names. Mode answers, “What type of link operation are we attempting?” Role answers, “What is this machine’s relationship to the session?” Phase answers, “Where are we in the negotiation lifecycle?” Around that box I am drawing arrows to the overworld map and script system on the left, and to the battle or trade system on the right. The important feature of the diagram is that battle and trade do not receive every raw RFU detail. They receive a completed handoff, or they do not start.

Now, where do maps enter this? They enter as controlled multiplayer entry points. FireRed does not let arbitrary grass tiles become ad hoc wireless negotiation surfaces. It has Cable Club spaces, and those spaces are data. The packet points us to pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, for the link map group. That source is useful because it reminds us that multiplayer has geography in this engine. It is not only a protocol. It is also a map group, a set of rooms, warps, and scripts that constrain how the player reaches the protocol. Then pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, gives us the 4-player Colosseum map header and warps. That matters because a four-player battle space is not just a larger menu. It is a map with explicit entry and exit structure, and those warps are part of the system boundary.

Think about that from the player’s point of view. The player believes they walked through a door, talked to an attendant, and entered a battle room. From the engine’s point of view, several systems lined up. The map data positioned the player in a controlled space. The event script requested a link activity. The link manager negotiated communication state. The battle subsystem received control only at the appropriate boundary. And after completion, the overworld has to be resumed in a coherent place. That is the full-stack decomposition we have been building since week one: runtime shell, overworld maps, event scripting, creature data, battle, inventory and UI, save, and now link systems. Week 21 is where the shell has to coordinate with another machine’s shell.

Here is the concrete example I want you to keep for the lab. We will call it a two-player linked battle, though the same shape extends to four-player. Player A enters the Cable Club map and selects battle. Player B does the same. Player A’s system enters a “seeking partner” phase. Player B’s system also enters a “seeking partner” phase. One machine becomes parent, one becomes child. The parent may be responsible for coordinating session parameters. The child may wait for those parameters. Both machines receive callback messages, but not necessarily in the same order at the exact same frame. This is the part that advanced students must take seriously: the state machine must allow temporary asymmetry.

Let me say that again in a slightly different way. Synchronized play does not mean identical local histories. It means compatible local histories that converge at synchronization points. Player A may know “I am parent” before Player B has rendered its next prompt. Player B may know “I found a parent” before Player A has finished a local UI transition. If both machines require all flags to flip in the same frame, your model will be unrealistic. FireRed’s use of link states, modes, callback messages, and VBlank-aware timing exists because communication is temporal, and temporal systems need intermediate representations.

Now, VBlank. We are not going to re-teach the GBA rendering model today, but we do need to bring one prior idea forward. In a GBA game, certain work is synchronized around vertical blanking because graphics updates, timing, and hardware interaction are sensitive to frame boundaries. When the week context says VBlank timers and link state, it is pointing at the fact that link maintenance cannot be treated as an occasional blocking function call from a menu. It is part of the per-frame life of the program. Earlier, when we studied AgbMain and main callbacks, we saw that the runtime shell has explicit callback slots, with SetMainCallback2 in pokefirered/src/main.c, chunk_6cfd90f6d0136799, serving as one of our anchors for how high-level modes take over the frame loop. We also studied RunTasks in pokefirered/src/task.c, chunk_3ffebc0341225936, as per-frame dispatch. Bring that foundation forward now: link setup lives in that world of repeated frame advancement, callbacks, and state progress, not in a single blocking “connect” call.

Check for understanding here. Why would a blocking “connectToOtherGame” function be dangerous in this architecture? One reason is user experience: the screen would freeze or fail to animate properly. Another reason is hardware timing. Another reason is cleanup: if the remote side disappears, you need to update local state, messages, and possibly map scripts over multiple frames. And a fourth reason, more architectural, is ownership. The runtime shell expects work to be broken into callbacks and tasks. A blocking function would fight the grain of the engine.

This is also where save system knowledge quietly matters, even though we are not re-teaching save sectors. A link session should not casually mutate durable state at every intermediate phase. You do not want “partner found” to write a permanent outcome. You do not want “entered room” to imply “trade completed.” From week 20, remember the principle of save block ownership and validation boundaries. Link activities may eventually produce persistent consequences, such as a traded creature or battle record, but those writes must occur at committed points, not during speculative negotiation. The link state machine protects the save system by separating attempt from result.

Now let us layer in battle handoff. The battle engine already has its own complex state, controllers, turn logic, messages, and party reads. In pokefirered/src/battle_setup.c, chunk_494d132a59422c85, we saw battle setup sitting at the boundary between field conditions, scripts, transitions, and battle entry. That source is a good reminder that battles do not simply appear. They are staged. Link battles add another staging requirement: the battle setup cannot be complete until communication roles and session parameters are stable. So in your lab model, do not let the map trigger directly instantiate battle state. Let it request link setup, let link setup resolve, and then let a handoff function create the linked battle mode.

If you are thinking, “This sounds like overengineering for a class model,” good, that is the moment I want. It is overengineering if your only goal is to make two mock objects call each other once. It is not overengineering if your goal is to model a creature-collector RPG architecture with failure, cancellation, and return-to-overworld behavior. The architecture is not complicated because the designers loved complexity. It is complicated because the player can leave, the peer can vanish, the wrong mode can be selected, the UI can be ahead of communication, and the battle engine must not inherit a half-formed session.

Let us do a small verbal simulation. I will call out local state and remote state, and you decide if battle may start. Local state: in Cable Club map, battle selected, RFU mode initialized, no partner found. May battle start? No. Local state: partner found, local role parent, waiting for child confirmation. May battle start? No. Local state: parent and child roles assigned, both report session ready, map script has completed fade transition, battle handoff requested. May battle start? Yes, now it is plausible. Local state: session ready, but remote sends cancellation before handoff complete. May battle start? No, and the system needs a cleanup path back to overworld or menu. That final case is the one that separates a toy state machine from a robust one.

Notice how the map remains relevant even when communication is central. If the session fails, where does the player stand? What script resumes? Which attendant text appears? Which warp returns the player? The data in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, is not merely decorative because warps define how the multiplayer space connects back to the rest of the overworld. The link manager can say “session failed,” but the map and script layer must turn that into a player-visible recovery. This is why we keep drawing the architecture as cooperating state machines rather than one giant global procedure.

Now, let me make a comparison that will help those of you who used Pokemon Showdown in the week 20 homework. Showdown is very useful for deterministic battle data hooks and simulator tests, and the research packet repeatedly points us toward pokemon-showdown/data and test-oriented battle modeling as a secondary comparison. But for today’s topic, FireRed remains the main authority because we are studying a handheld runtime with maps, callbacks, hardware timing, and communication state. Showdown can teach us a lot about battle determinism. It does not replace FireRed’s problem of moving from overworld room to RFU negotiation to linked battle and back again. So do not flatten today’s lab into a server-style matchmaker unless you explicitly preserve the local runtime shell.

I am going to put one more diagram on the board. It is a sequence, left to right. “Cable Club trigger,” then “link init parameters,” then “mode selection,” then “parent-child negotiation,” then “ready messages,” then “battle or trade handoff,” then “return and cleanup.” Under that line I am drawing a second line labeled “possible failure points.” Under Cable Club trigger, cancellation. Under mode selection, wrong mode. Under negotiation, no partner. Under ready messages, timeout or disconnect. Under handoff, remote cancel. Under return, cleanup mismatch. A strong state machine does not pretend those failure points are exceptions outside the design. It names them.

Let us connect that to constants. Constants are not glamorous, but in this part of the engine they are a design tool. RFU mode constants from pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b, prevent the code from treating every communication attempt as the same kind of attempt. Callback message constants from chunk_b75d95e146826302 prevent the code from collapsing every event into “success” or “failure.” Link state constants and init parameters from chunk_fc7074757ec1be96 prevent startup from becoming an unstructured pile of flags. The LINK_MANAGER state from chunk_f549b5be90786ef0 gives the process memory across frames. Together, those pieces are the communication state machine’s skeleton.

Now, a check for understanding before we move into designing the lab model. Suppose your simplified implementation has a variable called linkedBattleReady. It starts false. When the player selects battle, you set it true. What bug have you just created? The bug is that you have confused local intent with negotiated readiness. Suppose instead you set linkedBattleReady true when the remote partner is found. Still wrong. Partner discovery is not the same as agreed battle parameters. Suppose you set it true after both sides exchange a ready message and the overworld has yielded to the handoff. Now you are much closer, because the variable corresponds to a stable boundary.

And I want you to hear how this relates to the course objective about decomposing a complete creature-collector RPG. Decomposition is not making a list of modules. It is deciding what facts belong to which module and when they become valid. Creature data belongs in the party and species systems. Battle turn resolution belongs in battle. Bag selection belongs in inventory and UI. Durable writes belong behind save boundaries. Map entry belongs to overworld and scripts. Link role negotiation belongs to the link manager. The handoff boundary is where those facts are translated, not where all modules become one another.

Let us take trade for a moment, because it exposes the same pattern with a different consequence. In battle, the consequence is a synchronized contest. In trade, the consequence may be permanent party or storage mutation. That makes the timing of commitment even more delicate. A trade UI can show a candidate creature before the trade is committed. The remote side can choose a candidate before both sides confirm. The link session can exist before the save-impacting exchange is finalized. If you write the traded creature too early, cancellation or disconnect can corrupt the player’s collection. So the communication state machine is also a data integrity tool. It keeps “preview,” “agreement,” “transfer,” and “commit” conceptually distinct.

That is the same discipline we used in week 20 when discussing save loading and sector validation, just applied one layer earlier. The save system protects persistent bytes. The link manager protects the path toward events that might eventually change those bytes. It is a guardrail before the guardrail.

Now, in your lab, I want your simplified machine to include at least one temporary disagreement between parent and child. For example, parent state says “sent battle offer, waiting for child accept,” while child state says “received battle offer, showing confirmation.” Those are compatible states, not identical states. Then model the child accepting, parent receiving acceptance, and both sides entering “ready to hand off.” Add a cancellation path where the child backs out while the parent is waiting. The parent should not crash. It should receive or infer cancellation, clean up link state, and return control to the Cable Club script or menu. If your model can represent that sequence cleanly, you have captured the architectural lesson.

As we continue after this, we are going to look more closely at the handoff boundary itself: not the RFU electrical details, but the software contract between the communication state machine and the battle or trade system. Keep the current board diagram in view. The key idea for the next stretch is that “connected” is still not the same as “owned by battle,” and “ready” is still not the same as “safe to persist.”

So let’s put a vertical line on the board between those two ideas. On the left side I’m going to write “link ownership,” and on the right side I’m going to write “mode ownership.” Link ownership means the communication layer has enough peer information, timing, role assignment, and message exchange discipline to keep the session alive. Mode ownership means some higher system, battle or trade or record mixing or whatever feature we are entering, is now allowed to interpret the session as its own gameplay context. Those are not the same boundary.

This distinction is one of the reasons the link manager belongs in our architecture course, not just in a hardware course. If all we cared about were radio bytes, we could hide this behind a send and receive API and move on. But FireRed is a creature-collector RPG with a callback shell, overworld maps, event scripts, creature data, battle state, inventory state, UI state, save state, and link state. The link manager sits right at the junction where several of those subsystems could make dangerous assumptions about one another. The player thinks, “I walked into the Cable Club, talked to the attendant, and now I’m battling.” The software has to say, much more carefully, “I was in an overworld map, an event script requested a multiplayer feature, the communication layer negotiated roles and readiness, the shell is about to swap control to another state machine, and only then may battle code treat the other Game Boy as a battle participant.”

Let me point to the source anchors for that claim. The RFU mode constants in pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b, give us the vocabulary for the communication modes. The callback message taxonomy in the same header, chunk_b75d95e146826302, gives us a vocabulary for the events that cross from the link manager into the rest of the program. The link state constants and initialization parameters in chunk_fc7074757ec1be96 tell us this is not merely a boolean connected flag. And the LINK_MANAGER state in chunk_f549b5be90786ef0 is the clearest architectural signal: this system has memory. It has a current phase, timers, role-related data, and enough accumulated state that a later callback message only makes sense when interpreted against that state.

Now, keep your eye on a pattern we have used all semester. FireRed often prevents one subsystem from directly owning everything by putting a small state machine at the boundary. Earlier, with menus, we saw task-backed state machines mediate between input, UI drawing, and data mutation. We are not going to re-teach that today, but I want you to use it as prior foundation. In pokefirered/src/learn_move.c, chunk_af3598df6f092723, the move relearner is explicitly organized as a state machine around menu interactions and eventual creature data changes. That was a local single-player version of the same design instinct. The user sees a menu. The code sees staged permission. You can scroll, inspect, confirm, cancel, and only at the correct state does party data change. Here, the user sees another player. The code sees staged permission again. You may discover a peer, accept a peer, advertise a mode, become ready, hand off, cancel, or time out. Only at the correct state does battle or trade get to claim the session.

Let’s make the boundary concrete. Suppose we have two players entering a link battle from the Cable Club. The map layer gets us into the right physical context. The map group data in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, is part of that: the Cable Club and Colosseum spaces are not abstract menu screens floating in nowhere; they are map entries in the same data-driven world system we studied earlier. And the 4-player Colosseum header and warps in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, show that multiplayer entry is still expressed through normal map metadata. That matters. Multiplayer does not begin by deleting the overworld architecture. It begins inside it.

So player A enters the Cable Club map, talks to an attendant, and chooses a linked battle option. Player B does the same. The script and map systems are still active at that moment. The communication layer has not yet made battle real. The link manager begins operating under a chosen RFU mode. It determines, or participates in determining, who behaves as parent and who behaves as child. It exchanges callback messages that tell the rest of the program things like progress, success, failure, or cancellation. I’m being careful with the wording there because the exact taxonomy is defined in the header, pokefirered/include/AgbRfu_LinkManager.h, chunk_b75d95e146826302. Architecturally, though, the important point is that these are not battle commands yet. They are communication-state facts.

Now I’m going to draw three stacked lanes on the board. Top lane: overworld and script. Middle lane: link manager. Bottom lane: battle or trade. At time zero, top lane owns the player experience. It has the map, the NPC, the menu prompt, and any field transition. The middle lane may be initialized, but it is not yet the player-facing mode. The bottom lane is dormant. At time one, the script says, “Please begin the link negotiation for this feature.” The link manager begins moving through its states. At time two, callback messages report discovery and acceptance. At time three, the link manager reaches a state we might casually call ready. But ready means ready at the link boundary. It does not necessarily mean the battle engine has already allocated all of its state, loaded all battler party data, selected battle controllers, and taken over callbacks.

That is the subtle bug farm. If you collapse time two and time three and time four into one conceptual moment, you will eventually write code that persists too early, displays the wrong UI after cancellation, or lets battle assume a peer exists after the link layer has already observed disconnection. In the actual FireRed architecture, that risk is handled by explicit constants, explicit callback messages, and explicit link manager state. Again, see AgbRfu_LinkManager.h chunks fc7074757ec1be96 and f549b5be90786ef0 for the fact that link status and manager state are modeled as structured phases, not as a single yes or no.

Pause here and check your understanding. If I say, “The two Game Boys have discovered each other,” which lane owns the mode? Not battle yet. If I say, “The parent has accepted the child,” which lane owns the mode? Still not automatically battle. If I say, “The Cable Club script is still waiting for the link callback to report a handoff-safe state,” which lane is currently coordinating the player experience? The top lane, the overworld/script lane, is still in charge of the visible transition, while the middle lane is providing readiness facts. Good. That distinction is the core of today.

Now let’s connect that to VBlank timers, because this is where advanced students need to stop thinking in terms of ordinary blocking function calls. The Game Boy Advance is not going to run a luxurious threaded networking stack for us. The program advances through frame-based work. We have already used the main callback and task system as our general runtime shell model. The main loop calls into the currently installed callback; tasks run per frame; VBlank provides timing-sensitive work. The link manager has to live in that environment. That means a state can be “waiting” without blocking the whole game forever. A timer can advance while the UI remains responsive enough to cancel. A callback message can be received and stored, then interpreted by the next frame’s state handler.

This is why link code and menu code rhyme architecturally, even though the stakes are different. The move relearner state machine in pokefirered/src/learn_move.c, chunk_af3598df6f092723, waits for input, updates UI, then eventually performs data mutation. The link manager waits for peer state, updates communication state, then eventually permits mode handoff. But link adds another axis: the other console is also a state machine. So every state transition has to be valid locally and valid relative to remote progress. In a menu, cancel means “the player backed out.” In a link session, cancel means “the player backed out, and the other machine must not continue as though the session is still valid.”

Let’s use the cancellation example from the previous passage and push it through the boundary. Child joins. Parent receives acceptance. Both move toward ready-to-handoff. Then the child cancels while the parent is waiting. What should not happen? The parent should not enter battle alone. The parent should not keep displaying a permanent waiting state. The parent should not write a save record that assumes a battle occurred. The parent should not leave the overworld in a half-disabled condition where scripts are paused but no mode owns the screen. The link manager’s job is to make cancellation visible as a state fact. Then the overworld or Cable Club flow can cleanly return to its prior menu or message.

Notice how this intersects with save architecture. Last week and the week before, we separated save block ownership, load-save initialization, and sector validation. We are not going back through flash sectors today. But the principle is important here: a multiplayer negotiation is volatile until a higher-level mode has reached a point where persistence is meaningful. “Ready” is not the same as “safe to persist.” If a trade is being negotiated, the game must not permanently alter party data merely because the peers found each other. If a battle is being negotiated, the game must not record battle consequences simply because a parent and child exchanged readiness messages. Persistence belongs after the appropriate gameplay transaction boundary, not after link discovery.

Now, for battle, the handoff boundary is especially interesting because battle is already a large state machine. The battle setup side of the code, represented in the research packet by pokefirered/src/battle_setup.c, chunk_494d132a59422c85, pulls together task, overworld, Pokémon party, script, field transition, battle, and battle transition includes. That include cluster itself is evidence of integration pressure. Battle setup is not a pure algorithm that receives two numbers and returns a winner. It is a bridge between field context and battle mode. When link battle enters the picture, that bridge has to be crossed only after link state says the remote participant is stable enough to become a battle participant.

Let me give you a simplified pseudocode description verbally, not as source code. The Cable Club script says, “start link battle negotiation.” The link manager initializes with mode parameters from the RFU side. The manager advertises or searches. A child sends intent. Parent accepts. Both sides exchange a callback message that means, in effect, “the communication side has met the prerequisites for this feature.” The overworld script receives that outcome and begins a transition. Only after that does battle setup prepare battle-specific structures. And from that point forward, the battle engine can interpret incoming link data as battle data rather than generic negotiation data.

That phrase, “interpret incoming link data as battle data,” is the handoff. Same underlying communication substrate, different semantic owner. Before the handoff, a byte or message is about pairing, role, readiness, or cancellation. After the handoff, the communication may carry battle choices, timing synchronization, controller decisions, or other battle-mode information. If you let battle parse negotiation messages, you have a category error. If you let the link manager make battle rules decisions, you have the opposite category error. Good architecture keeps the boundary firm.

Now, someone might ask, “Could FireRed have used one giant multiplayer state machine instead?” It could have, in the sense that any program can centralize everything. But then your Cable Club map data, your event scripts, your battle setup, your trade UI, and your RFU state would all be tangled in one procedural corridor. That is exactly what this course has been training you to avoid when you build your own creature RPG. The more appropriate pattern is a chain of state machines with explicit ownership transfer. Runtime shell owns frame dispatch. Overworld owns map and script execution. The link manager owns communication negotiation. Battle owns battle rules and battle presentation. Trade owns trade presentation and creature exchange semantics. Save owns persistence only at stable transaction points.

Let’s do a quick board exercise. I’m drawing a state labeled “Cable Club prompt.” From there, an arrow goes to “Initialize link manager.” From there, two branches: “become parent” and “become child.” Both feed into “session negotiation.” From negotiation, there are three outgoing arrows. One is “cancel or fail,” returning to Cable Club prompt. One is “timeout,” also returning through cleanup. One is “ready for mode handoff,” which goes to a small diamond labeled “mode type.” From there, battle goes to battle setup, trade goes to trade setup. Now here is the question: where do we put save writes? Not on the parent branch. Not on the child branch. Not on session negotiation. Not merely on ready for mode handoff. Save writes belong later, where the mode has completed a meaningful transaction, and even then only through the save system’s own disciplined boundary.

This is also why the map side matters more than it may first appear. The 4-player Colosseum map in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, gives the multiplayer feature a physical staging environment. Warps and map headers describe where players appear and how they move between spaces. But that map is not the link session itself. Think of it as the lobby and stage. It constrains entry points and presentation. It can trigger scripts. It can route players to the right place. But it cannot guarantee that a remote machine is ready for battle. That guarantee can only come from the communication state machine.

Let me check for understanding with a slightly adversarial scenario. Suppose a student implementation says, “When the player steps on the Colosseum warp, immediately create a battle and then wait for link readiness inside the battle loop.” What is wrong with that? The obvious problem is user experience: battle starts before the opponent is actually established. But the deeper architectural problem is ownership inversion. Battle becomes responsible for connection negotiation, cancellation recovery, role agreement, and Cable Club return paths. Then battle has to know too much about maps and scripts. You have turned a mode handoff into a mode hostage situation. A cleaner implementation waits for the link manager to report a handoff-safe condition, then lets the battle system begin.

Now compare that to single-player wild battle setup. In pokefirered/src/battle_setup.c, chunk_494d132a59422c85, the field context can determine that a battle should begin, and battle setup can proceed because all participants are local and known. The wild Pokémon data, trainer data, player party, and transition can be assembled within one cartridge’s state. In link battle, the remote participant is not local data until the communication state has made it safely interpretable. So link battle is not just “wild battle plus another player.” It is “field-to-battle handoff gated by remote session state.”

This same reasoning applies to trade, but with a sharper persistence warning. In a battle, the system may need synchronized choices and outcome handling. In a trade, the system may eventually mutate party ownership, species position, held item state, Pokédex-related observations, and possibly other records. We have spent enough time with party data and save data that you should feel the danger immediately. A trade cannot commit merely because two players are connected. It must commit at a transaction boundary where both machines agree about what is being exchanged. If one side cancels before that boundary, the correct behavior is cleanup, not partial mutation.

Now, the callback message taxonomy in AgbRfu_LinkManager.h, chunk_b75d95e146826302, is important because it gives the rest of the program a way to react without knowing every internal RFU detail. A callback message can say, in a compact way, “this meaningful communication event occurred.” That is exactly what a boundary object should do. It should not expose every internal timer and bit of radio housekeeping to the Cable Club script. But it should expose enough information for the script or manager task to decide whether to continue, cancel, display a message, or hand off.

Let’s be precise about the timer issue. A VBlank timer or frame-driven counter is not just a delay mechanism. It is a correctness mechanism. It prevents ambiguous waiting states from lasting forever. It also gives the UI and link state a shared rhythm. If you send a request and never receive the corresponding remote progress, you need a way to decide that the session is no longer viable. That timeout path is not an error afterthought. It is part of the state machine. In your lab model this week, if your diagram only has success arrows, it is incomplete. You need cancellation, timeout, and cleanup paths with the same dignity as success paths.

Here is a concrete trace. Player A is parent. Player B is child. Both choose link battle. Player B sends join intent. Player A’s link manager records child presence and emits a callback message to the upper layer. The upper layer displays or advances the waiting UI. Player A accepts. Player B receives acceptance. Both now approach ready. Then Player B presses cancel before the mode handoff. Player B’s local state goes to cleanup and returns to the Cable Club prompt. Player A either receives a cancellation message or observes loss of expected progress by timeout. Player A’s link manager emits a failure or cancellation outcome upward. The Cable Club script regains visible ownership. No battle setup occurs. No party data mutates. No save commit happens. That is a successful cancellation, not a failed program.

The word “successful” is important there. We often treat success as “the happy path completed.” In communication systems, success also means failed sessions fail coherently. A robust link manager makes negative outcomes legible. It turns absence, cancellation, and mismatch into states that the rest of the game can handle. That is why the link manager is a communication state machine rather than a bag of helper functions.

Let me now tie this back to the full course decomposition. Runtime shell gives us callback control. Overworld maps give us physical spaces and event triggers. Event scripting gives us the human-scale sequence: talk to attendant, choose option, wait, enter room, return. Creature data gives us the parties that battle or trade will eventually inspect. Battle gives us the rules engine and presentation once the handoff is valid. Inventory may participate indirectly through held items or eligibility rules. UI gives us prompts, waiting screens, cancellation affordances, and error messages. Save gives us durable persistence after stable transactions. Link gives us remote coordination and readiness. The advanced lesson is not that these systems exist. You already know that. The advanced lesson is that the handoff boundaries between them are designed artifacts.

So when you design your simplified link-session state machine for the workshop, do not start by drawing radio waves. Start by drawing ownership. Who owns the visible screen? Who owns peer discovery? Who owns role assignment? Who owns cancellation? Who owns battle rules? Who owns mutation of party data? Who owns persistence? If two boxes both claim the same responsibility, you probably have a bug waiting. If no box claims cleanup, you definitely have a bug waiting.

We are going to continue from this ownership diagram into the specific role of the Cable Club maps as multiplayer entry points, because the next step is to see how the map group and map headers give the link system a controlled place to begin. Keep the three-lane drawing in mind: overworld and script above, link manager in the middle, battle and trade below. The clean architecture is not that one lane dominates the others. The clean architecture is that control passes between them at named, testable, recoverable boundaries.

So, look back at the board for a second. I want to move the map lane from being a vague “place where multiplayer starts” into a concrete architectural boundary. In FireRed, the Cable Club is not just decorative geography. It is a staging area. It is a controlled runtime shell where the normal overworld can still do what it is good at, but where the game can narrow the range of possible player actions before handing authority to the link system.

That is why the map data matters. When we cite pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, we are not citing it merely to say, “There is a map group for link rooms.” We are saying that the project’s data layer gives multiplayer its own spatial category. And when we cite pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, the important point is that the four-player Colosseum is represented as a map header with warps, dimensions, events, and connections in the same general map architecture we have studied all semester. Multiplayer entry is not a separate game bolted onto the side from the first instruction. It begins as overworld data.

Now, that may sound obvious, but it is not obvious architecturally. A weaker design might say, “When the player chooses link battle from a menu, immediately jump into the wireless code.” FireRed’s structure is more disciplined. The player enters a room. The room has map identity. The room constrains movement. The room gives the script engine a place to run eligibility checks, dialogue, waiting behavior, and cancellation behavior. Only then does the communication machinery become the dominant state machine.

Let me draw that. On the left side of the board, I am writing “map group.” Under it, “map header.” Under that, “warps and objects.” Then an arrow into “scripted entry point.” Then an arrow into “link manager negotiation.” Then arrows down into “battle” and “trade.” This is the line I want you to remember: the Cable Club map is not the link system. It is the vestibule that makes the link system safe to enter.

And this connects very directly to what we did last week with persistence. In the save lectures, we cared about ownership: save block ownership, load-save initialization, sector validation, flash boundaries. We are not going to re-teach those details today, but the same discipline applies here. A map trigger may ask for multiplayer. The link manager may negotiate peers. The battle engine may mutate battle state. The trade system may mutate party data. The save system may later persist those mutations. If all of those are blurred together, then cancellation, disconnection, or partial progress becomes almost impossible to reason about.

So let’s make a concrete example. Suppose four players enter the Battle Colosseum. The visible place, from the player’s point of view, is the four-player Colosseum map. The reason we care about pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, is that this room has map-level structure before it has link-level structure. It has a header. It has warps. It has the normal data identity of a FireRed map. The player can be positioned, animated, and handed off using the same overworld machinery that handles other maps. But once the session is actually forming, the source of truth changes. The question is no longer primarily “which tile am I standing on?” The question becomes “which communication role am I in, which peers are present, and which stage of negotiation has completed?”

That is where the RFU link manager header becomes so useful as a teaching source. The constants in pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b, give us RFU mode vocabulary. The callback message taxonomy in pokefirered/include/AgbRfu_LinkManager.h, chunk_b75d95e146826302, gives us a vocabulary for events that arrive from the communication layer. The link state constants and initialization parameters in pokefirered/include/AgbRfu_LinkManager.h, chunk_fc7074757ec1be96, describe how the manager is configured and how its state is named. And the LINK_MANAGER state structure in pokefirered/include/AgbRfu_LinkManager.h, chunk_f549b5be90786ef0, tells us something crucial: this is not a loose collection of helper functions. It is a stateful subsystem.

Pause there. That is the advanced point. The existence of a struct does not automatically mean you have a state machine. But when you see named modes, callback messages, state constants, initialization parameters, and a manager state object, you should start asking state-machine questions. What are the states? What events advance them? What events cancel them? Which states are stable across frames? Which states are transient? Which states are only legal for a parent? Which states are only legal for a child? Which fields are reset on entry? Which fields survive until teardown?

Check for understanding: if a callback message says, in effect, “a peer has connected,” should the battle engine immediately start a battle? No. Not by itself. The callback message is an input to the link manager’s state machine. The manager has to interpret that input relative to the current state. A peer connecting while you are searching is different from a peer connecting after cancellation has already begun. The same event-shaped thing can have different meaning depending on the state that receives it.

That is the reason VBlank timing enters the conversation. The Game Boy Advance does not give us a luxurious threaded runtime where communication, rendering, input, and scripting all run independently with infinite isolation. The engine has a per-frame rhythm. Earlier in the course, when we studied AgbMain and the callback shell through pokefirered/src/main.c, chunk_b35676322b169f85, and the main callback transition point through pokefirered/src/main.c, chunk_6cfd90f6d0136799, we saw that the runtime is organized around callbacks. When we studied tasks through pokefirered/src/task.c, chunk_cb0bb91a147bfb59 for task creation and chunk_3ffebc0341225936 for RunTasks, we saw how per-frame state advances without blocking the whole program. Link management has to live inside that same world. It cannot simply stop time and wait for a peer in a blocking loop whenever it feels like it.

So when I say “VBlank timers and link state,” I want you to hear “temporal ownership.” Some parts of the system are allowed to update during the frame. Some communication maintenance must be coordinated with the display cadence. Some states mean “keep polling.” Some states mean “we are waiting, but the rest of the presentation must remain alive.” This is why a link room can display waiting messages, animate, respond to cancellation, and still maintain the wireless protocol. It is not magic. It is staged state advancement.

Let’s take the simplified lab model you are building this week and map it to the real architecture. Your lab state machine begins from a Cable Club map trigger. That trigger does not itself know the whole RFU protocol. It asks to start a session. Then your state machine negotiates parent and child state. In FireRed terms, that corresponds to the role assignment boundary reflected by the RFU mode constants and link-state vocabulary in pokefirered/include/AgbRfu_LinkManager.h, chunks_8df030184c481b3b and_fc7074757ec1be96. Then, after enough peers are discovered and agreement is reached, your model enters linked battle mode. That transition is the important handoff. At that point, the battle system can become authoritative over battle rules, while the link layer remains authoritative over communication. Then your model returns to overworld, which means cleanup and restoration are first-class states, not afterthoughts.

Notice what I did not say. I did not say “the map starts battle.” I did not say “the link code edits the party directly.” I did not say “the battle engine owns peer discovery.” The point of the lab is to make those separations uncomfortable enough that you remember them when you design your own creature RPG.

Let me put a small example on the board. Imagine these states, not as exact FireRed enum names, but as a faithful simplified model: idle in overworld, entered Cable Club room, preparing RFU session, searching for peers, role assigned, session confirmed, loading linked battle, in linked battle, returning from battle, cleaning up link, back to overworld. Now I will add events above the arrows: player steps on trigger, script accepts, manager initialized, peer discovered, parent selected, child synchronized, battle mode requested, battle complete, teardown complete.

The key is that every arrow is conditional. “Peer discovered” does not always mean “role assigned.” “Role assigned” does not always mean “battle mode requested.” “Battle complete” does not automatically mean “safe to save.” You have to know what data changed, who owns it, and whether all participants reached the same boundary.

This is also why the callback message taxonomy matters. In pokefirered/include/AgbRfu_LinkManager.h, chunk_b75d95e146826302, the header gives us names for messages between the RFU layer and the higher manager. Those messages are a contract. They are not merely debug labels. A message taxonomy allows the rest of the code to avoid interpreting raw communication noise directly. Instead of every system asking, “What did the wireless hardware just do?” the manager can ask, “What message did my communication layer report, and what does that mean in my current state?”

That pattern should remind you of our earlier menu-state discussions, but at a higher risk level. In the move relearner lecture, we used pokefirered/src/learn_move.c, chunk_af3598df6f092723, as a state-machine example for UI flow. A menu state changed, input moved selection, confirmation caused a data write, cancellation returned without mutation. That was already important. But multiplayer adds a second machine on another console. In a menu, cancellation is local. In a link session, cancellation may need to be communicated, acknowledged, and reflected back into both players’ user interfaces. A local state machine with one input stream becomes a distributed state machine with timing uncertainty.

And this is where advanced students sometimes overcomplicate the wrong layer. You do not solve distributed uncertainty by making the map script smarter and smarter. You solve it by letting the script do orchestration at the boundary and letting the link manager own communication state. The map can say, “We are in the room where multiplayer may start.” The script can say, “The player chose battle and passed eligibility checks.” The link manager can say, “I have peers, roles, state, messages, and timers.” The battle engine can say, “Given a linked session, I can run a battle under linked rules.”

Now, let’s connect the four-player room specifically. A four-player Colosseum has an obvious gameplay difference from a two-player trade room: more participants, stricter coordination, and different readiness semantics. But architecturally, it still enters through map data. That is why map group data in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, and the 4P Colosseum header in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, belong in the same lecture as RFU constants. One side gives us spatial staging. The other gives us communication staging. Multiplayer gameplay is the composition of those two staged systems.

Here is another check for understanding. Suppose the player is in the four-player Colosseum map, but the RFU manager is still idle. Are we in multiplayer? From the architecture’s point of view, not yet. We are in a multiplayer entry environment, but not necessarily in a live multiplayer session. Now suppose the RFU manager has discovered peers, but the battle engine has not accepted control. Are we in battle? No. We are in a negotiated or negotiating link session. Now suppose linked battle has started. Does the map disappear as a concept forever? No. It is temporarily not the dominant controller, but the return path still has to know where to place the player, what state to restore, and how to resume the overworld shell.

That return path is where many student architectures fail. They build a dramatic entry transition, but they do not design exit. In FireRed terms, you should always ask: after link battle or trade, which callback regains control? Which task or script resumes? Which temporary state is cleared? Which player-facing state is restored? Which party mutations are committed? Which save responsibilities remain separate? You already have the tools to answer this from prior weeks. Use the callback shell from main.c. Use task-backed state progression from task.c. Use save ownership from week 20. Use battle setup boundaries from pokefirered/src/battle_setup.c, chunk_494d132a59422c85, where battle entry is treated as a transition involving overworld, event data, battle configuration, and related systems rather than as a random jump.

Let me say that more plainly. Link battle is not “battle plus wireless.” It is overworld staging plus communication negotiation plus battle handoff plus result cleanup. If you omit any one of those, your clone may work in the happy path, but it will be fragile under cancellation, disconnects, retries, or unusual player timing.

Now I want to spend a minute on parent and child roles, because this is the conceptual center of today’s lecture. In a local menu, there is no parent and child. There is just the local program advancing through states. In an RFU session, one participant often acts as a coordinating parent while other participants join as children. The exact constants and modes are named in the RFU manager header, especially pokefirered/include/AgbRfu_LinkManager.h, chunk_8df030184c481b3b for RFU mode constants and chunk_fc7074757ec1be96 for link state and initialization vocabulary. The architectural lesson is that role is state, not personality. A console is not metaphysically “the parent” forever. It is assigned a role for this session, under these conditions, so the session can coordinate.

Why does that matter? Because many bugs come from treating role as a global truth instead of a session truth. If you cache role too broadly, or fail to clear it during teardown, the next session can inherit stale authority. If a child thinks it is still joined after the parent has canceled, you get a waiting room that cannot recover. If the parent advances to battle while a child is still in confirmation, you get divergent state. The LINK_MANAGER state structure cited in pokefirered/include/AgbRfu_LinkManager.h, chunk_f549b5be90786ef0, is exactly the kind of place where you expect those session facts to be represented and maintained.

At the board, I am circling “role assigned” and “teardown complete.” These are the two states I want you to include explicitly in your lab model. Do not compress them into “connected” and “done.” Connected is too vague. Done is too vague. A robust architecture distinguishes “we have discovered a peer,” “we have assigned role,” “we have confirmed readiness,” “we have transferred control,” and “we have cleaned up.” Those distinctions are not ceremony. They are how you prevent an old communication state from contaminating a new player-facing mode.

Let’s run the four-player battle example in slow motion. Player A enters the Cable Club and chooses the appropriate battle option. The map and script layer handle visible interaction. Player B, C, and D do similar things on their consoles. The link manager begins RFU initialization. The system enters a searching or advertising state, depending on role and mode. Callback messages arrive: peers found, connection changes, maybe error or cancellation signals. The manager interprets those messages against its current state. When all required peers and settings line up, the session crosses a readiness boundary. Only then does battle setup become appropriate. During battle, battle logic owns turn rules, move execution, targeting, fainting, and battle-visible state, while link communication ensures the participants stay synchronized. At the end, the system exits battle, resolves results, returns control upward, cleans link state, and resumes overworld presentation.

You can hear how many verbs there are in that paragraph. Enters. Chooses. Initializes. Searches. Advertises. Arrives. Interprets. Crosses. Owns. Synchronizes. Exits. Resolves. Returns. Cleans. Resumes. A state machine is how we keep those verbs from turning into spaghetti.

Now, let’s compare this briefly with Pokemon Showdown, since the research packet reminds us to use it as a secondary comparison, not as the main source. Showdown’s architecture often makes communication visible as protocol text and server-mediated battle rooms, and the packet points us toward pokemon-showdown/PROTOCOL.md as a recurring comparison source. That is useful conceptually because Showdown names messages that clients and servers exchange. But FireRed is our primary architecture because the constraints are different. FireRed has local hardware timing, GBA callbacks, VBlank rhythm, maps as data, and RFU-specific role negotiation. So the comparison is not “Showdown is the same.” The comparison is “both systems benefit from named messages and explicit state transitions, but FireRed embeds that discipline in a handheld runtime shell.”

That distinction matters for your projects. If you are building a modern clone with sockets, you may not have VBlank in the hardware sense. But you still have a frame loop, render loop, or async event loop. You still need ownership. You still need named session states. You still need a boundary between lobby geography, peer discovery, battle authority, trade mutation, and persistence. FireRed gives you the architecture under severe constraints. Modern hardware does not excuse you from the same clarity.

Let me ask a diagnostic question. In your simplified state machine, where does the player press B to cancel? If your answer is “anywhere,” your design is probably under-specified. Cancellation in the map dialogue state is not the same as cancellation while advertising a session. Cancellation before role assignment is not the same as cancellation after other peers have joined. Cancellation after battle handoff may not be allowed in the same way at all. So instead of saying “B cancels multiplayer,” say, “In state searching_for_peers, B sends or records a cancellation request, transitions to link_teardown, then returns to the Cable Club overworld state after cleanup.” That is the level of precision I expect in week 21.

And here is the related persistence question. When do we save? Not when a peer is merely discovered. Not when a role is assigned. Not when a trade animation starts, unless the system has reached the correct commit boundary. For battle, the answer depends on what battle effects need to persist. For trade, the stakes are even higher because party data changes ownership. The reason we spent time on save block ownership and sector validation in week 20 is so you would not casually write persistent data from the wrong boundary in week 21. Communication success is not the same as persistent commit.

We will come back to trade more explicitly, but the handoff boundary is already visible. The link manager should not be the module that understands every semantic detail of party mutation. It should provide session structure and communication reliability. The trade code can understand the exchange. The party code can own creature data representation. The save system can persist finalized state. This is the creature RPG decomposition from the course objective: runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. Week 21 is where those boxes all touch, so sloppy boundaries become expensive.

Before we move on, I want a quick mental exercise. Picture the three-lane drawing again. Top lane: overworld and script. Middle lane: link manager. Bottom lane: battle and trade. Now imagine an error message arrives from the RFU callback taxonomy in the manager header, pokefirered/include/AgbRfu_LinkManager.h, chunk_b75d95e146826302. Which lane receives it first? The middle lane. Which lane decides whether to show text to the player? Likely the top lane, through UI or script-facing behavior. Which lane decides battle damage? The bottom lane, if battle has actually begun. The error may eventually affect all three lanes, but it should not erase their responsibilities.

That is the architecture I want you to practice in the lab. Start with the Cable Club trigger, but do not let it become a god function. Add RFU-like modes, but do not make them arbitrary strings sprinkled everywhere. Add callback-like messages, but do not let every subsystem consume raw communication events. Add battle handoff, but make the transition explicit. Add return-to-overworld, because cleanup is part of the feature.

So, the recap before we continue is this: Cable Club maps are multiplayer entry points because they give the overworld a controlled place to stage the player. The RFU link manager is a communication state machine because it has modes, messages, states, initialization parameters, and session-owned state, as shown across pokefirered/include/AgbRfu_LinkManager.h chunks_8df030184c481b3b, b75d95e146826302, fc7074757ec1be96, and f549b5be90786ef0. The 4-player Colosseum map data in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, reminds us that multiplayer still begins in the map system. And the clean handoff is not a single jump; it is a sequence of owned transitions.

In the next stretch, we are going to tighten that sequence further. I want to look at the boundary between “linked session exists” and “battle mode has accepted control,” because that is where students most often confuse communication readiness with gameplay readiness. Keep your diagram open, because we are going to add one more column between the link manager and the battle engine.

Good. So on the board, we now have four columns, not three. The left column is Cable Club map and script trigger. The second column is the RFU link manager. The third column, the one I am adding now, is the multiplayer handoff layer. And the fourth column is battle or trade mode, depending on which service the room is about to enter.

Why do I want that extra column? Because if we draw only “map trigger, link manager, battle,” we accidentally imply that once the wireless or cable session is alive, the battle engine can simply begin. That is too coarse. A link can be physically or logically established while the gameplay systems are still not ready to consume synchronized commands. The players may still be positioned on the map. A fade may still be active. A task may still be waiting on a message. One side may know it is parent, the other may know it is child, but the battle engine may not yet have party order, battle type flags, player count, partner identity, or the final agreement that both machines are entering the same mode.

So let me say the distinction plainly. Communication readiness means, “The machines can exchange link protocol messages.” Gameplay readiness means, “The destination subsystem can safely assume the same world of facts on every machine.” Those are not the same state.

And this is where the header organization matters. In pokefirered/include/AgbRfu_LinkManager.h, the RFU mode constants in chunk_8df030184c481b3b give us the vocabulary for the communication layer’s broad operating modes. The callback message taxonomy in chunk_b75d95e146826302 gives us the vocabulary for what the manager reports upward. The link state constants and initialization parameters in chunk_fc7074757ec1be96 give us the vocabulary for lifecycle and startup shape. And the LINK_MANAGER state in chunk_f549b5be90786ef0 tells us there is session-owned state, not just a collection of stateless helper functions. That division is a hint about architecture. The link manager owns link progression. It should not also be asked to own every rule about how battles, trades, menus, maps, and save boundaries behave after connection.

Now, watch the trap. Suppose you are building a minimal clone for this week’s lab. The player walks into a Cable Club room, steps onto a trigger, chooses a linked battle, and the system starts RFU negotiation. You might write a state named CONNECTED and then immediately call StartLinkedBattle. That feels reasonable, because in everyday language “connected” sounds like the thing you were waiting for. But in a game engine, CONNECTED is often still too early. Connected only means the transport has crossed one threshold. It does not prove the overworld has stopped accepting local movement. It does not prove both players selected the same service. It does not prove both parties are serialized in the same order. It does not prove the visual transition has left the screen in a state where battle can own VRAM and callbacks.

Let me put a concrete sequence on the board. I’ll write it horizontally.

Player enters Cable Club map. Map script offers service. Service choice locks player. Link manager initializes. Parent and child roles are negotiated. Link manager emits readiness callback. Handoff task collects session facts. Handoff task freezes or fades overworld. Battle setup consumes agreed facts. Main callback changes to battle. Battle begins exchanging deterministic commands.

That middle part, “handoff task collects session facts,” is what students often compress away. But it is doing important design work. It converts link facts into game facts.

And yes, this connects directly to the maps. The link rooms are not decorative content. The link map group in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, places the Cable Club style maps inside the same map registry machinery we have been studying all semester. The 4-player Colosseum header and warps in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, remind us that even a multiplayer feature begins as map data. You still enter from an overworld map. You still have warps. You still have a controlled location. The map is a staging area that reduces the number of possible states the link system has to tolerate.

Check for understanding here. If the player could initiate a wireless four-player battle from anywhere in the overworld, what would the handoff layer have to defend against? Think about surf state, NPC movement, scripts in progress, trainer approach, field effects, menus, pending text boxes, and map transitions. The Cable Club room is a design constraint that makes the engineering problem smaller. It creates a predictable precondition.

Now, let’s compare this to something we already know, but I’ll keep it brief because we covered it in depth. When we looked at menu callbacks and task states in the move relearner material, especially pokefirered/src/learn_move.c, chunk_af3598df6f092723, we saw a UI process split into states: fade, list, confirm, cancel, apply. The point was not that every system is a menu. The point was that FireRed likes to make transitions explicit. Link handoff follows the same discipline, but the cost of getting it wrong is higher, because two or more machines must cross the boundary together.

In a single-player menu, if your state machine is sloppy, you may draw the wrong text or allow a bad selection. In link play, if your state machine is sloppy, machine A may think it is in battle while machine B is still in overworld. That is not a visual bug; that is a protocol failure.

So, what does the handoff layer need to know? Let’s name the facts.

First, it needs role. Is this machine parent, child, leader, guest, or participant with an index? I am using general words here, but the idea is grounded in the RFU manager’s distinction between modes, state, and session data in pokefirered/include/AgbRfu_LinkManager.h chunks_8df030184c481b3b and f549b5be90786ef0. Role affects who is allowed to make certain decisions, who broadcasts final mode selection, and how player slots are assigned.

Second, it needs service. Are we entering battle, trade, record mixing, or some other linked activity? For today we are focusing on battle and trade boundaries, but the principle generalizes. The link manager may be reusable across multiple services, so service selection cannot be hidden inside transport connection.

Third, it needs participant count and slot mapping. A 2-player Colosseum and a 4-player Colosseum are not the same gameplay contract. The presence of BattleColosseum_4P/map.json, chunk_f18829d873bc1246, is a data-level reminder that FireRed models multiplayer spaces with distinct entry points. The runtime must eventually turn that into concrete assumptions: how many players, which local player index, which remote player index, which side in battle, and what happens if one participant fails readiness.

Fourth, it needs payload readiness. For battle, that means party data, battle flags, trainer identity, and any required link battle setup values. For trade, it means selected creature, validation state, animation entry, and save implications after the exchange. We are not re-teaching save sectors today, but carry forward last week’s point: persistence is an ownership boundary. A linked trade is not just an animation; it is a coordinated mutation that must eventually be persisted safely.

Now, I want to connect this to battle setup without pretending the battle setup source is a link manager file. pokefirered/src/battle_setup.c, chunk_494d132a59422c85, is useful here because it shows battle setup as a distinct module that includes overworld, script, event data, battle transition, and battle-related headers. That file’s include surface is evidence of what we have been saying all course: entering battle is a cross-system event. Battle setup is not “just battle.” It is the place where overworld circumstances, event state, transition effects, and battle configuration converge. Link battle handoff has to arrive at that convergence point with a consistent set of facts.

Let me give you an example. Imagine two players entering a linked battle from a two-player room. Player A is the session leader. Player B joins. The RFU manager reaches a state where it can report that the peer is present. If player A’s machine immediately starts battle setup and player B’s machine is one frame behind still processing an overworld task, is that always fatal? Not necessarily, because robust state machines can tolerate small frame differences if they exchange explicit confirmations. But if player A advances from “peer present” to “battle command exchange” before player B has acknowledged “I am entering battle with this exact configuration,” then you have a semantic mismatch. The link is alive, but the gameplay contract is not locked.

This is why callback messages are so important. The callback message taxonomy in pokefirered/include/AgbRfu_LinkManager.h, chunk_b75d95e146826302, should be read not as a random enum list, but as the set of events by which the communication system tells the rest of the game, “Something meaningful changed.” A callback message is not automatically a command to enter gameplay. It is input to another state machine.

I’ll repeat that because it is a very useful design sentence: a callback message is input to another state machine. It is not, by itself, the whole transition.

Now, somebody will ask, “Professor, why not just let the link manager own the whole process? It knows the link status. It could start the battle.” That would work for a small prototype, but it scales badly. The link manager would have to know map rules, script rules, battle setup rules, trade rules, UI messaging, save timing, and failure recovery. At that point, it is no longer a link manager. It is an everything manager with a radio inside it.

FireRed’s architecture, as we have traced it across the course, avoids that style more often than not. It uses globals, callbacks, tasks, scripts, and data tables, yes, and that can look messy from a modern abstraction perspective. But the ownership boundaries are still meaningful. The map system stages the player. The script system asks questions and gates progression. The link manager negotiates communication state. The handoff logic translates communication events into service-specific readiness. The battle or trade system then owns its own runtime.

Let’s do a check. If I give you a bug report that says, “In a linked battle room, both players connect, but one system fades to battle while the other returns to the overworld,” where would you start looking? Not in damage calculation. Not in species data. Not in item effects. You would start at the boundary between link callback messages and the handoff state that decides whether to enter battle or recover. You would ask whether the callback was interpreted symmetrically. You would ask whether the parent and child machines had the same service selection. You would ask whether the overworld task was allowed to continue after the session should have locked player control. You would ask whether the map script and the link manager disagreed about who owns the next transition.

Notice how that debugging path uses the full course architecture. Runtime shell: which callback is active? Overworld maps: which room and warp staged the player? Event scripting: which trigger or NPC started the service? Creature data: what party payload is being offered? Battle: has battle setup accepted control? Inventory is less central here, but held items and trade constraints can enter depending on service. UI: what prompt or message is waiting? Save: what must persist after the activity? Link: what state and callback message did the manager report? That is the decomposition objective for this course showing up in a real debugging workflow.

Now, let me draw a slightly more formal version. I’m going to write states vertically under the handoff column.

Idle in room. Service selected. Waiting for link manager initialization. Waiting for peers. Role assigned. Session ready. Service agreement pending. Service agreement locked. Overworld release pending. Destination callback pending. Destination active. Recovery or return.

These are not necessarily the exact names used in FireRed; I am modeling the architecture. The source evidence we are using is that FireRed exposes RFU modes, callback messages, link state constants, init parameters, and LINK_MANAGER state across the AgbRfu_LinkManager.h chunks we named. For your lab, your simplified machine can use these conceptual states even if your names differ.

The important move is separating “session ready” from “service agreement locked.” Session ready says communication exists. Service agreement locked says all participants have agreed what the communication will be used for. And then we separate “service agreement locked” from “destination active.” Destination active says battle or trade now owns the runtime.

Let’s test this with the 4-player Colosseum. Why does four-player make the distinction sharper? Because with two players, you can sometimes hide sloppy thinking behind symmetry. There is me and there is you. Parent and child. Leader and guest. But with four players, slot identity becomes a real design object. If player three is missing, do we wait, cancel, or proceed in a reduced mode? If player two disconnects during agreement, does player one still own the session? If two machines disagree about player order, the battle engine can assign battlers incorrectly. The map data for BattleColosseum_4P in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, is therefore not just an environmental detail. It points toward a more complex participant topology.

This is also where VBlank timers enter the conversation. We are not going to re-derive GBA timing from scratch, but remember the runtime shell from week one: the game is frame-driven. Work gets divided across callbacks, tasks, interrupts, and per-frame updates. Link state cannot be imagined as a blocking desktop network call where the game simply waits in a frozen function until everything is done. The VBlank timing context matters because communication progress, visual updates, and task dispatch have to coexist. The link manager’s state constants and initialization parameters in pokefirered/include/AgbRfu_LinkManager.h, chunk_fc7074757ec1be96, belong to that frame-based world.

In other words, “waiting for peers” does not mean the CPU is asleep inside a while loop forever. It means the current runtime continues to pump the appropriate callbacks and tasks while the link state advances and messages arrive. That is why our earlier weeks on SetMainCallback2, task IDs, and RunTasks remain relevant. I am not reteaching those mechanisms, but you should feel them underneath today’s topic. Link is another client of the same runtime shell.

Let me give you a clone implementation sketch verbally. Suppose your lab machine has an enum called LinkHandoffState. It starts at HANDOFF_IDLE. When the Cable Club script finishes the service prompt, it stores requestedService equals LINK_SERVICE_BATTLE and moves to HANDOFF_INIT_LINK. The link manager begins initialization with the selected mode. The handoff state waits for callback messages. When the manager reports a peer-ready or session-ready event, the handoff does not start battle. Instead, it moves to HANDOFF_EXCHANGE_SERVICE. In that state, parent broadcasts “battle, two players, rule set, local party summary hash,” and child responds with “accepted, party ready, local trainer id.” Only after both sides have an agreed packet does the state move to HANDOFF_LOCK_OVERWORLD. Then the overworld releases control through a fade or transition. Then the system calls into battle setup with the agreed flags. Only then does the main callback become battle-owned.

That sketch is simplified, but it has the right shape. It respects map staging, link negotiation, service agreement, visual transition, and destination ownership.

Now, here is the advanced part. Failure handling should usually return to the owner that can explain the failure to the player. If the RFU manager detects communication failure while still searching, the handoff layer can return the player to a Cable Club prompt. If the service agreement fails, the handoff layer can say the other player is not ready. If battle has already accepted control, battle or a link-battle controller may need to own the disconnect behavior. The user-facing message depends on where ownership currently sits.

That is a general architecture rule: recovery belongs to the highest-level system that still has enough context to make the failure meaningful.

So if you are before service agreement, “communication failed” is enough. If you are after service agreement but before battle, “the battle could not be started” is more accurate. If you are during trade after the creature mutation has begun, now persistence and rollback questions become serious. This is why we spent week 20 on save block ownership, load-save initialization, and sector validation. Link features are not isolated; they stress the save model because some multiplayer outcomes become durable state.

Quick check for understanding. What is the difference between a link callback that reports readiness and a battle setup call that consumes readiness? The callback is an observation from the communication layer. The battle setup call is a transfer of control into a gameplay subsystem. Between them, some state machine must validate that the observation is sufficient, stable, and agreed upon by all participants.

Let’s anchor this in the course-wide decomposition one more time. A creature-collector RPG is not one giant loop labeled “game.” It is a runtime shell that schedules work; overworld maps that give spatial context; event scripts that express local interaction; creature data that gives parties and species persistent meaning; battle systems that run high-stakes deterministic gameplay; inventory and UI systems that mediate player choice; save systems that persist ownership and progress; and link systems that synchronize multiple machines. Week 21 sits near the end of the course because link play forces all of those systems to coordinate under timing pressure.

And this is why today’s lecture is not merely about RFU constants. Constants are the vocabulary. The architecture is the conversation among systems using that vocabulary.

Look again at the board. Map trigger does not equal link session. Link session does not equal battle. Battle request does not equal battle ownership. Each arrow is a contract. The source files we are using show those contracts from different angles: pokefirered/include/AgbRfu_LinkManager.h chunks_8df030184c481b3b, b75d95e146826302, fc7074757ec1be96, and f549b5be90786ef0 for the communication state vocabulary; pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, and pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, for map staging; and pokefirered/src/battle_setup.c, chunk_494d132a59422c85, for the fact that entering battle is its own cross-module setup problem.

Now, before we move to the next segment, I want you to update your mental model in one sentence. The RFU link manager does not make multiplayer happen by magic. It advances communication state and reports communication events. Multiplayer happens when the overworld, script, handoff, battle or trade, UI, and save systems interpret those events in the right order.

In the next stretch, we will take that order and put timing pressure on it. We will ask what VBlank and per-frame progression imply for a state machine that cannot block, cannot skip visual transitions, and cannot let two machines drift into different gameplay worlds.

So put a vertical line on the board, and label it “frame.” Not “function call,” not “while loop,” not “wait until partner is ready.” Frame. That is the unit of truth for this part of the engine. The runtime shell we studied much earlier gives us the repeated opportunity to do a small amount of work, then return control so graphics, input, sound, DMA, and interrupts remain healthy. That earlier foundation matters here, because a link session is exactly the wrong place to write code that says, “I will just wait here until the other machine responds.”

Now, why is that so dangerous? Imagine two Game Boy Advances. One player is the parent, one is the child. The parent enters a Cable Club room, confirms a battle, and begins advertising or negotiating. The child enters the matching room and begins searching or responding. If either side blocks inside a local loop, the screen may stop animating, input may stop being sampled at the expected cadence, serial communication may stop being serviced in the way the RFU system expects, and the other machine may continue advancing. So the design pressure is this: each machine must appear patient to the player while being extremely disciplined internally. It must advance one small communication step at a time.

This is why the link manager vocabulary in pokefirered/include/AgbRfu_LinkManager.h is so important. The RFU mode constants in chunk_8df030184c481b3b tell us that communication has named modes, not just vague “connected” and “not connected” conditions. The callback message taxonomy in chunk_b75d95e146826302 tells us that the manager reports meaningful events outward. The link state constants and initialization parameters in chunk_fc7074757ec1be96 tell us that setup itself is a staged operation. And the LINK_MANAGER state in chunk_f549b5be90786ef0 gives us the recurring architectural pattern for this course: a subsystem owns a compact state record, and the rest of the game must respect that ownership instead of poking random assumptions into it.

Pause there. Check for understanding. If I ask, “Where is multiplayer state?” the answer is not “in the battle engine.” The battle engine eventually receives a linked battle context, yes. But the path into that battle begins earlier. Some state is in the map and script layer, some is in the communication manager, some is in global link-facing runtime state, some is in the transition handoff. Advanced architecture is mostly learning not to collapse those into one blob.

Let’s make the timing concrete. Suppose we are in the four-player Battle Colosseum. The map exists as authored data, not as a hard-coded special screen. The link map group appears in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, and the BattleColosseum_4P map header and warps are represented in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246. That means the player’s body is still in the overworld system. The room has geometry, warps, coordinates, object placement implications, and scripts or triggers that can begin the multiplayer flow. So when we say “enter link battle,” we are not jumping from title screen to battle. We are moving from map-authored staging, through event interpretation, through communication negotiation, through a handoff boundary, into battle setup.

I’ll draw that as a chain on the board: Cable Club map, trigger or attendant script, communication mode selection, parent-child negotiation, ready confirmation, fade or transition, battle setup, linked battle loop, result handling, return map. Do not read this as a call stack. Read it as a baton pass between state machines. Each segment has to finish enough of its responsibility before the next segment is allowed to take over.

Here is the mistake I want you to be able to spot in your own clone projects. A beginner implementation says, “When the player presses A on the attendant, call StartLinkBattle.” That sounds reasonable until you ask what StartLinkBattle means. Does it allocate the session? Does it decide parent versus child? Does it show waiting UI? Does it process RFU events? Does it freeze the player avatar? Does it close the text box? Does it validate parties? Does it fade out? Does it seed battle communication? Does it know where to return afterward? If the answer is yes to all of those, you have built a giant knot.

A better simplified model says this. The attendant script requests a link activity. The overworld marks the player as engaged in an interaction and routes control to a link-preparation task or callback. The link manager is initialized with the desired mode. Per frame, the manager advances communication and emits messages. The UI listens to those messages and updates waiting, confirmation, or error displays. When the communication state reaches a battle-ready boundary, the game invokes battle setup. And that last phrase, battle setup, should remind you of pokefirered/src/battle_setup.c, chunk_494d132a59422c85. We used that source earlier to show that battle entry is not merely “call the battle loop.” It is a cross-module setup problem involving overworld state, battle transition, event data, party assumptions, and the battle module itself.

Now let’s bring VBlank into the room, because this is where timing stops being abstract. VBlank is the short interval when the display is not actively drawing visible scanlines, and GBA programs use it for operations that need to synchronize with the screen, including graphics updates and timekeeping patterns. In a link context, a VBlank timer is not just a stopwatch for user interface polish. It is part of how the program maintains a rhythm while waiting for external communication. If your state machine says, “I have been waiting for too many frames without the expected response,” that is a different condition from “the partner explicitly declined,” and different again from “the local player pressed B before negotiation completed.”

Notice the granularity. We are not saying “timeout” as a single dramatic event. We are saying, frame by frame, the system accumulates evidence that it is still waiting, still connected, still negotiating, or no longer synchronized. The link manager callback messages from pokefirered/include/AgbRfu_LinkManager.h, chunk_b75d95e146826302, matter because callback messages are how the communication subsystem can report those distinctions without letting the map script micromanage radio behavior.

Here is a concrete example. The player enters BattleColosseum_4P. The map data from pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, places the player in a room that has warps and a multiplayer-specific context. The player talks to an attendant or steps into a trigger. The script does not itself become an RFU protocol implementation. It begins a controlled flow. The local game enters a “preparing link” state. A small waiting window appears. Behind that window, each frame advances tasks, samples input, updates animation, and gives the link manager a chance to process. The manager may report, in effect, “searching,” “partner found,” “child connected,” “parent ready,” “error,” or another named communication event from its taxonomy. I am paraphrasing the behavior here, not quoting constants, but the point is grounded in that header’s separation of mode constants, message taxonomy, and state constants across chunks_8df030184c481b3b, b75d95e146826302, and fc7074757ec1be96.

If all four machines reach the ready boundary, then and only then does the game cross into the battle handoff. At that boundary, the communication state machine has done its job. It has not computed damage. It has not selected moves. It has not rendered the battle HUD. Its job was to bring multiple machines to a synchronized agreement about what activity is starting. After that, the battle system must run as a linked battle, which is why the handoff boundary is architectural, not cosmetic.

Let me ask it another way. Why not let the map keep running while the linked battle starts? Because the map and the battle are different worlds of authority. In the map, coordinates, object events, scripts, and warps are meaningful. In battle, battler slots, move selections, controller commands, battle scripts, and synchronized decisions are meaningful. If one machine thinks it is still in the Cable Club room while another has begun battle command selection, the session is already broken at the gameplay level even if some bytes are still being exchanged.

This is also why return paths matter. Multiplayer handoff is not complete when the battle starts. It is complete when the battle or trade returns control to a coherent overworld state. You need to know which map to restore, which script or callback resumes, whether the player should still be locked, whether music changes back, whether link state must be torn down, and whether any persistent data must be written. I am deliberately keeping save discussion brief because we covered save block ownership, load-save initialization, and sector validation in week 20. But link features touch persistence at the edges. A trade can modify party or storage data. A battle may update records or flags. So the communication state machine must not pretend it owns persistence. It must deliver the program to the system that does.

Now, slide cue, look at the full course stack again. Runtime shell at the bottom gives us frame progression. Overworld maps provide authored places, like the Cable Club rooms. Event scripting turns map interactions into requests. Creature data provides parties and legality constraints. Battle consumes parties and produces battle outcomes. Inventory and UI may be involved in preparation or restrictions. Save persists legitimate changes. Link coordinates multiple machines so those systems can act together. Week 21 is not an extra appendix after the “real game.” Week 21 is the integration test for the whole architecture.

And if you want to know whether your mental model is mature, ask whether you can describe a link battle without starting from the battle engine. Start from the map data. Use pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, to remind yourself that the Cable Club spaces are organized as maps. Use pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, to remind yourself that the multiplayer room is staged through the same map machinery as other places, even though its purpose is specialized. Then use pokefirered/include/AgbRfu_LinkManager.h, chunks_8df030184c481b3b, b75d95e146826302, fc7074757ec1be96, and f549b5be90786ef0, to describe the communication vocabulary. Finally, use pokefirered/src/battle_setup.c, chunk_494d132a59422c85, to explain why battle entry is a setup boundary rather than a casual function call.

Let’s do a quick oral exercise. I’ll state a bug, and you diagnose which boundary was probably violated. Bug one: the waiting message stays on screen, but the partner has already entered battle. That suggests the UI or overworld-side state did not consume the communication-ready event correctly, or one machine crossed the handoff boundary while another remained in the preparation state. Bug two: both players enter battle, but after the battle ends, one player returns to the wrong room. That is probably not a damage-calculation bug. That is a return-context or map-restoration bug. Bug three: a trade changes party data but the game does not retain the change after reset. That points toward persistence handoff, not RFU discovery itself. Bug four: pressing B during negotiation leaves the avatar frozen in the Cable Club. That is a cancellation path failing to unwind overworld locks and UI state.

Those are the kinds of bugs that state machine diagrams help reveal. Not because diagrams are magic, but because they force you to name ownership. Who owns “waiting”? Who owns “connected”? Who owns “ready to fade”? Who owns “currently in battle”? Who owns “safe to save”? If your diagram has one giant state called “linking,” it is not yet doing enough work for you.

Now I want to connect this to the lab. Your workshop model is 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. I do not expect you to reproduce FireRed’s RFU implementation. I do expect you to preserve the boundaries. Your model should have a map-facing entry point. It should have explicit parent and child negotiation states. It should advance per tick or per frame, not by blocking. It should represent callback-style messages or events from the communication layer. It should have a battle handoff state. And it should have cleanup states for success, cancellation, timeout, and return.

A good advanced submission will also distinguish local intent from remote agreement. Local intent is “I want to battle.” Remote agreement is “the other machine is ready to enter the same battle session.” The space between those two is where link architecture lives. Many bugs come from treating local menu confirmation as if it were remote synchronization. We saw the same philosophical pattern in prior weeks with UI-driven data writes. A menu selection is not the same as a committed party mutation. A save request is not the same as a validated flash write. Here, a player choosing battle is not the same as all machines being synchronized for battle.

Let me give you one final board sketch in words. At the top left, write “Overworld: player in Cable Club.” Arrow to “Script: request linked activity.” Arrow to “Link prep: initialize mode.” Under that, a loop: “advance frame, process RFU, receive callback message, update UI, test transition.” From there, three arrows. One arrow goes to “cancel or error: unwind to overworld.” One goes to “timeout: report failure, tear down link, restore control.” One goes to “ready: fade and call battle setup.” Then from battle setup, arrow to “linked battle runtime.” Then arrow to “result and cleanup.” Then arrow back to “overworld map.” That is the lecture in miniature.

So the synthesis for today is this: FireRed’s link manager is best understood as a communication state machine embedded inside a larger game-state choreography. Its header gives us named modes, messages, link states, initialization shape, and manager state. The Cable Club maps give the player a physical and scriptable entry point into that machinery. Battle setup marks the boundary where synchronized communication becomes synchronized gameplay. And the frame-based runtime, including VBlank-aware timing, is the reason all of this must be incremental rather than blocking.

Carry that forward into the next lecture on Cable Club maps as multiplayer entry points. We will shift the emphasis from the communication manager outward to map staging: how rooms, warps, attendants, and scripts create a controlled social interface for link features. But do not leave today thinking those rooms are just decoration. They are architectural buffers. They slow the player down just enough for the engine to prepare the communication state, validate the activity, coordinate partner machines, and hand the game to battle or trade without tearing the runtime into inconsistent pieces.

## 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
