# Resolving Battle Outcomes Back to the World

Lecture ID: lecture_w15_d02
Week 15, Day 2

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

## Transcript
All right, let’s pick up exactly where yesterday’s lecture left us. Yesterday we traced encounter data into battle setup. Today we are going to follow the return path: battle outcome back into the world. That sounds symmetrical, and in a clean architecture it should feel symmetrical, but in FireRed it is not just “call battle, get result.” It is a coordinated handoff among data tables, globals, callbacks, scripts, flags, party mutation, UI cleanup, and map resumption.

So, let me put the framing on the board. I’m drawing three boxes. The left box is “world cause.” The middle box is “battle mode.” The right box is “world consequence.” Under world cause, I’m writing “trainer table,” “wild encounter table,” “event script,” and “map runtime.” Under battle mode, I’m writing “setup state,” “battle main,” “battle scripts,” “battle result state,” and “cleanup.” Under world consequence, I’m writing “return to map,” “set defeated flag,” “run post-battle script,” “blackout or continue,” “party or inventory persistence,” and “save-visible state.”

The important architectural claim today is this: battle is a modal excursion from the overworld, not a replacement for the overworld. The world asks for a battle. The battle runs its own engine. Then the battle returns a compact result that the world interprets. If you let the battle engine directly own every world consequence, the design becomes brittle very quickly. If you let the overworld micromanage turn-by-turn battle details, the design also becomes brittle. FireRed lives in the compromise between those two extremes.

Now, I want you to connect this to our whole course structure. Since week 1, we have been decomposing the game into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. We are not treating those as isolated chapters. We are learning where the boundaries are. This lecture is one of the best boundary lectures in the course, because battle resolution touches almost everything without being allowed to become everything.

For source grounding, the course packet named pokefirered/src/data/trainers.h as the high-signal trainer table source, pokefirered/src/data/wild_encounters.json as the high-signal wild encounter source, pokefirered/src/battle_setup.c chunk_494d132a59422c85 as the representative battle transition and load bridge, pokefirered/src/battle_main.c chunk_1c5a1226d8d9afb7 as the battle main result flow, pokefirered/src/battle_main.c chunk_46361bdbd92dae37 as cleanup and result continuation, and pokefirered/include/battle.h chunk_f5cd027bf13ab47d as the BattleStruct and result-related state contract. Keep those citations in your notes, because they are the evidence trail for today’s architecture.

Let’s start with trainer battles, because they make the clearest world consequence. Suppose the player walks in front of a route trainer. The overworld is running. Object events are active. Movement scripts and field callbacks are active. The trainer sees the player, the game locks normal control, and an event script starts to drive the encounter. At that point the trainer is still an overworld object. The trainer has a map position, facing direction, movement behavior, dialogue, and a defeated or undefeated state. None of that is battle logic yet.

The trainer data itself comes from a table, and the packet calls out pokefirered/src/data/trainers.h as the source to keep in mind. That table is not “the battle.” It is a record source. It describes the trainer’s class, party, items, AI-relevant configuration, and presentation-level details. The overworld event uses a trainer identity to request a battle. So, on the board, I am drawing an arrow from “trainer id” to “battle setup.” That arrow is where many clone projects accidentally smear responsibilities. They write code like, “if trainer Youngster Timmy, load these sprites, play this music, run this AI, and after victory set this map flag,” all in one function. That works for three trainers. It collapses for three hundred.

FireRed’s structure is more layered. The battle setup layer is visibly a bridge layer. The packet excerpt for pokefirered/src/battle_setup.c chunk_494d132a59422c85 shows it including task.h, overworld.h, script.h, event_data.h, event_object_movement.h, field control and player avatar headers, battle.h, battle_transition.h, battle_controllers.h, quest log and safari-related headers. That include list is not random noise. It is architectural evidence. battle_setup.c must know enough about the field system to leave it cleanly, enough about battle to start it correctly, enough about scripts and event variables to preserve continuation, and enough about transitions to make the mode switch visible and coherent to the player.

Notice what it does not mean. It does not mean the overworld should know the damage formula. It does not mean the battle script bytecode should know where the trainer object lives on the map. It means the setup file is an adapter. We have used that word before, but today it is very literal: it adapts overworld causes into battle initial conditions.

Now let’s contrast that with wild encounters. For wild battles, the cause is not a trainer object and not a trainer script in the same sense. The cause is usually map movement intersecting encounter rules. The high-signal source for this week is pokefirered/src/data/wild_encounters.json. That JSON-shaped data is a record of encounter possibilities: map-associated tables, encounter types, species, levels, and rates. The world cause is “player stepped in a place where random encounter logic says yes,” and the chosen species and level come from the encounter table. Again, that chosen wild Pokémon is not the battle yet. It is an input to battle setup.

So the same middle box, “battle setup,” receives two different kinds of causes. One cause says, “trainer battle, opponent trainer id, trainer party from trainers.h, trainer presentation and script continuation.” The other says, “wild battle, species and level from wild_encounters.json, terrain or method context, no defeated trainer flag.” That is the heart of the lab this week: implement a wild encounter resolver and a trainer battle resolver that both create the same battle setup object and return a battle result consumed by overworld logic. The lab is not really about random numbers. It is about boundary design.

Let me pause and check understanding. If I ask you, “Should a wild encounter resolver and a trainer battle resolver return two completely different result types?” the advanced answer is: probably not at the boundary where the overworld resumes. Internally, they may carry different metadata. A trainer battle has trainer identity and defeated-flag consequences. A wild battle may have catch, flee, or species-specific consequences. But the battle engine should still speak a common result vocabulary: battle ended, player won, player lost, player fled, opponent was caught or removed, and here are the consequences already applied inside party or inventory state. Then the overworld can decide what script or map route happens next.

Now, let’s move into the battle side, but carefully. We are not going to re-teach the battle main callback, chosen actions by battler, turn-order arrays, battle script command dispatch, current move data, damage, or status timers. Those were the last several lectures. You should already have the mental model that battle runs as its own state machine under the runtime shell, with battlers choosing actions, turn order resolving, battle scripts executing effects, and shared battle state carrying the current move and damage consequences. Today we care about the terminal moment. What happens when that whole machine decides, “this battle is over”?

In the source packet, pokefirered/src/battle_main.c chunk_1c5a1226d8d9afb7 is named for battle main result flow, and pokefirered/src/battle_main.c chunk_46361bdbd92dae37 is named for cleanup and result continuation. That tells us that result is not just a single line at the end of the last move. The battle main loop must recognize terminal conditions, record an outcome, unwind battle resources, and hand control toward the next callback or script continuation. The include-level contract is represented by pokefirered/include/battle.h chunk_f5cd027bf13ab47d, which the packet identifies as containing BattleStruct and result-related state. That is the point where “battle internals” become “something other modules can legally depend on.”

Here is the distinction I want you to write down in your own words: outcome is not consequence. Outcome is the battle engine’s summarized answer to “how did this battle end?” Consequence is what the surrounding game does with that answer.

Let’s test that with examples. In a trainer battle, the player wins. The outcome is player victory. The consequences include post-battle text, trainer defeated state, money reward, possible quest log entry, party experience and level-up changes already accumulated, and resumption of the map with the trainer no longer initiating battle. In a trainer battle, the player loses. The outcome is player defeat. The consequences do not include setting the trainer defeated flag. They may include blackout routing, money loss, party healing or relocation depending on the game’s blackout system, and returning to a safe location rather than simply resuming one tile in front of the trainer.

Now a wild battle. The player defeats the wild Pokémon. Outcome: player victory or wild opponent fainted. Consequence: return to map, possible experience and EV changes already applied, no persistent enemy flag. The player catches the wild Pokémon. Outcome: caught. Consequence: party or box mutation, Pokédex-visible state, maybe nickname flow, then return to map. The player runs. Outcome: fled. Consequence: return to map without defeat or capture. The player loses to a wild Pokémon. Outcome: player defeat. Consequence: blackout route, not a trainer post-battle route.

So if you store only a Boolean named won, your architecture is already under-specified. If you store every little line of post-battle script inside the battle result, your architecture is over-specified. The result boundary has to be expressive enough to distinguish the routes, but not so expressive that battle owns the world.

I’m going to add a small table verbally here, and I’ll write it on the board as three columns: cause, outcome, world route. Cause: trainer battle. Outcome: win. World route: defeated trainer continuation. Cause: trainer battle. Outcome: loss. World route: blackout or loss handling. Cause: wild battle. Outcome: caught. World route: capture continuation. Cause: wild battle. Outcome: fled. World route: normal map return. Cause: link battle. Outcome: battle ended. World route: link-specific result and communication cleanup. That last one is not our main focus today, but we include it because this course objective explicitly includes link systems. Link battles are an excellent stress test for whether your result vocabulary is too overworld-centric.

The advanced part is recognizing that “return to overworld” is itself a technical operation. It is not a poetic phrase. The game has to restore the main callback path, restore or reinitialize visible field state, resume scripts if a script was waiting, decide whether player control returns immediately, and ensure that party, item, and flags are in a coherent state before the player can act again. This is why battle setup and cleanup are allowed to touch so many systems. The boundary is narrow conceptually, but mechanically it crosses many modules.

Think back to the runtime shell material from week 1 and week 2. We talked about AgbMain, callback slots, SetMainCallback2, task allocation, task IDs, and RunTasks as per-frame dispatch. We are not re-teaching that, but we need the concept. A battle is not a blocking function in the normal desktop sense. The GBA program does not say, “call RunBattle() and wait until it returns a struct.” Instead, it changes callbacks, tasks, and state so that battle becomes the active mode over many frames. Therefore, “returning a result” means arranging state so the next mode reads the result after the battle mode has finished. This is why globals and cross-module structs matter so much in FireRed architecture.

That is also why pokefirered/include/battle.h chunk_f5cd027bf13ab47d matters as a contract. A header like that is not merely a list of declarations. In this style of C architecture, it is where modules agree on which pieces of battle state are visible enough to coordinate around. If result-related state is declared there, other battle-adjacent systems can participate without including private implementation details from battle_main.c. When you design your clone, you may use a modern language and return explicit objects instead of globals, but the boundary question is identical: what is the stable result contract?

Let’s do a concrete walkthrough. Imagine the player is walking north on a route. A trainer sees the player. The field system locks movement and starts the trainer encounter script. The script identifies the trainer, and the trainer table in pokefirered/src/data/trainers.h supplies the party definition and trainer properties. battle_setup.c, represented by chunk_494d132a59422c85, is the adapter that pulls together script context, event state, transition, and battle configuration. The battle transition plays. The battle main mode takes over.

Inside battle, turns resolve. We are not unpacking those turns today. Eventually the player’s last attack faints the trainer’s last Pokémon. The battle engine detects a terminal condition. The result flow in pokefirered/src/battle_main.c chunk_1c5a1226d8d9afb7 records the appropriate outcome. Cleanup and continuation, represented by pokefirered/src/battle_main.c chunk_46361bdbd92dae37, then move the game out of battle mode. The world resumes not as a fresh map load with no memory, but as a continuation of the suspended encounter flow. The trainer says the after-battle line. A defeated flag or equivalent event state prevents the same trainer from re-challenging on the next frame. Player control returns when the script allows it.

