# Damage and Status Through Shared State

Lecture ID: lecture_w14_d02
Week 14, Day 2

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

## Transcript
All right, let’s pick up exactly where Monday left us. Last time we treated battle scripts as combat bytecode: not bytecode in the academic virtual-machine sense where we are trying to be language-neutral, but bytecode in the practical engine sense, meaning compact commands that advance battle behavior by mutating shared state, jumping through script pointers, and asking other subsystems to display, animate, or wait. Today we are going to stay inside that same machinery, but we are going to put more pressure on one question: when damage and status happen, where does the truth live?

That question sounds simple until you trace a real move. The move has a data-table identity. It has a selected user. It has a selected target. It may miss. It may be blocked. It may deal zero damage. It may deal calculated damage. It may set burn, poison, paralysis, confusion, flinch, a side condition, a volatile timer, or a future delayed effect. It may display text before or after animation. It may cause a faint, which then itself causes more script flow. So if you imagine “move execution” as a nice local function where all facts are passed as arguments and returned as a clean result, FireRed will fight you. FireRed’s architecture is much more stateful than that. The battle script command functions coordinate through shared globals, current battler indexes, current move variables, damage variables, status structures, and script pointers.

Let me put that on the board. I’m writing: selected action, current move, current battlers, damage accumulator, status fields, script cursor, controller output. That is our working chain for today. Not a class diagram. Not a pure pipeline. A shared-state chain.

Now, this is where your earlier work matters. We are not re-opening the whole battle startup path from overworld state. You already traced wild and trainer setup. You already worked through battler positions, chosen actions, turn-order arrays, and the battle main callback. So today we assume we have arrived at the moment where an action has been selected and the battle engine is executing the move script. That assumption lets us be more precise. The question is not “how does a battle begin?” The question is “once a move is being executed, how do damage and status propagate through a stateful script interpreter without losing synchronization with messages, animation, fainting, and end-of-turn cleanup?”

In the FireRed source packet for this week, the center of gravity is pokefirered/src/battle_script_commands.c. The chunks named in your packet, especially chunk_9779e386ddb42cb0 for battle script commands, chunk_c405e26d69cb40ac for script command effects, and chunk_98989865d85f275c for battle script flow, are there because this is the layer where command dispatch and effect execution meet. Notice the architectural significance of that. The battle scripts do not merely narrate what already happened. They are part of causing it to happen. A command checks accuracy. Another command performs damage calculation or applies stored damage. Another command sets a status flag. Another queues a message. Another checks fainting. The visible move is assembled from this sequence.

Let’s make that concrete. Suppose a damaging move with a chance to burn hits its target. The naive implementation many of you would write in a first prototype might look like this: call calculateDamage, subtract HP, roll secondary effect, apply burn, print message, done. That is fine for a toy. It is not enough for this engine. In FireRed, the command sequence has to coordinate with accuracy logic, type and ability checks, protections, battle animation, text buffers, HP bar updates, faint checks, and later turn effects. So the damage is not just a returned integer. It is staged in shared battle state so that multiple commands and subsystems can see it at the right time.

This is why the week’s source focus also includes pokefirered/src/battle_util.c, chunk_9335e5727aa0657d. The excerpt from that chunk is mostly include structure, but that include structure is a useful architectural clue. battle_util.c pulls in battle.h, battle_anim.h, battle_scripts.h, battle_message.h, battle_controllers.h, constants for battle, moves, items, weather, abilities, Pokemon, hold effects, and battle move effects. That is not accidental clutter. It tells us that “utility” in the battle engine is not a detached math library. It sits at the crossing point of move rules, battle state, messaging, animation, controller communication, and constants. When we say damage and status travel through shared state, we mean shared across these exact boundaries.

Check for understanding before we go further: if a move command computes damage, why might the engine store that damage in a global or shared battle variable instead of immediately hiding it inside one local function call? Take ten seconds. The key answer is not “because globals are easier,” though on the Game Boy Advance that is sometimes part of the practical story. The better answer is that later commands need the result. The HP animation needs it. The faint check needs it. The text path may need it. The substitute or endure logic may modify it. The script cursor may branch based on it. Shared state lets a compact script command sequence behave like one coordinated move execution.

Now, I want to contrast this with Pokemon Showdown, but very carefully. Showdown is not our replacement architecture. FireRed remains the primary source for this course. Showdown is useful because it solves a related battle-modeling problem in a very different language and runtime. In Showdown, much battle behavior is represented through data objects with hooks. In pokemon-showdown/data/moves.ts, chunk_d4dc7d8b20aa96e6, the excerpt gives us moves with fields such as accuracy, basePower, category, priority, flags, target, and then hooks like onTry, onTryHit, onHit, and damageCallback. For example, the excerpt includes a move where onTry checks whether the user has enough HP, onTryHit manipulates boosts, onHit applies direct damage, and another move uses damageCallback to return damage based on current HP. That is a very explicit hook model.

FireRed does not arrange its move execution primarily as TypeScript objects with callbacks attached to each move definition. It uses battle script commands, constants, current move data, and shared battle state. But the comparison helps us name a design axis. Showdown localizes many special cases as hooks attached to data definitions or modded scripts. FireRed localizes many special cases through command handlers, effect IDs, battle utility helpers, and script flow. Both architectures need insertion points. Both need to answer: where can a move alter normal damage, status, target selection, or messages? Showdown’s answer often looks like “run this hook on this data object.” FireRed’s answer often looks like “advance the battle script and mutate the current shared state.”

Let me draw a two-column comparison on the board. Left column: FireRed. Script command dispatch, current move variables, battler globals, status structs, controller messages. Right column: Showdown. Move data object, event hooks, Dex lookup, modded data, simulator turn event. Now I’m underlining “insertion points” in both columns. That is the shared architectural problem. The surface form differs, but both engines need controlled places where rule exceptions can attach.

This also connects to the lab this week. In the workshop, you are building a minimal battle script interpreter with commands for accuracy, damage, status, message, faint check, and end-turn cleanup. The point of that lab is not to imitate all of FireRed. It is to make you feel the dependency order. If your damage command immediately prints everything and ends the move, your status command becomes awkward. If your status command directly subtracts HP for poison without participating in end-turn cleanup, your turn model becomes awkward. If your message command cannot see the status result, your user-facing output becomes awkward. The lab is designed to show that the architecture emerges from sequencing and shared state, not from any one elegant formula.

Now let’s talk about status, because status is where shared state becomes more interesting than raw damage. Damage is at least numerically obvious. HP goes down, maybe to zero. Status is distributed. A major status condition is stored differently from a volatile condition. A side condition is not the same as a battler condition. Protection is not the same as paralysis. Disable, encore, protect, confusion, flinch, substitute, leech seed, reflect, light screen, weather, and future move effects all have different scopes and durations. This is why the week’s packet points us to pokefirered/include/battle.h, chunk_a08e84ff1272f826, specifically for disable, protect, and special status structures. That header is not just declarations. For us, it is a map of how the battle engine divides temporary truth.

Be precise with that phrase: temporary truth. A burned Pokemon’s status is not temporary in the same way a one-turn protect flag is temporary. A side screen timer is not temporary in the same way confusion is temporary. FireRed has to put each kind of truth somewhere durable enough for the commands that need it and scoped enough that it does not leak into the wrong entity. That is why status design is not merely “set an enum.” It is a memory layout and ownership problem.

Here is a concrete example. Imagine a target uses Protect, and then the attacker uses a damaging move that would also poison. Architecturally, what should happen? The move should not damage the target. The poison should not apply if the hit is blocked. A message should communicate protection. The move may still count as having been attempted. Other effects may still care that a move was used. So the engine needs a protection flag available during hit resolution. It needs damage to remain zero or be suppressed. It needs the secondary status path not to fire as if damage landed. It needs the script flow to branch into the correct message and animation behavior.

If you were to place all of that inside the move’s static data row, the data would become too operational. If you were to place all of it inside the HP subtraction function, the status and message logic would become tangled. FireRed instead uses shared battle state and command sequencing: the script can check, branch, set, clear, and display through commands. Again, source focus matters here: battle_script_commands.c chunk_98989865d85f275c is included for battle script flow because the flow itself is a rule mechanism, not just a control convenience.

Now, a subtle point for advanced learners: shared state is not automatically sloppy architecture. It becomes sloppy when ownership, lifetime, and command order are unclear. FireRed’s battle engine is stateful, yes, but the state is organized around the constraints of the platform and the domain. The GBA battle has to step through frames, wait for controllers, animate HP bars, print messages, and resume script execution. A purely functional “return one giant battle result” model would not naturally fit that visible pacing. The script interpreter can pause between commands, allow animation or text to complete, and then continue with the same shared state. That is a reasonable architecture for this runtime.

Showdown, by contrast, usually does not need to pause for a GBA text box or sprite animation in the same way. It needs deterministic simulation, log generation, competitive rule fidelity, and testability across generations. That leads to a different emphasis. The research packet points us to pokemon-showdown/sim as a recurring module around Battle, Dex, and Move, and to pokemon-showdown/data and data/mods as recurring modules around moves, protect, learnsets, and generation-specific data. When Showdown uses Dex and mods, it can load a rule set, override move behavior, and run simulator turns in a testable way. That is exactly the secondary comparison we want: Showdown is excellent for thinking about data hooks, Dex/mod layering, simulator turns, and regression testing. But FireRed remains the source of truth for the architecture of this course because we are studying a shipped GBA creature RPG and its callback, task, script, and shared-memory style.

Let me pause and ask: what is the mistake I do not want you to make with the Showdown comparison? Right, I do not want you to say, “Showdown has hooks, therefore FireRed should have hooks.” That is backwards. The useful question is, “What architectural role do hooks play in Showdown, and what plays that role in FireRed?” In FireRed, battle script commands, move effect handlers, shared globals, and utility helpers often occupy that role.

Let’s work through a move execution at the level of state handoff. I’ll speak it as if we are watching the interpreter. First, the turn-order machinery, which we covered earlier, has selected the acting battler. The current move is known. The script pointer is set to the relevant move script or effect script. The first commands establish whether the move can proceed: enough PP, not prevented by status, target valid, accuracy or special accuracy path, protection checks, immunity checks. Some of those checks read from gBattleMons-like battler state. Some read from special status or protect structures. Some consult utility routines and constants. Then, if the move is allowed to hit, the damage calculation path writes a damage value into shared state. Later commands apply that value to HP, animate the HP bar, and trigger faint checks. If an additional effect applies, a later command may set status bits or timers. Finally, the engine transitions toward after-move effects and eventually end-turn cleanup.

Notice how many times I said “later command.” That is the core of today’s lecture. The architecture does not require every command to know the whole move. Each command needs to know the shared state contract. The accuracy command leaves the world in a state that the damage command understands. The damage command leaves the world in a state that HP update and faint check understand. The status command leaves the world in a state that message and end-turn systems understand.

