# Simulator API and Online Runtime Boundary

Lecture ID: lecture_w23_d02
Week 23, Day 2

## Objectives
- Design minimal clone versions of map loading, script execution, party data, battle turns, item use, and save persistence.
- Connect week 23 material to the overall course plan.

## Transcript
All right, let’s pick up from where we left off last time. Last lecture, we stayed inside the Showdown turn loop. We looked at the request state, the player’s choice string, the commit step, the queue, and then the runMove-style responsibility boundary. Today I want to move one layer outward. Not “how does a turn resolve,” exactly, but “what is the simulator allowed to know, and what is the online runtime allowed to know?” That boundary is the heart of today’s lecture.

And I want you to keep FireRed in view while we do this. Showdown is useful here because it makes certain boundaries explicit that FireRed tends to express through globals, callbacks, tasks, scripts, and module handoffs. But the course is not changing its center of gravity. FireRed remains our main architecture source. Showdown is our comparison instrument.

So, if I write on the board: “simulator API,” I do not mean “the entire game.” I mean the narrow surface through which an external runtime can ask a deterministic battle model to advance. In Pokemon Showdown, that surface becomes visible in the BattleStream API, which is in pokemon-showdown/sim/battle-stream.ts, chunk_ced1577d9e2d59ac. A stream can receive protocol-like input, create or address a battle, accept choices, and emit output. That is a very different shape from FireRed, where the battle engine is embedded into the game runtime, scheduled through callbacks and tasks, and fed by state already resident in memory.

Now pause on that contrast. Showdown is server-friendly because the simulator can be treated like a service. FireRed is cartridge-friendly because the simulator, the UI, the save blocks, the overworld, and the battle loop all share the same machine. Those are not just implementation details. They produce different architecture.

In FireRed, when the player steps into tall grass or talks to a trainer, battle setup is not a network request. It is a local transition from the overworld state machine into the battle state machine. The source packet points us to pokefirered/src/battle_setup.c, chunk_494d132a59422c85, and even from the includes alone you can see the coupling surface: task.h, overworld.h, item.h, pokemon.h, load_save.h, script.h, event_data.h, field_player_avatar.h, battle.h, battle_transition.h. That include list is not trivia. It tells you that FireRed’s battle entry boundary is a junction among overworld control, scripted event state, party data, battle configuration, and transition effects.

In Showdown, by contrast, the boundary is narrower. A battle request says, in effect, “Here is what this player is currently allowed to choose.” The player responds with a choice. The simulator commits the choices when ready. The action queue resolves. That request-state model is visible in pokemon-showdown/sim/battle.ts, chunk_3b063b1b44a6d638, and the choose and commitChoices flow is the focus of pokemon-showdown/sim/battle.ts, chunk_889e310105810281. Last time we studied those as turn mechanics. Today, treat them as API design.

Let me say that again because it is the first important pivot of the day: a battle request is not only a UI object. It is an authority boundary. It says what the simulator believes the player may legally decide right now.

That is the reason advanced engine work cares so much about request state. If the client says, “I want to use move three,” the simulator should not merely trust the client. The simulator should already know whether move three is selectable, whether the Pokemon is trapped, whether it must recharge, whether it is fainted, whether it is locked into a move, whether a switch is legal, and so on. The request is the simulator’s way of exposing a small window into legal agency. The online runtime can display choices and transmit intent, but it does not become the source of battle truth.

Now let’s connect that to FireRed. In FireRed, the Game Boy Advance client is not untrusted in the same sense. There is no remote web client trying to spoof a move string. But FireRed still has internal boundaries. The battle UI may display a menu, the input system may read A or B, the battle controller may encode a chosen action, and the battle script engine may execute the consequences. Those pieces still need agreement about who owns truth. In a local cartridge architecture, “trust boundary” often becomes “state ownership boundary.”

Here is a concrete example. Imagine we are designing our minimal clone, which your week 23 homework will push you toward. We want a stripped creature RPG with map loading, script execution, party data, battle turns, item use, and save persistence. A naive implementation says, “The battle screen has buttons, and when the player clicks Tackle, subtract HP.” That is fine for a toy demo, but it is not architecture. The stronger design says, “The battle screen asks the battle model for a request. The request includes legal actions. The player selects an action. The battle model commits that action and emits events: move used, damage dealt, HP changed, faint checked, turn advanced.” The UI does not calculate damage. The save system does not calculate damage. The map loader does not calculate damage. The battle model does.

Now, board cue: I’m going to draw six boxes in a row. Map loader. Script VM. Party model. Battle simulator. Inventory service. Save store.

Those six boxes are not equally independent in FireRed, but for our minimal clone, we intentionally separate them so we can see the contracts. The map loader answers, “Where am I, what objects exist, what scripts are attached, what tiles or encounters matter?” The script VM answers, “What event logic is currently executing, and what state changes does it request?” The party model answers, “What creatures exist, what are their stats, moves, HP, status, held items if any?” The battle simulator answers, “Given participants and choices, what happens next?” The inventory service answers, “What items are present and what can each item do in this context?” The save store answers, “What durable state must survive reset?”

Notice how this maps back to the course arc. In weeks 1 and 2, we traced data tables into callbacks and visible behavior. Then we spent a long time on maps, scripts, tasks, headers, generated assets, and runtime globals. In weeks 22 through 24, we are not abandoning that. We are verifying the architecture by comparison. Showdown forces us to ask: if FireRed’s battle is embedded, what would it look like if battle were a module with a narrow API?

That is why BattleStream matters. In pokemon-showdown/sim/battle-stream.ts, chunk_ced1577d9e2d59ac, the simulator surface can be driven by textual protocol input and can produce protocol output. The server runtime around it is separate. The server README, pokemon-showdown/server/README.md, chunk_1ba53f1706d86463, gives the practical side of that: this is software you run as a server process, with localhost behavior during development. That server runtime is not the battle simulator itself. It hosts users, rooms, networking, chat, authentication-adjacent behavior, and process concerns. The battle simulator is a core engine used by that runtime.

This is the contrast I want you to carry: FireRed’s runtime shell hosts everything in one compiled game loop; Showdown’s server hosts simulator instances across an online protocol boundary.

Check for understanding here. If I ask, “Where should weather damage be resolved in a Showdown-like architecture?” the answer is not “in the browser,” and not “in the chat room,” and not “in the save file.” It belongs inside the simulator, because it is battle truth. If I ask, “Where should the selected move button be highlighted?” that belongs in the client or battle UI. If I ask, “Where should the legality of selecting a move be decided?” that belongs in the simulator, surfaced through request state.

Now let’s make the example more FireRed-like. Suppose our minimal clone has a potion item. The player opens the bag during an overworld moment and uses Potion on a party creature. There is no battle turn. The item service validates that the item exists, that the target is a party creature, that the creature is not at full HP, and then it mutates party HP and decrements inventory. Save persistence later records the updated party and bag. Fine.

But now suppose the player uses Potion during battle. The same item data may be involved, and the same party creature may be healed, but the authority path is different. In battle, using an item is a battle choice. It competes with moves and switches. It may consume a turn. It may be disallowed in some formats or contexts. It may trigger messages and ordering rules. So the inventory service cannot simply mutate HP behind the battle simulator’s back. The battle simulator needs to receive an item choice, schedule it in the turn flow, execute it at the correct priority or phase, then emit events indicating the item was consumed and HP changed.

That is one of the main architectural lessons of the day: the same domain object can cross different runtime boundaries depending on context. A Potion in the overworld is an inventory command. A Potion in battle is a battle action that touches inventory.

Showdown makes this kind of distinction highly visible because choices are committed into an action queue. The battle action queue source is pokemon-showdown/sim/battle-queue.ts, chunk_f572d84e556bce5e. Again, we covered the anatomy last time, so I will not re-teach the queue from scratch. Today I want you to see the queue as the place where external intent becomes ordered internal work. A player’s choice is not the same as an executed move. The choice has to become an action. The action has to be ordered. The ordered action has to resolve through battle rules.

That matters for FireRed too. FireRed may not have the same TypeScript class shape, but the conceptual stages still appear. Input is gathered. A command is encoded. Battle scripts and controllers step through consequences. Animation, text, HP changes, fainting, and post-turn effects are not all the same responsibility.

Now I’ll put another phrase on the board: “commit flow protects determinism.”

Why commit? Why not resolve player one as soon as player one chooses? Because in a simultaneous-turn battle, resolving too early leaks information and breaks fairness. You need both sides’ choices, or you need a well-defined partial-choice policy for AI, forced moves, or single-player contexts. Showdown’s choose and commitChoices flow in pokemon-showdown/sim/battle.ts, chunk_889e310105810281, is useful because it separates receiving intent from advancing the turn. That is an API-level decision, not just a convenience.

In FireRed single-player wild battles, the CPU’s choice can be generated locally and immediately, so the pressure is different. But if you remember our link lectures, and just remember them, we do not need to reopen RFU mode constants today, the same concern reappears: once multiple participants or devices are involved, local input timing cannot be allowed to corrupt the shared battle truth. That earlier link material is part of the foundation for understanding why an online runtime boundary has to be disciplined.

Let’s work a small model aloud. We have a minimal battle simulator object. It stores two active creatures. Each creature has HP, speed, moves, volatile flags, and trainer side. The simulator exposes getRequest(playerId). For player one, maybe the request says: active creature is Embermouse, moves are Scratch and Spark, switch options are none, item options include Potion if the ruleset allows items. The client displays that. The player selects “move 2.” The client sends choose(player1, “move 2”). The simulator records that choice but does not execute it yet. Player two chooses “move 1.” Now commitChoices runs. It translates each choice into an action, sorts by action type and priority and speed, resolves first action, calls something like runMove, applies damage, checks fainting, handles residuals, then emits protocol events.

That is almost Showdown-shaped. The runMove responsibilities are exactly why pokemon-showdown/sim/battle-actions.ts, chunk_95a7524b39636e24, is in our source focus for today. A move is not just “base power minus defense.” Running a move may involve target validation, immunity, accuracy, deducting PP, applying secondary effects, invoking event hooks, causing damage, and producing messages. Even when we design a minimal clone, we should preserve the responsibility grouping: move execution belongs in the battle action layer, not in the UI button handler.

Now, to avoid overcomplicating your clone, I am not asking you to rebuild Showdown. Your minimal version can have only a few action types. It can have fixed priority. It can have deterministic damage like “level plus power minus defense, clamped to at least one.” It can ignore abilities and complex event hooks. But it should still have the correct boundary shape. Request. Choice. Commit. Queue or ordered action list. Execute. Emit result. Persist only durable state.

This distinction between durable state and transient state is another place where FireRed and Showdown comparison helps. FireRed has save blocks and persistent player state. Showdown battles are often ephemeral simulator instances hosted by a server. A Showdown battle log may be stored or replayed, but the simulator state itself is not “the player’s cartridge save.” FireRed, on the other hand, must preserve party, inventory, flags, variables, map position, and progression across power cycles. That means our minimal clone save system should not blindly serialize every runtime object.