Now change just one fact. The player loses. Same trainer table. Same setup adapter. Same battle main machinery. But the recorded outcome is different, and therefore the world route is different. The trainer should not be marked as defeated. The after-battle victory line may not run. The game may route to blackout handling. That means the “trainer id” and the “battle result” must meet in overworld logic. The battle engine can say “loss.” The overworld decides what loss means in the campaign.

Here is a check for understanding. If the player loses to a trainer, should the trainer’s defeated flag be set just because the battle reached a terminal state? No. Terminal state means the battle is over. It does not imply campaign progress. Campaign progress is a world consequence of a particular outcome. That sentence is one of the central design rules for today.

Now let’s apply the same reasoning to wild encounters. The player steps into tall grass. The map’s encounter configuration eventually resolves through data like pokefirered/src/data/wild_encounters.json. A species and level are selected. The battle setup layer configures a wild battle instead of a trainer battle. In battle, the player throws a ball and catches the Pokémon. The terminal outcome is not just “win.” If your engine compresses catch into win, you will immediately lose important world behavior. A caught Pokémon may need to be placed into the party or sent to storage. Pokédex state may change. Nickname UI may appear. The map returns afterward, but the continuation route is capture-specific.

This is where inventory and UI enter the picture. A Poké Ball is an inventory item used from inside battle UI. The catch result mutates creature ownership state, possibly inventory count, Pokédex flags, and maybe storage state. But again, do not take the wrong lesson. The inventory UI does not become the overworld. The battle result does not become the entire save system. Instead, the battle flow coordinates legal mutations and then returns with enough result state for the world to continue. Save persistence comes later when the game writes the now-mutated state. The player experiences this as one smooth event, but architecturally it is a chain of ownership boundaries.

Let me phrase that another way. The player says, “I caught a Pidgey.” The engine says, “A wild encounter selected species and level from encounter data, battle setup created a wild battle, battle main reached a catch terminal condition, party or storage state changed, Pokédex-visible state changed, result state indicated capture, cleanup returned to overworld, and the field resumed.” Advanced architecture is learning to hear both sentences at once.

Now, why is this week placed after the battle internals and before the final course synthesis? Because once you understand battle entry and battle exit, you can finally see the whole creature RPG loop. Maps create situations. Scripts contextualize them. Data tables parameterize them. The runtime shell switches modes. Battle consumes creature and inventory state. Battle produces outcome and state mutation. The overworld interprets the outcome. Save persistence makes the consequences durable. Link systems complicate the same boundary by requiring remote synchronization and constrained result handling.

That is the course plan in miniature. We are no longer memorizing files. We are learning to decompose the full game.

I want to spend a moment on a common design mistake in student clone projects. You create a Battle class. It has a method called startTrainerBattle. Then inside that method it loads the trainer, changes the music, displays the opening text, runs the whole battle loop, marks the trainer defeated, awards money, updates the map, saves the game, and teleports the player if they lose. At first that feels convenient. Everything related to a battle is in one place. But “related to a battle” is not the same as “owned by battle.” The defeated flag belongs to the event or world progression model. Teleport on loss belongs to the overworld and progression rules. Saving belongs to persistence. Trainer party selection belongs to data resolution and setup. Battle owns the combat mode and the outcome vocabulary.

The FireRed sources named in the packet show that the real game uses bridge modules because the boundary is inherently cross-system. battle_setup.c chunk_494d132a59422c85 is not pure battle logic. battle_main.c chunks chunk_1c5a1226d8d9afb7 and chunk_46361bdbd92dae37 are not map data sources. include/battle.h chunk_f5cd027bf13ab47d is not a trainer table. Each piece participates in the handoff.

Let me do a quick recap before we push deeper. The encounter source can be a trainer record from pokefirered/src/data/trainers.h or a wild encounter record from pokefirered/src/data/wild_encounters.json. The setup adapter turns those causes into a configured battle. The battle engine runs as a modal state machine using the foundations we already studied. At terminal conditions, battle records an outcome through result-related state, with the relevant flow represented in pokefirered/src/battle_main.c chunk_1c5a1226d8d9afb7. Cleanup and continuation, represented by chunk_46361bdbd92dae37, move control back toward the appropriate world route. The world interprets the outcome as campaign consequence.

So, as we move into the next segment, keep your eye on one question: when the battle ends, who is allowed to know what? That question will tell you whether your architecture is clean, or whether your modules are quietly reaching through each other’s walls.

So let us answer that question in architectural terms. A clean battle system is allowed to know the combat rules, the participants, the battle flags, the battle result, and the local battle resources it needs to draw, animate, and resolve turns. It is not supposed to know, in a rich way, that the player is on Route 3, or that a youngster’s defeated flag should be set, or that the overworld camera must resume at a particular object-event position. Those are world-level meanings. The battle can produce facts. The world assigns consequences.

I want that distinction to feel almost mechanical to you. So, imagine I draw two boxes on the board. Left box: world. Right box: battle. Between them I draw a narrow bridge. On the way into battle, the bridge carries setup: battle type, opponent source, party references, trainer identity if relevant, wild species and level if relevant, terrain or transition style, maybe special flags like Safari or link battle. On the way out, the bridge should carry outcome: won, lost, fled, caught, drew, special ended state, plus a few details the world must consume. What I do not want to draw is a big tangled cloud where battle directly edits every campaign flag and the map loader directly pokes combat internals. FireRed is an older C codebase, so you will see globals and shared state, but conceptually the pattern is still very much this bridge.

This is why pokefirered/include/battle.h chunk_f5cd027bf13ab47d matters for today. A header like that is not just a convenience list of declarations. In our course language, it is a cross-module contract. It tells other systems, “Here are the battle-facing structures and result-related state you may coordinate through.” We have used that phrase, cross-module contract, since the runtime shell weeks, because on this kind of engine the module boundary is often not an object-oriented class boundary. It is a header, a global, a callback, a task, and a small collection of conventions. Advanced work in this codebase means seeing those conventions, not pretending they are invisible because they are not wrapped in a modern interface.

Now, let’s tie that back to the two entrance paths we discussed in the previous segment. A trainer battle begins from authored data. The high-signal table named in our packet is pokefirered/src/data/trainers.h. A wild battle begins from encounter-area data, with the high-signal input named as pokefirered/src/data/wild_encounters.json. Those two sources look very different at the data layer. One describes a person-like opponent with trainer class, party composition, AI and reward implications. The other describes probabilistic availability of species by location and encounter type. But once the engine has decided, “Yes, a battle is happening,” both of those must become something that the battle engine can run without constantly asking where they came from.

Let me say that again because it is the key design pressure in the lab this week. Trainer data and wild encounter data are not the same data. They should not be forced into the same table. But the battle setup object, or in FireRed’s actual style, the collection of battle globals and setup calls, must normalize them enough that the battle engine can proceed through one modal engine. In your clone implementation, that is why I am asking you to implement a wild encounter resolver and a trainer battle resolver that both create the same battle setup object and return a battle result consumed by overworld logic. The sameness is at the runtime boundary, not at the authoring boundary.

Put another way, if your wild resolver returns “spawn a level 4 Pidgey from grass table slot 3,” and your trainer resolver returns “start trainer Youngster Ben,” and the battle system has separate top-level engines for those, you have missed the architectural lesson. If instead your wild resolver and trainer resolver both fill something like BattleSetup, with a cause, participants, battle flags, transition parameters, and post-battle continuation token, then you are moving in the right direction. The battle may branch internally based on flags, but the outer shell remains a single contract.

Now look at pokefirered/src/battle_setup.c chunk_494d132a59422c85. The packet identifies that chunk as representative for battle transition, load, and event coordination. Even just the include list is a useful architectural fingerprint. It pulls in task, overworld, item, sound, pokemon, load_save, safari_zone, quest_log, script, event data, event object movement, field effects, field map, player avatar, message boxes, battle, battle transition, and more. That is not random clutter. That tells you battle setup sits at a high-traffic boundary. It must speak enough overworld to leave the field cleanly, enough script to honor event causes, enough battle to configure the modal engine, enough save or quest state to preserve relevant side effects, and enough transition code to hide the mechanical swap from one mode to another.

And this is where many advanced students accidentally overcorrect. They say, “Ah, battle_setup.c includes overworld and script, therefore battle is coupled to everything.” Slow down. The setup layer is expected to be coupled. Its job is mediation. The important question is whether that coupling is concentrated at the boundary, or whether it leaks throughout the inner battle loop. We already spent recent lectures on the battle main callback, chosen actions by battler, turn-order arrays, battle script command dispatch, current move data, damage and hit effects. Those pieces should not be repeatedly asking, “Which map event started me?” If they do, the combat engine is no longer portable even inside its own game.

Let’s do a concrete example. Suppose the player walks into tall grass on Route 2. The overworld movement system detects a step on an encounter-capable metatile. The wild encounter logic consults data ultimately generated from pokefirered/src/data/wild_encounters.json. Maybe the chosen species is Rattata at level 3. Now, the setup boundary constructs the battle cause. It says, in effect, “This is a wild battle. Opponent side has one generated creature. The player party is the player’s current party. The result continuation should return to this map state, with the player avatar, camera, scripts, and field music handled appropriately.” Once the battle engine begins, the turn system does not care that the Rattata came from Route 2 grass rather than a cave table. It cares about species data, level, stats, moves, status, battle flags, and controllers. If the player defeats it, the battle outcome is something like win. If the player flees, the outcome is flee. If the player catches it, the outcome is catch, and now the world and party systems have consequences to apply.

Now compare trainer battle. The player talks to, or is spotted by, a trainer object event. The trainer identity points back into pokefirered/src/data/trainers.h. That record gives the authored opponent data. The setup boundary says, “This is a trainer battle. Opponent side comes from this trainer record. Trainer defeat may set a world flag. Prize money, post-battle dialogue, rematch state, and script continuation may be relevant.” The battle engine still runs its modal loop. The combat rules still use chosen actions and turn-order arrays. Damage still goes through the battle script machinery we studied. But the loss and win routes are not identical to wild battles. Winning a trainer battle usually means setting defeated state and returning to the trainer’s script continuation. Losing means routing to the whiteout or loss consequence path, not simply standing in the grass with no change.

Check your understanding here. If I ask, “Where should the code decide that a defeated trainer will no longer challenge the player on sight?” your answer should not be “inside the damage formula” or “inside the move script that made the last HP drop to zero.” The immediate terminal condition can be detected in battle main flow. The campaign meaning belongs to the post-battle route. In FireRed source terms, the result flow represented by pokefirered/src/battle_main.c chunk_1c5a1226d8d9afb7 gives us the battle-side handling of terminal results, while cleanup and continuation represented by pokefirered/src/battle_main.c chunk_46361bdbd92dae37 moves us toward returning control to the right world route. That is the division you should be modeling.

Let me write a small pseudo-flow on the board, and I’ll keep it intentionally abstract so we do not mistake the teaching model for a literal copied implementation. First line: encounter cause. Second line: setup battle. Third line: run modal battle. Fourth line: produce battle result. Fifth line: cleanup battle resources. Sixth line: resume overworld continuation. The danger is putting line six into line three. The other danger is putting line three into line one. Both mistakes make the game harder to reason about.