In your lab interpreter, you should model this explicitly. You do not need fifty FireRed commands. You need enough commands to make ordering meaningful. For example, you can have a context object with attacker, defender, move, damage, hitResult, pendingStatus, messages, fainted, and turnFlags. Then implement command functions: accuracy reads attacker, defender, move, and writes hitResult. damage reads hitResult and move, writes damage. apply damage reads damage and mutates defender HP. status reads hitResult and writes defender status or pendingStatus. message appends text based on previous results. faint check reads HP and writes fainted. cleanup decrements timers. That is not FireRed’s exact memory layout, but it teaches the FireRed lesson: state has to survive across command boundaries.

Now, let’s connect this to the overall course plan. In week 1 and week 2 we kept returning to the trace: data table to runtime globals to callback, task, or script to visible behavior. This week is that trace under maximum pressure. A battle move is one of the densest examples in the whole game because the data table alone is not enough. A move row can tell us power, type, accuracy, and effect identifiers, but player-visible behavior emerges only when that data enters the battle runtime. The runtime chooses a battler. The script interpreter advances. The status structures are read and written. Controllers and animations display the result. Save data may later remember permanent outcomes such as party HP or major status after battle. So week 14 is not an isolated battle-engine curiosity. It is the same course thesis, now applied to the hardest real-time rules subsystem.

This is also why the homework and quiz expectations rise this week. Earlier assessments asked you to decompose the RPG and trace broad gameplay through globals, callbacks, tasks, scripts, structs, and functions. Homework week 13 asked you to identify files, structs, functions, constants, data tables, and state variables for major systems. Now we are no longer satisfied with “this file handles battle commands.” For week 14, you should be able to say which state is being staged, which command consumes it, which structure owns its duration, and where a comparison engine such as Showdown would place the analogous rule insertion point.

Let’s bring in one more Showdown example because it sharpens the status discussion. In pokemon-showdown/data/mods/gen9ssb/moves.ts, chunk_f562e1983e9fdeff, the excerpt describes a custom move with onTryMove, onPrepareHit, volatileStatus, onTry checks against existing volatile states such as smackdown or ingrain, and a condition object. The specifics of that modded move are not our FireRed target, but the architecture is revealing. A volatile status in Showdown is attached with named condition behavior and event hooks. In FireRed, analogous temporary conditions are represented through battle status flags, side timers, disable or protect structures, special status structures, and script commands that know how to interpret them. Again: same problem, different placement.

A useful mental model is to ask three questions for every damage or status rule. First, what is the scope? Is it on the battler, the side, the field, the move, or the whole battle? Second, what is the lifetime? Does it last one command, one hit, one turn, several turns, until switch, until faint, or beyond battle? Third, what consumes it? Is it read by accuracy, target selection, damage calculation, end-turn cleanup, message generation, AI, animation, or save update? If you can answer those three questions, you can usually find the correct architectural home.

Let me test that. Suppose we are modeling flinch. What is the scope? Battler. Lifetime? Usually until that battler attempts to move, so very short, turn-local. What consumes it? The action-prevention logic before move execution. It also produces a message. It should not be treated like poison because poison consumes at end-turn and persists beyond the immediate action. Now suppose we are modeling Reflect. Scope? Side. Lifetime? Several turns. Consumed by physical damage calculation. It also needs a message when it starts and when it ends. That belongs in a side-status or side-timer area, not in the individual move execution local variables. Suppose we model burn. Scope? Battler or Pokemon status. Lifetime? Potentially persists beyond the current turn and may remain after battle depending on game rules. Consumed by damage modification for physical moves and end-turn HP loss. That makes it much more durable than flinch.

That classification exercise is exactly what I expect from advanced students at this point. We are not memorizing one file. We are reading the file as evidence of an architectural strategy.

Now I want to return to battle_script_commands.c and the command-dispatch idea from Monday. In a command interpreter, the dispatch table or switch-like mechanism is only half the story. The other half is the implicit contract between commands. A command advances the script pointer. It may set a wait state. It may jump to another script. It may store a battler index. It may mark an effect as failed. It may write damage. It may set a status bit. If that command’s write is wrong, a later command may still be perfectly implemented and still produce wrong behavior because it was fed the wrong shared truth.

That is why regression testing matters, and here Showdown gives us a useful secondary lens. A simulator designed around deterministic turns and data hooks can run large battle test suites. The research packet explicitly names pokemon-showdown/test as a recurring high-signal area for regression testing, and pokemon-showdown/sim for battle, Dex, and move behavior. FireRed’s original architecture is not organized around modern unit tests in the same way, but when we build course labs, we can borrow the testing idea without replacing the architecture. For your minimal interpreter, you should write tiny regression cases: a miss should not deal damage; Protect should block damage and status; poison should tick during cleanup; a fainted defender should be detected after HP reaches zero; a side timer should decrement at end-turn. These are not merely correctness checks. They protect the command contract.

Let me say that again in a form you can use while coding: test the boundary between commands, not only the final transcript of the move. If accuracy fails, assert hitResult is false and damage remains zero. If damage is calculated, assert HP is not changed until the apply-damage command runs, assuming that is your chosen design. If poison is applied, assert it is stored in the status location that cleanup reads. That is how you make shared state disciplined rather than accidental.

We will stop this first segment at the point where the move has successfully entered the damage-and-status pipeline. When we continue, we will go deeper into the exact shape of damage staging, secondary effects, and end-turn cleanup, and we will keep comparing FireRed’s command-and-global style with Showdown’s hook-and-Dex style only where that comparison clarifies the architecture. The important takeaway for the moment is this: in FireRed, damage and status are not isolated return values. They are shared battle facts, staged across script commands, scoped by battle structures, and consumed over time by animation, messaging, faint logic, and cleanup.

Good, so let’s resume exactly where we left off. The move has made it into the damage-and-status pipeline. At this point, I want you to stop thinking of “damage” as a single arithmetic answer and start thinking of it as a staged battle fact. That distinction matters. In a small toy RPG, you might write a function called calculateDamage, return an integer, subtract it from HP, and move on. FireRed is doing something more layered, because the same move result has to feed calculation, animation, text, substitute behavior, faint checks, held item reactions, ability checks, status timers, and end-of-turn cleanup.

So, on the board, I’m drawing three boxes. The first box is “compute.” The second box is “stage.” The third box is “consume.” Compute means the engine decides what should happen. Stage means the result is written into shared battle state, not necessarily applied to the party data yet. Consume means later commands, controllers, messages, and cleanup routines read that shared state and turn it into visible behavior.

This is why the source focus for today sits across both battle script commands and battle utilities. The script command layer, especially pokefirered/src/battle_script_commands.c chunk_9779e386ddb42cb0, gives us the command-dispatch view: the bytecode-like battle script advances one operation at a time. The effect-oriented parts, like pokefirered/src/battle_script_commands.c chunk_c405e26d69cb40ac, are where the move’s intended effect becomes mutation of shared state. And the utility layer, pokefirered/src/battle_util.c chunk_9335e5727aa0657d, shows the dependency surface: battle utility code includes battle data, battle animation, battle scripts, battle messages, controllers, constants for moves, abilities, hold effects, weather, items, and battle move effects. That include list is not decorative. It tells us damage and status are architectural crossroads.

Now, notice what I did not say. I did not say every command should know everything. That would be chaos. The discipline comes from convention: this command writes the staged damage, this command reads it, this command applies it, this command checks fainting, this command emits a message, this command advances the script pointer. Shared state is not automatically bad. Undisciplined shared state is bad.

Let’s take a concrete example. Suppose the active battler uses a damaging move that can also poison. In the interpreter you are building for workshop, you might have commands named accuracy, damage, status, message, faint check, and end-turn cleanup. In FireRed’s style, the damage command should not immediately do every consequence. It should calculate or retrieve the move’s base data, apply battle modifiers, stage the final damage amount, and leave the script in a state where the next commands can decide what to display and when to subtract HP. Then the status effect command can stage or apply poison in the target’s permanent status field, depending on whether your simplified interpreter separates “pending status” from “applied status.” Then the message command reads the outcome and prints “It was poisoned” or “It had no effect,” depending on the state that was actually reached.

Here is the important design test. If I pause the interpreter right after damage calculation but before apply-damage, what should be true? HP should still be unchanged, if your model has a distinct apply step. The staged damage should be available. The current move identity should still be available. The target should still be available. Any temporary flags needed by later commands should be valid. If I pause after apply-damage, HP should have changed, and faint logic should now have meaningful input. If I pause after status application, the permanent or volatile status location that end-turn cleanup reads should contain the new condition.

Check your understanding here. If the poison message command prints successfully, but end-turn cleanup does not damage the poisoned creature, where is the bug likely to be? It is probably not in the text command. It is probably not in the animation command. It is probably in the contract between the status application step and the cleanup step. The message may have been driven by a transient “effect succeeded” flag, while cleanup reads the real persistent status field. That is exactly the kind of mismatch shared state makes possible, and it is exactly why we want to reason about the state locations explicitly.

Now let’s compare this carefully with Pokemon Showdown, but only as a secondary architecture comparison. Showdown’s battle engine is organized around data hooks and simulator events in a way that feels very different from FireRed’s battle scripts. In pokemon-showdown/data/moves.ts chunk_d4dc7d8b20aa96e6, for example, moves can carry properties such as accuracy, basePower, category, target, and hooks such as onTry, onTryHit, onHit, or damageCallback. The excerpt there includes moves where the move object itself contains executable behavior, such as checking whether a source has enough HP or computing special damage. So in Showdown, a move can be more like a structured object with event hooks attached.

FireRed is not primarily doing that. FireRed’s move behavior is distributed across tables, constants, command dispatch, shared globals, and script commands. So the comparison is not “which one is better.” The comparison is: where is behavior attached? In Showdown, behavior is often attached to data entries and simulator hooks. In FireRed, behavior is often attached to battle script commands that interpret current battle state. Both architectures need shared state. They just expose it differently.

Let me say that again because it is a recurring theme in week 14. Showdown makes a lot of battle data feel local to the move definition, while FireRed makes a lot of battle behavior feel local to the script command stream. But the actual battle still has to coordinate the same facts: source, target, move, damage amount, status, side conditions, turn state, and cleanup timing.

On the board now, I’ll write “current move data is a lens.” That means current move data is not the whole battle. It is the lens through which the next command interprets shared battle state. If the current move is Thunder Wave, the status command should interpret success, immunity, type, ability, and existing status one way. If the current move is Flamethrower, the damage command and secondary-effect command have a different path. If the current move is a fixed-damage move, the damage step may bypass ordinary base power logic. If the current move has a self-effect, later commands may point back to the source instead of the target.

And this is where regression tests become architectural tools, not just correctness tools. One reason Showdown is useful for comparison in this course is that its simulator culture makes regression testing very visible. Its old simulator documentation and sim module emphasize battle simulation as reproducible turn processing, and the research packet points us toward pokemon-showdown/sim and pokemon-showdown/test as important modules for deterministic battle behavior. For our FireRed-focused work, the lesson is not that we should rewrite FireRed into Showdown. The lesson is that once state is shared across commands, tests should assert the intermediate contracts.