Let me make that concrete. Do not save the current rendered menu. Do not save the current animation frame. Usually do not save a pending text box callback unless your engine is explicitly designed for mid-script saving. Save the map id, player coordinates, event flags, variables, party creatures, inventory, money, and maybe current story state. If saving in battle is disallowed, then battle state is transient. If your design supports battle resume, then battle state becomes durable, but that is a deliberate contract.

FireRed’s architecture makes that distinction through modules like load_save.h appearing at boundaries, and the battle setup include surface in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, reminds us that entering battle is a coordinated transition, not just a function call from a map tile. The transition touches current overworld state, party state, scripts, battle configuration, and later returns control. In a minimal clone, you can express that with a GameMode enum or a runtime state stack, but the important point is the same: entering battle is a boundary crossing.

Let’s do a quick comprehension check. Suppose a trainer script says, “Start battle with trainer 17, and after victory set flag BEAT_TRAINER_17.” Where should that flag be set? Not when the battle starts. Not when the player selects the last attack. It should be set after the battle result is known and control returns through the script or event continuation path. In a FireRed-style architecture, the script system and battle setup cooperate so that the overworld event can resume after the battle. In a minimal clone, your script VM might yield an instruction StartBattle(trainerId), the game mode switches to battle, and when battle returns Victory, the script VM resumes and sets the flag.

That yield-and-resume pattern is the clean clone version of what we have been seeing all semester in more specialized C forms. We have already studied main callback slots, RunTasks, and script execution as per-frame dispatch. Today’s job is to apply that same thinking to an online-simulator comparison.

Now I want to say something about “server boundary,” because it is easy to misunderstand. A server boundary is not automatically a clean architecture boundary. You can put bad architecture behind an HTTP endpoint. You can also have clean boundaries inside a single-process cartridge game. What Showdown gives us is a good case study where the online runtime must separate concerns because of scale and trust. The server has users and rooms. The simulator has battles. BattleStream gives a controlled way to drive battles. The server README in pokemon-showdown/server/README.md, chunk_1ba53f1706d86463, is mundane but important evidence: the server is an executable runtime environment, not just a library import. That runtime can run locally for development, and then the simulator API is used inside a broader online system.

So when you design your clone, even if it is single-player and local, ask this question: “If I had to put my battle engine behind a stream tomorrow, what would I regret?” You would regret if your move buttons directly changed HP. You would regret if map scripts reached into battle internals to force damage. You would regret if saving required serializing half the renderer. You would regret if item effects were hard-coded separately in overworld and battle with inconsistent healing rules. You would regret if party data existed in three incompatible copies.

But you would not regret a small, explicit party model. You would not regret an item-use function that can be called through either an overworld command path or a battle action path, with context-specific validation. You would not regret a battle event log that the UI consumes. You would not regret a script VM that yields to battle and resumes from a result. Those are the design moves that keep the clone small while still architectural.

Let’s briefly connect this to data lookup, without re-teaching the Dex lectures. In Showdown, the Dex and generation mods give battle code a uniform way to ask for species, moves, abilities, items, and format-specific behavior. We covered that recently. In FireRed, data tables and constants serve the equivalent role, though with a much more static C shape. For today, the important point is that the simulator should pull rule data from a data layer, not from the online runtime. The server should not decide what Thunderbolt does. The simulator should. Likewise, in a FireRed-inspired clone, the map loader should not know the damage formula for Scratch. It can know that a trainer has a creature with Scratch. The battle module resolves Scratch.

Now I’m going to walk through a minimal clone boundary in one coherent scenario.

The player loads a save. The save store returns durable state: current map PalletRoad, coordinates 12 and 8, party with one creature, inventory with two Potions, flags for starter chosen and rival not beaten. The runtime initializes the overworld mode. The map loader reads the PalletRoad map definition, spawns objects whose flags allow them to appear, and attaches scripts. The player walks to an NPC. The script VM begins the NPC script. It displays text, then yields StartBattle with trainer Rival1. The runtime calls the battle setup layer, which copies or references the relevant party data, constructs enemy party data from trainer records, and enters battle mode.

Now the battle simulator produces a request. The battle UI renders the request. The player selects a move. The enemy AI selects a move. The commit flow creates ordered actions. The first creature uses Scratch. The runMove layer validates target, calculates damage, applies HP change, and emits “move used” and “damage” events. The second creature maybe faints, so the battle emits a faint event and checks win conditions. Battle ends with player victory. The runtime exits battle mode and returns Victory to the suspended script. The script sets BEAT_RIVAL1, gives money, maybe changes an NPC movement state, then ends. The save store later persists the updated flags, party HP, inventory, and map state.

That scenario uses all six systems from the objective: map loading, script execution, party data, battle turns, item use if we include Potion, and save persistence. More importantly, it gives each system a reason to exist. It is not six folders because we like folders. It is six responsibilities because different kinds of truth move at different times.

Check yourself on this: in that scenario, if the battle UI crashes and redraws, should the battle outcome change? No. Rendering is not battle truth. If the player saves after the rival battle, should the rival be beaten after reset? Yes. That flag is durable progression truth. If the player’s creature lost HP during battle, should that HP persist after battle? In FireRed-style design, yes, unless a particular battle mode heals afterward. Party HP is durable party truth. If an animation played at frame 17, should save persistence care? No.

This is why I keep using the phrase “truth owner.” Advanced architecture is often less about inventing clever systems and more about refusing to let every system own the same fact.

Now, before we go further into BattleStream itself in the next segment, I want to sharpen one subtle point. A protocol event is not necessarily the same as an internal event. Showdown emits protocol output that clients can consume. Internally, the simulator may have richer objects, function calls, side conditions, volatile statuses, and queue entries. FireRed similarly has internal battle scripts, controller commands, OAM sprites, text buffers, and palette transitions, but the player sees only a coherent battle scene. In your clone, you may have internal events like DamageApplied with numeric fields, and UI events like “show HP bar animation.” Keep those separate if the project grows.

A useful minimal pattern is this: internal battle result entries are semantic, and UI presentation derives from them. For example, internal event: MoveUsed, actor id, move id. Internal event: DamageDealt, target id, amount, resulting HP. Internal event: Fainted, target id. The UI can turn those into text, sounds, animations, and timing. The save system can ignore most of them unless you are recording replays. A test harness can assert on them. That last part is one reason Showdown is so valuable to study: a simulator with a narrow API and deterministic output is testable in a way that an all-in-one visual game loop is not.

And that is where this week fits into the overall course plan. Weeks 1 through 21 built the mental model of FireRed as cooperating state machines over data tables. Week 22 asked you to trace behavior from records through globals, callbacks, tasks, scripts, structs, and functions. Week 23, this week, asks you to compare that architecture against Showdown’s simulator boundary so you can see which parts of FireRed are essential RPG architecture and which parts are consequences of platform, language, and era. Week 24 will turn that into final verification: can you decompose, trace, and defend a complete creature RPG architecture without confusing UI, runtime, simulator, and persistence responsibilities?

So the first synthesis for today is this: Showdown’s BattleStream and choice commit model give us a clean vocabulary for simulator boundaries, while FireRed gives us the embedded runtime reality of a cartridge RPG. A strong clone design borrows the boundary clarity without pretending FireRed was built as an online service. It keeps battle truth inside battle, progression truth inside scripts and flags, durable truth inside save data, and presentation truth inside UI.

Now, with that foundation in place, we can move from the conceptual boundary to the actual exchange pattern: what a minimal BattleStream-like conversation looks like, how a request becomes a choice, how choices become queued actions, and how the resulting protocol maps back onto FireRed-style battle concepts.

So let’s make that exchange concrete. Imagine I am drawing a vertical line on the board. On the left side I write “client,” and on the right side I write “simulator.” The client may be a browser, a test harness, a local debug console, or, in our FireRed clone, a battle menu task feeding selections into a battle engine. The simulator is the authority for battle legality and battle consequences. The important point is not that the client and simulator must be separate processes. The important point is that they must have different responsibilities.

In Showdown, the BattleStream API is the cleanest version of that idea for our purposes today. The relevant source focus is pokemon-showdown/sim/battle-stream.ts, chunk_ced1577d9e2d59ac, and what matters architecturally is that you can treat battle as an exchange of inputs and outputs. You push choices in, you receive protocol messages out. That is very different from a FireRed cartridge implementation, where battle, UI, memory, save state, and hardware scheduling all live in the same embedded runtime. But as a design vocabulary, it is extremely useful.

Now, suppose the simulator says, “Player one, here is your request.” That request is not the same as the whole battle state. It is a carefully shaped slice of state: what active creature you have, what moves are selectable, whether switching is possible, whether you are fainted and must replace, whether an item menu is relevant in this mode, and so on. Showdown’s Battle class request-state model is the source anchor here, pokemon-showdown/sim/battle.ts, chunk_3b063b1b44a6d638. The battle engine knows more than it reveals. That is not accidental. It is a boundary.

Now I want you to pause on that phrase: the battle engine knows more than it reveals. That principle is exactly what prevents your clone architecture from becoming a pile of global variables with buttons attached. In a beginner implementation, the menu might inspect everything directly: enemy HP, move list, party list, faint flags, trainer flags, battle terrain, inventory, save file, and script variables. It “works,” briefly. But then item use changes battle state, fainting changes script flow, a forced switch changes party data, and suddenly the menu is half simulator, half renderer, half persistence layer. Yes, I know that is three halves. That is the point.

A better design says: battle request state is a contract. The UI receives a request, displays legal choices, and returns a choice command. It does not decide the outcome. It does not subtract HP. It does not apply poison. It does not declare victory. It asks the player, “What choice do you commit?”

Now, let’s put a tiny exchange on the board in words. The battle starts. The simulator emits: request for player one, choose a move or switch. Request for player two, choose a move or switch. Player one sends: move one. Player two sends: move two. The simulator commits both choices. Then it resolves the queue. It emits protocol messages: Charmeleon used Ember; Bulbasaur lost health; Bulbasaur used Vine Whip; Charmeleon lost health; end-of-turn poison damage, if any; new request.

Notice the rhythm. Request, choice, commit, queue, execution, output, next request. That rhythm is the subject of week 23.

In Showdown, the choose and commit flow is anchored in pokemon-showdown/sim/battle.ts, chunk_889e310105810281. The battle action queue is anchored in pokemon-showdown/sim/battle-queue.ts, chunk_f572d84e556bce5e. The move execution responsibilities are anchored in pokemon-showdown/sim/battle-actions.ts, chunk_95a7524b39636e24. We are not copying those files into a FireRed clone. We are extracting the architectural lesson: player input should become a committed battle choice before it becomes an ordered action, and action resolution should be centralized enough that damage, messages, fainting, replacements, and end-of-turn effects do not scatter across UI handlers.

Check for understanding here. If I click “Tackle” in the UI, has damage happened yet? No. In this architecture, clicking “Tackle” creates or submits a choice. It may be validated. It may be committed. It may enter a queue. But damage happens when the battle engine resolves the action. That distinction sounds small, but it is the difference between a testable simulator and an untestable animation script.