In your lab implementation, I expect you to name this boundary explicitly. You might use BattleSetup and BattleResult if you are writing in a higher-level language. You might use a struct with fields like sourceKind, battleType, playerPartyRef, enemyPartySpec, trainerId, encounterLocation, transitionKind, and continuation. For result, you might use outcomeKind, winnerSide, caughtCreature if any, moneyDelta, faintedPartyState, and continuationSignal. But be careful: not every consequence belongs in BattleResult as a fully processed world change. A result should report facts and minimal required payload. The world should interpret them.

For example, should BattleResult contain “set flag TRAINER_BEN_DEFEATED to true”? In a small clone, you could do that, but architecturally I prefer “trainer battle won against trainerId Ben,” and then the overworld event system maps that to the proper flag operation. Why? Because the same result may be consumed differently depending on mode. A rematch system, quest log, tutorial script, or link battle wrapper might all care about a trainer victory differently. If the battle result is already a bag of script mutations, the battle layer has stolen the event system’s job.

This is exactly where week 15 connects to the overall course plan. Back in the first module, when we studied AgbMain, SetMainCallback2, CreateTask, and RunTasks, the theme was runtime shell: the game is a set of state machines scheduled under a small callback/task framework. In the map weeks, we studied world data becoming object events and scripts. In the creature data weeks, we studied species, party state, moves, and derived stats. In the battle weeks, we studied the modal combat engine and its internal bytecode-like battle scripts. Now week 15 is the join point. It asks whether you can trace an authored trainer or a wild encounter table entry all the way into battle, and then trace the result all the way back to campaign state without blurring every layer into one pile.

That is also why the assessments have been getting less forgiving. In week 12 and week 13, if you could identify the relevant file and explain the callback path, that was a good answer. By week 14, your homework and quiz asked you to design minimal clone versions of map loading, script execution, party data, battle turns, item use, and save persistence. So now, in week 15, I am expecting you not only to identify “this table feeds this system,” but to design the adapter between systems. If your answer says, “The wild encounter table starts a fight,” that is true but too shallow. I want to hear, “The encounter table is sampled by overworld encounter logic, converted by a setup boundary into battle configuration, executed by the battle engine, and reduced to a result consumed by overworld continuation logic.”

Let’s pause for a comprehension check. Suppose a player loses to a trainer. What are the minimum pieces of information the world needs after battle? Think for a moment. The world needs to know the outcome was loss, and it needs enough context to choose the loss route. It may need current party fainted state, money loss calculations or already-computed money consequences depending on your architecture, location or respawn information, and script continuation behavior. Does the world need the full turn order from the final turn? No. Does it need the random rolls used by damage? No, unless you are doing replay auditing. Does it need every battle message printed? No. Those are battle-local details. The world consumes the terminal meaning.

Now the inverse question. Suppose the player catches a wild Pokémon. Does the battle engine need to know the PC box layout screen that may appear if the party is full? In a strict architecture, no. The battle needs to report that a catch succeeded and provide the creature instance. The storage or party system then decides whether it goes into the party, into a box, or triggers naming and registration UI. FireRed, as a real production GBA game, has historical and performance-driven coupling, so you will see practical shortcuts. But the teaching architecture remains useful because it tells you which shortcuts are local compromises and which shortcuts would damage the whole design if copied into your clone.

I want to bring in save here because it is easy to forget. Battle outcomes often alter persistent state. Defeated trainers, caught creatures, consumed items, money, experience, level-ups, learned moves, Pokédex seen and caught flags, and location after loss all touch persistence eventually. But “eventually” matters. You do not need to write save data every time the battle result variable changes. You need the authoritative runtime state to be updated in the right module, so that the next save operation serializes it. This is the same principle we used when discussing inventory and UI. The bag menu is not the save system. The bag menu mutates inventory state through its proper path; save later records the state.

Here is a subtle point about result storage. A battle result is not only a value; it is also a synchronization device. The main callback or task running battle has to finish its visual, audio, and state cleanup before the overworld can safely resume. If the result is written too early and the world resumes immediately, you risk returning while sprites, palettes, windows, or battle resources are still active. If the result is written too late, world scripts may not know what happened. So the cleanup and continuation flow represented by pokefirered/src/battle_main.c chunk_46361bdbd92dae37 is as important as the moment of victory detection. Architecture is temporal. Who knows what is only half the question. When they are allowed to act on it is the other half.

That timing concern is also visible at the setup side. pokefirered/src/battle_setup.c chunk_494d132a59422c85 is representative of battle transition and load coordination, and that coordination exists because you cannot just call a battle function in the middle of overworld movement and hope the display sorts itself out. You fade, transition, set callbacks, initialize resources, and enter the modal battle state. On return, you unwind in the opposite direction. This is why our runtime shell material keeps coming back. SetMainCallback2 and the task runner are not trivia from week 1. They are the machinery that makes “mode switch” a concrete operation.

Let me give you a design smell. If your battle resolver function returns directly to the overworld in the same call stack that detected the encounter, you may be writing a synchronous toy, not a callback-driven RPG runtime. That can be fine for a command-line prototype, but it does not model the FireRed architecture well. In FireRed’s world, battle is a mode that occupies time. It has frames, animations, input windows, scripts, and cleanup. Your clone may simplify, but it should still preserve the conceptual asynchronous handoff: start battle now, receive result later.

Now, there is one more distinction I want to layer in: source identity versus participant identity. A wild encounter’s source identity might be “grass table for this map.” Its participant identity is “this generated creature instance.” A trainer battle’s source identity is “trainer table entry.” Its participant identity is “the trainer party instantiated for this battle.” Why does that matter? Because results attach to different identities. Experience and fainting apply to participants. Defeated flags and rematch state apply to source identity. If you collapse those, you will create bugs like marking the wrong trainer defeated, or treating a generated wild creature as if it had persistent map-object identity.

Let’s make that concrete. A trainer’s first Pokémon faints. That is participant state. The trainer is not defeated yet if more party members remain. At final victory, the trainer source can be marked defeated. For a wild encounter, the wild Pokémon faints and the encounter is simply over; there is usually no persistent “this grass slot is defeated” state. If caught, the generated participant becomes a persistent creature in the player’s collection. It crosses a boundary from transient battle opponent to party or storage entity. That crossing is a major architectural event. Your resolver should make it explicit.

This is a good place to recall our comparison to Pokémon Showdown from the broader research packet, but only lightly. Showdown’s battle data and deterministic test orientation are useful as a contrast because it foregrounds battle as a simulation with clear inputs and outputs. FireRed, however, is our authority for this course because it embeds battle in an overworld RPG. The lesson today is not merely “simulate a battle correctly.” It is “simulate a battle correctly while preserving the larger game’s state machine boundaries.” That is the FireRed-specific architectural challenge.

All right, quick verbal quiz. I’ll ask three questions, and you answer silently before I give the answer. First: Should pokefirered/src/data/trainers.h and pokefirered/src/data/wild_encounters.json be normalized into exactly the same authoring schema? No. They represent different design concepts. Second: Should they eventually feed a shared battle setup pathway? Yes, because the battle engine needs a common runtime contract. Third: Should the battle engine directly decide every overworld consequence? No. It should publish terminal facts and necessary payload; the world, script, party, inventory, and save-adjacent systems interpret those facts through their own responsibilities.

Now, when you implement this for homework_w15, I want you to test both the ordinary route and the annoying route. Ordinary route: wild battle starts, player wins, world resumes. Annoying route: trainer battle starts from a script, player loses, cleanup still runs, world does not continue as if the trainer was defeated. Another annoying route: wild battle catch succeeds, the result is not merely “win”; it carries catch-specific meaning. Another: player flees from wild battle, but cannot flee from a trainer battle unless your rules explicitly allow it. These are not edge cases in the sense of rare accidents. They are the cases that prove whether your outcome model is expressive enough.

Let me underline that phrase: expressive enough. A Boolean battleWon is not expressive enough for a creature-collector RPG. It can handle “trainer defeated” if you squint, but it cannot cleanly handle flee, catch, loss, draw, Safari-specific outcomes, link outcomes, scripted battles, or tutorial exceptions. FireRed’s battle flags and result-related state, represented in the contract around pokefirered/include/battle.h chunk_f5cd027bf13ab47d and the battle_main chunks we named, exist because the real game has more than one terminal meaning. In an advanced architecture course, the correct simplification is not to pretend there are only two outcomes. The correct simplification is to model a small outcome enum and let each mode consume the values it supports.

So, carry forward the mental model from the previous segment. Encounter data is cause. Battle setup is translation. Battle main is modal execution. Battle result is terminal fact. Cleanup is controlled unwinding. Overworld continuation is interpretation. We are going to spend the next part looking more closely at win and loss routing, especially how trainer scripts and map state resume after the battle has consumed the screen. Keep the bridge drawing in mind, because the next question is not whether the bridge exists. The next question is what exact payload is allowed to cross it, and how small we can keep that payload without losing the meanings the RPG actually needs.

Now, let’s make that payload concrete. I’m going to write three words on the board: result, context, continuation. And I want you to notice that those are not the same thing. The result is what happened inside the battle. The context is why the battle was started. The continuation is what the overworld should do next. A very common architectural mistake, especially in student clone projects, is to collapse all three into one giant “after battle” function. That function knows whether the player fought a trainer, whether the wild encounter came from grass, whether the player blacked out, whether the opponent was caught, whether a script should resume, whether music should restore, whether the map object should turn back toward the player, whether a flag should be set, and whether the save block should be touched. That is too much meaning in one place.

FireRed’s architecture is interesting because it does not achieve cleanliness by being purely abstract. It is very much a C program with globals, callbacks, and shared state. But it still has a disciplined separation of concerns. The battle setup side pulls from trainer data and encounter data; the battle engine runs as its own modal state machine; then result-related state is made available to the caller. The packet points us to pokefirered/src/battle_setup.c, chunk_494d132a59422c85, and the important thing about that chunk is not one individual function name. The important thing is the include surface. It reaches into overworld, script, event data, field player avatar, battle transition, battle tower, quest log, Safari Zone, and battle. That is a signal that battle setup is a boundary module. It is not “just battle.” It is where the overworld side prepares to surrender control to battle and later regain it.

So if you are building the lab resolver, do not make your battle object responsible for knowing how to restart map scripts. The battle object may carry a result. It may carry battle type. It may carry participants. It may carry reward information or identifiers needed to compute reward. But the continuation should be owned by the system that requested the battle. That distinction is how we keep the clone version honest without copying every global from FireRed.

Let’s take a concrete example. The player walks in front of a trainer on Route Whatever, the trainer sees the player, the exclamation mark plays, the trainer walks up, dialogue starts, and then the battle begins. At the data level, the trainer has a table record. The lecture packet names pokefirered/src/data/trainers.h as the high-signal trainer table for this week. That record gives identity and combat-relevant information, but the act of seeing the player is map and script behavior. The trainer table is not patrolling the route. The route script and object event logic are what create the situation. Once the battle setup is built, the battle engine can run without caring that the trainer walked three tiles south before the fight. After the result returns, however, the overworld absolutely cares. If the player won, the map should not let that trainer challenge again as if nothing happened. If the player lost, the loss route may send the player away from that map entirely, which means the original script continuation may not simply resume at the next command.