So in your week 14 homework, when you work on a minimal battle script interpreter, I do not want only final HP assertions. Final HP matters, of course, but final HP alone hides too much. I want tests like this, stated in prose for now: after accuracy succeeds, the script pointer advances to damage; after damage calculation, staged damage equals the expected value and target HP is unchanged; after apply-damage, target HP is reduced; after status application, the status field read by end-turn cleanup is set; after cleanup, poison damage is applied once, not zero times and not twice.

That “once” is worth emphasizing. End-turn cleanup is where many novice designs become fragile. They treat status as a message-time effect instead of a turn-state effect. In FireRed’s architecture, status and side timers are not just flavor text. They are long-lived facts. They may survive across commands, turns, switches, or even faint checks depending on the kind of effect. The structures in pokefirered/include/battle.h chunk_a08e84ff1272f826 are relevant here because disable, protect, and special-status style data are not all the same kind of state. Some are persistent creature status, some are volatile battler state, some are side state, and some are turn-scoped flags. The advanced move is recognizing which lifetime each field is supposed to have.

Let’s do a quick example with Protect-style state. I am not going to re-teach battler positions or turn order; we already used those as foundations in the previous lectures. Just assume the active battler uses Protect before the opponent attacks. Protect cannot simply be “a message that says protected.” It has to write a fact somewhere that the later incoming attack command can read. It also has to interact with repeated-use failure logic, and then cleanup has to decide what persists into the next turn. If you put that fact in a local variable inside the Protect command, the later damage command cannot see it. If you put it in permanent status, it lasts too long. If you put it in side condition state, you may accidentally protect the whole side when you meant one battler. So the state location is the design.

Showdown gives us a useful mirror here. In pokemon-showdown/data/text/moves.ts chunk_1620277751dc01f4, the move text for redirection and screen-breaking effects makes timing explicit: effects can last until the end of the turn, redirect attacks, or end side conditions before damage is calculated. That text file is not the simulator itself, but it reflects the same architectural pressure. A move effect is often defined by when it is visible to other parts of the battle. Before damage? After hit? Until end of turn? While active? On switch? Those timing phrases are really state lifetime phrases.

Now, let’s connect that back to FireRed’s battle scripts. A battle script command stream is powerful because it lets designers and engine code sequence these lifetimes. Accuracy before damage. Damage before message. Hit effect before faint check, or faint check before certain follow-up effects, depending on the script. The script pointer is not just a program counter; it is the engine’s memory of which consequences have become valid. That is why pokefirered/src/battle_script_commands.c chunk_98989865d85f275c, the battle script flow area in our source focus, matters for more than control flow. Flow determines when shared state is trustworthy.

Pause for a check. If a move misses, should the status command still run? In a typical damaging move with a secondary status chance, no. The miss should route the script around the hit effect. But if the move is a status move with special failure text, the command sequence may still need to run message logic. So the answer is not “always skip everything.” The answer is “the command flow must encode which later state mutations depend on a successful hit.” That is a more precise architectural answer.

Now imagine implementing this in the lab interpreter. You may be tempted to write one function called executeMove that does all of this in one call. For a tiny project, that might pass the simplest tests. But it will not teach you the FireRed architecture. Instead, make the interpreter visibly staged. Let your script commands advance through shared state. Let the damage command set a damage slot. Let the apply command consume the damage slot. Let the status command write the same status field that cleanup reads. Let the message command read flags instead of recomputing the battle. This is how you learn why FireRed can coordinate animation, controller output, text, and battle logic on hardware that is not pretending every action is an isolated pure function.

And yes, this is less immediately elegant than a purely functional model. But remember our course thesis from the early weeks: FireRed is a stack of cooperating state machines plus data tables. In week 1, that meant the runtime shell, callbacks, and tasks. In week 2, that meant data records flowing into visible behavior. In the overworld weeks, that meant map headers, object events, and scripts. Now in week 14, the same thesis is still operating. The battle engine is not an exception. It is the densest version of the same pattern: data table, shared globals, command interpreter, visible behavior.

Let’s take one deeper damage example. Suppose a move has a custom damage rule. In FireRed, that rule may be represented by a move effect constant and implemented by script command logic or battle utility logic. In Showdown, a comparable special case might be represented directly as a damage callback. The research packet gives us a clear example in pokemon-showdown/data/mods/gen2stadium2/scripts.ts chunk_3480e574205d5d0f, where the simulator checks immunities, handles OHKO moves, consults damageCallback, handles level-based damage, fixed damage, and then proceeds into ordinary damage logic. That is a clean comparison point. Showdown’s function says, in effect, “before normal damage, ask whether the move object has a special damage pathway.”

In FireRed terms, the same concept is spread across command sequence and helper functions. The current move data and move effect determine which path the battle script takes, and the shared damage value becomes the handoff between calculation and application. The architectural question is the same in both systems: where do exceptional damage rules attach, and how do later phases know the answer?

Now, if you are advanced, here is the subtle part. Shared damage state should be treated as invalid outside its window. If gBattleMoveDamage, or your interpreter’s equivalent stagedDamage, still contains last move’s value, that does not mean the current move should use it. You need a validity convention. FireRed’s engine relies heavily on command ordering and known state conventions. In your lab, you can make that explicit by clearing stagedDamage at the start of move execution, or by storing a flag that says damageIsStaged. The production engine and the classroom interpreter do not need to have identical mechanics, but they need to share the same architectural discipline.

Let me ask a diagnostic question. You run a test where Tackle deals 12 damage. Then Growl runs and somehow subtracts 12 HP. What happened? Almost certainly, the non-damaging move reached an apply-damage command or read stale staged damage. The bug is not that your arithmetic is wrong. The bug is that your command flow and staged-state validity rules are wrong. That is the kind of bug week 14 is training you to see.

Now let’s widen to status. Permanent status, volatile status, and side timers are all “status” in casual speech, but architecturally they are different. Poison attaches to the creature in a long-lived way. Confusion, depending on generation and implementation, is usually a volatile battler condition. Reflect attaches to a side. Protect attaches to a battler and a turn window, with extra repeated-use state. Disable attaches to a battler and move availability. These differences matter because they decide who reads the state later.

If end-turn cleanup reads poison from the party creature’s status, then poison must be written there. If move selection reads disable from a battler disable structure, then Disable must update that structure. If incoming damage reads Reflect from side status, then Reflect must not be stored as a personal volatile. This is why pokefirered/include/battle.h chunk_a08e84ff1272f826 is so important for today’s topic. The header is not just declarations. It is a map of state lifetimes.

And here is where we connect to animation and message hooks. A common beginner mistake is to let the animation be the truth. They say, “I played the poison animation, so the target is poisoned.” No. The animation is evidence for the player, not the authoritative state. The authoritative state is what later logic reads. FireRed has to coordinate with battle controllers and battle animation code, and pokefirered/src/battle_util.c chunk_9335e5727aa0657d shows that the utility layer knows about battle_anim, battle_scripts, battle_message, and battle_controllers. That is the architecture telling us something: visible output is downstream of battle facts, but it is not the same thing as battle facts.

So when you design your minimal interpreter, you can separate “emit animation event” from “apply status.” The message and animation commands should be allowed to read the result of the logic, but they should not be the only place the logic exists. If a headless regression test runs without animation, the battle should still be correct. That is another place Showdown is a useful secondary comparison. Its simulator can test turns without a Game Boy Advance presentation layer. FireRed’s production architecture has presentation deeply integrated through controllers and scripts, but we can still learn to test the logical contracts underneath.

Let’s recap the chain so far, because this is the part you will need for the quiz. A move enters the pipeline with current move data, source, target, and selected action already established from prior phases. The battle script command stream determines what happens next. Damage is computed and staged before it is consumed. Status writes must land in the state location that future logic reads. Side timers and volatile effects are scoped by lifetime, not by how similar their text sounds. Animation and messages display facts; they should not be mistaken for the facts themselves. And Showdown’s hook-based move data is useful as a contrast because it makes some of these timing points explicit through hooks like onTry, onTryHit, onHit, and damageCallback, as seen in pokemon-showdown/data/moves.ts chunk_d4dc7d8b20aa96e6, but FireRed remains our main source for the command-and-global architecture.

Now, before we move into secondary effects in more detail, I want to leave you with one design habit. Every time you see a battle command, ask three questions. What shared state does it require before it runs? What shared state does it write after it runs? What later command or cleanup phase depends on that write? If you can answer those three questions, the battle engine becomes legible. If you cannot answer them, you are probably treating the script as a list of effects rather than as a staged state machine.

All right, next we are going to take that habit and apply it to secondary effects specifically: flinch chances, stat drops, status chances, and move effects that alter the target’s side before or after damage. That is where the ordering becomes less forgiving, because two moves can have the same text shape, “does damage and then something else,” while requiring very different state timing internally.

So, let us slow down on that phrase, “secondary effect,” because it is a deceptively soft phrase. It sounds like flavor. It sounds like the damage is the real thing and the secondary effect is a little afterthought. Architecturally, that is not how you should think about it. A secondary effect is another write into the same battle state graph. Sometimes it is less visible than HP loss, but it may be more important for later control flow.

If a move causes damage and may flinch, the flinch is not just text. It is a write to the target’s per-battler status-like bookkeeping. If a move causes damage and may lower Defense, that stat stage change is a write to the target’s battle stat state, and it changes the damage calculation of a later move. If a move removes Reflect or Light Screen from the target’s side, that is a side-condition write that can affect every battler on that side, not just the immediate target. So the ordering question is not, “When do we print the cute message?” The ordering question is, “At which point in the combat transaction does this write become true?”

Put that on the board. I am writing three columns: damage, hit effect, later dependency. Under damage, I write “uses current move, attacker, target, modifiers.” Under hit effect, I write “writes status, stat stage, volatile, side timer, or flag.” Under later dependency, I write “message, faint check, next action, end-turn cleanup, future damage.” That is the mental model I want you using in the lab interpreter. Not “move calls function.” Not “status happens.” It is a pipeline of guarded writes.

Now, in FireRed, our primary architectural source this week remains the battle script command layer. The course packet points us specifically at `pokefirered/src/battle_script_commands.c`, including `chunk_9779e386ddb42cb0` for battle script commands, `chunk_c405e26d69cb40ac` for script command effects, and `chunk_98989865d85f275c` for battle script flow. The important point is that the command dispatcher is not merely dispatching isolated C functions. It is coordinating shared battle globals, current move data, battler identities, damage buffers, status fields, and script pointers. That is why we spent time earlier on current move state. The current move is a context object, even if in C it is distributed across globals and fields rather than packaged as a neat object.