Now let’s translate that back into a minimal FireRed-style clone. We need map loading, script execution, party data, battle turns, item use, and save persistence. Those six systems are not equal layers. They form a chain of authority.

Map loading establishes where the player is and what event objects and triggers exist. Script execution interprets interactions and progression rules. Party data stores the player’s creatures in a durable-ish runtime structure. Battle turns temporarily take authority over combat consequences. Item use may be field-level, battle-level, or party-level depending on context. Save persistence snapshots selected durable state, not every transient animation or menu cursor.

So, let’s use a concrete example. The player walks into a trainer’s line of sight on Route 3. In a FireRed-like architecture, map loading has already placed that trainer object and associated it with event data. The script system detects or is invoked by the encounter. The trainer script says, in effect, lock player movement, display dialogue, configure battle, transition to battle, then after battle set a defeated flag and resume the map. We have source support for that boundary in FireRed’s battle setup area: pokefirered/src/battle_setup.c, chunk_494d132a59422c85, which sits among includes for overworld, item, script, event data, field control, battle, battle transition, and trainer-related constants. Just from that include neighborhood, and from its role in battle setup, we can see that battle entry is a coordination boundary. It is not merely “call damage function.”

Now, in our clone, we might implement that as: map trigger calls script command startTrainerBattle. The script command creates a BattleStartConfig from trainer ID, player party reference, enemy party template, battle type, and post-battle callback. Then the runtime switches from overworld mode to battle mode. In FireRed terms, this echoes our earlier weeks on callbacks and tasks, but we do not need to re-teach those today. We just need the prior foundation: runtime mode changes and per-frame dispatch are the shell that lets battle own the screen for a while.

Once battle starts, Showdown’s model helps us avoid a common clone mistake. The battle should not ask the overworld script, “How much damage does Ember do?” The script’s job was to start the battle and later observe the result. Battle owns the turn loop. The script may receive “player won” or “player lost” afterward. That is the boundary.

Now watch how item use exposes the same issue. If the player uses a Potion from the field menu, the item use flow belongs mostly to inventory plus party data plus UI. If the player uses a Potion during battle, the item choice is a battle choice. It competes with moves and switching in the turn economy. In a simple clone, we might say items always have priority before enemy moves. In a more faithful design, we encode item actions into the same action queue as moves, switches, and possibly escape attempts. Showdown gives us the vocabulary of committed choices and queued actions; FireRed gives us the embedded reality that item menus and battle menus are state machines running in a shared game shell.

This is where we can use the move relearner as an analogy without re-teaching it. In pokefirered/src/learn_move.c, chunk_af3598df6f092723, the source packet highlights a “Move relearner state machine,” with menu states, scroll positions, confirmation, and transitions. Then chunk_e820ee15ae8caa6d continues with confirmation behavior around teaching a move. Why mention that in a battle-runtime lecture? Because it is a non-battle example of the same discipline: UI state asks questions, gathers confirmation, and then applies a bounded mutation to party move data. The menu is not the whole game. It is a state machine around a specific operation. Battle choice UI should have the same restraint.

Let me phrase that as an implementation rule for your minimal clone: every command that mutates durable creature data should have an owning subsystem. Learning a move is owned by party or creature data services, mediated by a menu state. Using a potion is owned by item-effect logic applied to party or battle targets, mediated by inventory and possibly battle action choice. Dealing move damage is owned by battle action resolution, mediated by committed choices. Saving is owned by the persistence layer. If you cannot name the owner, your code will eventually put the mutation in the nearest button handler.

Now, back to the stream. A BattleStream-like exchange in your lab does not need sockets. It can be a function call interface. For example, you could have a BattleSession object with readOutput and writeInput. Or, more simply, advanceBattle(choiceP1, choiceP2) returns a list of protocol events and the next request objects. The important point is that output is explicit. Do not make your test inspect twenty private fields to determine whether the turn worked. Make the simulator say what happened.

Showdown’s protocol style is especially valuable here because it separates the event log from the renderer. “Move used” is not the same as “play this exact animation frame.” “Damage occurred” is not the same as “shake this sprite for twelve frames.” In a FireRed clone, you may later bind those protocol events to animations, sound effects, text boxes, waits, and sprite updates. But the simulator should be testable before the animation exists.

Here is the board version. I write: BattleEvent: move, damage, faint, status, message, request. Under that, I write: Renderer consumes events. Save file does not. Script may consume final result. That last sentence matters. Your save file should not store “halfway through Ember animation frame seven” unless you are intentionally building suspend-state emulation. A normal RPG save stores durable progression and party state at controlled moments. It does not serialize every temporary queue entry.

This connects directly to the course plan. In weeks 1 through 22, we kept tracing from data records through globals, callbacks, tasks, scripts, structs, and visible behavior. The week 22 assessments asked you to defend that trace. Week 23 adds a comparison layer: Showdown lets us name a cleaner simulator boundary, and then we ask what parts of that boundary are useful when designing a FireRed-like clone. Next week, as we move toward final verification, the question becomes: can you test these boundaries without confusing them? Can you prove that your map loader does not secretly own battle damage? Can you prove that your battle simulator does not secretly write story flags? Can you prove that your save system persists the right state and ignores transient UI?

Let’s test that with a scenario. A wild Pidgey appears. The player chooses Bag, selects a Poké Ball, and catches it. Where does each responsibility live? The map system caused or permitted the encounter, perhaps through grass encounter logic. The battle system owns the current wild opponent and the turn context. The inventory system verifies that a Poké Ball exists and decrements it at the appropriate committed moment. The capture formula belongs to battle or a battle-adjacent capture service because it depends on battle target state. Party data receives the new creature if capture succeeds. The script or overworld runtime receives the battle result and returns the player to the map. Save persistence later stores the updated party, inventory, flags, and location.

Now, if your answer was “the item button catches the Pidgey,” that is a red flag. The button can request the item use. It should not own the capture result. If your answer was “the save file adds the Pidgey,” that is also a red flag. The save file persists the party after the party has changed. It does not decide gameplay.

And yes, in a very small student clone, you can cheat this. You can write a single useItem function that both decrements the bag and adds the caught creature. But by this point in the course, the bar is higher. The recent homework and quizzes have already checked decomposition and tracing. So I am no longer asking, “Can you make it work once?” I am asking, “Can you place the responsibility where future systems will not break it?”

Now, let’s tie this to runMove. In Showdown, runMove responsibilities are substantial because move execution is not just base power arithmetic. It includes targeting, immunities, secondary effects, failure conditions, messages, damage application, and hooks. The source anchor is pokemon-showdown/sim/battle-actions.ts, chunk_95a7524b39636e24. In our clone, a minimal runMove might do only five things: validate that the user can act, resolve target, calculate damage, apply damage, and emit events. That is enough for a first architecture. But even that minimal version should be in the battle layer, not inside the UI menu and not inside the creature struct.

Let me give you a deliberately small example. Charmander has Scratch and Ember. Bulbasaur has Tackle and Growl. The request sent to player one says Scratch and Ember are available. The player chooses Ember. The request sent to player two says Tackle and Growl are available. The enemy AI chooses Tackle. Commit happens. The queue orders actions by priority and speed. Charmander acts first. runMove receives Charmander, Ember, Bulbasaur, and the battle context. It emits “move used.” It computes damage. It applies HP reduction. It emits “damage.” If Bulbasaur faints, it emits “faint” and the later action may be skipped. If Bulbasaur survives, Tackle resolves. End-of-turn effects resolve. Then the simulator emits the next request.

The key is that request generation happens after the previous consequences have settled. You should not ask for the next move while fainting, forced switches, capture, or victory are unresolved. This is why action queues matter even in a minimal clone. Without a queue, students often write turn code as a linear if statement: player move, enemy move, poison, check win. Then they add Quick Attack. Then they add switching. Then they add items. Then they add faint replacement. The if statement becomes a maze. A queue is a way of admitting that a turn is not one thing. It is an ordered set of actions, some chosen by players, some generated by rules.

Now, a check for understanding. In our minimal clone, should end-of-turn poison damage be represented as a player choice? No. It is an automatically generated residual action or effect resolution step. Should it appear in the protocol output? Yes, if it changes visible state or matters for replay and testing. Should the UI calculate it? No. The UI displays it.

That last answer is one of the strongest connections between Showdown and FireRed. FireRed is not architected as a server simulator, but it is still full of boundaries between data, state machines, callbacks, and visible behavior. Earlier in the course, we saw that headers act as cross-module contracts, that generated data differs from runtime JSON, and that visible behavior often comes from data table to callback to task to renderer. Week 23 says: apply that same discipline to battle turns and online-style simulation boundaries.

We should also mention the server boundary, because the lecture title includes online runtime boundary. Showdown’s server README, pokemon-showdown/server/README.md, chunk_1ba53f1706d86463, is relevant because it places the simulator inside a server runtime that can be run locally and interacted with through localhost behavior. For our purposes, the server is not the battle engine. It hosts users, rooms, networking, and process behavior around the simulator. That distinction is essential. If you are building a clone with online battles, the network server should not become the owner of type effectiveness. It should authenticate users, carry messages, coordinate sessions, and call the simulator. The simulator should remain the authority for battle rules.

In FireRed terms, think of the link system we covered previously as the foundation, not the topic today. RFU modes, link manager callback messages, and VBlank-sensitive link state taught us that multiplayer communication is a runtime coordination problem. Today we add: communication should carry choices and state updates across a boundary, but it should not dissolve the boundary. The remote player’s “move one” arrives as input. The local or shared battle engine resolves it according to battle rules.

Now, let’s bring save persistence into the picture one more time, because it is a common final-project failure mode. Suppose a battle is in progress and the player uses a Potion. Do we save immediately after the Potion is consumed? In a standard FireRed-like design, usually no, not as an automatic battle save. The inventory count changes in runtime state. If the battle ends and the game reaches a save point or explicit save command, persistence writes that durable state. Some modern games autosave more aggressively, but then autosave is a policy layer with carefully chosen boundaries. It is not “every subsystem writes disk when it feels important.”

So your minimal save model should probably persist player location, map group or map ID equivalent, event flags and variables, party data, inventory, money, and maybe play time. It should not persist the current battle action queue unless you are explicitly designing suspend and resume. It should not persist request objects, because requests can be regenerated from battle state if battle state is durable, and in many RPGs battle state is not saved at all.

One more concrete mapping. Map loading: loads map header, layout reference, object events, warps, triggers. Script execution: runs command stream for NPCs, signs, trainers, and progression. Party data: stores species, level, HP, moves, status, experience, and identity fields. Battle turns: consume party snapshots or references, generate requests, commit choices, resolve action queues. Item use: dispatches item effects according to field, party, or battle context. Save persistence: serializes durable state at defined save boundaries.

That is the minimal clone architecture I expect you to be able to defend. Not because it is the only possible architecture, but because every boundary has a reason.

Let me check the room. If a trainer is defeated, who sets the defeated flag? The battle engine knows who won, but the script or event system should own progression flags. The battle can return a result: player won against trainer 17. The post-battle script can set trainer 17 defeated. If battle directly flips arbitrary map flags, then battle now knows story progression. That is too much coupling. FireRed’s battle setup area sitting among script and event headers in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, reminds us that battle entry and exit are coordinated with scripts and event data, but coordination is not the same as ownership.