Pause there. Check for understanding. If I ask, “Who owns the fact that the trainer should not rebattle immediately after losing?” the best answer is not “the battle engine.” The battle engine owns that the player defeated the trainer. The event or progression layer owns how that fact becomes a flag, a script variable, a changed object state, or altered dialogue. In FireRed terms, you should be thinking about event data and scripts as the consumers of result facts, not as code that gets embedded into the core damage loop.

Now, there is a subtlety here, and this is where advanced architecture starts to show up. “Player defeated trainer” sounds like one fact. But in a real creature RPG, several facts may be produced near the same boundary. The battle outcome may be win. The player’s party may have changed experience, levels, moves, evolution opportunities, friendship, PP, HP, status, and held item effects. Money may be gained or lost. Pokédex or seen data may be updated. Trainer defeat flags may need to be set. Quest log events may be recorded. Music and map state may need restoration. The trick is that not all of those should travel through the same narrow result enum. Some of them are already written into persistent party state during battle. Some are event consequences applied after battle. Some are UI or transition cleanup. So when I say “small payload,” I do not mean “only one bit in the whole program changed.” I mean the explicit control-flow answer should stay small.

Let me say that another way. A battle can mutate party data directly because the creatures are the subjects of the battle. HP, experience, learned moves, and status are not merely return values to the overworld; they are the durable creature model being operated on. But whether the script resumes at the victory branch or the loss branch is a control-flow result. That should be represented compactly. That is why the source focus includes pokefirered/include/battle.h, chunk_f5cd027bf13ab47d, for BattleStruct and result-related state. The struct-level contract is the right place to look when you want to understand what battle exposes to other systems. Headers are contracts, as we said all the way back in the early architecture weeks. Here the contract is not philosophical; it is how separate callback worlds agree on what happened.

I’m going to draw a small timeline now. On the left: overworld callback active. Then: script or field encounter requests battle. Then: battle setup stores enough context and changes the main callback. Then: battle main runs, using the foundations we have already covered, chosen actions, turn ordering, script dispatch, move effects, all of that. Then: terminal result is recorded. Then: cleanup callback restores or redirects. Then: overworld logic interprets. Notice I did not put “battle calls trainer script directly” in the middle. That is the point.

The packet’s battle main references are useful here. pokefirered/src/battle_main.c, chunk_1c5a1226d8d9afb7, is identified as the battle main result flow, and pokefirered/src/battle_main.c, chunk_46361bdbd92dae37, is identified as cleanup and result continuation. Those two labels alone tell you there are at least two phases after ordinary turn execution: deciding or storing the result, and unwinding from the battle mode. In a novice implementation, these become one phase: “when HP reaches zero, return to map.” But FireRed cannot be that simple. The end of combat has text, animations, experience, trainer payout, evolution opportunities, link constraints, Safari behavior, and screen transitions. So the terminal result may be known before the player visibly returns to the map.

That distinction matters for your lab. Suppose your wild encounter resolver returns immediately when the wild creature reaches zero HP. That sounds reasonable until you add experience text. Now the battle is mechanically over, but the UI is not done. Suppose the player catches the wild creature. The battle is over in a different way, but you still need nickname prompts, party or storage placement, Pokédex registration, and then cleanup. If your overworld continuation fires too early, you will see the map underneath while battle aftermath UI still needs to run. That is not just a cosmetic bug. It means your callback ownership is wrong.

Let’s make the example sharper. Imagine a minimal result enum with values like PLAYER_WIN, PLAYER_LOSS, CAUGHT, RAN, DRAW, and maybe FORCED_END if your engine needs one. Now imagine a trainer battle. Which of those are valid? PLAYER_WIN and PLAYER_LOSS are ordinary. RAN is usually not valid for a normal trainer battle. CAUGHT is not valid. DRAW is maybe rare or mode-specific depending on rules. The trainer resolver should not need a completely different result type from the wild resolver. It can receive the same enum and reject or ignore values outside its mode. That is what I meant in the previous segment by “each mode consumes the values it supports.” One shared result shape, multiple interpreters.

Now, someone usually asks here, “Professor, if invalid outcomes are possible in the enum, isn’t that less type-safe?” Yes, in a language with strong sum types, you might model this with mode-specific result variants. In C, and in a legacy handheld architecture, you often get a compact integer result plus mode flags. The safety comes from disciplined ownership and tests around the resolver boundary. For your course project, if you are writing in TypeScript, Rust, Kotlin, C#, or even modern C++ with variants, you can make the illegal states harder to represent. But do not confuse the modern type expression with the underlying architecture. The architecture is still: mode enters battle, battle produces terminal fact, mode-specific continuation consumes it.

Let’s return to trainer scripts. A trainer battle is not just a random combat; it is embedded in a script conversation. The pre-battle dialogue and post-battle dialogue are part of the same authored event. The trainer’s table entry supplies battle identity and party information; the map script supplies presentation and progression. When the player wins, the continuation often needs to set defeated state and show after-battle text. When the player loses, the game may route to a blackout or loss handler, and the trainer’s local post-battle branch may not play in the same way. That means win/loss routing is not merely “show victory screen” or “show defeat screen.” It is a continuation decision in the script VM and map runtime.

This connects directly to the course objective of decomposing the complete RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. Week 15 is where those boxes stop looking separate. A battle outcome touches creature data because HP and experience changed. It touches inventory because balls, items, or prize money may change. It touches UI because result messages and transitions must finish. It touches save-adjacent state because flags and party state may later be persisted. It touches link systems in special modes because both peers must agree on outcome and cleanup. But the answer is not to create a giant “GameSystem” object that knows everything. The answer is to let the boundary module coordinate and let each subsystem own its state.

The source packet’s pokefirered/src/battle_setup.c, chunk_494d132a59422c85, supports that interpretation because the setup file imports the field and script side as well as battle side. It is sitting at the seam between map reality and combat reality. The same packet’s battle_main chunks, chunk_1c5a1226d8d9afb7 and chunk_46361bdbd92dae37, support the idea that battle result flow and cleanup are not incidental afterthoughts. They are part of battle main’s responsibility before control is safely returned.

Let’s do a quick board exercise. I’ll write a pseudo-structure verbally. BattleSetup has mode, opponent source, player party reference, opponent party or species reference, environment flags, return callback, and maybe script continuation token. BattleResult has outcome, maybe winning side, maybe caught species or caught monster index, maybe payout or reward summary if not directly applied. OverworldContinuation has resume script, set flags, restore music, move player or object, and choose map callback. Now ask yourself: where does trainer ID live? It likely lives in setup and continuation context. Where does “player won” live? Result. Where does “set trainer defeated flag” live? Continuation. Where does “opponent’s third creature used Tackle” live? It does not cross this boundary unless you have logging or analytics. The payload should not carry battle history unless a later system truly consumes it.

That last point matters because advanced students often overgeneralize. They build an event bus that emits twenty-seven battle events to the overworld: move used, damage dealt, fainted, switched, item consumed, turn ended, battle ended. Event buses have their place, but if all you need is overworld routing, you have leaked battle internals. FireRed is highly global, but it is not indiscriminately verbose at every boundary. The visible result of the battle is summarized into state that other systems can use.

Let me check comprehension with a scenario. The player encounters a wild Pidgey from grass. The source packet names pokefirered/src/data/wild_encounters.json as the high-signal wild encounter input for this week. The encounter table can help choose species and level based on location and method. Battle setup translates that into a wild battle. The player throws a ball and catches the Pidgey. Question: should the map event script have to know the catch formula? No. Should the catch formula know which map tile produced the encounter? No. Should the battle result communicate that the wild battle ended by capture? Yes, or at least some post-battle state must allow the wild encounter resolver to distinguish capture from fainting or fleeing. The map continuation may then resume normal movement after capture-related UI finishes.

Now compare that to a trainer battle. The player defeats Brock. The result is win. But “Brock defeated” is not merely a creature state; it is progression state. The reward and badge logic may be script-driven. The trainer battle’s continuation can branch into after-battle dialogue and flag setting. If the player loses, the continuation is different. That is the pattern you need to be able to design from memory by now, because homework_w14 and quiz_w14 already asked you to design minimal clone versions of map loading, script execution, party data, battle turns, item use, and save persistence. Week 15 assumes you can now connect those minimal systems into one runtime handoff.

And this is why I keep using the phrase “controlled unwinding.” A battle is not a function call in the simple sense. You do not call Battle() and get a return value on the next line, at least not in the architecture we are studying. You change modes. You give control to a different callback loop. Tasks, sprites, windows, palettes, audio, and input handling shift into battle ownership. Later, cleanup must remove or restore those resources before the overworld can safely accept input again. If you pretend it is a synchronous function, your mental model will be wrong even if your pseudocode compiles in a modern engine.

In FireRed, that modal nature is consistent with what we covered earlier about the runtime shell. We already studied main callback slots and SetMainCallback2, and we do not need to re-teach that today. Just carry forward the implication: changing the main callback changes which world is currently being advanced each frame. The battle result must survive that callback transition. That is one reason global or shared structs appear in this style of engine. They are not automatically bad design. They are a practical way for separate callback regimes to communicate across time.

Now, there is a design danger on the other side. Once students accept shared state, they sometimes use it as an excuse to let every module read every flag. That is not architecture; that is entropy. The more disciplined reading is: shared result state exists because callback-separated systems need a handoff. The header defines what can be seen. The setup and cleanup code decide when it is meaningful. The overworld continuation consumes it at a stable point. So yes, the C architecture uses globals, but the timing contract is still real.

Let’s talk about loss routing for a moment, because win routing is easier. On win, you usually resume local authored content. On loss, many RPGs perform a global recovery route. The player may black out, lose money, be moved to a healing location, have party restored, and restart from a safe map. If you are not careful, loss handling can accidentally resume the losing trainer’s script after teleporting the player somewhere else. That is a serious control-flow bug. The continuation must understand that loss can invalidate the local map context. If the map is no longer active, object references from the old map are not safe. If the script pointer belonged to an event on that map, resuming it may be meaningless.

So in your resolver design, treat PLAYER_LOSS as a potentially global transition, not simply the other branch of PLAYER_WIN. This is especially important for advanced projects with dungeons, scripted boss fights, or link battles. A loss in a tutorial fight may intentionally continue locally. A loss in an ordinary trainer fight may route to blackout. A loss in a link battle may return to a communication room. Same enum, different consumer. Again: result is fact; continuation is interpretation.

Let’s put that into a clean sentence you can use in your design notes: battle outcome values should describe what happened, not what screen to load next. “Player lost” is a result. “Warp to last healing center” is a continuation policy. “Caught wild creature” is a result. “Open nickname screen” may be an aftermath UI policy. “Trainer defeated” may be partly result and partly progression interpretation, depending on how your setup identifies the opponent. Keeping those apart gives you room to add special cases without corrupting the battle core.