Let us use a concrete example. Suppose the active battler uses a damaging move that has a chance to paralyze. At a player-text level, the move says something like: hit the target, deal damage, maybe paralyze. But the engine needs a much stricter sequence. First, is the move allowed to proceed? Accuracy, immunity, protection, substitute-like conditions, and other gates may prevent the hit effect from being eligible. Second, if it proceeds, how much damage is assigned into the shared damage state? Third, after damage is known, what happens if the target faints? Fourth, if the target does not faint, is the status effect allowed? Does the target already have a major status? Is the target immune? Does an ability or side condition block it? Then, finally, which message and animation are queued?

Notice the subtlety there. If the target faints from the damage, many secondary effects should not apply in the same way they would to a surviving target. If the move misses, the status chance usually does not occur. If the move is blocked by Protect, the target should not be paralyzed by the move’s secondary effect. This means the secondary effect is downstream of hit validation, not merely downstream of move selection.

Check for understanding: if I gave you a command named “try secondary effect,” what state would you expect it to read? It should read the attacker and target identities, current move metadata, hit result or move-result flags, the target’s current status fields, and probably some random source. What should it write? It might write a status bit, a stat stage change, a volatile flag, a side timer, and one or more message or animation requests. And what later code depends on that? End-turn status damage, speed recalculation, “can this battler move” checks, faint checks, and future damage calculations.

That is the advanced move here. We are no longer treating damage as an isolated formula. We are treating damage as one phase in a shared-state protocol.

This is also where `pokefirered/include/battle.h`, `chunk_a08e84ff1272f826`, matters as more than a header. Earlier in the course, we used `battle.h` as a subsystem map, and I am not going to re-teach that. But now you should see the header’s disable, protect, and special-status structures as architectural evidence. They show that “status” is not one field. There are durable party statuses, temporary per-battler disables, protection flags, move-specific volatile conditions, side timers, and special cases that exist because combat rules are full of exceptions. The engine does not solve that by pretending all effects are the same. It solves it by giving different categories of battle memory different lifetimes.

Now compare that, carefully, with Pokemon Showdown. We are not replacing FireRed with Showdown. FireRed is our main architecture source because this course is about how the GBA RPG organizes runtime state through callbacks, tasks, scripts, globals, tables, and memory-constrained systems. But Showdown is a useful secondary comparison because it exposes many of these same rule boundaries as explicit data hooks. In `pokemon-showdown/data/moves.ts`, `chunk_d4dc7d8b20aa96e6`, the packet shows move definitions with fields like `accuracy`, `basePower`, `category`, and hook-shaped behavior such as `onTry`, `onTryHit`, `onHit`, and `damageCallback`. That is a very different implementation style, but the timing questions are the same.

For example, the excerpt for Final Gambit in `pokemon-showdown/data/moves.ts`, `chunk_d4dc7d8b20aa96e6`, shows a `damageCallback` that derives damage from the user’s HP and then causes the user to faint. The architectural lesson is not “FireRed should do this in TypeScript.” Absolutely not. The lesson is that battle engines need named timing slots. “Before trying the move,” “when trying to hit,” “when calculating damage,” “on hit,” “after damage,” “at end of turn.” Showdown names many of those slots with hooks. FireRed often expresses comparable timing through battle script command order plus shared fields and helper functions.

So when you build the workshop interpreter, do not hard-code “damage then status” as one giant function. Even your minimal interpreter should have phases. It can be very small: command reads current move, command checks accuracy, command assigns damage, command applies damage, command tries status, command prints message, command checks faint, command runs end-turn cleanup. But keep those as commands or command-like phases. The educational value is in preserving the timing boundary.

Now let me put another example on the board: a move that breaks side protection before damage is calculated. The research packet gives us a Showdown text source for Raging Bull in `pokemon-showdown/data/text/moves.ts`, `chunk_1620277751dc01f4`, where the description says that if the attack does not miss, the effects of Reflect, Light Screen, and Aurora Veil end for the target’s side before damage is calculated. Architecturally, that is a beautiful ordering case. It is not “damage and then remove screens.” It is “after hit confirmation, before damage calculation, remove side defenses, then calculate damage under the new side state.”

If you implemented that as an ordinary after-damage secondary effect, you would get the player-facing text mostly right but the battle result wrong. The same move would do too little damage because the screen would still be active during calculation. That is exactly why we are studying shared state rather than just move text. The move description looks like English. The engine needs a transaction schedule.

Now pause with me. What shared state does that move require before it runs? It needs current attacker, current target, current move, hit result, and target side conditions. What does it write? It clears side-condition timers or flags for Reflect, Light Screen, and Aurora Veil-like effects. What later command depends on that write? The damage calculation command. That is the three-question habit working on a concrete edge case.

In FireRed terms, you should be looking for whether the script path runs a protection or side-condition mutation before or after damage is computed. The exact implementation details belong in the source focus for `battle_script_commands.c`, especially the command effects chunk, `chunk_c405e26d69cb40ac`, and the flow chunk, `chunk_98989865d85f275c`. In Showdown terms, the same ordering may appear as a hook that fires before base power or damage resolution. Different expression, same architectural issue.

Let me also connect this back to `pokefirered/src/battle_util.c`, `chunk_9335e5727aa0657d`. The packet excerpt emphasizes that this utility module includes move constants, abilities, items, weather, hold effects, battle move effects, battle animation, battle messages, and AI script commands. That include list is not trivia. It tells you that move support is cross-cutting. Damage and status are not owned by one tidy file. They depend on move metadata, weather, item effects, ability effects, controller output, animation, and text. The battle script command layer is where many of those dependencies are sequenced into visible behavior.

This is one of the big week 14 lessons in the larger course plan. Back in the earlier weeks, when we traced data table to runtime globals to callback and task behavior, it may have seemed like a general reverse-engineering recipe. Now you can see why that recipe matters. A battle move begins as data. Then the battle engine copies or references that data as current move state. Then commands interpret it. Then utility code answers questions about abilities, weather, items, immunities, and move effects. Then controller and message layers make it visible. That is the same course thesis, but in the densest subsystem in the game.

Now, I want to be precise about status categories because advanced students often over-compress them. Burn, poison, paralysis, sleep, and freeze are not the same architectural category as flinch. Flinch is typically a temporary condition that affects whether the battler can act in a specific turn window. Confusion is also not the same as poison, because it has a duration and its own per-action check. Disable is not the same as stat reduction. Protect is not the same as a status ailment; it is a move-interaction shield with counters and failure behavior. Side timers are not battler statuses, because Reflect belongs to a side and changes calculations involving multiple battlers.

If your design collapses all of these into one “status” map without lifetimes, you will make every later rule harder. FireRed’s structures in `pokefirered/include/battle.h`, `chunk_a08e84ff1272f826`, are a warning against that simplification. The real rule system wants separate buckets because the cleanup moments differ. Some things clean up when the battler switches. Some persist in the party data. Some decrement at end of turn. Some clear after one incoming move. Some attach to a side. Some are global field state.

Here is a quick check. If a target is paralyzed by a move, where does the later dependency show up? It can affect speed ordering and action execution later. If a target flinches, where does the later dependency show up? It affects whether that battler’s already-selected action can execute this turn. If Reflect is removed before damage, where does the later dependency show up? Immediately in the damage calculation of the same move. These are all “effects,” but they point to different later consumers.

That is also why regression testing matters. Pokemon Showdown is especially useful as a comparison point here because its simulator and data hooks make battle timing testable. The packet points us toward `pokemon-showdown/sim` as a recurring module around battle, Dex, and moves, and toward `pokemon-showdown/test` as a course-relevant area for regression testing. Again, do not misread this as “FireRed has to become Showdown.” The comparison is about practice. Showdown’s architecture makes it natural to encode a move interaction as a deterministic simulator test: set up sides, apply a move, assert HP, statuses, volatiles, and messages. In FireRed-style architecture, you can still adopt that discipline in your own tools, even if the original engine is organized around scripts and globals.

For your lab, that means you should not only test “Tackle reduces HP.” That is the week 5 version of the lab. At week 14, I expect you to test ordering. Make one test where accuracy fails and the status command must not write paralysis. Make one test where damage faints the target and the later status attempt is skipped or treated according to your chosen rule. Make one test where a side defense is removed before damage, so the damage number differs from the protected case. Make one test where an end-turn cleanup decrements a timer only after all battlers’ actions have resolved. Those tests are the difference between a toy move runner and a minimal battle script interpreter.

Let us come back to Showdown for one more comparison, because it helps name the idea. In `pokemon-showdown/data/mods/gen2stadium2/scripts.ts`, `chunk_3480e574205d5d0f`, the packet excerpt shows a damage path that checks immunities, handles OHKO moves, calls a move’s `damageCallback`, handles fixed level damage, handles fixed damage, and then continues into ordinary calculation. That is hook ordering in code form. It is saying: before you reach the ordinary formula, several move-specific exits can redefine what “damage” means. FireRed’s battle scripts and utilities support the same general need, even when the shape is less declarative.

So, when you are reading FireRed source, resist the urge to search only for a move name and stop. Instead, trace the category. Is this move using a common effect code? Is it routed through a battle script? Does it rely on current move data? Does it write into a status structure? Does a later command inspect a move-result flag? Does end-turn cleanup consume the flag or timer? That is how advanced source reading works in this part of the course.

Now I am going to mark a small recap on the board. First, damage is not a single calculation; it is a phase in a script-controlled transaction. Second, secondary effects are shared-state writes with later consumers. Third, the lifetime of the written state determines where it belongs: battler status, side condition, volatile timer, protection field, special status, or persistent party status. Fourth, Showdown’s hooks and Dex/mod structure give us a useful vocabulary for timing slots and regression tests, but FireRed remains our main case study.

And that fourth point matters for the whole course. We are building toward the final architectural synthesis in weeks 15 and 16, where you should be able to explain how a complete creature RPG stays coherent even though no single file “is the game.” The battle engine is not separate from the data model, the UI, the animation layer, the save model, or the script system. A move effect may begin in a data table, route through battle script bytecode, touch globals, call utility helpers, schedule animations, emit messages, update party-visible state, and influence a later turn. That is the architecture.

Let me give you a final concrete walk-through for this segment. Imagine our minimal interpreter has these commands: accuracy, damage, status, message, faint check, and end-turn cleanup. The current move is Thunderbolt-like: damaging, with a paralysis chance. The script begins by loading attacker, target, and current move. Accuracy checks the move and writes a result flag. If the move misses, the script jumps to a miss message and ends the hit path. If it hits, the damage command computes damage using attacker stats, target stats, type, modifiers, and any side conditions. It writes a pending damage value. The apply-damage command subtracts HP and writes a hit result. The faint-check command sees whether the target reached zero. Only if the target is still eligible does the status command roll the paralysis chance and write the target’s major status. Then message commands display the status text, if needed. End-turn cleanup later reads status and volatile timers, not because it knows Thunderbolt, but because Thunderbolt wrote into the shared state that cleanup understands.

That is the design goal. Commands should not need private memory of every previous command. They should communicate through explicit shared state and script control flow. That is why the three questions are so powerful: required before, written after, depended on later.