If a move is learned after leveling up at the end of battle, who owns that? This is a richer case. The battle result may produce experience gain and level-up. The party data must mutate the creature’s level and stats. The move-learning UI may ask the player whether to replace an old move. The move relearner state machine source in pokefirered/src/learn_move.c, chunk_af3598df6f092723 and chunk_e820ee15ae8caa6d, is useful as a model of menu-mediated party mutation. The battle can trigger the level-up sequence, but the choice to forget a move is not damage resolution. It is a party-data mutation mediated by UI. Again, boundaries.

So, to close this segment, not the whole lecture, I want you to hold onto a compact synthesis. Showdown gives us a crisp simulator conversation: requests go out, choices come in, choices are committed, queued actions resolve, protocol output describes consequences. FireRed gives us the cartridge RPG context: map scripts start battles, party and inventory are shared gameplay data, UI is task-and-callback driven, and save persistence is explicit durable state rather than a transcript of every frame. A strong advanced clone takes both lessons seriously. It uses the stream model to make battle testable, and it uses the FireRed architecture to keep battle embedded in a complete RPG rather than floating as an isolated damage calculator.

Now, in the next part, we will push this one level further and ask how to write tests around this boundary: not just “did Ember do damage,” but “can the same battle exchange be replayed, inspected, compared to protocol output, and then cleanly handed back to overworld script state without leaking simulator internals into the rest of the game.”

So, if I write that question on the board, it becomes our architectural test: can one player-visible event cross three runtimes without losing ownership? The overworld owns position, map, script cursor, and durable progression. The battle simulator owns turn state, choices, queue resolution, damage, fainting, and battle protocol. The save layer owns what survives power-off. The test is not simply whether those systems can call each other. The test is whether each one can be replaced, replayed, or inspected without the others cheating.

Let’s make that concrete. Imagine a minimal clone scene. The player steps onto a route tile, an NPC trainer script fires, the script says, in effect, “lock player movement, show encounter line, start trainer battle with party template 17, then if the player wins set event flag 211 and continue at the reward line.” That is the FireRed-flavored framing. We are not going to re-teach map headers today, but remember the established pattern from earlier weeks: data selects runtime behavior through tables and callbacks. In FireRed, battle setup sits at a crossroads of overworld, script, item, party, and transition includes. The source packet points us to pokefirered/src/battle_setup.c, chunk_494d132a59422c85, where battle setup pulls in headers like overworld, item, script, script_pokemon_util, event_data, battle, and battle_transition. That include list is not an implementation by itself, but it is evidence of the ownership boundary: battle setup is not a pure damage function. It is a bridge from field gameplay into a battle mode.

Now compare that with Showdown. In the Showdown material for this week, pokemon-showdown/sim/battle-stream.ts, chunk_ced1577d9e2d59, gives us the API-shaped lesson: a battle can be driven as an exchange of inputs and outputs, rather than as an invisible pile of callbacks. BattleStream is not “the server.” It is a stream interface around the simulator. That distinction matters because an advanced clone should be able to run the same battle in three places: in a local cartridge-style runtime, in a headless test, and perhaps later inside an online room. If the battle engine assumes direct access to your overworld globals, you lose that option.

So, board cue here: I’m going to draw three boxes. First box, overworld runtime. Second box, battle stream or battle session. Third box, persistence. Between overworld and battle, I write “start request” going right, and “battle result” going left. Between battle and persistence, I do not draw a direct arrow yet. That absence is intentional. In a clean clone, the battle simulator should not save the game. It should emit facts: player won, opponent fainted, item consumed if the battle API is allowed to consume it, experience gained if that is in scope, maybe captured species if this is a wild battle. The script or game coordinator then decides how those facts modify durable state.

This is where students often over-couple the clone. They make the move executor say, “if trainer defeated, set flag 211.” That feels efficient because the battle code has all the momentum. But it is wrong at the boundary. The move executor should not know what map script flag means “Brock defeated” or “route trainer defeated.” By the time you are in the part of the simulator analogous to runMove responsibilities, you are inside a local tactical world. The Showdown source focus for pokemon-showdown/sim/battle-actions.ts, chunk_95a7524b39636e24, is our reminder that runMove-style responsibilities are about resolving move execution: target legality, move effects, damage, secondary consequences, messages. They are not about editing the RPG’s map script cursor.

Let’s test that claim with a small example. Suppose our clone has a Potion. Outside battle, Potion is an inventory action that selects a party member, heals HP, decrements quantity, and returns to the bag screen. In battle, Potion is a turn choice. It competes with the opponent’s move, has timing, can fail if the target is fainted or full HP, and emits messages into the battle log. Same item data, different runtime ownership. If your item function directly modifies both overworld menus and battle queues, you have made one function responsible for two runtimes. A better design is to let item data be shared, but item execution be contextual. The battle session receives “player chooses item potion on slot 0,” and that becomes an action in the queue. The overworld bag receives “use item potion on party slot 0,” and that becomes an immediate menu-driven operation.

This is exactly why last session’s request and commit model is worth keeping in your head. In pokemon-showdown/sim/battle.ts, chunk_3b063b1b44a6d638, the Battle class and request-state model give us a vocabulary for what the simulator is waiting for. The player is not always free to send any command. The simulator asks for a move, a switch, a team choice, or some other decision. Then pokemon-showdown/sim/battle.ts, chunk_889e310105810281, gives us the choose and commitChoices flow as the comparison point: choices are gathered, validated, committed, and then resolution proceeds. For our FireRed-style clone, that means the battle UI should not directly call “apply ThunderShock.” It should answer the current request. The simulator decides when a complete set of choices exists.

Check for understanding here. If I ask, “Where should the A button handler for the fight menu apply damage?” the answer is: nowhere. The A button handler should translate cursor position into a choice, probably something like move index 0 or move id 84, and submit that choice to the battle session. Damage belongs after commit, inside queue resolution and move execution. If you let the menu handler apply damage, you have skipped the simulator boundary and made the UI part of the battle rules.

Now, let’s bring this back to test design. A weak test says, “Create two creatures, call Ember, assert target HP went down.” That test has value, but it only checks a leaf behavior. A stronger week 23 test says, “Start battle from a script command, observe a request, submit player choice, submit opponent choice or let AI choose, commit, drain protocol output, assert HP changed, assert the script is still blocked until battle result is returned, then finish the script and assert event flag and save state.” That is the kind of test that proves the boundary.

If I were writing the minimal clone test harness, I would make a fake map loader first. Not a full map renderer. Just enough to load map id, object events, script entry points, and event flags. Then a script executor with a small instruction set: lock, message, startbattle, waitbattle, setflag, giveitem, release. Then a party model with stable creature records: species, level, current HP, max HP, moves, status, held item if you support it. Then a battle stream adapter that consumes startbattle and exposes request-output-choice cycles. Then inventory. Then save persistence.

Notice the order. I did not begin with save files. I began with runtime ownership. Save persistence becomes clean only after you know which state belongs to the RPG world and which state belongs to the temporary battle. FireRed’s architecture keeps reminding us of that distinction. Save data is not a transcript of every callback frame. It is a durable snapshot of meaningful game state. So in a minimal clone, save should contain player position, map id, event flags, party, bag quantities, maybe money and trainer metadata. It should not usually contain “currently resolving action queue item number 6,” unless your design explicitly supports mid-battle saves, which classic FireRed does not in the normal way.

Let’s pause on the queue, because this is the part where the online comparison gives us a tool that FireRed by itself does not make as explicit. Pokemon-showdown/sim/battle-queue.ts, chunk_f572d84e556bce5e, is the source focus for battle action queue behavior. The queue is an ordering device. It turns submitted choices and generated follow-up actions into a deterministic resolution sequence. For a clone, the key is not to duplicate every Showdown action type. The key is to use a queue as a testable middle representation. “Player uses move.” “Enemy uses move.” “Residual poison damage.” “Faint check.” “Prompt switch.” These can be inspected before they resolve. That is immensely useful when debugging.

Here is a concrete failure mode. Student clone A has a function called runTurn. It contains fifty lines: if player speed greater, player attacks, if enemy alive enemy attacks, apply poison, check faint, maybe end. Then the student adds Quick Attack. Then they add healing items. Then switching. Then two-turn moves. Then forced switch. The function becomes a nest of exceptions. Student clone B has choices converted into actions, actions sorted or inserted by priority, and resolution calls specialized handlers. Student B still has complexity, but now the complexity has addresses. You can test action ordering separately from damage math. You can print the queue. You can replay.

Now I’ll write a tiny transcript exchange on the board, because this is how I want you to think about the boundary. The map script calls startBattle with player party and opponent party. The battle session outputs: request player active choice. The UI submits: choose move 1. The AI submits: choose move 2. The battle session commits. The queue contains two move actions. The session resolves action one, emits protocol: player creature used Vine Whip, target lost 8 HP. Then action two, if the target can act, emits: opponent used Tackle, player lost 5 HP. Then maybe it emits: request next choice. At no point did the map script need to know whether Vine Whip was physical, special, super effective, or blocked by accuracy. It waits for either “battle still active” or “battle ended with result.”

That example also tells you what to snapshot in tests. Snapshot the protocol output at the stream boundary, not private internal fields. In Showdown, BattleStream gives us that discipline because the API naturally produces protocol text. In our clone, the exact protocol format can be simpler, but it should exist. A line like “|-damage|p2a: Rattata|12/20” is more stable as a regression artifact than inspecting five internal booleans. You may still assert internal state in focused unit tests, but integration tests should prefer boundary-visible facts.

Now, careful: this does not mean everything has to be stringly typed. Your internal event can be structured, and you can render it to a protocol line for logs. In an advanced clone, I would probably have BattleEvent objects internally and a protocol serializer at the edge. The important part is that the overworld receives a battle result object, not a pointer to the battle’s private mutable state. That result object might say winner, defeatedTrainerId, partyDelta, inventoryDelta, experienceEvents, captureResult, and transcript hash. You do not need all of those on day one, but naming the boundary prevents accidental leakage.

Let’s connect this to the server boundary, because that is the second half of today’s title. The pokemon-showdown/server/README.md, chunk_1ba53f1706d86463, is included this week specifically because the server runtime is not the same thing as the simulator. The server can run locally and host rooms, users, connections, and commands. But the simulator API is narrower: it advances battles. This is a crucial distinction for your final projects. If you build an online mode, do not put matchmaking, chat, accounts, and save persistence inside the battle engine. The battle engine should be able to run without a socket. The socket should submit choices and receive protocol. That is the same principle we used for the local UI.

You can now see why the RFU and link material from earlier matters without re-opening it. We already studied link manager messages and timing as one boundary style. Today’s boundary is a higher-level simulator boundary. The old handheld link system cared about synchronizing small pieces of state under strict timing. The Showdown-style comparison cares about request, choice, commit, queue, and protocol under a server or stream abstraction. Both are boundaries. Both punish hidden global coupling. Different technology, same architectural pressure.