A student in a previous term implemented this with a BattleResult object that had a nextMap field. That worked for wild battles in grass, then failed for trainer battles, then failed harder for scripted legendary encounters. The bug was conceptual. The battle engine should not choose the next map. At most, the battle engine can say, “this mode ended by player victory,” or “this wild opponent was caught,” or “the player fled.” The caller or a continuation manager chooses where the player goes.

Now, to connect this to the source again, the setup chunk in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, includes script and event systems right next to battle transition and battle setup dependencies. That supports a model where battle setup is the bridge from field-authored context into combat. The battle result and cleanup chunks in pokefirered/src/battle_main.c, chunk_1c5a1226d8d9afb7 and chunk_46361bdbd92dae37, support the other half: once battle has reached a terminal condition, it still has structured work to do before the outside world should interpret the result. And pokefirered/include/battle.h, chunk_f5cd027bf13ab47d, matters because the result-related state is not just an implementation detail hidden in one C file; it participates in a cross-module contract.

Let me ask another check-for-understanding question. Suppose your trainer resolver and wild resolver both create the same BattleSetup object, as the lab asks. Does that mean they must have identical continuation logic? No. It means they share the translation into battle. They do not share all interpretation afterward. The wild resolver might interpret CAUGHT, RAN, PLAYER_WIN, and PLAYER_LOSS. The trainer resolver might interpret PLAYER_WIN and PLAYER_LOSS, while treating RAN as impossible or mode-blocked. Sharing setup prevents duplicate battle-launch code. Separate continuation preserves game meaning.

This is also where you should be careful with naming. If you name the field battleWon, your design is already biased toward trainer battles. If you name it outcome, you leave room for catch, flee, draw, Safari end, link disconnect, or scripted termination. If you name a callback OnTrainerDefeated and reuse it for wild battles, you are smuggling mode assumptions into a generic pathway. Good architecture often starts with names that do not lie.

Now, what should be saved? Not necessarily the result object itself. The result object is usually transient. The consequences are saved. If a trainer is defeated, a flag or variable persists. If party HP changed, party state persists. If money changed, money persists. If the player was warped after losing, location persists. But “last battle result equals win” is often not persistent unless some script or feature specifically needs it. That distinction will matter next week when we talk more about save and late-game integration. Save systems should not become dumping grounds for temporary control-flow facts.

Let’s close this segment with a compact recap, not the lecture conclusion, just a checkpoint. Encounter data gives the cause and initial parameters. Trainer tables and wild encounter tables are not battle engines; they are inputs. Battle setup translates those inputs into a modal combat context, and pokefirered/src/battle_setup.c, chunk_494d132a59422c85, is our source anchor for that boundary role. Battle main executes combat and records terminal facts, with result flow and cleanup represented in the packet by pokefirered/src/battle_main.c, chunk_1c5a1226d8d9afb7, and pokefirered/src/battle_main.c, chunk_46361bdbd92dae37. The exposed result-related state belongs to a contract surface, which is why pokefirered/include/battle.h, chunk_f5cd027bf13ab47d, is part of today’s evidence. The overworld does not need every turn of history. It needs a stable terminal fact and enough context to interpret it correctly.

In the next stretch, we are going to tighten this into an implementation pattern for the workshop: one shared setup object, two resolvers, one result enum, and mode-specific continuation code. As we do that, keep watching for the architectural line between “the battle knows what happened” and “the world knows what that means.”

So, on the board, I am going to draw this as a narrow waist. Above the waist, we have encounter sources: a trainer script, a grass tile, a fishing rod, a cave step, maybe a scripted legendary encounter. Below the waist, we have world continuation: remove the trainer’s exclamation state, set a defeated flag, warp to a Pokémon Center, resume the map script, give experience consequences, update Safari state, or just let the player stand back on the same tile. The narrow waist is not “battle.” The narrow waist is “battle setup plus battle result.”

That distinction matters because battle itself is huge. We spent earlier lectures inside the battle main callback, chosen actions by battler, turn-order arrays, battle script command dispatch, current move state, damage, hit effects, and status timers. Those systems are rich, but today we are deliberately zooming out. We are asking, how does a world system ask for a battle without becoming a battle engine, and how does the world receive the answer without becoming a combat log parser?

A useful advanced design rule is this: the overworld should not ask, “How did the last turn end?” It should ask, “What terminal category did the battle produce, and what initiating context did I attach before entering it?” That is why the result contract in pokefirered/include/battle.h, chunk_f5cd027bf13ab47d, is so important. Header surfaces like this are not just C convenience. They are architectural borders. They tell other modules which battle facts are allowed to escape.

Now, let’s make that concrete. Suppose the player steps into tall grass on Route 3 and the wild encounter system decides to spawn a level 8 Pidgey. The wild encounter table input, represented in the packet by pokefirered/src/data/wild_encounters.json, helps choose species, level range, encounter method, and map-local distribution. The battle setup layer does not need to preserve the whole probability table. It needs to build a battle context: this is a wild battle, here is the opponent creature, here are the battle type flags, here is the return path. In FireRed, the packet gives us pokefirered/src/battle_setup.c, chunk_494d132a59422c85, as the boundary anchor where battle transition, load, event, overworld, item, safari, quest log, script, and battle headers all meet. That include list alone is a clue. This file sits at a crossroads.

Now change the example. The player walks in front of a Bug Catcher. The trainer’s object script notices line of sight, starts an event, and identifies a trainer entry. The trainer table in pokefirered/src/data/trainers.h is the source side for that encounter. Different input data, yes. Different overworld trigger, yes. But once we reach the modal combat boundary, we should want the same shape of setup object. Trainer battle resolver and wild battle resolver differ before the waist. They should converge at the waist.

Let me pause there and check understanding. If I say, “Wild encounters and trainer encounters should create the same battle setup object,” I am not saying their data tables are the same. I am saying the battle engine should not need two unrelated entry protocols just because one opponent came from grass and one came from an NPC script. The mode belongs in flags and context fields. The protocol should be shared.

Put another way, the battle engine can know, “I am in a trainer battle,” because that changes fleeing, prize money, party presentation, AI assumptions, and end messages. But it should not know, “This trainer was activated by an object event that turned down from coordinate x equals 12, y equals 7 after a line-of-sight check.” That belongs to the map and event layer. When battle ends, the world continuation code can interpret the stored trainer context and say, “Set this trainer defeated flag, run this after-battle script, unblock movement.” The battle engine just reports that the player won, lost, fled if legal, caught the wild creature, or hit some other terminal condition.

For the workshop, I want you to implement this in a deliberately small architecture. On the board, I’ll write four names: EncounterInput, BattleSetup, BattleResult, WorldContinuation. These names do not need to match FireRed exactly. They are pedagogical names. But each maps to a real architectural responsibility we can see in the source packet.

EncounterInput is source-specific. For wild battles, it is assembled from a map’s wild encounter table, method, random roll, and party constraints. For trainer battles, it is assembled from a trainer table row, trainer flag state, and event script context. BattleSetup is normalized. It says battle mode, enemy data reference or generated enemy party, player party reference, field conditions, transition style, and continuation token. BattleResult is small. Win, loss, flee, capture, draw-like special case, or scripted abort if your clone supports it. WorldContinuation is mode-specific again. It consumes the result and decides what the overworld does next.

Notice the hourglass shape. Wide source variety, narrow setup contract, huge battle internals, narrow result contract, wide continuation variety.

Now, why does this scale? Because a creature RPG keeps adding weird encounter sources. Static overworld Pokémon. Gift battles. Rival fights. Tutorial capture battles. Safari battles. Link battles. Battle Tower-style facilities. Rematches. Double battles. Script-forced battles that must resume at a particular instruction. If each one tunnels custom behavior into the battle core, the battle core becomes a pile of exceptions. If each one has its own return convention, the overworld becomes fragile. But if they all lower into a setup contract and return through a result contract, then special behavior can be isolated to setup construction and continuation interpretation.

This is one of the places where FireRed’s older C architecture teaches a modern lesson. It does not have the language features we might reach for today. We do not have typed algebraic data types, dependency injection containers, or a neat async scene stack. Instead, we see headers, globals, callbacks, task functions, script state, and data tables. But the architecture problem is the same. The source packet’s battle setup anchor, pokefirered/src/battle_setup.c, chunk_494d132a59422c85, shows that entering battle is not just calling a function named startBattle. It is a coordinated transition across overworld state, script state, player avatar state, loaded resources, and battle state. The battle main chunks, pokefirered/src/battle_main.c, chunk_1c5a1226d8d9afb7, and pokefirered/src/battle_main.c, chunk_46361bdbd92dae37, then represent the other side: terminal result flow and cleanup before continuation.

Let’s use a very concrete flow. The player loses to a trainer. What must happen?

First, battle must determine loss. That is internal battle logic. We are not re-teaching faint checks today, but you already know from prior weeks that battler state, chosen actions, damage scripts, and party state eventually produce a terminal condition. Second, battle result state must record something equivalent to player lost. Third, battle cleanup must release or reset battle-owned resources, stop battle-specific callbacks or tasks, and arrange for control to return. Fourth, overworld continuation must interpret the loss. In a standard trainer battle, losing does not mark the trainer defeated. It sends the player through a blackout or whiteout route, often involving money loss, healing, and warp behavior. In a clone, maybe you implement a simple “return to last checkpoint.” The point is that loss routing is not just a battle result. It is a world policy triggered by that result.

Now compare a wild loss. In many cases, the player still blacks out, so the continuation might share code. But compare a wild flee. If the player successfully runs from a wild battle, no trainer flag is set. No NPC post-battle script should run. The player should simply return to the map, usually standing where they were. Compare a trainer win. That must set a defeated flag, unlock or modify NPC dialogue, maybe pay prize money, maybe run after-battle text, then resume the map. So the same result enum value, “win,” has different meaning depending on context. Winning a wild battle might just mean the wild opponent fainted. Winning a trainer battle updates progression state.

Here is the subtle design claim: BattleResult alone is never enough. BattleResult plus BattleSetup context is enough.

Say that back to yourself. BattleResult alone is never enough. If all I give the overworld is “BATTLE_WON,” the overworld must guess what battle this was. That guess becomes global-state archaeology. It starts asking, “Was there a trainer id set? Was there a wild species set? Was a script pointer waiting?” That kind of design works at small scale but becomes error-prone. A cleaner clone design gives the continuation layer a token or context object that says, “This result belongs to trainer encounter number 42, initiated by object event 3, with after-battle script pointer X,” or “This result belongs to wild encounter generated from map Y with no persistent defeated flag.”

Now, FireRed uses the idioms of its engine, so its continuation information is distributed through globals, vars, flags, scripts, and battle structs rather than one tidy object. That is why the packet’s include/battle.h chunk_f5cd027bf13ab47d matters: it gives us part of the shared contract, while battle_setup.c and battle_main.c show the practical handoff points. For your workshop implementation, I want you to create the tidy object because it exposes the architecture more clearly.

Let’s do a quick board-work sketch in words. I draw a rectangle named BattleSetup. Inside it I put mode, playerParty, opponent, battleFlags, transitionId, continuation. Then I draw two arrows entering it. One arrow is ResolveWildEncounter. That function reads map encounter data, rolls species and level, creates an opponent creature, sets mode to wild, and sets continuation to ReturnToMap. The other arrow is ResolveTrainerEncounter. That function reads trainer data, creates or references an enemy party, sets mode to trainer, and sets continuation to TrainerAfterBattle with trainer id and script id.