Before we move to the next segment, quick check for understanding. If I move the status command before the damage command, what breaks? Sometimes nothing visible in a trivial demo, which is dangerous. But in real rules, you may apply status on a miss, apply status to a fainted target, produce messages in the wrong order, or alter speed before the current transaction should observe it. If I move screen removal after damage, what breaks? The damage number. If I store Reflect as a target status instead of a side timer, what breaks? Doubles behavior, switching expectations, and any move that queries the side rather than the individual battler.

Good. That is the level of precision I want. Next, we will take this same shared-state framing and look at animation and message hooks, because those are not merely cosmetic either. They are the player-visible serialization of the battle transaction, and if they read the wrong state at the wrong time, the game can be mechanically correct while still explaining itself incorrectly to the player.

So let’s pick that up exactly there. Animation and messages are not decorations pasted on after the battle engine has finished doing the “real” work. They are part of the transaction boundary. They are the way the battle engine commits its result to the player’s understanding. And in a creature RPG, the player’s understanding is not optional. If the UI says, “The attack missed,” and then poison appears, or if it says, “Reflect wore off,” after showing a reduced damage number that depended on Reflect being gone, the mechanical state might be internally consistent, but the game has failed to serialize the cause-and-effect chain.

Put another way: the battle system has two audiences. One audience is the machine. The machine needs hit flags, damage integers, status bits, side timers, substitute state, protection flags, volatile counters, and script pointers. The other audience is the player. The player needs an ordered explanation. “Charizard used Flamethrower.” “It’s super effective.” “Venusaur was burned.” “Venusaur fainted.” These are not merely flavor lines. They are an interface over shared state mutation.

Now, eyes up here for a second. I’m going to write three layers on the board.

First, rule state. Second, presentation state. Third, control state.

Rule state is things like current HP, permanent status, volatile status, side timers, stat stages, move effects, protection, disabled moves, weather. Presentation state is animation selection, health bar movement, text buffers, battler sprites, sound cues. Control state is where the script pointer is, which battler is acting, whether we are waiting for a controller response, whether the next command is damage, a message, a jump, or an end-turn cleanup.

The danger is that these layers are distinct, but not independent. FireRed does not implement battle as a pure rules engine that returns a complete event log, then a renderer plays that log. That is a very common modern architecture, and it is a useful contrast, but it is not the architecture we are studying. In FireRed, the script command layer coordinates rule mutation and presentation emission through shared globals. That is why our primary source focus remains pokefirered/src/battle_script_commands.c, especially the command dispatch and flow chunks, chunk_9779e386ddb42cb0 and chunk_98989865d85f275c, with script command effects in chunk_c405e26d69cb40ac. The architecture is command-by-command, stateful, and interruptible.

So when we say “message hook,” we do not mean a high-level observer sitting safely outside the battle. We mean a command that often depends on the exact contents of current move data, battler indices, effect flags, and string variables at that moment. When we say “animation hook,” we do not mean “play a video.” We mean “ask the battle controller and animation system to display an effect whose parameters are derived from the current move transaction.”

Let’s do a concrete example. Suppose the active battler uses a move that hits, deals damage, and has a secondary burn chance. The script sequence is not simply “calculate everything, then show everything.” FireRed’s battle script bytecode model interleaves operations. You get accuracy resolution, damage calculation, damage application, health box update, message production, maybe animation, maybe secondary effect, maybe status message, then faint check. The exact order is rule-significant.

Now imagine we implement our lab interpreter from this week. Minimal commands: accuracy, damage, status, message, faint check, end-turn cleanup. A beginner implementation might write one function called executeMove, calculate hit, calculate damage, apply damage, apply status, then print messages after the fact. But that loses ordering. If the target fainted from damage, should the burn secondary still apply? Usually no, because the target is gone as an active recipient. If the move missed, should the damage animation play? No. If the target is protected, should the move animation play against the target or should a protect message and protect animation happen instead? That depends on where the protection check happens in the script and what state it sets.

Check your understanding here. If the message system reads the target’s HP after faint cleanup has already switched control to a replacement, what wrong explanation might the player see? Yes, exactly. You can get the right faint mechanically but attach the explanation to the wrong battler, or show a later battler’s name in a message caused by the previous one. That is why these global indices matter. The problem is not just “globals are ugly.” The problem is that globals are the transaction context.

And this is where pokefirered/include/battle.h, chunk_a08e84ff1272f826, becomes important again, but in a more advanced way than when we first used battle.h as a subsystem map. Earlier in the course, we used that header to orient ourselves: battlers, actions, disable structures, protect structures, special statuses. Today, we care about those structures as shared state surfaces. A protect flag is not just a boolean. It is a fact that the script interpreter, damage logic, AI checks, message logic, and sometimes animation logic may all consult. A disable timer is not just a counter. It is a piece of state whose decrement timing affects whether a move is legal, what message is shown, and whether end-turn cleanup is allowed to clear it before the player has been told what happened.

Now, let’s compare this very carefully with Pokemon Showdown, but remember the role of the comparison. Showdown is our secondary source. It is not replacing FireRed as the architectural authority for this course. It is useful because it makes certain hooks explicit in data and simulator code that FireRed distributes across C commands, globals, tables, and scripts.

In Pokemon Showdown’s data model, move behavior can include hooks such as onTry, onTryHit, onHit, damageCallback, volatileStatus, and condition behavior. You can see that style in pokemon-showdown/data/moves.ts, chunk_d4dc7d8b20aa96e6, where a move like Final Gambit uses a damageCallback and direct faint-related behavior, while another move can use onTry, onTryHit, or onHit to sequence rule checks. That is not FireRed’s bytecode script model. But it is solving a similar architectural problem: where do we attach rule behavior so that damage, status, and messages happen in a predictable order?

Showdown’s architecture says, in effect, “a move is a data object with executable hooks, and the simulator calls those hooks at defined phases.” FireRed’s architecture says, “a move selects effects and scripts, and the battle script interpreter advances through commands that mutate shared state and request presentation.” Different shape, same pressure: battle rules are phase-sensitive.

Now I’ll write one comparison phrase on the board: FireRed scripts are command-driven; Showdown effects are hook-driven.

Do not overstate that. FireRed also has helper functions and tables. Showdown also has a simulator turn engine and shared battle objects. But as a mental model, that contrast helps. In FireRed, we ask, “What script command runs next, and what globals does it read or write?” In Showdown, we ask, “What event hook fires next, and what battle, pokemon, move, or field object does it mutate?”

This matters for animation and message hooks because Showdown can often log protocol messages from the simulator as part of event resolution. FireRed, by contrast, is much closer to the hardware-facing presentation model. Battle scripts call into controllers, animations, text buffers, and health bar updates as part of the same command sequence. When pokefirered/src/battle_util.c, chunk_9335e5727aa0657d, includes battle animation, battle message, controller, constants for moves, abilities, hold effects, and move effects all in one utility region, that is not accidental trivia. It tells you the battle utility layer sits at a junction: rule support, message support, animation support, and controller support all have to agree on the same current transaction.

Let’s slow down and make that tangible.

Suppose a move removes Reflect before damage. In a data-hook architecture, you might model that with an onTryHit or beforeMoveDamage hook, depending on the engine’s event phases. In the source packet, pokemon-showdown/data/text/moves.ts, chunk_1620277751dc01f4, even the text description for Raging Bull says that if the attack does not miss, Reflect, Light Screen, and Aurora Veil end before damage is calculated. That sentence is a design contract. It is telling the engine, the UI, the tests, and the designer: this side effect is pre-damage, conditional on not missing.

In FireRed terms, that kind of rule has to be encoded in the script/effect sequence so the side timer is removed before damage calculation reads side conditions. If the message is emitted after damage, but the state is removed before damage, the player can still understand the sequence if the text is clear enough. But if the state removal happens after damage and the message claims it happened before, then the UI is lying about the rule. If the state removal happens before damage but the health bar animation appears before any indication of the screen breaking, the player may still be confused. That is the presentation state problem.

A common advanced-engineering mistake is to say, “The rule is correct, so the UI can be fixed later.” In battle systems, the UI is often a debugger for the player. It is how the player builds a mental model. If the UI serialization is wrong, players will infer the wrong rule, report phantom bugs, and make bad decisions.

Now, quick check. If I build the lab interpreter so that commandDamage immediately subtracts HP, but commandMessage later formats text using a generic lastDamage value and current target index, what must be preserved so the message remains correct? Say it out loud. The acting battler, the target battler, the move, the damage amount, and any relevant effectiveness or effect flags from that transaction. Good. And what happens if end-turn cleanup runs before the message command? We may clear volatile state or counters that the message needs to explain. Exactly.

This is why “shared state” is not a dirty phrase in this lecture. It is the architecture we are studying. The discipline is not to pretend shared state does not exist. The discipline is to know ownership, timing, and invariants.

Let me connect this to the course arc. Back in the runtime shell weeks, we traced AgbMain, callbacks, and tasks. Then in overworld and scripting, we saw that visible behavior emerges from data records plus interpreter state plus per-frame dispatch. In battle, the same course thesis is now at its highest density. A move is not just a row in a move table. It becomes current move data, selected battler action, turn order entry, script pointer, damage calculation inputs, status flags, controller commands, animation requests, and messages. This is the same “data table to runtime globals to callback or script to visible behavior” path we have used since week 1, but now every step can affect competitive correctness.

And that is why week 14 is positioned after we have already done battle setup, main battle callback, chosen actions, and turn-order arrays. I am not re-teaching those today because they are now prerequisites. The selected action and turn order determine which battler becomes the transaction owner. The battle main callback provides the per-frame shell that lets scripts wait on animations, controllers, and healthbox updates. The current move data gives the script enough local meaning to run generic commands like damage or status without hardcoding every move in C.

Now, let’s look again at status through this presentation lens.

A permanent status like burn has at least four different meanings. It is a rule flag that modifies later physical damage. It is an end-turn damage source in later generations and contexts, with generation-specific details. It is a switch-visible condition on the party member. It is a message and icon in the interface. If you apply the flag but fail to schedule the message, the player sees a sudden icon or later damage with no cause. If you schedule the message but fail to apply the flag before later checks, the player sees “was burned” but the next calculation behaves as though no burn exists. If you apply the flag to the battler but fail to synchronize the party data when required, the battle looks correct until switching, saving, or post-battle state exposes the mismatch.

This is where Pokemon Showdown’s regression testing culture is a helpful comparison. Showdown’s simulator can run deterministic battle tests because its battle events are represented in a form that can be asserted against. Its Dex and mods system lets it express generation-specific rule differences in data and modded scripts. The context packet points us to pokemon-showdown/sim as a recurring module around Dex, battle, and moves, and to pokemon-showdown/data and pokemon-showdown/data/mods as high-signal areas for move and generation data. That is valuable for us because it shows another way to keep hooks and rule variants testable. But again, our FireRed lab is not to rebuild Showdown. Our lab is to understand why FireRed’s script interpreter needs enough structure that we can test ordering: accuracy before damage, damage before faint check, side timers at the correct phase, status messages after successful application, cleanup after all relevant messages.