Let’s do another check. Suppose your online client receives “request move” from the battle session, and the player disconnects. Should the battle action queue continue resolving the player’s unchosen move? No, because no committed choice exists yet, unless your rules define a timeout choice. The battle is waiting at request state. That answer follows directly from the model in pokemon-showdown/sim/battle.ts, chunk_3b063b1b44a6d638, and the choose/commit flow in chunk_889e310105810281. Request state is not decoration. It is the simulator’s contract for what inputs are legal right now.

Now let’s extend the minimal clone to persistence. The test should prove that battle temporary state is not accidentally serialized as permanent world state. I would run this scenario. Save before trainer battle. Start battle. Use a Potion in battle. Win. Script sets trainer defeated flag and maybe awards money. Save after battle. Reload. Assert player is on same map, trainer flag is set, bag has one fewer Potion, party HP and experience match post-battle state, and there is no active battle session. That final assertion is important. You are proving the coordinator collapsed the battle transcript into durable RPG state.

If you support losing, the same test becomes more interesting. In FireRed-like rules, losing may warp you to a healing location, reduce money, heal party, and reset certain script context. Again, the battle engine should not own that whole policy. It can report loss. The game coordinator or script system applies the loss consequence. Otherwise every battle mode has to know about map warps, healing locations, money formulas, and story flags.

This is also where item use becomes a good boundary stress test. Let’s say the player has one Potion. The battle request allows item use. The player chooses Potion on Bulbasaur. During commit, the battle session validates that the item exists, the target is valid, and the item can be used in battle. Where does the quantity decrement happen? There are two defensible designs. In one, the battle session is given an inventory facade and consumes the item atomically during action resolution. In the other, the coordinator reserves the item at choice time and confirms consumption when the battle action succeeds. What I do not want is UI decrementing quantity on cursor click before the simulator validates the turn. That creates rollback problems.

For a course project, I would choose the simpler version: battle session receives a mutable battle inventory view at battle start, validates and decrements during item action resolution, and returns an inventory delta at battle end. That keeps tests understandable. Later, if you build online battles where item legality differs by format, you can split validation from inventory mutation more formally.

Now, I want to tie in one more FireRed source example that shows state machines are not just for battle. The packet includes pokefirered/src/learn_move.c, chunk_af3598df6f092723, with a comment describing the move relearner state machine, including callback setup and menu states. That is not our main topic today, but it is a useful reminder that FireRed frequently organizes UI flows as explicit states rather than as one giant blocking function. The move relearner is a menu-driven feature, not a battle turn engine, yet it uses state progression. That gives you permission, in your clone, to make the battle UI, script executor, and item menu all stateful front ends over narrower domain operations. They do not have to be synchronous functions that finish instantly.

So when we say “minimal clone,” we do not mean “toy with no architecture.” We mean minimal surface area with honest boundaries. Map loading can be minimal: map id, collision, object scripts. Script execution can be minimal: a program counter and a few opcodes. Party data can be minimal: six creature structs. Battle turns can be minimal: request, choice, commit, queue, run move, emit events. Item use can be minimal: item id, count, effect handler. Save persistence can be minimal: serialize world state after the battle result is integrated. But each piece should be in the right box.

Let me give you a small mental rubric. If a function needs to know about tile coordinates and move base power, it is probably crossing a boundary. If a function needs to know about protocol output and save file checksums, it is probably crossing a boundary. If a function needs to know about script flags and accuracy calculation, it is probably crossing a boundary. There are bridge functions, yes. FireRed’s battle setup is a bridge, and we cited pokefirered/src/battle_setup.c, chunk_494d132a59422c85, precisely because it lives in that loaded neighborhood of dependencies. But bridges should be visible and few. They should not be hidden inside every handler.

Here is the advanced version of the test, and this is close to what I would expect from strong homework_w23 submissions. You run the same battle twice with the same seed and same choices. The protocol transcript must match. The final party state must match. The script continuation must match. Then you change only the map script reward line, not the battle setup, and the battle transcript should remain identical while the post-battle world delta changes. That proves battle determinism and script ownership are separated. Then you change only the move data, perhaps Tackle power, and the transcript and HP delta should change, but map loading should not. That proves data-driven battle behavior is properly localized.

That three-way comparison is the week 23 bridge to the final course plan. Weeks 1 through 22 taught you to trace data through runtime globals, callbacks, tasks, scripts, structs, and source functions. Weeks 22 through 24 ask you to compare, verify, and defend architecture. Showdown is useful here because it makes battle simulation highly inspectable. FireRed remains the primary architecture source because it shows how a battle belongs inside a full creature RPG: maps, scripts, party, inventory, UI, save, and link-adjacent concerns.

So, as we move into the next segment, keep this sentence in mind: a simulator boundary is not a wall that isolates battle from the RPG; it is a contract that lets battle rejoin the RPG cleanly. The overworld should be able to start a battle without knowing turn internals. The battle should be able to request choices without knowing the UI implementation. The queue should resolve actions without knowing save flags. The save system should preserve consequences without preserving simulator machinery. If those four statements are true in your clone, then your project is starting to look less like a pile of features and more like an architecture.

Now, let us make that contract concrete by designing the smallest clone architecture that deserves to be called an RPG runtime, not just a battle toy.

I am going to draw six boxes on the board. Not because there are only six systems in FireRed, of course there are more, but because these are the six systems your week twenty-three homework needs to keep honest: map loading, script execution, party data, battle turns, item use, and save persistence. Under those boxes I am going to draw one horizontal line. Above the line: player-facing runtime systems. Below the line: data and durable state. The battle simulator sits above the line while it is running, but its results are written back below the line only through a small result object. That distinction is the whole lecture in miniature.

So, first box: map loading. A minimal clone map loader does not need every metatile behavior, every event object movement mode, every camera edge rule, or every warp transition effect. But it does need a stable map identity, a layout reference, spawn points, object events, scripts, and exits. If you cannot load a map, inspect its object records, and answer the question “what happens when the player presses A here,” then you do not have a creature RPG map system yet. You have a background renderer.

Now connect that to FireRed. Earlier in the course we traced map headers and layout registry references, so I will not re-teach that whole chain. What matters today is the boundary shape. FireRed’s battle setup code is not floating in a pure simulator vacuum. The include surface in `pokefirered/src/battle_setup.c`, chunk `chunk_494d132a59422c85`, is already a clue: it pulls in task, overworld, item, script, event data, event object movement, metatile behavior, field control, player avatar, battle transition, and battle headers. That does not mean every one of those modules should be tangled together in your clone. It means starting a battle in a full RPG is a cross-system event, and the architecture needs a controlled place for that crossing.

So in your clone, the map loader should not call `runMove`. It should not know whether Quick Attack has priority, or whether a fainted creature can act. Its job is much smaller. It loads map state. It says, “there is an NPC trainer at coordinate x, y; that trainer has script id `trainer_03_route_2`; that script may request a battle.” The battle request is produced by the script layer, not by the map renderer.

Second box: script execution. In a minimal clone, the script system is the translator between world interaction and system commands. I walk up to a trainer, press A, and the script interpreter executes something like: show text, lock player movement, start trainer battle, wait for battle result, set defeated flag, show post-battle text, release player movement. The exact instruction names do not matter. The shape matters.

Here is a common mistake in student clones. The trainer object has an `onInteract` function, and inside that function the student directly constructs the enemy party, starts battle UI, applies prize money, sets the trainer defeated flag, and teleports the player back to the map. That works for the first trainer. Then it collapses when you add wild battles, item gifts, post-battle scripts, rematches, link handoff, or save reload. Why? Because there is no script boundary. There is only one huge interaction callback pretending to be architecture.

Let me say that more sharply. An advanced clone should not be proud that it can start a battle from anywhere. It should be proud that battles start from a small number of well-defined commands.

Now, compare that with the Showdown side. In the Showdown turn loop material, we looked at battle request state in `pokemon-showdown/sim/battle.ts`, chunk `chunk_3b063b1b44a6d638`, then the choose and commit flow in `pokemon-showdown/sim/battle.ts`, chunk `chunk_889e310105810281`. Showdown’s model is not “the button directly changes HP.” The simulator emits a request. The player, or client, responds with a choice. Choices are committed. The action queue resolves. That request-choice-commit pattern is the reusable lesson. For your FireRed-style clone, scripts should issue a battle start request, the battle scene should issue player action requests, and the outer runtime should receive battle results. Do not let button presses leak across every layer.

Pause for a check. If I ask, “who owns the trainer defeated flag,” what is the answer? Not the battle queue. Not the move execution function. Not the damage calculator. The script or progression layer owns that flag. The battle can report, “player won,” “player lost,” “battle fled,” or “battle captured,” depending on your scope. Then the script decides what durable world consequences follow.

Third box: party data. This one is deceptively important. Your party model is not just a list of combatants for battle. It is the bridge between overworld persistence and battle runtime. In FireRed, the party creature is a durable data structure with species, experience, moves, PP, status, held item, and other fields; battle then derives active battlers and battle-only state from that. In a minimal clone, keep that same separation. The party record says: species id, level, current HP, max HP, moves with PP, status, maybe experience, maybe held item. The battle combatant record says: active stat stages, volatile conditions, selected move, temporary lock, last damage, turn order metadata.

If you merge those, your save file starts preserving nonsense. You save in the middle of a battle and accidentally preserve “is currently flinched” as a permanent creature property. Or you finish a battle and forget to decrement PP in the party because the move only existed inside a temporary battle object. The clean version is this: when battle starts, create battle participants from party records; during battle, mutate battle state; at specific commit points, write back durable deltas: HP, PP, status, experience, fainting, capture result, item consumption if applicable.

This is where Showdown is both useful and dangerous as a comparison. Showdown’s Dex and simulator abstractions are beautifully inspectable for tests and online play, and we used that in the previous lecture. But FireRed remains the primary architecture source because party data has to live in a cartridge RPG loop. The Dex lookup comparison from earlier weeks, including move, species, ability, and item lookup, gives us the data facade idea. But the party object in your clone must still serve the map, the menu, the battle, and the save system. It is not merely a simulator team export.

Let us make a concrete example. Suppose the player has a level 12 Charmander-like creature with 31 current HP, 35 max HP, Scratch with 30 PP, Ember with 20 PP, and no status. The map script starts a trainer battle. The battle constructor should receive either a copy or a controlled mutable reference to the party member, depending on your language and engine. It should produce a battler with derived stats, move slots, and battle-only fields. On turn one, the player chooses Ember. The battle action queue spends one PP and applies damage to the enemy. At the end of the battle, your result writeback says: player party member now has Ember at 19 PP, current HP maybe 24, gained 88 experience, and no permanent status. It should not write back “selected action was move 1” or “was in queue position 2.” Those are simulator internals.

Fourth box: battle turns. Here we can be more direct, because week twenty-three has already raised the bar. You have seen Showdown’s request-state model, choice commit flow, action queue, and `runMove` responsibilities. So your minimal clone battle turn should not be a single function called `playerAttackEnemyThenEnemyAttackPlayer`. That is fine in week three. It is below the standard now.