Then I draw one arrow from BattleSetup into RunBattle. RunBattle does not care which resolver created the setup. It reads mode and flags because those are part of battle semantics, but it does not call wild encounter code or trainer line-of-sight code. Finally, RunBattle returns BattleResult. The continuation consumes both the result and the continuation token. If mode was trainer and result was win, set trainer defeated and run after-battle text. If mode was trainer and result was loss, do not set defeated; route to blackout. If mode was wild and result was captured, add or queue the creature, update Pokédex-like registration if your clone has it, then return to map. If mode was wild and result was flee, simply restore overworld.

Check for understanding: what is the bug if your battle engine directly sets the trainer defeated flag the moment the enemy party faints? The bug is not always visible in the normal case, because normal trainer wins should set the flag. The design bug is that battle has reached across the boundary and mutated progression state. Now special cases become dangerous. What if the battle is a tutorial? What if the battle is part of a rematch system? What if a script wants to branch before setting the flag? What if a facility battle should not touch overworld trainer flags at all? The more robust answer is: battle reports win; continuation decides which persistent world flags to modify.

This is exactly the kind of distinction I expect from you now, because by week 15 you have already traced data records through globals, callbacks, tasks, scripts, structs, and visible behavior. The midterm and the week 14 work checked whether you could decompose a creature RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. So today the bar is higher. I am not asking, “Where is the battle code?” I am asking, “Which module is allowed to decide that a combat result changes world progression?”

Let’s connect save data briefly. If a trainer flag is persistent, it belongs to world progression and save state. If a wild battle generated a temporary enemy, that enemy may disappear after battle and never enter save state unless captured. If captured, now we cross into creature data and party or storage persistence. So battle result routing may touch save-relevant systems, but again, through continuation policy. The battle engine should not independently decide how the PC storage system serializes a captured creature. It can produce the fact of capture and the creature data. The world or capture continuation path decides whether to put it in the party, send it to storage, show a nickname screen, or update UI.

Inventory is similar. A Poké Ball used in battle is consumed during battle because item use is part of battle action execution. But the consequences of successful capture have world-facing meaning. Trainer prize money is even more clearly post-battle policy. A trainer battle result of win can trigger money gain and trainer defeated state. A loss can trigger money loss through blackout behavior. Those are not just HP arithmetic. They are economy and progression rules.

Now, one more concrete example, because this is where many clone implementations get tangled. Imagine you implement a “battleScene.onComplete(result)” callback and inside it you write:

if result == win, returnToMap.

That works for wild fainting. It fails for trainer battles because you still need after-battle dialogue and flag updates. Then you patch it:

if result == win and currentTrainer != null, set defeated and run trainer script.

Now it works for ordinary trainers. Then you add rival battles where the post-battle script gives an item. Fine. Then you add a scripted battle where losing is allowed and should continue the story rather than black out. Your global if-chain starts to sprawl. Then you add Safari encounters where “run” and “fled” mean different things. The problem was not the missing if statement. The problem was that continuation was not first-class.

So instead, your setup object carries continuation strategy. For a normal trainer, continuation is NormalTrainerContinuation. For a story battle, continuation is StoryBattleContinuation. For a wild battle, WildContinuation. In C, that might be an enum plus fields. In an object-oriented language, it might be an interface. In a data-driven engine, it might be a script label. In FireRed’s C-and-script architecture, this concept is spread across script vars, flags, callbacks, and battle setup routines. The architectural concept remains stable even though the implementation vocabulary changes.

Let’s tie this back to pokefirered/src/battle_main.c, chunk_46361bdbd92dae37. The reason cleanup and result continuation deserve their own attention is that ending battle is not just stopping combat. It is restoring the rest of the game’s invariants. Palettes, audio, callbacks, tasks, memory allocations, party state, field state, and script state all need to make sense again. Earlier in the course, when we studied SetMainCallback2 and the task system, the lesson was that FireRed’s runtime shell is always asking, “What callback owns the frame now?” Battle takes ownership for a while. Returning to the overworld means ownership changes back. The result handoff rides on that ownership transfer.

And this is where bad architecture shows itself as a visible player bug. You win a battle and the screen fades back but the NPC still blocks you forever. You lose and return to the map with zero usable party members. You catch a creature and it appears in the party but the Pokédex state is wrong. You flee a wild battle and the step counter immediately triggers the same battle again because the overworld did not reset movement or encounter suppression state correctly. These are not battle move bugs. These are outcome routing bugs.

Let me give you a diagnostic question you can use in your lab code. For every line in your battle completion function, ask: is this line interpreting the result, or is it restoring battle-owned resources? Cleanup belongs near battle. Interpretation belongs near continuation. Freeing battle sprites, ending battle music, clearing temporary battler state: cleanup. Setting trainer defeated flags, warping to a center, resuming a script branch, adding a captured creature to long-term storage: continuation. Some real engines blur this for practical reasons, and FireRed sometimes uses global conventions that make the boundary less visually tidy, but as designers we should still be able to name which responsibility is being performed.

Quick check: if your battle result says “caught,” should the battle engine set the map’s wild encounter cooldown? Think carefully. The capture happened inside battle, but encounter cooldown is overworld policy. A reasonable implementation may perform the state write during the transition back, but conceptually it belongs to the world continuation path. That distinction helps when you later support fishing, surfing, cave encounters, repel logic, scripted stationary encounters, or Safari Zone rules.

Now, for the shared setup object in the workshop, I want three constraints. First, do not let wild and trainer resolvers return unrelated shapes. That will force duplicated battle entry code. Second, do not let battle return a giant transcript of internal turn history unless you are building a replay or analytics system. For this lab, a compact terminal result is the point. Third, make continuation explicit enough that a unit test can assert world effects. A trainer win test should check that the trainer defeated flag changes. A trainer loss test should check that it does not. A wild flee test should check that no trainer state changes at all. A capture test should check party or storage routing.

This gives us a nice bridge to the overall course plan. Weeks 12 through 16 are the Battle Engine Architecture module, but we are not treating battle as an isolated minigame. Week 12 gave us runtime ownership and callbacks. Week 13 pushed us through battle resources, battlers, actions, and turn ordering. Week 14 moved through scripts, moves, damage, and status as shared state. Week 15, today, connects battle back to encounter data and world consequences. Week 16 will ask you to integrate and reason about the whole loop with enough discipline that a save, a UI flow, a battle, and an overworld script do not corrupt each other’s assumptions.

So the deeper pattern is not “FireRed has a battle result variable.” The deeper pattern is that a data-driven RPG needs reversible modality. You leave the world, enter a specialized mode, mutate controlled shared state, compute an outcome, clean up the mode, and return to the world with a compact result that a script or continuation layer can consume. Battle is the most dramatic example, but we have seen smaller versions in menus, summary screens, naming screens, item use flows, and move learning. The packet even reminds us through files like pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, and pokefirered/src/learn_move.c, chunk_af3598df6f092723, that many subsystems temporarily take over presentation and input, then return a decision to the caller. Battle is bigger, but it follows the same modal control principle.

Do not overgeneralize that into a vague “everything is a state machine” answer. At this level, I want the precise version: each modal subsystem needs an entry contract, ownership of frame execution while active, a cleanup path, and a result or continuation contract. The entry contract for battle is constructed from encounter data. The ownership period is battle main and its supporting controllers and scripts. The cleanup path is represented by the battle main cleanup and continuation chunks in the packet. The result contract is exposed across module boundaries in battle-related headers and state. The continuation path belongs to overworld, script, progression, inventory, party, and save policies.

Let’s return to the source anchors one more time before we move into the lab design. pokefirered/src/data/trainers.h and pokefirered/src/data/wild_encounters.json show two different kinds of encounter source data. pokefirered/src/battle_setup.c, chunk_494d132a59422c85, is the translation boundary where those sources can become a battle context. pokefirered/src/battle_main.c, chunk_1c5a1226d8d9afb7, and chunk_46361bdbd92dae37 represent battle result flow, cleanup, and return. pokefirered/include/battle.h, chunk_f5cd027bf13ab47d, is part of the shared contract that lets these systems talk without every module knowing every internal detail. That source chain is the evidence for the architecture we are modeling.

Now, take thirty seconds and imagine your own implementation. The player steps on grass. Your WildEncounterResolver returns no encounter eighty percent of the time. On the twentieth percent, it returns a BattleSetup. The battle scene runs. It returns FLED. Which code resumes player input? Which code prevents the same step from immediately retriggering? Which code knows that no defeated trainer flag should change? If your answer is “the battle scene does all of it,” you have collapsed the boundary. If your answer is “the overworld continuation does all of it, using the result and setup context,” you are much closer to the FireRed-scale architecture we are studying.

Now imagine the trainer version. The player enters line of sight. Your TrainerBattleResolver checks whether the trainer’s defeated flag is already set. If not, it builds BattleSetup with trainer id, trainer party, intro text, and continuation information. The battle returns WON. The continuation sets the defeated flag, pays prize money, runs after-battle dialogue, and restores the overworld script. If the battle returns LOST, the continuation does not set the defeated flag, and it routes to blackout. Same battle engine. Different continuation interpretation.

That is the clean implementation target. When we come back to the workshop portion, we will turn this into a small interface and test matrix, and I am going to ask you to prove, with tests rather than hope, that wild and trainer encounters enter battle through the same setup path while producing different world consequences after the result comes back.

So, keep that phrase in your head: same battle engine, different continuation interpretation. That is the architectural move. And now I want to raise the abstraction one level, because if you only implement this as “wild battle function over here, trainer battle function over there,” you will probably get something working, but you will miss the reason FireRed’s organization is so instructive.

The battle does not merely return to the world. It returns to a world that has been paused with obligations.

Let me say that again, because it is the heart of today’s second half. The overworld is not just waiting for control flow to resume. It is waiting to find out which obligations are now valid. Does a flag need to be set? Does the script pointer need to advance? Does a warp need to run? Does the player’s party need to be healed? Does an item use callback need to continue? Does the map music need to restart? Does the game need to enter blackout recovery? All of those are world obligations, not battle obligations.

If I were drawing this on the board, I would put a vertical line down the middle. On the left I would write “battle decides combat truth.” On the right I would write “world decides narrative truth.” Under battle truth: won, lost, fled, caught, fainted target, player out of usable creatures, enemy trainer defeated. Under world truth: set trainer defeated flag, clear forced movement, resume script, run post-battle text, remove wild encounter overlay, send player to last heal location, preserve or consume Safari state. The mistake is to collapse those into one enum and let every subsystem read it casually. That is how you get bugs where a trainer is marked defeated after a loss, or a wild encounter tries to run trainer dialogue, or a blackout recovery path leaves the script engine half-suspended.