Let me give you a lab-grade example.

Imagine your minimal interpreter has these commands: accuracyCheck, playMoveAnimation, applyDamage, printDamageMessage, tryApplyStatus, printStatusMessage, faintCheck, endTurnCleanup. You test Ember against a target with 10 HP. Damage is 12. Burn chance succeeds. What is the correct outcome? The target faints. Burn should not be applied to a fainted active target. Therefore no burn message. If your interpreter simply rolls secondary effects after damage without consulting faint state, your final state may contain a fainted burned target. That is a rule bug.

Now test Ember against a target with 20 HP. Damage is 8. Burn chance succeeds. Correct outcome: HP falls to 12, burn flag is set, burn message appears, then no faint. If your message appears before HP movement, the player may still understand, but the ordering feels off compared with the transaction. If the burn flag is applied after faint check, the faint check is unaffected for this example, but another move effect might depend on status being present before the next branch. So even harmless-looking reorderings become dangerous when generalized.

Now test a protection case. Target used Protect earlier in priority order. Attacker uses Ember. Accuracy may not even be the central question, depending on the engine’s ordering; protection intercepts the move. Damage should not happen. Burn should not happen. The message should indicate protection. The move animation may be suppressed, altered, or replaced by a protection animation. If your interpreter performs accuracy first, prints “Ember missed,” and then notices Protect, the player sees the wrong cause. The mechanical result, zero damage, is the same. The explanation is wrong.

That distinction is advanced architecture. Beginner programmers test only final HP. Advanced engine programmers test event order.

This is one reason Showdown is useful for our thinking. Its move data hooks give you visible names for phases. In pokemon-showdown/data/mods/gen9ssb/moves.ts, chunk_f562e1983e9fdeff, the custom move example includes hooks like onTryMove, onPrepareHit, onTry, volatileStatus, and a condition object. Do not focus on that custom format as something FireRed has. Focus on the architectural lesson: rule effects are split into named timing points, and those timing points are what make complex behavior composable. FireRed’s names are different. Its timing points are script commands, helper calls, and state checks. But the need for timing points is the same.

Let’s pause and check for understanding with a slightly harder question.

Suppose a move’s animation reads the move type after a type-changing effect has modified the current move. Should the animation show the original move type or the modified type? There is no universal answer. The correct answer is: it depends on the intended rule and where the engine defines “current move type” for presentation. But what must not happen is accidental inconsistency. You do not want damage to use the modified type, effectiveness text to use the original type, and animation to use whichever value happened to remain in a global from the previous move. That is why the battle transaction needs clear current move state.

And this takes us back to the source focus on current move data. FireRed battle scripts rely on globals that identify the current move, attacker, target, effect, damage, and script cursor. Those globals are not just conveniences. They are the implicit arguments to many commands. A message command does not necessarily carry a full payload. It may say, in effect, “print the prepared battle string using the current battler and string variables.” An animation command may say, “play the animation for the current move from this attacker to this target.” That makes scripts compact, which matters on GBA hardware and in a ROM-oriented architecture. But compactness increases the importance of not corrupting shared state between commands.

Now, I want to connect this to battle_script_commands.c more concretely. The command dispatch chunk, pokefirered/src/battle_script_commands.c chunk_9779e386ddb42cb0, is important because it frames battle scripts as an interpreter rather than as ordinary straight-line C. The flow chunk, chunk_98989865d85f275c, matters because jumps, waits, branches, and script pointer movement determine which state mutations happen before others. The effects chunk, chunk_c405e26d69cb40ac, matters because commands are where abstract battle rules become mutations of HP, status, side timers, and flags. When you read those chunks, do not read them as isolated functions. Read them as a tiny virtual machine whose registers are battle globals.

That phrase is worth writing down: battle globals are the VM registers.

In a CPU, an instruction like add may implicitly read registers. In FireRed’s battle script VM, a command like damage or status or message implicitly reads “registers” such as attacker, target, current move, damage, effect flags, and scripting state. If an earlier command leaves a register in the wrong condition, a later command does the wrong thing even if that later command is locally correct.

Now, does that mean the architecture is bad? No. It means the architecture has a specific discipline. On a constrained system, this is a reasonable design. It avoids allocating rich event objects for every micro-step. It allows scripts to be compact. It lets designers encode many move behaviors through reusable commands. It integrates naturally with a per-frame controller model where the interpreter may pause for animation, text, or input. The tradeoff is that correctness lives in ordering and shared-state conventions.

Let’s bring in regression testing here, because homework_w14 and quiz_w14 should be harder than “find the damage function.” You should be able to propose tests that catch ordering bugs. Not just “Tackle reduces HP.” That is week 12 thinking. Week 14 thinking is: “If a move removes a side condition before damage, the damage calculation observes the removed side condition, and the message order does not imply the reverse.” Or: “If a target faints from primary damage, secondary status is not applied and no secondary status message is emitted.” Or: “If a protection effect blocks a move, the block message and animation are generated instead of miss or damage messages.”

Showdown’s simulator tests are useful as a model of this mindset. Because Showdown has a deterministic simulator turn architecture and generation-specific Dex/mod organization, it can encode regressions around exact event behavior. Its data/mods directories, which the research packet highlights repeatedly, show how modern battle engines isolate generation differences and custom behavior. FireRed, as our primary system, does not hand us that testing apparatus. But our lab interpreter can borrow the idea: make the battle transaction observable. Record an event log. Assert not only final state but sequence.

For example, your test should not merely assert target.hp equals 12 and target.status equals burn. It should assert an ordered trace like: move started, animation played, damage applied, HP bar updated, status applied, status message printed, faint check passed. In a FireRed-inspired engine, that trace corresponds to script commands and controller waits. In a Showdown-inspired engine, that trace corresponds to event hooks and battle log messages. Two architectures, same verification target: phase correctness.

Now, one more subtle point before we shift fully into end-turn cleanup. Messages can themselves depend on prior message hooks. FireRed uses prepared text buffers and battle string machinery; the exact source packet points to battle_message inclusion through pokefirered/src/battle_util.c chunk_9335e5727aa0657d, and the script command file is where those messages are requested. If you overwrite the string variables too early, the eventual printed line can name the wrong move or battler. This is a very concrete shared-state hazard. It is not theoretical. Any engine that uses reusable buffers for performance has to treat message preparation and message display as a stateful protocol.

So, if I write on the board: prepare string, request print, wait for print, then mutate battler context, what am I warning you about? I am warning that “request print” may not mean “the player has already seen it.” In a per-frame battle main loop, the command can initiate presentation and then yield until controllers report completion. If you mutate the context before the print system has consumed it, you can get a mismatch. Some systems solve this by copying all message arguments into a stable event object. FireRed often solves it through careful sequencing and shared buffers. Again, different tradeoff.

Let’s do a quick oral check. Why can’t we treat animation as purely cosmetic in this architecture? Because animation commands can impose waits, consume or preserve transaction context, and communicate causal order to the player. Good. Why can’t we treat messages as pure logging? Because message generation may depend on current shared state, and the log order is part of the rule explanation. Good. Why is Pokemon Showdown useful but secondary? Because it exposes comparable hooks, Dex/mod organization, simulator turns, and regression testing patterns, while FireRed remains our main source for the GBA battle script architecture. Good.

Now, keep that in mind as we turn the screw one more notch. End-turn cleanup is not “everything left over.” It is a scheduled phase with its own ordering rules. Status damage, weather, side timers, volatile decrementing, disabled move timers, protection reset, flinch clearing, trapping state, and faint checks all compete for position. Some are per battler. Some are per side. Some are per field. Some are per move transaction. If you store them all in one undifferentiated cleanup bucket, the engine will eventually produce a subtle bug.

And here the structures in pokefirered/include/battle.h chunk_a08e84ff1272f826 are again our map. Disable and protect structures exist because not all temporary state is the same. A protect flag that should reset after the turn is not the same as a multi-turn disable timer. A special status that exists only during move resolution is not the same as a side condition that persists across switching. When these live in separate structures, the code is telling us something about lifetime. Lifetime is architecture.

Let me say that again because it will matter in the workshop: lifetime is architecture. If a value lasts one command, it belongs near the script transaction. If it lasts one move, it belongs in current move or special-status state. If it lasts one turn, it belongs in turn-scoped battler or protect-like state. If it lasts across turns but not switching, it is a volatile battler condition. If it lasts across switching, it is party or side state. If it lasts across the whole battle, it is field or battle state. If it lasts after battle, it must synchronize back to party/save-relevant data.

That lifetime ladder is how you avoid stuffing everything into “status.”

So, as a local synthesis before we continue: FireRed’s damage and status system works through shared state because its battle scripts are an interpreter running inside a coordinated battle main loop. Damage is not just arithmetic; it is a script-visible transaction. Status is not just a flag; it is a timed mutation with messages, icons, prevention checks, and later effects. Animation and message hooks are not decorative; they serialize the transaction for the player and can expose ordering bugs. Pokemon Showdown gives us a useful secondary vocabulary of hooks, Dex/mods, simulator turns, and regression testing, but the FireRed lesson is command dispatch plus shared VM registers plus careful phase ordering. Hold onto that, because next we are going to use the same lifetime ladder to classify side timers and end-turn effects, and that is where many otherwise clean battle interpreters start to drift out of correctness.

So put that lifetime ladder on the board again, but this time do not read it as a list of features. Read it as a list of ownership questions. Who owns this effect? Who is allowed to decrement it? Who is allowed to clear it? Who is allowed to print the message that makes the player believe it changed?

At the bottom of the ladder we have one command, one immediate script moment. Accuracy check, damage calculation, print the “it missed” message, branch to the miss script. Above that, we have one move execution, which may include multiple commands: check protection, calculate damage, apply damage, apply status, play animation, check fainting. Above that, we have one battler’s action inside a turn. Above that, the whole turn. Above that, persistent battle state, such as weather, screens, trapping, disable, confusion, substitute, and the permanent status fields that survive across turns. And above that, party and save state, where poison or paralysis can remain attached to the Pokemon after the battle system gives control back to the overworld.

Now, why am I being fussy about this ladder? Because if you put an effect at the wrong lifetime, your interpreter may still pass the obvious test. It may even look correct in a single battle. But later, when you add double battles, switching, faint replacement, multi-turn moves, or item callbacks, the behavior starts to become unexplainable. The state is not wrong because one arithmetic formula was wrong. It is wrong because ownership was wrong.

Let me make that concrete. Suppose we implement Reflect in our workshop interpreter. A beginner version might attach a boolean called targetHasReflect to the defending battler. That seems plausible, because the defender receives less physical damage. But architecturally, Reflect is not owned by one defender. It is a side condition. If the defender switches, the side still has Reflect. If another Pokemon on the same side is hit, the protection still applies. If the timer expires, the expiration message should be associated with the side, not with whichever battler happened to be hit last. So Reflect belongs higher on the ladder than a single hit and higher than a single active battler. It belongs to side state with a turn counter.