A minimal advanced turn loop has at least five stages. One, build request state. Two, collect choices. Three, commit choices into actions. Four, sort and resolve the action queue. Five, emit protocol or event output and decide whether the battle continues. That is the small version of the Showdown architecture. In `pokemon-showdown/sim/battle-queue.ts`, chunk `chunk_f572d84e556bce5e`, the action queue is the conceptual center for ordering actions after choices exist. In `pokemon-showdown/sim/battle-actions.ts`, chunk `chunk_95a7524b39636e24`, `runMove` responsibilities sit inside resolution, not inside the UI click handler. And in `pokemon-showdown/sim/battle-stream.ts`, chunk `chunk_ced1577d9e2d59ac`, BattleStream exposes a boundary where inputs and outputs are exchanged without the caller needing to reach into private battle machinery.

Now, for a FireRed-style clone, do we need the full complexity of Showdown? No. And if you implement all of it blindly, you will probably drown your project. But you should preserve the separation. The battle scene can ask the player for a move. The AI can choose a move. The commit function can create two actions. The queue can sort by priority and speed. The resolver can call move execution. Move execution can calculate hit, damage, secondary effects, fainting checks, and messages. Then the battle can return a result or produce the next request.

Board cue: I am writing this as a vertical flow now. Request. Choice. Commit. Queue. Resolve. Output. Repeat. If you can label a function in your clone with each of those words, your design is probably testable. If one function does all six, your design is probably only playable by accident.

Let us do a small turn example. The player’s creature chooses Ember. The enemy chooses Tackle. The battle builds a request asking the player to choose among available moves. The player selects move slot 1. The AI selects Tackle. Commit creates two actions: `move(player, Ember, enemy)` and `move(enemy, Tackle, player)`. The queue compares priority first, then speed. Suppose the player is faster. Resolution runs Ember. `runMove` checks whether the user can act, spends PP, checks accuracy if your clone supports it, computes damage, applies damage, emits “Charmander used Ember,” emits “Enemy took 9 damage,” checks for fainting, and maybe applies burn if you have secondary effects. Then the queue proceeds to Tackle if the enemy is still able to move. At the end, the battle produces either the next request or a battle-ended result.

Notice what is absent. The map did not inspect speed. The save system did not sort the queue. The UI did not calculate damage. The move executor did not decide which trainer flag to set. That absence is design.

Fifth box: item use. Items are where boundaries get tested because items appear everywhere. You use potions from the bag in the overworld. You use potions inside battle. You may give held items. You may have key items that run field scripts. You may have capture items that alter battle resolution. So a minimal clone needs an item API that is not just “if item id equals potion, add 20 HP.”

Think of item use as a command with context. The context answers: are we in the field menu, battle menu, shop, PC, or scripted event? Who is the target? Is the item consumed? What durable state changes? What messages should appear? A Potion-like item in the field targets a party member and changes durable HP immediately. In battle, it creates a battle action, usually with item priority rules, consumes the item from bag inventory, and then changes the active or party creature’s HP through the battle resolver. A Poké Ball-like item, if your clone has capture, is even more battle-specific: it consumes inventory, runs capture logic, and may end the battle with a capture result.

This is why FireRed’s include surface in `battle_setup.c`, chunk `chunk_494d132a59422c85`, is a useful reminder. Battle start and battle-adjacent behavior sit near item, overworld, script, and event modules. And `pokefirered/src/learn_move.c`, chunk `chunk_af3598df6f092723`, is a different but related example: the move relearner is a menu state machine connected to party and move data, not a raw data edit screen. The excerpt explicitly frames `CB2_MoveRelearner_Init` and `MoveRelearnerStateMachine` as stateful menu progression. The later `pokefirered/src/learn_move.c`, chunk `chunk_e820ee15ae8caa6d`, continues that menu-state emphasis around teaching confirmation. That matters because item use, move learning, and battle choice all share a pattern: the player-facing UI is a state machine that eventually commits a small data-changing command.

Here is the design rule I want you to write in your notes, in your own words. Item definitions should describe capabilities, but item use should run through context-specific handlers. Data says Potion heals 20 HP. The field item handler knows how to choose a party target and write HP. The battle item handler knows how to create an action, consume a turn, emit battle messages, and write back consequences. If the item data itself opens menus, starts animations, mutates save flags, and ends battles, then the item table has become a dumping ground.

Check for understanding. If I use a Potion during battle, when should the inventory count be decremented? There are a few valid answers depending on your engine, but the answer must be explicit. You might decrement when the choice is committed, and then restore only if the battle transaction is cancelled before resolution. Or you might decrement when the item action resolves. What you should not do is decrement in the UI preview before the player has confirmed, and then also decrement in the resolver. That is how duplicate consumption bugs appear. So for the homework, I want you to name the commit point.

Sixth box: save persistence. Save is not a screenshot of RAM. Save is a curated projection of durable game state. That has been true since our early FireRed weeks, and week twenty-three gives it sharper meaning. BattleStream-style boundaries make simulation inspectable, but you do not save the stream. You save party, bag, map position, flags, variables, options, PC storage if in scope, and maybe pending script position if your clone supports saving mid-script. You do not save the action queue after a normal trainer battle. You do not save temporary request state after the battle ends. You do not save the UI cursor unless your design explicitly preserves menu state.

Now, there is a subtle advanced point here. Some games do preserve more than others. A game might support suspend saves in the middle of battle. A server might persist battle logs and reconnect clients. Showdown’s server runtime is built for online rooms, users, and processes; the `pokemon-showdown/server/README.md`, chunk `chunk_1ba53f1706d86463`, is part of that contrast because it points to running a server and localhost behavior, which is a very different runtime container from a handheld RPG cartridge loop. So I am not saying “never persist simulator state” as a universal law. I am saying for the FireRed-style clone objective, your default save model should persist durable RPG state, not private battle machinery.

Let us connect this to the overall course plan. Weeks one through five gave us the runtime shell: startup, callbacks, tasks, and the habit of tracing visible behavior through source functions and globals. Weeks on maps and scripts gave us the overworld as a data-driven event surface. The party and battle weeks gave us creature records, move data, and battle execution. Inventory and UI weeks taught us that menus are not decoration; they are state machines that commit commands. Save weeks taught us durable projection. Link and RFU weeks gave us the discipline of handoff boundaries and mode constants, which we are using here as prior foundation, not re-teaching. Now weeks twenty-two through twenty-four are asking: can you compare architectures without losing the main one? Showdown helps us name request state, commit flow, queue resolution, and stream APIs. FireRed keeps us honest about full RPG integration.

So if I were grading your minimal clone design, I would look for the following behavior, though I am saying it as speech, not as a formal rubric. I would start the game on a map. I would interact with a trainer. That interaction would run through a script command. The script would request a battle. The battle would construct temporary battlers from durable party data. The battle would ask for choices, commit them, queue actions, resolve moves, emit messages, and eventually return a result. The script would consume that result and set flags or continue dialogue. The item system would work both in field and battle through context-aware handlers. The save system would persist the durable consequences and reload them without needing to reconstruct yesterday’s action queue.

And now the important part: each of those steps should be independently inspectable. You should be able to log the script command that starts battle. You should be able to print the battle request state before a player choice. You should be able to inspect committed actions before queue resolution. You should be able to test move execution without rendering the map. You should be able to serialize the save file without importing the battle UI. That is the practical payoff of the simulator boundary.

Let me bring in one more comparison from the source packet before we move on. The Showdown data text excerpt in `pokemon-showdown/data/text/moves.ts`, chunk `chunk_92a6298340525a85`, describes move effects in terms of turn-based consequences, repeated damage, trapping, durations, and end-of-turn behavior. That kind of data is rich because the simulator has machinery to interpret it during resolution. The Gen 2 Stadium script excerpt in `pokemon-showdown/data/mods/gen2stadium2/scripts.ts`, chunk `chunk_3480e574205d5d0f`, shows damage logic considering immunities, OHKO moves, callbacks, fixed damage, and level-based damage. You do not need all of that in the clone. But if your architecture has a real move execution boundary, you can add those cases later without changing map loading or save persistence. That is why we care.

And there is a historical caution in `pokemon-showdown/data/mods/gen1/README.md`, chunk `chunk_21fe92e0a0e0853f`, where the notes distinguish implemented behavior from glitches not implemented. For us, that is a verification lesson. Your clone should know what it is modeling and what it is intentionally omitting. “I did not implement mid-battle save,” fine. “I did not implement secondary effects,” fine. “I accidentally made burn permanent in the wrong place because party and battler are the same object,” not fine. Advanced work is not measured by the number of mechanics you cram in. It is measured by whether the mechanics you do include have a defensible home.

Let us pause and test the architecture verbally. I will say a user action, and you tell me which system should receive it first. The player steps onto a warp tile. Map and field movement first, then map transition, maybe script if the warp is scripted. The player presses A facing a trainer. Map object interaction first, then script. The trainer script starts a battle. Script emits battle request. The player chooses Ember. Battle request UI collects choice, battle commit accepts it. Ember deals damage. Move execution inside queue resolution. Enemy faints. Battle state records fainting, result logic checks victory. Trainer defeated flag changes. Script/progression layer after battle result. Potion used from bag outside battle. Field item context. Potion used as a battle turn. Battle item context and action queue. Game is saved after the battle. Save projection reads durable state. If your answers match that chain, you have the boundary.

Now, I want to talk briefly about protocol output, because it is the piece many FireRed-focused students underappreciate when first reading Showdown. In a cartridge game, the battle engine can call animation and message routines more directly because the runtime is local and tightly integrated. In Showdown, the simulator has to communicate with clients and server runtime through a protocol-like stream. BattleStream is not just a convenience; it is an architectural statement. Inputs come in as choices or commands. Outputs leave as messages that another layer can display, log, test, or transmit. For your clone, even if it is single-player and local, copying that idea at a small scale is valuable. Have the battle resolver emit events like `message`, `hpChanged`, `statusApplied`, `fainted`, `battleEnded`. Then your UI consumes those events. Tests can consume them too. Later, if you add replay, debug logs, or online experiments, you already have a boundary.

That also protects you from one of the classic clone traps: animation timing controlling game truth. The creature should not faint because the animation reached frame 42. The creature should faint because damage resolution reduced HP to zero, and the animation should display that fact. FireRed’s callback and task machinery can make this feel interleaved on hardware, and earlier in the course we spent plenty of time on `RunTasks` and main callbacks. Today the design lesson is that frame scheduling and battle truth should be coordinated, not confused. A task can animate a hit. A battle event can say the hit happened. Those are related, but they are not the same responsibility.

So, for the next part of the lecture, we are going to use this six-box clone design as the reference frame and walk through a minimal BattleStream-style exchange side by side with a FireRed-style trainer encounter. Keep your board diagram visible: map, script, party, battle, item, save, with the simulator boundary running through battle but not swallowing the RPG around it.