This is why the source packet’s emphasis on pokefirered/src/battle_setup.c, chunk_494d132a59422c85, matters. Even from the include surface, that file is not “just battle.” It sits at the handoff boundary. It includes task, overworld, item, sound, pokemon, load_save, safari_zone, quest_log, script, event_data, event_object_movement, field control, field player avatar, field screen effects, message boxes, trainer systems, and battle transition headers. That include neighborhood is evidence of a boundary module. It is not evidence that battle owns all of those systems. It is evidence that battle setup is the switchyard where a world event becomes a battle mode and later needs enough continuation information to stitch the player back into the world.

Now, quick check for understanding. If I ask, “Where should prize money be awarded?” I do not want “in the damage engine,” obviously. But I also do not want the lazy answer “in the battle engine” unless you can qualify it. Prize money is a consequence of a trainer battle result interpreted by the world-facing trainer continuation. The battle may compute or expose facts needed by that consequence, but the decision that this trainer battle pays out and advances dialogue belongs to the trainer battle resolution path. If this distinction feels fussy, it is because you have not yet debugged the bug where losing to a trainer accidentally advances the map’s story state. Once you have debugged that once, you become religious about the boundary.

Let’s make this concrete with a small example. Imagine a youngster on Route 3. The trainer record in pokefirered/src/data/trainers.h gives us the identity and battle-facing trainer data. I am using that path because the packet names it as the high-signal trainer table for this week. The map object and script know that this youngster has a defeated flag. The player steps into line of sight. The overworld script engine stops ordinary movement and asks the trainer battle resolver to begin. At this moment, nothing has been won. The defeated flag must remain unset. The resolver creates the battle setup: trainer id, party source, battle type flags, intro text, callback or continuation state, and whatever transition information is needed.

Then the battle runs. We are not re-teaching the main battle callback today; we already built that foundation in the earlier week 15 material and in the action and turn-order lectures. You already know that chosen actions by battler, turn-order arrays, battle scripts, current move data, and damage effects cooperate inside the battle loop. Today we only care about the output surface. In the source focus, pokefirered/src/battle_main.c, chunk_1c5a1226d8d9afb7, is identified as the battle main result flow, and pokefirered/src/battle_main.c, chunk_46361bdbd92dae37, is identified as cleanup and result continuation. Those are the areas to inspect when you want to follow “the battle has concluded, now what state is written, and what callback or continuation is selected?” The important lesson is that the loop’s internal richness gets compressed into a result that the outside world can consume.

Suppose the result is WON. The youngster continuation now has permission to perform trainer-world consequences. It can set the defeated flag, route after-battle dialogue, allow the script to proceed, handle reward money, and restore overworld control. Suppose the result is LOST. The same youngster record was used, the same battle engine ran, and many of the same cleanup steps still happen, but the continuation must not set the defeated flag. Instead, it routes toward blackout recovery. The difference is not in whether the battle was “real.” The difference is in which postcondition the world is allowed to commit.

Now compare that to a wild encounter from pokefirered/src/data/wild_encounters.json, also named in the packet as a high-signal source for this week. A grass tile or cave step contributes encounter context. The wild encounter resolver chooses a species and level from the area table, builds a battle setup with a generated opponent creature, and starts the same battle shell. If the result is WON, there may be experience and party changes, but there is no trainer defeated flag. If the result is FLED, the player returns to the same map without narrative progression. If the result is CAUGHT, inventory state, party or box state, and Pokédex-style registration may change, but again, no trainer script should advance as if a human opponent just delivered after-battle dialogue. The result enum may have overlapping values, but the continuation context is different.

This is where I want you to be precise in your lab design. Your battle setup object should not be just “enemy data.” It should carry or reference an origin. Not in a sloppy, stringly typed way, like origin equals “route grass maybe,” but as a deliberately modeled source: wild encounter, trainer battle, scripted special battle, Safari encounter, link battle, tutorial battle, and so on, depending on the size of your clone. For the workshop, we only need wild and trainer. But we want the design pressure to point in the same direction as the real architecture.

On the board I would write a little pseudo-structure, verbally, not as final code: battle setup has participants, battle mode, transition policy, reward policy, and continuation token. Participants answer “who is fighting?” Battle mode answers “which ruleset is active?” Transition policy answers “how do we enter visually and musically?” Reward policy answers “what facts may be computed as battle rewards?” Continuation token answers “who gets called, and with what world context, when combat is finished?” In a C codebase like FireRed, these responsibilities are often spread across globals, flags, callback slots, and structs rather than one neat object with modern names. But conceptually, that is what you should see.

The packet also points us to pokefirered/include/battle.h, chunk_f5cd027bf13ab47d, specifically for BattleStruct and result-related state. That is a useful reminder that result state is not just an afterthought at the edge. The battle module needs structured memory for its own lifecycle. But be careful: a struct containing result-related state does not mean all world consequences belong inside that struct. It means the battle needs to remember enough to terminate coherently. The consuming context still matters.

Let’s do a check here. If your wild encounter resolver and your trainer battle resolver both call the same function, StartBattle, have you automatically satisfied the lab requirement? No. You may have only shared the last inch of the path. The requirement says they both create the same battle setup object and return a battle result consumed by overworld logic. So I am looking for shared shape, not just shared launch. If the wild code directly mutates the player party and the trainer code directly mutates script flags before a battle result exists, you have not built a resolver. You have scattered consequences before the decision point.

A good test will catch this. For example, create a fake battle engine that returns WON. Feed it a wild encounter setup. Assert that the setup includes a generated enemy creature and that the post-result world logic does not set any trainer defeated flag. Then feed it a trainer setup. Assert that the trainer id is present and that WON sets exactly the intended defeated flag. Now change the fake result to LOST. The trainer flag must remain unset. The wild path should route to its own loss or blackout handling according to your simplified rules. What I do not want is a test that merely says “battle function was called.” That proves almost nothing architecturally.

Let’s connect this back to the course-wide decomposition, because week 15 is not floating by itself. In week 1 we framed the game as a runtime shell coordinating callbacks and tasks. In week 2 we talked about headers and generated data as contracts. In the overworld module we traced map headers, layouts, object events, and script entry points. In the creature data portion we separated species data, party instances, moves, and growth. In the battle architecture weeks, we spent real time on action choice, turn order, battle script bytecode, damage, status, and shared battler state. Today’s topic is the return seam between two large state machines: battle and overworld. You are being asked to show that you can decompose the complete RPG, not just admire individual files.

That is why FireRed is such a good teaching specimen. It is not organized like a modern engine with pristine domain objects and dependency injection. It is a production handheld RPG with callbacks, tasks, global state, generated tables, flags, and tight memory expectations. Yet the architectural ideas are very visible if you trace behavior from data table to runtime globals to callback or script to player-visible result. The packet’s research synthesis explicitly describes the course thesis as FireRed being a stack of cooperating state machines plus data tables, and that is exactly what this outcome path demonstrates.

Now, let’s slow down and talk about loss, because loss is the branch students under-design. Winning is easy because it feels like progression. Loss is more architectural because it interrupts progression. If the player loses to a trainer, the world must recover from an unfinished local context. The trainer script may have been suspended. The player avatar may have been locked. Music and palette transitions may be mid-managed by the battle transition system. The party has fainted. The map location may no longer be the next place the player should stand, because blackout sends the player to a recovery point. So loss is not just result equals LOST. Loss is a control-flow escape from the local scene into a global recovery routine.

In a clone implementation, you can simplify this, but do not erase the shape. A minimal blackout resolver might say: clear active encounter, restore player control, move player to last heal map, heal party, show a message, resume overworld loop. That is enough. But notice the order. If you resume the old trainer script first and then warp the player, you have made a ghost continuation. If you set the trainer defeated flag and then blackout, you have changed story state despite failure. If you forget to clear the active encounter, the next map tick may think a battle is still pending. These are not cosmetic bugs. They are violations of ownership.

Let me give another concrete scenario. The player is in a cave and triggers a wild Zubat encounter. In your simplified project, the wild table says this cave can produce Zubat at levels 8 through 10. The resolver rolls level 9 and builds a temporary creature instance. The player loses. What should happen? In most simplified clones, loss sends the player to a heal point. But there is no trainer to mark. There is no after-battle dialogue. There may not be a persistent opponent at all. The generated creature should be discarded unless captured or otherwise preserved. The overworld’s cave map should not receive a “Zubat defeated” flag, because random encounters are not map objects with durable identity. That is the difference between table-generated encounter data and authored trainer state.

Now compare that to the youngster. The youngster is not generated each time from a frequency table. He is an authored piece of map progression. His data comes from trainer tables, yes, but his presence in the world is mediated by map object data and event flags. Defeating him changes future behavior. The next time the player enters line of sight, the resolver should consult the defeated flag and decline to start a battle. That is a persistent world consequence. So when I say “battle result consumed by overworld logic,” I mean the result is interpreted against a durable context.

This also explains why event scripting cannot be treated as a thin UI layer. The script system is often the owner of what comes next. The battle may end, but the script has to know whether to continue with after-battle text, branch to a loss route, or stop because a global recovery flow took over. In the source packet, battle_setup.c chunk_494d132a59422c85 includes script and event_data alongside battle and overworld headers. Again, do not read that as accidental clutter. Read it as a clue that battle initiation and return live in the space where authored events meet combat simulation.

Now I want to address a common advanced-student objection. Someone will say, “Couldn’t the battle system just emit domain events? BattleWon, BattleLost, CreatureCaught, TrainerDefeated?” Yes, in a modern engine you might choose that. But the hard part remains the same: who subscribes, and what context do they have? If TrainerDefeated is emitted without a trainer id and a map event identity, it is unusable. If CreatureCaught is emitted without knowing whether this is Safari, tutorial, link, or normal wild mode, it can trigger the wrong inventory or storage behavior. So event-driven design does not remove continuation context. It just changes how continuation context is delivered.

That is a useful bridge to link systems, which we will treat more carefully later. A link battle can have a battle result, but the overworld consequence is radically different. You do not set an NPC defeated flag. You do not award ordinary trainer prize money from a map script. You have synchronization and communication state to unwind. This is why the course objective names link systems alongside runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, and save. A complete creature-collector RPG is not a battle simulator with some menus attached. It is a coordinated application where each subsystem has its own durability and ownership rules.

Let’s take a small pause for self-check. If I give you three facts, can you place them in the right subsystem? Fact one: “The opponent’s second creature has fainted.” That is battle and creature party state during combat. Fact two: “This NPC should no longer challenge the player on sight.” That is overworld event state, probably a flag, committed after a trainer win. Fact three: “The player selected a potion during combat and the bag count decreased.” That crosses inventory and battle through an item-use path. The answer is not that these systems never talk. The answer is that their communication should happen through explicit handoffs, not random writes from wherever the programmer happened to be standing.

The save system adds another layer. When should the trainer defeated flag become save-relevant? After the world consequence is committed, not when the battle starts. If the game saved in the middle of a transition, which real games often prevent or tightly control, you would need carefully staged state. In a simplified architecture, you can avoid saving during battle and save only in overworld-safe states. But your mental model should still be precise: persistent progression belongs to the world state after validated outcomes. Temporary battle state belongs to battle memory and can be thrown away after cleanup. Party changes are interesting because they are both battle consequences and persistent creature data, so they need a controlled commit path.