This is where the structures in FireRed’s battle state become more than “a bunch of C globals.” They are the memory model of the battle. The source focus for today includes pokefirered/include/battle.h, chunk_a08e84ff1272f826, precisely because structures like disable, protect, and special status fields tell us what the engine considers persistent, transient, per-battler, or side-related. You do not have to memorize every field. That is not the point. The point is to look at the shape of the data and ask, “What lifetime does this field imply?” If a status has its own counter, prevention bit, or per-battler byte, that is an architecture clue.

Now compare that to Pokemon Showdown, carefully, as a secondary comparison. Showdown often represents effects through hook-bearing data objects and condition objects. In pokemon-showdown/data/moves.ts, chunk_d4dc7d8b20aa96e6, we see move definitions that can include fields like basePower, accuracy, category, flags, and callbacks such as onTry, onTryHit, onHit, or damageCallback. The design vocabulary is different from FireRed’s script bytecode, but the ownership problem is the same. A hook must run in the right phase, against the right object, with the right lifetime. If a Showdown effect is implemented as a volatile status when it should be a side condition, it will fail in analogous ways to our bad Reflect implementation.

Let’s do a board example. I’m going to write three columns: FireRed script command, shared battle state, visible consequence. Under the first column, write “damage command.” Under the second, write “current move, attacker, target, computed damage register, hit marker, status-capable side effects.” Under the third, write “HP bar moves, message appears, faint branch may execute.” Now draw arrows both directions between the first and second columns. That is the key. The command reads shared state, mutates shared state, and then leaves behind facts for later commands. The later commands are not recomputing the whole event from scratch. They are consuming the transaction record left by earlier commands.

This is why I keep calling damage script-visible. Once damage exists in a register-like global, later commands can inspect whether it was zero, whether the target fainted, whether an effect should apply, or whether a message should be selected. In FireRed, pokefirered/src/battle_script_commands.c, chunk_9779e386ddb42cb0, is important because the battle script command dispatcher is where bytecode-like script instructions become C behavior. The script pointer advances, branches, waits, or calls into helper routines. That makes the battle script not just a list of visual messages, but a control program over shared combat state.

Check for understanding: if Thunderbolt deals damage and then has a chance to paralyze, where should the paralysis chance live? It should not live in the animation system. It should not live in the HP bar routine. It belongs in move effect execution, after hit success and damage relevance are known, and before the script commits to the later messages and checks that depend on the result. And if an immunity, substitute interaction, ability prevention, or existing status blocks it, those conditions must be checked before the permanent status field changes. Good. That ordering is what keeps the player-visible sequence honest.

Now, side timers complicate this because they are neither purely move-local nor permanent. Let’s use Light Screen, Reflect, Safeguard, Mist, and similar effects as the family. The move that creates the effect is a one-time script execution. But the effect itself must outlive that script. It must participate in later damage calculations or status prevention. It must decrement at a phase boundary. It must produce an expiration message at the correct time. So the move script is the constructor, not the owner of all future behavior.

In our lab interpreter, that means the “status command” is not enough. We need a minimal model of ownership. For permanent status, we write to the creature or battler status field. For volatile status, we write to a battler-scoped volatile field with a timer. For side effects, we write to side state. For field effects, such as weather, we write to field state. And for one-shot script facts, such as “this hit missed” or “this damage amount was just calculated,” we use temporary execution state. That division is the core of correctness.

Notice how this connects backward to the course plan. In week 1, we were decomposing a creature-collector RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. That decomposition was broad. By week 14, we are using the same idea at a smaller scale. A battle is itself a federation of subsystems: action selection, turn ordering, command dispatch, damage math, status storage, animation, message rendering, controller communication, and party persistence. The same architectural habit applies. Do not ask only, “What function changes this number?” Ask, “Which subsystem owns this lifetime?”

Let’s bring Pokemon Showdown back in for a moment, because it gives us a clean testing vocabulary. In Showdown, the simulator has a notion of a battle turn, with effects implemented through callbacks and condition hooks. The research packet points us to pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, where damage calculation explicitly considers immunities, OHKO moves, damage callbacks, level-based damage, fixed damage, and so on. That example is useful because it shows damage as a pipeline with extension points. FireRed does not expose the same TypeScript hook structure. FireRed’s primary form here is script command dispatch plus C helpers plus shared globals. But both systems need a stable pipeline.

If we were building regression tests for our workshop interpreter, Showdown suggests a style of thinking: write tests as battle scenarios, not only as isolated function tests. A function test can confirm that burn halves physical attack, or that poison damage is one eighth of max HP if that is the rule we choose for the generation. But a regression scenario confirms that the burn message occurs at the end of the turn, that the HP loss happens before faint replacement, that the faint check runs after HP changes, and that the script does not continue as though the fainted battler can still act. That is a higher-value test because battle bugs are often ordering bugs.

Here is a concrete scenario. Imagine Charmander is poisoned and has 3 HP at the end of the turn. Poison damage will reduce it to zero. The engine must apply poison damage, display the poison damage message, animate or update HP, detect fainting, print fainting text, and then move toward replacement or battle end. If our cleanup phase simply loops over statuses and subtracts HP, but the faint check happens only during move damage commands, then Charmander sits at zero HP until some later unrelated event notices. That is a phase bug. The arithmetic was fine. The lifetime and dispatch boundary were wrong.

Now look at FireRed’s source organization for support. pokefirered/src/battle_util.c, chunk_9335e5727aa0657d, includes battle utility and move support alongside constants for moves, items, weather, abilities, hold effects, and battle move effects. That tells us a lot. Damage and status are not isolated in a single “formula file.” The utilities sit at the junction of move data, item behavior, weather behavior, ability behavior, and message or animation support. In a small teaching interpreter, we can compress those dependencies. In a production battle engine, they become cross-cutting because almost every combat feature wants a conditional say in damage or status.

Let me pause and ask this another way. Why not make each move a giant function? Why not write function Thunderbolt, function Toxic, function Reflect, function Substitute, and call it a day? For a toy, that works. For a full creature RPG, it collapses under repetition and ordering ambiguity. Many moves share accuracy checks. Many share damage calculation. Many share status application. Many share immunity checks. Many share message templates. Many need to branch to common faint logic. FireRed’s battle script approach allows move effects to be composed from common commands and common helper routines, while still giving special moves script-level control where needed.

Showdown lands at a different point in the design space. Its data files can attach hooks directly to move definitions, as in pokemon-showdown/data/moves.ts, chunk_d4dc7d8b20aa96e6, where a move can define a damageCallback or onHit behavior. That is expressive and testable in a simulator that runs in a high-level language. FireRed is constrained by the GBA environment, by C, by fixed data, by script bytecode, and by a runtime architecture built around callbacks, tasks, controllers, and global battle state. So we should not say, “Showdown is the better version.” We say, “Showdown makes some extension points explicit as hooks; FireRed makes many extension points explicit as script commands and shared state.”

Now, on the board, I want to write “hook” and “command” with a line between them. A hook says, “When the simulator reaches this named phase, call this effect-specific code.” A command says, “When the script interpreter reaches this opcode, execute this shared operation.” Those are not identical, but both are ways of controlling phase. In Showdown, the phase name may be onTry, onHit, onResidual, or a condition event. In FireRed, the phase is often encoded by the script’s command order and by the battle main loop’s larger turn machinery. This is why you can use Showdown to sharpen your vocabulary without replacing FireRed as the architectural source for this course.

Let’s examine status through that lens. A permanent status like paralysis has at least four architectural faces. First, it is a stored condition on the battler or underlying Pokemon. Second, it is a prevention or modification rule: speed may change, and the Pokemon may sometimes be unable to move. Third, it is a message and icon concern. Fourth, it is a persistence concern, because it may remain after switching or after battle depending on the game’s rules. If you implement only the first face, you have a flag. If you implement all four in the right phases, you have a battle status system.

Volatile statuses have a similar pattern but a different lifetime. Confusion, disable, flinch, trapped, protected, charging, recharging, and similar effects may be attached to a battler for a short period. Some decrement when the Pokemon attempts to act. Some clear at end of turn. Some clear on switch. Some exist only for the current action. FireRed’s battle.h structures, again in chunk_a08e84ff1272f826, matter because they separate these concerns into data that the rest of the engine can consult. You can read those structures as a catalog of “things that must survive longer than one command but not necessarily forever.”

Here is the mistake advanced students often make after they understand data-driven design: they assume all data should be declarative. So they try to describe every effect as a table entry. But battle systems are full of exceptions that are not random exceptions; they are phase-sensitive rules. Declarative data is excellent for base power, type, accuracy, PP, category, target, and flags. It is often good for simple secondary effects. But when an effect says, “Before damage, remove the target side’s screens,” or “after a successful hit, sacrifice the user’s HP,” or “redirect single-target moves until the end of the turn,” you need either hooks, script commands, or some other controlled execution mechanism.

The packet gives us a Showdown text example for Follow Me in pokemon-showdown/data/text/moves.ts, chunk_1620277751dc01f4, where the description is all about redirection until the end of the turn and interactions with other redirection or reflection mechanics. That is not just flavor text. Architecturally, it is warning us that the effect lives in the targeting phase, lasts for a bounded turn lifetime, and competes with other targeting rules. In FireRed terms, you would ask where current move target selection is stored, when redirection is allowed to alter it, and which shared state indicates that a battler is attracting attacks. In Showdown terms, you ask which hook modifies target choice and when the volatile condition expires.

Now let’s connect that to the homework and quiz level. Earlier assessments already asked you to trace source files, structs, functions, constants, data tables, and state variables. So at this point, I am not satisfied with “the status is in battle.h” as an answer. A week 14 answer needs a chain. For example: move data selects an effect; the battle script dispatches commands for that effect; command C code reads the current attacker, target, move, and side state; it mutates a status or timer field; message and animation commands serialize that mutation; later turn or end-turn phases consult the same field and either apply damage, block an action, decrement a timer, or clear it. That is the level of answer expected now.

Let’s try a small check. I will describe a bug, and you classify it. A move applies sleep correctly. The sleep icon appears. On the next turn, the sleeping Pokemon cannot move. But after switching out and switching back in, the Pokemon is awake. Is that likely an arithmetic bug, a message bug, or a lifetime ownership bug? It is a lifetime ownership bug. Sleep was stored in a place that did not survive switching, or the switch code cleared it incorrectly. Now a second bug: Reflect reduces damage correctly, but only for the Pokemon that used Reflect, not for its partner or replacement. Same category: ownership bug. Reflect was modeled as battler-local when it should be side-local. Third bug: poison damage happens, but the faint message appears before the HP bar moves. That is a sequencing bug between state mutation, animation, and message hooks.