Now, imagine I type into a tiny simulator stream, “player one chooses move one,” and then “player two chooses move one.” What should happen next? Not visually. Architecturally. The important thing is that nothing fully resolves until the simulator has enough committed choice information to advance the turn. That is the distinction we studied in Showdown’s choose and commit flow, especially in pokemon-showdown/sim/battle.ts chunk_889e310105810281. The player-facing request is not the same thing as the resolved battle event. The request is a controlled opening in the state machine: “I need a legal choice from this side.” The commit says, “This side has supplied that choice.” The turn only becomes executable once the battle can assemble the committed choices into the queue.

So, on the board, under the battle box, write request, choice, commit, queue, execute, protocol. Those six words are the simulator-side turn skeleton. Now next to it, under FireRed-style battle, write input menu, chosen action, battle controller command, turn order, command execution, animation and text. I am deliberately not making those identical. They are not identical systems. But they rhyme. That rhyme is what we are trying to preserve in the clone architecture.

Let’s make the concrete example very small. We have a trainer encounter on a map. The player steps onto a tile. The map runtime notices a trainer object’s sightline. The script layer starts the trainer approach and dialogue. The party model supplies the player’s first usable creature. The opposing trainer data supplies the enemy’s first creature. The battle runtime receives a battle setup request. FireRed’s battle_setup.c, chunk_494d132a59422c85, is useful evidence here because the include surface itself tells you this boundary is cross-system: task, overworld, item, load_save, script, event data, battle, transition. A battle does not materialize from one pure module. It is assembled by the overworld and script world, then handed to battle.

Now compare that with a minimal BattleStream-style exchange. In Pokemon Showdown, the stream API in pokemon-showdown/sim/battle-stream.ts chunk_ced1577d9e2d59ac gives us a boundary where messages go in and protocol output comes out. We do not need the whole server to understand the simulator. We can feed it battle initialization and player choices, and then observe structured text output. That is the cleanest contrast with a full online runtime. The server has users, rooms, network behavior, and localhost operation, which the pokemon-showdown/server/README.md chunk_1ba53f1706d86463 places outside the simulator itself. The stream is the simulator API shape; the server is the multiplayer runtime around it.

Pause there and check your understanding. If I say, “BattleStream is like FireRed’s battle engine,” is that correct? It is partly correct, but too loose. BattleStream is more like a controlled communication wrapper around the simulator. FireRed’s battle engine is embedded inside a cartridge runtime with callbacks, tasks, global state, graphics, sound, save data, and map transitions. The better comparison is this: BattleStream exposes a clean textual boundary for battle simulation, while FireRed exposes battle through engine transitions and callbacks. Both need a boundary. They just choose different boundary materials.

Now, let’s put our six-box clone back in the center of the discussion. Map loading, script execution, party data, battle turns, item use, save persistence. If we were designing the smallest serious clone, not a toy, I would make each box own one kind of truth. The map loader owns where you are and which map resources are active. The script executor owns progression through event commands. The party model owns creature identity, HP, moves, status, held item, experience, and so on. The battle turn system owns legal battle state and resolution. The item system owns inventory counts and item effects. The save system owns durable serialization and restoration.

Notice the phrase “owns one kind of truth.” That is the advanced design point. A beginner clone often lets the map directly mutate the party, lets the item menu directly patch battle flags, and lets save code serialize whatever objects happen to exist. That can work for a weekend prototype, but it collapses under tests, online comparison, and replay. By week 23, after the labs and week 22 tracing work, I expect you to be more disciplined. We trace from data record to runtime global to callback or task to player-visible behavior because that discipline tells us where truth is supposed to live.

Let me walk through a minimal trainer battle from cold map state. I will speak it as if we are implementing it.

The map loader receives map id 3, route 2. It loads the layout, object events, connections, and scripts. It does not inspect Flamethrower’s power. It does not know damage formulas. It only knows that an object event has trainer metadata and a script pointer. Earlier in the course we covered map header and layout references, so I am not going to redraw that entire model. Today I want you to see the boundary. The map loader can say, “This trainer object is active and visible.” It cannot say, “The player will lose to this trainer.” That is outside its authority.

The script executor starts the encounter script. It locks player movement, moves the trainer toward the player, prints a line of dialogue, and then calls into battle setup. This is where FireRed’s architecture is instructive. In pokefirered/src/battle_setup.c chunk_494d132a59422c85, the dependencies include script, event data, overworld, battle transition, and load/save-related surfaces. That is not accidental. Trainer battle setup is a border crossing. The script world is asking the battle world to take control, and the runtime has to preserve enough overworld context to return afterward.

In our clone, I would express that border crossing as a plain event: startTrainerBattle with trainer id, battle type, return script continuation, and maybe transition style. That event should not contain the whole map object. It should not contain a pointer to the rendering component. It should contain stable identifiers and the data needed to initialize battle. If you later build a server-side verifier, or a regression test, or a BattleStream-like harness, stable identifiers are much easier to reproduce than live engine objects.

Then the party model is queried. The player party is not copied as random loose fields. It is converted into a battle participation view. That view might include species, level, current HP, max HP, moves with PP, ability if your ruleset has it, held item if your ruleset has it, volatile battle fields initially empty, and permanent status. Here is where the comparison to Showdown’s Dex work from earlier in the week matters, but we do not need to reteach Dex lookup. We already established Dex as a facade over battle data. In the clone, you can have a simpler data registry, but battle should still ask the registry for species and move definitions instead of hard-coding them inside turn execution.

Now we reach the request state. In Showdown, the Battle class and request-state model in pokemon-showdown/sim/battle.ts chunk_3b063b1b44a6d638 are a central clue. A side is asked for a choice when the battle needs one. The legal choices depend on the current state. Can the active creature move? Must it switch? Is it trapped? Is it fainted? Does the selected move have PP? A request is not just a menu. It is a snapshot of legal agency.

FireRed presents this through battle menus and controller behavior, not through a JSON-ish request object, but the design pressure is the same. The player cannot choose a fainted battler’s move. The player cannot use an item in every battle mode. The player cannot switch when a rule forbids it. If we are building a minimal clone, the worst mistake is to treat the menu as the authority. The menu is a view of the request state. The battle state is the authority.

Let me say that again because it is one of the exam-grade ideas: the menu is a view of the request state. If your clone has a “Fight” button that directly runs damage, your architecture has skipped the commit boundary. It might feel simple, but it makes AI, link battles, replays, and tests harder. A better design is: menu creates a proposed choice; battle validates it; side commits it; turn resolution begins only when all required choices are committed.

Now, on the board, I’m drawing two small timelines. Timeline one is FireRed-style. Player opens fight menu, chooses Tackle, opponent AI chooses Ember, battle engine orders actions, Tackle runs, Ember runs if the user can still act, text and animations are scheduled. Timeline two is Showdown-style. Request sent to player one, request sent to player two, choose move, choose move, commit choices, queue actions, run move, emit protocol. The words differ, but the functional steps line up.

The queue deserves special attention. pokemon-showdown/sim/battle-queue.ts chunk_f572d84e556bce5e is the source focus for action queue behavior this week. The queue is not just a list of moves. It is an ordering device for decisions that have become executable actions. Switches, moves, maybe residual effects, maybe forced events, all need a place in a consistent resolution order. In a minimal FireRed-inspired clone, you do not need to copy Showdown’s complete queue. But you do need a queue-shaped concept, because otherwise you will scatter priority rules across move code, item code, faint handling, and end-turn effects.

Here is a concrete design I would accept for homework_w23. A TurnChoice object records side id, actor slot, choice type, and choice payload. The choice type might be move, switch, item, or run. A BattleAction object is produced from the committed TurnChoice. It has action type, actor, priority bucket, speed tie key, and payload. The battle turn builder validates choices, converts them into BattleActions, sorts them, and then resolves them one at a time. That is enough to model a simple RPG battle. It is also enough to write deterministic tests.

Now suppose Bulbasaur uses Vine Whip and Charmander uses Ember. The player chooses Vine Whip. The opponent AI chooses Ember. We commit both. The queue builder compares priority, then speed. Charmander is faster, so Ember’s action is first. runMove is called for Ember. In Showdown, runMove responsibilities are covered in pokemon-showdown/sim/battle-actions.ts chunk_95a7524b39636e24, and the important architectural idea is that move execution is not a single arithmetic operation. It checks conditions, target validity, move data, immunities, damage, secondary effects, and output. In the packet, pokemon-showdown/data/mods/gen2stadium2/scripts.ts chunk_3480e574205d5d0f gives a focused example of damage logic checking immunity, OHKO behavior, callback damage, fixed damage, and other move-specific paths. That is a reminder that “calculate damage” is a pipeline, not a formula cell.

For our clone, runMove can be much smaller, but it should still be staged. First, confirm the actor can act. Second, resolve the target. Third, load the move definition. Fourth, check hit or immunity rules if your clone has them. Fifth, calculate damage or effect. Sixth, apply state mutation. Seventh, emit battle events. The battle event might say damage, hp changed, fainted, message, animation hint. The renderer can animate those events later. Again, battle truth and frame scheduling must be coordinated, not confused.

Let’s do a quick check for understanding. I choose Potion from the bag during battle. Is that a battle choice, an item choice, or an inventory mutation? The answer is: it touches all three, but they happen in a controlled order. The battle request state must allow item use. The player commits an item choice. The battle action queue schedules that item according to the rules. When the action resolves, the item system validates the item and decrements inventory, while the party or battle model receives the healing effect. If the item is not allowed, or the count is zero, the choice should fail before irreversible mutation. That ordering matters.

This is where FireRed’s item and menu architecture gives us useful texture without needing to reteach every bag function. The battle setup chunk from pokefirered/src/battle_setup.c chunk_494d132a59422c85 includes item.h alongside battle and script surfaces, which is exactly what we would expect from a runtime where encounters, item availability, and battle modes interact. And pokefirered/src/learn_move.c chunk_af3598df6f092723, although it is about the move relearner menu, is a good reminder that menu flows are often explicit state machines. That excerpt describes a MoveRelearnerStateMachine with menu states, list scrolling, confirmation, and battle-mode setup concerns. The lesson transfers: menus can be elaborate, but their state machine should feed a domain operation rather than become the domain operation.

So in the clone, Potion is not “button clicked, add 20 HP.” It is “request allows item; item choice selected; commit accepted; item action queued; item action resolves; inventory count changes; HP changes; protocol or UI event emitted.” That may sound heavier, but it buys you correctness. It also makes saves easier.

Now let’s talk save persistence, because this is where many architecture mistakes become permanent. A save file should not serialize the battle animation queue unless you are intentionally supporting mid-animation saves. A normal creature RPG save persists long-lived world state: current map, coordinates, facing direction, event flags, variables, party, inventory, money, trainer defeated flags, PC storage, and options. It should not persist temporary request prompts. It should not persist a half-consumed local menu pointer. If you save before a trainer battle, you restore before the trainer battle. If you save after, you restore after the battle with the defeated flag set and party/inventory changes applied.

In FireRed terms, we have repeatedly seen that save, event data, scripts, and overworld state are connected, but not identical. The inclusion of load_save.h and event_data.h in battle_setup.c chunk_494d132a59422c85 is a sign of that connection at battle boundaries. In our clone, I would make SaveState a durable projection of the world. That word projection is important. It means the save file is not your whole runtime object graph. It is the subset required to reconstruct a valid runtime.