The UI system is similar. The victory text, experience messages, fainting messages, and after-battle dialogue may look like one continuous presentation to the player. Architecturally they are not one thing. Some are battle UI messages driven by combat scripts. Some are field message boxes driven by overworld scripts. The player experiences continuity; the engine implements a baton pass. In prior lectures we discussed battle script command dispatch and damage effects, so you already know battle text can be driven by bytecode-like script sequences. Today, the important part is that after that battle-facing text finishes, control may leave battle UI entirely and return to a field message system.

This is also where transitions matter. The transition into battle is not just pretty animation. It hides loading, callback changes, palette state, sprite state, and mode changes. The transition out has the symmetrical job of making the world coherent again. The source focus on pokefirered/src/battle_setup.c chunk_494d132a59422c85 names battle transition and load/event responsibilities in the research note, and that is exactly the architectural role we care about. A transition module is allowed to know about both sides because its job is to cross the boundary. But a transition module should not become the owner of every consequence. It should move control safely.

For your workshop implementation, I would expect a clean division something like this. The encounter detector discovers that a battle should happen. The resolver builds a battle setup. The battle runner consumes the setup and eventually returns a result. The overworld result handler consumes the result plus the original context. The world handler commits flags, warps, messages, inventory consequences, or script continuation. The names can differ. The responsibilities should not.

And now let me point out one subtle bug in many student versions. They store the current trainer id in one global and the current wild species in another global, and then they decide result handling by checking which global is nonzero. That works for about four tests and then collapses. What if species id zero is valid in some table convention? What if a scripted battle has both a trainer-like presentation and a generated creature? What if a stale global was not cleared after the previous encounter? Better is an explicit active battle context with a type tag and a payload. In C, that might be a struct with a kind enum and a union-like payload. In a higher-level language, it might be a discriminated union or sealed class. The design goal is the same: make impossible states harder to represent.

Here is the check I would ask on an exam. A trainer battle starts, the player loses, and after blackout the trainer no longer challenges the player. Name two likely architectural mistakes. First, the defeated flag was set at battle start rather than after a WON result. Second, the result continuation treated any completed battle as a win, perhaps using “battle ended” rather than “battle won.” A third possible mistake is stale result state from a previous win. If your answer says “the battle damage formula is wrong,” you are in the wrong subsystem. The symptom is a world-state commitment bug.

Another exam-style question: A wild battle ends, and the game tries to print an NPC’s after-battle dialogue. What went wrong? Either the wild setup reused a trainer continuation token, or the result handler inferred the continuation from shared global trainer state that was not cleared, or the battle setup path failed to distinguish origin contexts. Again, the battle loop may have done everything right. The incorrect behavior appears after combat because the world consumed the result through the wrong lens.

Notice how this week ties back to the midterm skill: tracing from data records through globals, callbacks, tasks, scripts, structs, and source functions to visible behavior. For trainer battles, the data record is trainer configuration, the map and script provide authored context, the setup module crosses into battle, battle main produces result flow, cleanup selects continuation, and the overworld commits durable consequences. For wild battles, the data record is encounter table input, the map provides encounter conditions, the setup module generates a temporary opponent, battle main produces result flow, cleanup returns control, and the overworld usually resumes without authored progression. Same trace discipline, different data origin.

I am going to stop this segment at a point where the next move is implementation-facing. When we continue, we will turn the verbal model into a compact resolver interface, and then we will design the tests that prove the interface has the property we care about: wild and trainer encounters share battle setup and battle execution, but they do not share world consequences blindly.

So let’s now make that compact resolver interface precise. If I were drawing this on the board, I would put one box on the left called encounter source, one box in the center called battle setup, one box on the right called world continuation. The mistake I want you to avoid is drawing two separate center boxes, one for trainer battle and one for wild battle. That is the architectural smell. FireRed does have different inputs and different consequences, but the battle itself should not care whether the opponent came from a trainer table row or a grass encounter slot.

A clean resolver has three conceptual phases. First, collect encounter intent. Second, construct battle setup state. Third, consume battle result into world state. In FireRed terms, that middle crossing is represented by the battle setup layer, and the packet points us directly at pokefirered/src/battle_setup.c, chunk_494d132a59422c85, where the include surface already tells the story: it pulls from overworld, script, event data, field movement, field effects, battle, battle transition, trainer data, and related systems. That is not accidental. This module sits exactly at the boundary where field state becomes combat state.

Now, board cue: I am going to write a small pseudo-interface, not C syntax, just architecture. “ResolveEncounter(source, worldContext) returns BattleRequest.” Then lower down: “RunBattle(BattleRequest) returns BattleResult.” Then lower down again: “ApplyBattleResult(source, result, worldContext).” Notice the source is present in the first and third calls, but not conceptually in the second. Battle execution needs parties, battlers, rules, flags, terrain-like parameters, and callbacks. It does not need the authored reason why the fight started, except insofar as that reason has already been encoded into setup fields.

Concrete example. Suppose the player steps into grass on Route 2. The encounter source is wild. The encounter table inputs come from pokefirered/src/data/wild_encounters.json as named in the packet. The map context supplies where the player is, what tile behavior applies, whether encounter checks are currently allowed, and probably which encounter group applies. The resolver samples a species and level, creates a temporary opponent creature, sets battle type flags appropriate to a wild encounter, and starts the transition. After the battle, if the player wins, catches, flees, or blacks out, the world consequence is mostly local: resume field control, remove transient battle state, maybe update party HP, maybe route to a loss sequence, maybe persist captured creature state through party or storage systems. But there is no trainer object event to mark defeated.

Now compare that with a trainer on the same route. The encounter source is trainer. The source record comes from pokefirered/src/data/trainers.h as the high-signal trainer table named in the packet. The map and event script supply the authored context: this trainer saw the player, walked forward, displayed pre-battle text, and identified a trainer id. The setup resolver uses the trainer id to build the opponent party and battle parameters. But after the battle, the result is consumed differently. A win is not merely “resume overworld.” A win may set a defeated flag, unlock post-battle dialogue, update rematch state, trigger script continuation, and prevent that trainer from challenging the player again under the same conditions. Same battle execution, different world commit.

Check for understanding: if a wild Pidgey faints and a Youngster’s Rattata faints, did the damage system need two separate fainting algorithms? No. Did the battle result consumer need two different interpretations of victory? Yes. That distinction is the heart of today’s lecture.

This is where the battle result storage matters. The packet points us to pokefirered/include/battle.h, chunk_f5cd027bf13ab47d, specifically for BattleStruct and result-related state. Treat that as the cross-module contract. Battle main should produce a result in a place the continuation code can read. Then pokefirered/src/battle_main.c, chunk_1c5a1226d8d9afb7, is our representative result-flow area, and pokefirered/src/battle_main.c, chunk_46361bdbd92dae37, is the cleanup and continuation side. We are not re-opening the entire battle main callback today. You already traced chosen actions, turn order arrays, battle scripts, damage, and status in prior lectures. Today we care about the exit ramp: how does a resolved battle become a world decision?

And this is where advanced architecture gets less glamorous but more important. The bug you should anticipate in your lab is not “my damage formula is wrong.” The bug is “the battle ended correctly, but the wrong world consequence happened.” Maybe fleeing from a trainer battle is treated like fleeing from a wild battle. Maybe defeating a trainer resumes control but fails to set the event flag, so the trainer fights again. Maybe losing to a wild encounter skips the loss route because the result was collapsed into a generic “battle ended.” These are integration bugs at the boundary between systems.

So in your implementation-facing design, use an explicit result vocabulary. Do not just return true or false. A boolean cannot tell the overworld enough. You want something like player won, player lost, player fled, opponent fled, caught creature, draw or special termination if your clone supports it. Then you want encounter kind preserved separately: wild, trainer, scripted special, safari, link, tutorial, whatever your project needs. The pair of encounter kind and battle result determines the continuation.

Let me say that again because it is a good exam answer: battle result alone is insufficient, and encounter type alone is insufficient. The world consequence is a function of both.

If I put a table on the slide, the rows would be encounter kind and the columns would be result. Wild plus player won means resume field and retain party changes. Wild plus caught means add creature or send to storage, then resume. Wild plus player lost means route to the fainting or recovery flow. Trainer plus player won means mark trainer defeated and continue the trainer script. Trainer plus player lost means loss routing, money penalty if modeled, recovery flow, and no defeated flag. Trainer plus fled should usually be impossible or specially blocked by battle rules, not casually interpreted by overworld logic after the fact.

That last point is subtle. Some consequences should be prevented before result production, through battle rules. Others should be handled after result production, through world routing. A robust architecture does both. It makes illegal outcomes hard to produce, and it still makes result consumption explicit enough that debugging is possible if one appears.

Now, connect this back to the whole course. In week 1, we described FireRed as cooperating state machines. In week 2, we emphasized data tables becoming visible behavior. In the map weeks, we tracked map headers, layouts, object events, and scripts. In the creature weeks, we separated durable party data from transient battle participants. In the battle weeks, we studied callbacks, turn order, scripts, current move state, and damage effects. Today is the join back outward. A creature RPG is not a battle simulator with a walking screen attached. It is a runtime shell that repeatedly transfers authority among overworld, scripts, UI, inventory, party, battle, save, and sometimes link systems.

That is why the lab asks you to implement both a wild encounter resolver and a trainer battle resolver that create the same battle setup object and return a battle result consumed by overworld logic. I want the test to prove sameness in the center and difference at the edge. For example, in a unit test or small integration harness, you can assert that both resolvers produce a BattleRequest containing opponent party data, battle flags, background or transition information, and initial participant state. Then assert separately that applying a victory result to a trainer source writes a defeated flag, while applying a victory result to a wild source does not. That is the property. Not that the functions happen to share code, but that the architecture preserves the correct boundary.

One more check before we close. Suppose your battle engine returns “WIN.” The player has beaten a trainer. Where should the trainer’s defeated flag be set? Not inside the damage routine. Not inside turn order. Not inside the generic battle loop. The best answer is in the continuation or result application path that knows this was a trainer encounter. FireRed’s source organization supports that reading: pokefirered/src/battle_setup.c, chunk_494d132a59422c85, is visibly cross-boundary, while the battle main result and cleanup chunks in pokefirered/src/battle_main.c, chunk_1c5a1226d8d9afb7 and chunk_46361bdbd92dae37, represent the battle-side flow that must eventually hand control back outward.

So the closing synthesis for today is this: encounters enter battle from different data origins, but battle execution should be unified around a common setup contract. Outcomes leave battle through result state, but world consequences must remain source-aware. Trainer data, wild encounter data, scripts, maps, creature state, UI transitions, save flags, and battle callbacks are not separate trivia categories; they are cooperating pieces of one control-transfer pipeline. If you can trace that pipeline from pokefirered/src/data/trainers.h or pokefirered/src/data/wild_encounters.json into setup, through battle result flow, and back to overworld continuation, you are no longer just reading decompiled code. You are reading the architecture of a complete creature-collector RPG.

## Source References
- pokefirered/src/battle_setup.c
- pokefirered/src/daycare.c
- pokefirered/src/pokemon_summary_screen.c
- pokefirered/src/learn_move.c
- pokefirered/src/battle_util.c