These distinctions matter because they tell you where to inspect the code. If the wrong amount of damage is produced, inspect damage calculation helpers and modifiers. If the right amount is produced but the wrong message appears, inspect script command order and message selection. If the effect disappears at a switch boundary, inspect lifetime and cleanup paths. If a double battle target interaction is wrong, inspect targeting state, side state, and per-battler volatile state. Advanced debugging is largely about classifying the failure before reading half the engine.

Now let’s look forward to the workshop. The minimal interpreter we are building has commands for accuracy, damage, status, message, faint check, and end-turn cleanup. If you implement those commands as six independent functions with no shared transaction state, you will feel pain immediately. The accuracy command needs to tell later commands whether to skip damage. The damage command needs to tell the faint check what happened. The status command needs to know whether the hit connected and whether the target is still valid. The message command needs access to current move, current target, and selected result. End-turn cleanup needs persistent battle state, not the local variables from last move execution.

So I recommend a small but explicit execution context. Not a giant global free-for-all in the teaching code, but a context object that mirrors the architectural role of FireRed’s battle globals. It should have battlers, side conditions, field conditions, current attacker, current target, current move, last damage, hit result, pending message, and script pointer. For permanent creature state, store status on the creature or battler model that persists through switch. For volatile state, store it in active battler state and define switch cleanup. For side timers, store them on the side. For field timers, store them on the battle field. This is the small version of the lifetime ladder.

And yes, this is one reason FireRed can look intimidating when you first read it. A lot of names are global. A lot of state is shared. Modern taste often tells us shared mutable state is suspect, and that instinct is useful. But for this engine, the interesting question is not “could this have been written with fewer globals?” In a 2004 GBA C codebase, of course the style is different. The useful question is, “How is shared state disciplined by phase ordering?” The battle main callback, the turn-order arrays, the chosen actions, and now the script interpreter are all part of that discipline. We covered those earlier, so today we are using them as foundation, not reopening them.

Pokemon Showdown helps here because it gives us the mirror image. There, you see more explicit data hooks and simulator methods. In pokemon-showdown/data/mods/gen9ssb/moves.ts, chunk_f562e1983e9fdeff, a custom move can define onTryMove, onPrepareHit, volatileStatus, onTry, and a condition object. The source excerpt even shows checks against existing volatiles and field pseudo-weather before allowing the effect. That is a very hook-oriented expression of the same design problem: before an effect starts, check whether other state forbids it; if allowed, attach a condition; while active, let that condition participate in later phases. Again, different machinery, same architecture question.

Now, one more subtle point before we wrap this segment: regression testing must test the contract between state and script, not only isolated hooks. In FireRed, if a battle script command changes a field that later commands assume has changed, a refactor can break behavior without changing any visible formula. So a good regression case records the sequence: command dispatch reaches damage, damage amount is stored, HP changes, message runs, faint check branches, end-turn effects run. In Showdown, tests often encode full battle scenarios because the simulator’s correctness depends on turn-level interactions. We should borrow that testing mindset, even though our primary architecture source remains FireRed.

Let’s do one last concrete pass. Suppose a move has this behavior: it hits, deals damage, and if damage was dealt, it sets a volatile status on the target for three turns. The target then faints from the damage. Should the volatile status be set? In most designs, no, because the target is no longer an active participant. But do not answer by instinct. Answer by phase contract. After damage, run faint or at least validate target viability before applying follow-up status, unless the game’s specific rule says the effect applies before faint resolution. That means the battle script order matters. If the script says “damage, status, faint check,” then status code itself may need to check HP or faint state. If the script says “damage, faint check, status,” the branch may skip the status. Either design can work, but the contract must be explicit.

And that is the broader lesson of week 14. Battle scripts as combat bytecode give you compositional control. Shared state gives commands a common memory space. Damage and status make that memory space player-visible. Side timers and end-turn effects force you to respect lifetimes beyond the current move. Animation and messages serialize the transaction so the player can follow it. Pokemon Showdown gives us a valuable comparison point with hooks, Dex/mods, simulator turns, and regression scenarios, but FireRed remains the architecture we are reading closely: C helpers, battle globals, script dispatch, and phase-ordered mutation.

For the next stretch, keep the ladder visible. We are going to apply it directly to end-turn cleanup. That is where poison, burn, weather, Leech Seed-like effects, perish-style counters, side timers, and faint checks all want to run in the same neighborhood. The danger is not that any one of them is hard in isolation. The danger is that each one believes it owns the end of the turn. A battle engine becomes correct when none of them owns it alone; the phase owns it, and every effect gets a disciplined slot inside that phase.

So, let’s put end-turn cleanup on the board as a shared-state problem, not as a list of cute effects. I’m drawing one horizontal line labeled “turn phase,” and underneath it I’m writing: residual damage, timer decrement, message, faint check, replacement or continuation. The order matters because the player experiences this as a sequence, but the code experiences it as controlled mutation of shared battle state.

In FireRed terms, that shared state is not abstract. It is the battler globals, the disable and protection records, side status flags, temporary script variables, current move data, and battle script pointers. The reason I keep pointing you back to pokefirered/include/battle.h, chunk_a08e84ff1272f826, is that those disable, protect, and special status structures are not just storage. They are the architectural contract for which effects are allowed to survive past a single command and reappear later in the turn or at the end of the turn.

Now compare that to Pokemon Showdown, carefully, as our secondary lens. Showdown often expresses a move or condition with hooks: onTry, onHit, onResidual, damageCallback, volatileStatus, and so on. In pokemon-showdown/data/moves.ts, chunk_d4dc7d8b20aa96e6, Final Gambit is a useful example because the move’s damage is not merely a table value; it is computed through a callback tied to the current Pokemon’s HP. In pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, the simulator’s damage path checks immunities, OHKO rules, damage callbacks, fixed damage, and then ordinary calculation. That is a clean modern comparison point: behavior is distributed into hooks and then invoked by a simulator pipeline. FireRed is doing something older and more constrained, but conceptually related. It dispatches script commands and helper functions against shared globals, with the script interpreter acting like the schedule.

Here is the key distinction. In Showdown, the hook often belongs to the effect object, and the simulator asks, “Does this effect have something to say at this phase?” In FireRed, the phase and script command stream often own the flow, and the effect’s state is encoded in global structures that commands and helpers inspect. Neither approach magically eliminates ordering problems. They just locate the ordering responsibility in different places.

Let’s make this concrete. Suppose a battler is poisoned, protected last turn, seeded by an HP-draining effect, and standing under damaging weather. I’m not asking whether that exact stack is represented identically in FireRed and Showdown. I’m asking architecturally, what must the engine prevent? It must prevent poison from fainting the target and then letting weather damage the same already-fainted battler as though nothing happened. It must prevent a timer from decrementing twice because both a move effect command and an end-turn command think they are responsible. It must prevent a message from saying one thing while HP has already been changed by another thing. And it must preserve enough information for animations and text to make sense after the arithmetic has occurred.

That is why the FireRed source focus for this week includes pokefirered/src/battle_script_commands.c, chunk_9779e386ddb42cb0 for command dispatch, chunk_c405e26d69cb40ac for command effects, and chunk_98989865d85f275c for battle script flow. Those chunks matter because damage and status do not live in a single “damage system” file in the high-level sense. They emerge from dispatch, current move context, utility checks, flags, and scripts. Meanwhile pokefirered/src/battle_util.c, chunk_9335e5727aa0657d, shows the utility side of the same design: it pulls in battle headers, constants for moves, abilities, weather, hold effects, move effects, battle messages, animations, controllers, and AI script commands. That include list is a map of coupling. It tells us damage and status are not local concerns; they are traffic through the entire battle subsystem.

Pause there and check your understanding. If I ask, “Where is poison implemented?” the advanced answer is not a single filename. The better answer is, “Poison is represented in persistent battler state, interpreted during phase-specific logic, communicated through script commands and messages, and made visible through HP mutation, animation, and faint handling.” That is the level of answer I expect from you now, especially after the week 13 homework asked you to identify concrete files, structs, functions, constants, data tables, and state variables.

Now, for the lab interpreter, this becomes your design constraint. Do not write six independent end-turn functions that each directly decide whether the battle is over. Write a small scheduler. Accuracy, damage, status application, message, faint check, and cleanup are commands, but cleanup itself should still have internal order. On the board I’ll write it this way: collect active residual effects, run the next eligible effect, mutate HP or timers, emit message request, run faint check, then continue. You do not need FireRed’s full machinery, but you do need FireRed’s discipline: command dispatch plus shared state plus phase ordering.

This is also where regression testing enters the picture. Showdown’s architecture is much easier to regression-test in a text-driven way because simulator turns can be constructed and replayed around hooks and Dex data. The research packet points us toward pokemon-showdown/sim as the recurring module for Dex, mods, and battle simulation, and toward pokemon-showdown/data as the place where move data and modded behavior live. When we borrow that idea for our FireRed reading, we are not replacing the GBA architecture with Showdown. We are borrowing the testing instinct. If you fix burn damage ordering, write a scenario that proves poison, weather, and faint checks still serialize correctly. If you add a side timer, write a scenario where the timer expires on the same turn as a faint. The architectural lesson is portable even when the implementation style is not.

Let me connect this back to the whole course arc before we close. In week 1, we learned to see the game as cooperating runtime systems rather than as one big loop. In the callback and task weeks, you saw how the shell gives each subsystem a frame-by-frame place to run. In the map and event weeks, you saw data tables and scripts combine to produce visible behavior. In the creature data weeks, you saw species, moves, parties, and persistent records become runtime objects. Now, in week 14, the battle engine is forcing all of that together. A move begins as data, becomes selected action, becomes current move context, becomes script dispatch, becomes damage arithmetic, becomes status mutation, becomes message and animation, and finally becomes cleanup that prepares the next turn.

That is why “shared state” is not a vague warning. It is the central technique and the central risk. FireRed uses shared state because it is efficient, compact, and well suited to a script-driven battle engine on the GBA. But shared state only stays sane when phases are explicit and when every mutation has a disciplined place in the transaction. Pokemon Showdown shows us a more modern data-and-hook expression of many of the same ideas, especially through Dex/mod layering and simulator turns, but the comparison should sharpen your FireRed reading, not distract from it.

So the synthesis for today is this: damage is not just subtraction, status is not just a flag, and battle scripts are not just presentation glue. They are the transaction language of combat. The engine makes a promise to the player that complicated effects will resolve in a comprehensible order. Underneath that promise is a set of shared structures, command dispatch routines, utility helpers, timers, messages, animations, and faint checks, all cooperating through a strict phase schedule. Carry that model into the workshop. If your minimal interpreter can make damage, status, messages, faint checks, and end-turn cleanup share state without each subsystem pretending to own the whole turn, then you have captured the architectural heart of this week.

## Source References
- pokemon-showdown/data/text/moves.ts
- pokefirered/src/battle_util.c
- pokemon-showdown/data/moves.ts
- pokemon-showdown/data/mods/gen2stadium2/scripts.ts
- pokemon-showdown/data/mods/gen9ssb/moves.ts