Let’s return to BattleStream for one more pass. If I run a minimal stream exchange, I might initialize a battle, receive protocol output requesting choices, write player choices into the stream, and then receive turn output. That maps beautifully to tests. A test can assert that after Charmander’s Ember, Bulbasaur’s HP decreased and a damage protocol line appeared. The full Showdown server is not required for that test. That is the boundary contrast. The simulator can be tested through BattleStream; the online runtime can host users, rooms, authentication, chat, and networking around it. The server README chunk_1ba53f1706d86463 matters here because it anchors the idea that a server process and localhost behavior are operational concerns around the simulation, not the same thing as move resolution.

For FireRed, the comparable test harness is harder because the engine was not built around a textual stream API. But conceptually, we can still design one for a clone. We can create a BattleSession API with receiveChoice and drainEvents. The map and script runtime do not need to know whether BattleSession is backed by a local object, a deterministic test runner, or eventually a networked battle service. That is the forward connection to our prior link material. RFU mode constants, link manager callback messages, and VBlank timers gave us the hardware and synchronization foundation. Today we are placing a cleaner software boundary above that foundation: what is a choice, when is it committed, and what state changes are authoritative?

Let me give you a slightly richer example that includes the map and save boxes. The player is on Route 2 and has two party members. There is one Potion in the bag. Trainer Bug Catcher A has not been defeated. The save file stores map id, position, event flag trainer_A_defeated equals false, party data, and inventory Potion count one. The map loads. The trainer script fires. Battle setup begins. During turn one, the player uses Potion on the active creature. The item action resolves, Potion count becomes zero, HP increases, and battle events are emitted. During turn two, the player wins. The battle result returns to the script executor. The script sets trainer_A_defeated true, gives money, maybe plays victory dialogue, and releases player control. Now save persistence can record map id, updated party HP, Potion count zero, and trainer_A_defeated true.

What should not happen? The battle engine should not directly mark the overworld object invisible without going through the event or script result mechanism. The item menu should not directly write the save file in the middle of the turn. The map loader should not infer trainer defeat by inspecting enemy party HP from the last battle object. Each box has a job. The boxes communicate through small boundary events.

That is also how we connect week 23 to the overall course plan. Weeks 1 and 2 gave us the runtime shell, callbacks, tasks, headers, generated data, and the idea that source files and data tables form contracts. The middle of the course built the overworld, script, party, battle, inventory, UI, save, and link systems. Week 22 asked you to trace complete behavior across those systems. Week 23 is now asking a more architectural question: can we isolate the battle simulator boundary well enough that we can compare FireRed-style behavior with Showdown-style deterministic simulation without pretending they are the same program?

The answer should be yes, but with humility. Showdown is optimized around competitive battle simulation, data mods, request state, commit flow, queues, and protocol output. FireRed is a full cartridge RPG where battle is embedded into map progression, scripts, assets, callbacks, and save state. If your clone imports Showdown’s API shape wholesale, you may overfit the battle and starve the RPG. If your clone copies FireRed’s global coupling wholesale, you may make testing and online runtime boundaries painful. The advanced move is to learn from both.

So, before we move on, I want you to look at the board and ask: where would I put a regression test? For map loading, test that a map id loads the expected object events and scripts. For script execution, test that stepping on a trainer trigger produces a startTrainerBattle event. For party data, test that battle views are derived from persistent party records without losing HP or moves. For battle turns, test request, choice, commit, queue, runMove, and event output. For item use, test legality, count decrement, and HP mutation in the correct order. For save persistence, test that durable state after the battle can reconstruct the same world state.

That is our bridge into the next segment. We are going to zoom into the exact runtime boundary between a local simulator API and an online server process, and I want you to keep one question in mind while we do it: when a player clicks a button, which system is allowed to believe that the world has changed?

So, answer that question carefully. The player clicking a button is not, by itself, a world mutation. It is an input proposal. In Showdown language, it becomes a choice that is validated against the current request state, then committed when both sides are ready, then expanded into queue actions, and only then does move execution mutate battle state. That structure is visible across pokemon-showdown/sim/battle.ts chunk_3b063b1b44a6d638 for the Battle request-state model, pokemon-showdown/sim/battle.ts chunk_889e310105810281 for the choose and commitChoices flow, pokemon-showdown/sim/battle-queue.ts chunk_f572d84e556bce5e for the action queue, and pokemon-showdown/sim/battle-actions.ts chunk_95a7524b39636e24 for runMove responsibilities.

Now, let me put a boundary line on the board. Left side: client or controller. Middle: simulator API. Right side: online runtime, room, process, network, users. The important thing is that the simulator API is not the same thing as the online server. BattleStream, in pokemon-showdown/sim/battle-stream.ts chunk_ced1577d9e2d59ac, gives us a clean programmatic exchange: send input, receive protocol output. The server README, pokemon-showdown/server/README.md chunk_1ba53f1706d86463, is about running the broader server process and localhost behavior. Those are related, but they are not the same layer. One is the deterministic battle conversation; the other is the hosted multiplayer environment around it.

And this matters for our FireRed clone because FireRed does not have a web room process. It has callbacks, tasks, script execution, battle setup, save blocks, and hardware-bound input. But architecturally, it still needs boundaries. If the A button is pressed on an overworld object, the input handler should not directly rewrite the entire world. It should advance the correct state machine. If the player selects a move in battle, the menu should not immediately subtract enemy HP. It should produce a choice into the battle controller path. If the player uses a Potion, the item menu should not secretly become the save system. It should request an item-use operation, validate legality, decrement inventory at the right time, mutate party HP, then allow persistence to capture the result later.

Here is the concrete miniature version I want you to be able to design for homework_w23. Suppose we make a tiny clone runtime with one town map, one route map, one trainer, one player party member, one enemy party member, Potion, Tackle, and a save file. Map loading reads a map header, resolves the layout, loads object events, and installs the active script table. That is the FireRed-shaped part. We are not re-teaching map headers today, but remember the older pattern: data table to runtime globals to callback or script to visible behavior. For battle entry, pokefirered/src/battle_setup.c chunk_494d132a59422c85 is a useful anchor because that source sits at the boundary where overworld, script, item, event data, battle transition, and battle include files all meet. That does not mean one function should own all behavior. It means battle setup is a crossing point.

Now the trainer sees the player. The script engine emits something like startTrainerBattle. Do not have the map loader directly construct damage rolls. The script layer says, in effect, “we are entering a battle with this trainer and these party records.” The battle subsystem snapshots or views the party data it needs. This is where we use our prior Dex comparison lightly: Showdown often treats the Dex as a battle data facade, while FireRed is table-and-struct oriented. But in both cases, the battle engine must not be doing a global scavenger hunt every time it wants move power. It needs a controlled lookup boundary.

Now, watch the turn. I’ll write the flow slowly: request, choice, commit, queue, execute, output, persist later. The battle engine asks player one for a move. That request is a promise about what choices are legal right now. If the player chooses Tackle, the simulator records the choice, but we do not yet resolve damage unless the model says the turn can commit. When both sides have choices, commit creates queue entries. The queue sorts priority, speed, switches, residual effects, whatever the rules require. In our minimal clone, the queue can be tiny: player move, enemy move, end-turn poison if implemented. But even in the tiny version, the queue is the difference between a battle system and a button script.

Check for understanding here. If your UI calls applyDamage(enemy, 12) the instant the player clicks Tackle, what boundary did you violate? Right, you skipped request validation, commit, and queue resolution. You made the menu authoritative over the world. That may work for one demo turn, but it collapses when you add switching, paralysis, priority, fainting, link play, or replay verification.

Now compare this to item use. A Potion in the overworld or battle bag has a similar boundary problem. The menu can highlight Potion. It can show that it targets a party member. But the durable facts are owned elsewhere: inventory count, party HP, battle turn consumption if used in battle, and save persistence after the operation. For the move relearner, pokefirered/src/learn_move.c chunk_af3598df6f092723 and chunk_e820ee15ae8caa6d are useful because the comments describe a menu state machine with confirmation and later mutation. Again, we are not re-teaching that menu, but we are borrowing the architectural lesson: selection and confirmation are separate from the final data mutation.

So in your clone, “use Potion on Bulbasaur” should run as an operation. Validate that Potion exists. Validate that Bulbasaur is damaged and not fainted if your rules require that. Decide whether the item is consumed before or after successful application. In most clean designs, failed legality checks do not decrement the count. Successful application decrements count and mutates HP in one transaction-like operation. Then output a protocol event: itemUsed, hpChanged, inventoryChanged. Later, save persistence serializes the inventory and party state. The save system is not the item system. It is the durable image of state after systems have done their work.

Let’s bring the online boundary back in. In BattleStream, the client sends commands and receives battle protocol output. That lets tests run a battle without a full server room. In the full server runtime, users, rooms, sockets, authentication, and process management surround the simulator. Your FireRed clone should learn from that split. You can have a local BattleRuntime class with submitChoice and readEvents. You can later put it behind a network adapter. But do not bake sockets into damage calculation, and do not bake damage calculation into sockets.

Same idea for FireRed’s local runtime. The GBA main loop, callbacks, and tasks are the shell. Overworld scripts are a domain layer. Battle is another domain layer. Save persistence is a storage layer. Link systems, which we covered with RFU modes and link manager callback messages, add synchronization constraints, but they should not erase the boundaries. The reason week 23 sits after the FireRed tracing weeks is that now you have enough architectural vocabulary to compare without being seduced by the newer codebase. Showdown gives us a crisp simulator API and strong deterministic testing habits. FireRed gives us the complete cartridge-shaped runtime with callbacks, scripts, data tables, and persistent game state.

Let me do one final board pass. If I ask, “Where does the world change?” the answer is not “where the player clicks.” For map loading, the world changes when the map runtime installs the selected map state. For script execution, it changes when the script VM or script-controlled command commits event variables, flags, or battle entry. For party data, it changes through party operations, not through display widgets. For battle turns, it changes after choices commit and queued actions execute. For item use, it changes after legality succeeds and the item operation applies its effects. For save persistence, it changes on disk only when the save layer writes a coherent state image.

That is the week 23 synthesis. We used Showdown’s request, choice, commit, queue, runMove, and BattleStream layers as a comparison tool, not as a replacement for FireRed. We connected that comparison back to the course-long pattern: data records feed runtime structures, runtime structures are advanced by callbacks, tasks, scripts, and battle controllers, and player-visible behavior is the result of those state machines cooperating. Next time, as we move toward the final week, we will use this boundary discipline for verification: not just “does the clone appear to work,” but “can we prove each subsystem owns the right state, accepts the right inputs, emits the right events, and persists the right facts.”

## Source References
- pokefirered/src/battle_setup.c
- pokemon-showdown/data/text/moves.ts
- pokemon-showdown/data/mods/gen2stadium2/scripts.ts
- pokemon-showdown/data/mods/gen1/README.md
- pokefirered/src/learn_move.c
