# Battle Scripts as Combat Bytecode

Lecture ID: lecture_w14_d01
Week 14, Day 1

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

## Transcript
All right, let’s come back to the battle engine, and I want you to keep last week’s mental model active but not rebuild it from scratch. We already have the battle main callback. We already have battlers, chosen actions, turn-order arrays, and the shared globals that let the battle engine coordinate work over many frames. Today we are going one level deeper into how an actual move becomes a sequence of effects.

The key idea for today is this: FireRed does not treat move execution as one giant function that says, “Tackle does accuracy, then damage, then message, then faint check,” all hard-coded in one place. Instead, a large amount of combat behavior is represented as battle scripts, and those scripts are interpreted by command dispatch code. That is why I want you to think of them as combat bytecode. Not bytecode in the sense of a general-purpose JVM, and not bytecode in the sense that we would compile a high-level language into it for arbitrary computation. But bytecode in the architectural sense: compact command streams, an instruction pointer, command handlers, shared registers or globals, and a dispatcher that advances execution.

Now, if you look at the source focus for this week, especially pokefirered/src/battle_script_commands.c chunk_9779e386ddb42cb0, chunk_c405e26d69cb40ac, and chunk_98989865d85f275c, you can see the shape of that interpreter. The exact commands vary, and we are not going to memorize all of them. Memorizing the command list is not the point. The point is to recognize the pattern: battle script command dispatch gives the engine a way to describe combat procedures as data-directed sequences instead of as one monolithic switch over every move in the game.

Let me put that on the board. On the left, I’m writing “move selected.” Under that, “current move data loaded.” Under that, “battle script pointer.” Under that, “command dispatch.” Then to the right, I’m writing the visible outcomes: message, animation, HP change, status change, fainting, turn cleanup. The important arrow is not from move selected directly to visible outcome. The important arrow goes through shared battle state and the script interpreter.

This is the same architecture pattern we have seen all semester, but now it is under more pressure. In the overworld, a map script can say, effectively, show text, move object, set flag, give item. In battle, the script may need to say, check protect, check accuracy, calculate damage, apply damage, maybe apply secondary effect, print a string, wait for animation, check fainting, jump depending on result, and return control to the turn scheduler. That means the battle script system is a virtual machine embedded inside another state machine, which is the battle main loop. And because battle is animated, stateful, and link-sensitive, it cannot simply run everything as one blocking function call and be done.

Quick check for understanding before we go further. If I say “battle script pointer,” what is the analogy from our earlier event scripting discussions? Yes, it is like an instruction pointer in an event script or a tiny VM. The battle engine is not asking, “What line of C should I execute next for this move?” It is asking, “What command byte am I currently looking at, and which handler owns that command?”

Now, let’s make that concrete. Imagine a minimal clone move called Spark Tap. Spark Tap has 95 accuracy, 40 power, and a 20 percent chance to paralyze. If we were writing a beginner implementation, we might put all of that inside one function called executeSparkTap. That function would roll accuracy, calculate damage, subtract HP, roll paralysis, print messages, and check whether the defender fainted. That works for one move. It even works for ten moves. But by the time you have dozens or hundreds of moves, each with slightly different timing, messaging, immunity behavior, substitute interaction, protection behavior, faint checks, animation hooks, side conditions, and status timers, that approach becomes a trap. You end up duplicating “accuracy then damage then faint check” everywhere, and every exception becomes an if statement in a growing mess.

The FireRed design points us toward a different clone architecture. You keep current move data in shared state. You keep the active attacker and target in shared state. You implement command handlers like accuracy_check, damage_calc, apply_damage, try_status, print_message, play_animation, faint_check, and end. Then Spark Tap is not a bespoke function. Spark Tap is data plus a script path through reusable combat commands. The battle scripts become the language of combat procedure.

That is what we mean by combat bytecode.

When the research packet points us to pokefirered/src/battle_util.c chunk_9335e5727aa0657d, notice the included headers alone tell a story. That file 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 a source-backed sign that move support is not isolated. Damage and move behavior sit at the intersection of data definitions, animation systems, message systems, controller communication, weather, items, abilities, and status effects. So the script interpreter is not just a cute abstraction. It is one of the ways the engine coordinates this intersection without turning every move into its own private battle engine.

And this is why I am going to be careful with the word “minimal” in this week’s lab. When I ask you to create a minimal battle script interpreter, I do not mean “toy” in the sense of unstructured. I mean minimal in the sense of preserving the architectural pressure points. Your clone should have an instruction pointer. It should have command dispatch. It should have shared battle state, including current move, attacker, target, damage, status flags, and maybe a message buffer. It should have enough commands that you can express a normal damaging move, a status move, and an end-of-turn cleanup. If you just write executeMove as one big function, you may produce the same result on screen, but you will not have learned the architecture.

Let’s now connect this to the course plan. In weeks one through four, we built the idea that a creature RPG is not one program but a stack of cooperating systems: runtime shell, map loading, event scripts, party data, battle, inventory, UI, save, and link systems. The midterm asked you to trace concrete behavior through records, globals, callbacks, tasks, scripts, structs, and source functions. Week fourteen is where that tracing becomes more subtle, because battle move execution crosses more subsystem boundaries in less time. A move might read party data, use volatile battle globals, trigger animation, print localized battle text, update HP, set a status timer, consume an item, write outcome-relevant state, and eventually contribute to save-visible progression if the battle ends.

So, if you are designing a minimal clone, do not isolate today’s work from the earlier systems. A minimal map loader puts the player in a place. A minimal script executor lets an NPC or tile event start a battle. A minimal party model provides creatures with stats, HP, moves, status, held items, and experience. A minimal battle turn system selects actions and orders them. A minimal item system gives you potions, status cures, maybe held berries. A minimal save system persists party, inventory, map position, flags, and story state. Today’s battle script interpreter sits inside that stack. It is not the whole game. It is the part that says, once battle control reaches “execute this move,” how do we represent the move’s procedural behavior cleanly?

Now, watch the distinction here. The turn scheduler decides whose action is next. We covered that already with chosen actions and turn-order arrays. The battle script interpreter decides what the selected action actually does, step by step. Those are different layers. If you blur them, your clone becomes harder to extend. You will be tempted to put “if the move misses, skip damage” in the turn scheduler. But the scheduler should not know that. The scheduler should say, “It is battler zero’s turn to execute their chosen move.” The script should then say, “Check accuracy; if failed, go to miss message and end.”

Let me write a tiny pseudo-script on the board in spoken form. Command one: load current move. Command two: check accuracy, jump to miss if false. Command three: play attack animation. Command four: calculate damage. Command five: apply damage. Command six: print effectiveness message. Command seven: maybe apply secondary status. Command eight: check faint. Command nine: end move. Then there is a miss label: print miss message, end move.

In C, or C-like pseudocode, command dispatch might look like a loop that reads opcodes from a byte array. Opcode 01 means accuracy. Opcode 02 means damage calculation. Opcode 03 means apply damage. Opcode 04 means status attempt. Opcode 05 means print message. Opcode 06 means jump. Opcode 07 means end. Each handler can either advance the pointer, modify the pointer, or suspend because it is waiting on animation or message completion. That last part matters. In a frame-based Game Boy Advance game, visible behavior unfolds over time. Battle scripts are not merely mathematical procedures; they are also choreography.

This is where pokefirered/src/battle_script_commands.c chunk_98989865d85f275c becomes conceptually important. The research packet labels it as battle script flow, and that is exactly the lens I want you to use. Flow commands are not glamorous. Students often want to jump straight to damage formulas or status effects. But flow is what makes the script system a VM instead of a list of helper calls. The ability to jump, wait, call another script, return, or end gives the battle engine a compact way to share routines. Miss handling, failed status handling, faint handling, and message timing can be shared instead of pasted into every move.

Now, compare that very briefly with Pokemon Showdown, but keep FireRed as our authority. The packet points to pokemon-showdown/data/moves.ts chunk_d4dc7d8b20aa96e6, where modern move data can include callback-like hooks such as onTry, onTryHit, onHit, damageCallback, and move property fields like accuracy, basePower, category, priority, and target. That is a different architecture. Showdown’s data can hold JavaScript or TypeScript behavior hooks. FireRed’s battle scripts are closer to command streams interpreted by an engine. Both approaches solve the same broad problem: how do we avoid hard-coding every move as one enormous handwritten function? But they make different tradeoffs. Showdown leans into host-language callbacks and testable simulator behavior. FireRed leans into data tables, constants, shared globals, and script command dispatch inside a constrained runtime.

That comparison should sharpen your thinking, not replace the FireRed model. In your homework and quiz for this week, if you say, “A move has data and callbacks,” that is only half credit in FireRed terms. I want you to say, “A move has data, the engine loads current move state, and battle script command dispatch drives effect sequencing through shared battle globals.” That sentence is much closer to the architecture we are studying.

Let’s do another check. Suppose a move deals damage and then poisons the target. Where should the poison roll live in a minimal script interpreter? Not in the turn-order system. Not in the map script system. Not directly in the party storage model. It lives in a battle script command or helper reached by a battle script command, using current move effect data and current attacker-target state. The party model stores durable facts like HP and major status, but the battle script is the active procedure that decides whether to write those facts during this move.

Good. Now let’s talk about shared state, because combat bytecode is only useful if the commands agree on where intermediate results live. A damage command has to leave its result somewhere. A later apply command has to read that result. A status command has to know the target. A message command may need the current move name, target nickname, damage result, status result, or failure reason. This is why battle.h matters as a subsystem map, and we covered that in earlier lectures. I am not going to re-teach every field today. But the source focus includes pokefirered/include/battle.h chunk_a08e84ff1272f826, particularly disable, protect, and special status structures. The architectural point is that temporary battle conditions are first-class state. They are not all stored on the permanent creature record. Disable counters, protection flags, special status bits, side timers, and volatile conditions belong to the battle context.

That split is essential for clone design. Your party data should know that a creature is burned or fainted. It should not necessarily know that this creature used Protect last turn and is now subject to a success-rate penalty, unless your rules make that persistent, which they should not. Your battle state should know that. Likewise, a side condition like Reflect or a per-side timer belongs to battle-side state, not to one creature’s permanent party record. If you get this boundary wrong, save persistence becomes polluted by temporary combat state, and your map-to-battle-to-map transition becomes fragile.

Let me give a concrete clone data layout. On the board, I’m drawing four boxes. Box one is PartyCreature: species, level, current HP, max HP, stats, moves, PP, major status, held item. Box two is BattleBattler: party index, side, temporary stat stages, volatile flags, selected move, selected target. Box three is BattleShared: current attacker, current target, current move id, damage, script pointer, message id, random seed or RNG handle. Box four is BattleSide: reflect turns, light screen turns, spikes layers, escape flags, maybe tailwind turns if you are building later-generation rules. In a FireRed-inspired minimal clone, you do not need all modern side effects. But you do need the categories.

Now, here is the subtle part. Battle scripts should not directly know too much about the permanent party storage shape. If the apply_damage command reaches into PartyCreature and subtracts HP, fine, that is a deliberate bridge. But accuracy_check should not need to know save-file layout. try_status should not need to know map loading. print_message should not know how the bag is serialized. The script VM is powerful because it is localized: it coordinates combat effects while depending on well-defined state fields.

This brings us to item use. You might ask, why are we talking about item use in a lecture on battle scripts? Because item use is the test of whether your architecture has clean action boundaries. A potion used in battle is not a move, but it still participates in the battle turn system. It consumes an inventory item, targets a party creature or battler, changes HP, prints messages, may trigger animations, and then returns control to turn progression. In FireRed’s broader architecture, inventory, battle controllers, battle messages, and party data all intersect. In your minimal clone, you can choose whether item effects are implemented as battle scripts or as action handlers that call some of the same command helpers. The important thing is that item use should not bypass all the state coordination. It should still respect turn timing, message timing, faint or HP constraints, and persistence boundaries.

For example, a Potion action might have a small script: check item available, consume item, heal target HP, print used item message, print HP restored message, end action. That script is not identical to a move script because there is no accuracy roll or damage formula. But architecturally, it is the same kind of command sequence. That is a strong design option for your clone: one generic action script VM, with move scripts and item scripts as different script entry points. The danger is overgeneralizing too early. For the lab, a battle script interpreter for moves is enough. But when you design the larger clone, notice how item use pressures you toward reusable command machinery.

Let’s pause and recap the model so far. We have a turn scheduler from the prior lecture. It picks the next action. For a move action, it loads current move state and enters a battle script. The script interpreter reads command bytes or command IDs, dispatches to handlers, and those handlers use shared battle state. Some handlers compute numbers, some mutate HP or status, some request animation or messages, and some control flow. Durable outcomes are written back to party data or battle result state. Temporary effects remain in battle-local structures like the protect, disable, special status, and side timer state represented in the battle subsystem, with pokefirered/include/battle.h chunk_a08e84ff1272f826 as our source anchor for that kind of structure.

Now, let’s talk about damage and hit effects without getting ahead of tomorrow’s lecture. Tomorrow we will spend more time on damage and status through shared state. Today I just want you to see why damage belongs naturally in the bytecode model. Damage calculation is not one operation. There is “can this hit?” There is “what is the base power?” There is “what attacking and defending stats are being used?” There is “what modifiers apply?” There is “what number did we produce?” There is “did we hit substitute or the target?” There is “does this trigger an effect after damage?” There is “did the target faint?” That is a pipeline. Battle scripts let the engine express the pipeline while still routing specialized calculations to utility code.

Again, pokefirered/src/battle_util.c chunk_9335e5727aa0657d is relevant because battle utility and move support sit next to constants for moves, abilities, weather, items, and hold effects. That source relationship supports the claim that damage is not just arithmetic. It is arithmetic embedded in a rule environment. A clean clone should mirror that at a small scale. Start with a damage helper that takes attacker, defender, move, and battle context. Do not hide global reads everywhere at first. Then allow command handlers to call that helper and store the result in battle state. Later, as you add abilities, weather, items, and status modifiers, you have one place to extend the rule calculation instead of fifty move functions to edit.

Let’s make Spark Tap more precise. In our minimal bytecode, Spark Tap’s script might run accuracy_check with parameter 95. If it fails, jump to script_miss. If it succeeds, call calc_damage using the current move’s power 40 and type Electric. Then apply_damage subtracts the stored damage from the target’s current HP. Then maybe_status reads the move’s secondary effect: paralysis, 20 percent. But maybe_status must also check whether the target can be paralyzed. Maybe Electric types are immune in your rule set, or maybe you are modeling FireRed-era rules where type interactions differ from later expectations. This is where source discipline matters. Decide the rule generation you are modeling. FireRed first, Showdown as comparison only.

Then print messages. “It dealt damage.” “It’s super effective.” “The target is paralyzed.” These are not afterthoughts. In a game like this, the message and animation hooks are part of the battle procedure. If you mutate HP immediately but show messages later, you need the UI to remain coherent. If a target faints, you need faint animation and party replacement flow. If you are in a link battle, you need both sides to converge on the same result. So the script command sequence is both rule execution and presentation choreography.

Check for understanding: why not just calculate the entire result first, then play back a log? That is a valid architecture in some simulators. Pokemon Showdown often behaves more like a deterministic simulator with event logs, and the packet’s Showdown move data chunks, such as pokemon-showdown/data/text/moves.ts chunk_1620277751dc01f4 and chunk_92a6298340525a85, remind us that Showdown has rich textual descriptions and effect metadata around turn behavior. But FireRed is built for a frame-based console engine where battle controllers, animations, messages, and state transitions are tightly coordinated during execution. You can still think in terms of logs for testing your clone, but if you are imitating FireRed architecture, the script interpreter must be able to suspend and resume around visible effects.

So, for your lab interpreter, I recommend this minimal command set: accuracy, damage, status, message, faint check, and end-turn cleanup. That matches the workshop goal. But design it with an instruction pointer and a dispatch table, not a chain of if statements inside one move. Each command should be small enough that you can explain what state it reads, what state it writes, and how it advances the script pointer. If you cannot answer those three questions, your command boundary is probably fuzzy.

For instance, accuracy reads current move accuracy, attacker accuracy modifiers if you implement them, defender evasion modifiers if you implement them, and battle RNG. It writes a hit flag or redirects the script pointer to a miss branch. Damage reads attacker stats, defender stats, move power, move category, type chart, random factor if present, and modifiers. It writes battle damage. Apply damage reads battle damage and target HP. It writes target HP and maybe a last damage field. Status reads effect chance, target status, immunities, and RNG. It writes major status or a failure reason. Message reads message id and state substitutions. It writes to the message system or queues text. Faint check reads target HP. It writes faint flags or enters faint handling. End-turn cleanup reads timers and volatile effects. It writes decremented timers, residual damage, expired flags, and queued messages.

Notice how this gives you testing seams without turning the lecture into generic software design advice. You can unit test damage helper behavior. You can integration test a Spark Tap script with fixed RNG. You can test that a miss does not apply damage. You can test that paralysis does not apply if the target already has a major status. And in the spirit of the recent assessments, you should be able to trace from data record to runtime state to script command to visible behavior. That trace is the course’s core skill.

We will continue from here into actual command dispatch structure, including how jumps, waits, and command parameters make this bytecode model expressive enough for real battle behavior. For now, keep the local synthesis in mind: FireRed-style battle scripts are not just “move effects.” They are a compact combat procedure language sitting inside the battle engine, using shared state to connect move data, damage, status, animation, messages, fainting, item use pressures, and eventual persistence boundaries.

So, let’s pick up exactly there: if this is bytecode, then the most important engineering question is not “where is the move effect?” The better question is, “who owns the instruction pointer, and what shared state is each instruction allowed to mutate?”

On the board, I’m going to write three names. First, script pointer. Second, battler globals. Third, command handlers. Those are the three moving pieces I want you to hold together. The script pointer says, “what instruction are we executing next?” The battler globals say, “who is acting, who is being targeted, what move is current, what damage has been staged, what status flags are pending?” And the command handlers are the C functions that interpret one byte, or one opcode plus parameters, as a piece of battle behavior.

This is why the file focus for today matters. The research packet points us to pokefirered/src/battle_script_commands.c, especially chunk_9779e386ddb42cb0 for battle script commands, chunk_c405e26d69cb40ac for script command effects, and chunk_98989865d85f275c for battle script flow. Those chunks are not just a miscellaneous pile of battle helper routines. They are the interpreter surface. They are where compact script data gets turned into mutation of the battle state and eventually into text, animation, HP changes, status changes, or control-flow decisions.

Now, notice the design pressure. If you are building a Game Boy Advance RPG, you do not want every move to be a full custom C function with its own manually scheduled animations, messages, hit checks, damage updates, faint checks, and cleanup. That becomes enormous, and worse, inconsistent. You want a small command language. Accuracy check. Damage calculation. Type modifier. Apply damage. Print message. Play animation. Jump if missed. Jump if fainted. Set status if allowed. Wait. End. That is the bytecode vocabulary.

And this is where the advanced part begins. A naive student implementation says, “I’ll make a function useMove(move, user, target), and inside it I’ll do all the logic.” That is fine for a toy demo. But it is not enough for a FireRed-style architecture, because many battle behaviors need to interleave calculation, state mutation, messages, controller waits, animations, and branching. A move is not only a mathematical operation. A move is a scheduled combat performance.

Let me say that again because it is the key sentence for today: in this architecture, a move is not merely computed; it is performed by an interpreter.

Slide cue here. Imagine a simple script for our clone move Spark Tap. The script is not real FireRed syntax, but it has FireRed-like structure. It says: accuracy check; if miss, jump to missed label; calculate damage; play attack animation; wait animation; update HP bar; print damage message if needed; try paralysis; check faint; end. The user sees one smooth event. But internally, the engine is stepping through small commands, many of which pause until another subsystem is finished.

That last phrase, “pause until another subsystem is finished,” is important. Battle scripts are not just bytecode in the CPU emulator sense where everything runs to completion immediately. They live inside the battle main loop we studied last week. We already covered the battle main callback and the turn-order arrays, so I will not re-teach that machinery. But bring it forward mentally. The script command interpreter can advance a few commands, then hand control to animation or controller code, then resume later. That gives the battle engine the feeling of sequencing without making every sequence a giant hand-written state machine.

Let’s do board work. I’m writing a minimal interpreter loop.

“opcode equals read byte at pc. pc advances. switch opcode. command handler mutates context. command handler may change pc. command handler may request wait.”

That is the model. The word “may” is doing real work. Most commands simply continue to the next instruction. A jump command overwrites the program counter. A wait command effectively says, “do not proceed until the animation, message, or controller condition has completed.” A damage command probably does not immediately decide the whole move; it stages a value into shared battle state. A later command consumes that staged value to update HP.

Check for understanding: if damage calculation and HP bar update are separate bytecode commands, where does the damage number live between them? Not in the script. The script is compact. It does not carry a rich stack frame like a high-level VM. The value lives in shared battle state, in the same family of global battle variables we have been tracing throughout this module. That is why pokefirered/include/battle.h, chunk_a08e84ff1272f826, matters as more than declarations. It is a cross-module contract for status structures, disable/protect state, special status, side timers, and the sort of shared battle memory that script commands coordinate through.

Now, I want to distinguish this from a clean-room modern design. If I were writing a server simulator in TypeScript today, I might represent a move as an object with callbacks. Pokemon Showdown often does this. In pokemon-showdown/data/moves.ts, chunk_d4dc7d8b20aa96e6, you see move records carrying fields such as accuracy, basePower, category, flags, and callbacks like onTry, onTryHit, onHit, or damageCallback. That is a very different expression style. It is still data-driven, but the extensibility point is object callbacks in a high-level runtime. FireRed’s battle scripts are lower level: compact bytecode plus C command handlers plus global state. Both approaches answer the same architectural problem: how do we make many move behaviors share a battle engine without hard-coding every move as a separate monolith?

Do not overstate the comparison. Showdown is secondary for us. FireRed is the primary architecture source. The value of the comparison is that it makes the design choice visible. FireRed says, “represent battle procedures as scripts interpreted by command handlers.” Showdown often says, “represent special effects as callbacks on data objects.” The reason I bring this up is not to claim one is better universally. It is to train you to see the shape of the engine from the shape of the extension point.

Now let’s walk a concrete turn through the interpreter. Suppose battler zero uses Spark Tap against battler one. Again, Spark Tap is our clone example. Its move data says base power 40, type Electric, accuracy 100, effect paralysis chance 10 percent. The battle turn system has already selected this action and placed the acting battler in the appropriate turn slot. We are not redoing action selection today. We start at execution.

The engine sets current move data. That means the script commands can ask, “what is the current move’s type, power, accuracy, target, effect?” They do not need each instruction to embed those fields. Then the script pointer is set to the generic damage-move script, or to a specialized script that eventually calls common fragments. The first command might cancel the attack if the user is unable to move. Paralysis, sleep, flinch, confusion, recharge, and other conditions can intervene here. Some of those checks are not “the move effect”; they are preconditions on execution. This distinction is crucial for your lab. Do not put all status behavior inside the damage command. Status affects turn permission, accuracy, damage modifiers, end-turn cleanup, and messaging at different times.

Then we reach accuracy. Accuracy is a command because it can branch. If the move misses, the interpreter does not want to calculate damage and then discard it. It wants to jump to a miss sequence: maybe print “But it missed,” maybe perform a different animation response, maybe end the move without applying secondary effects. So the accuracy command reads parameters from the script: possibly a pointer to jump to on failure. In bytecode terms, this is control flow. In battle-design terms, this is a clean way to make misses share behavior.

Pause here. What is the minimum clone version? In your workshop, I do not expect a perfect recreation of FireRed’s accuracy formula. I expect an interpreter command named something like CMD_ACCURACY_CHECK. It reads the current move accuracy and a deterministic RNG source. If failure, set script pointer to the miss label. If success, continue. That is enough to prove the architecture. The homework for week 14 will care more about this separation than about exact cartridge-accurate numbers.

Then damage calculation. The command does not need to animate anything. It should compute staged damage using current move, attacker stats, defender stats, modifiers, and random factor if you include one. FireRed’s support logic is not isolated to the script command file. The research packet points us to pokefirered/src/battle_util.c, chunk_9335e5727aa0657d, which includes battle utility and move support with dependencies on battle.h, battle_anim.h, battle_scripts.h, battle_message.h, battle_controllers.h, constants for moves, items, weather, abilities, Pokemon, hold effects, and battle move effects. That include set is a hint about coupling. Damage and move support live in a dense neighborhood of systems. The battle script command can orchestrate, but utility code often supplies reusable checks and calculations.

Now, if you are designing the clone, resist the temptation to import that whole neighborhood at once. Minimal version: PartyMon has species, level, current HP, max HP, attack, defense, speed, status. Move has id, name, power, accuracy, type, category, effect id. BattleContext has attacker index, target index, current move id, staged damage, RNG seed, script pointer, message queue, animation queue, and maybe flags like hitResult. That is enough for this lab. Later, if you add items, abilities, weather, and side conditions, you add them as shared state read by commands, not as ad hoc arguments passed through every function.

Let’s continue the Spark Tap turn. Damage command stages 17 damage. Then the animation command asks the animation subsystem to play Spark Tap. It may set a wait state. The interpreter stops advancing for this frame. The main battle loop continues doing what we studied last week: callbacks and tasks keep the system alive. When animation reports complete, the script resumes. Next command updates HP. This command consumes staged damage, subtracts it from the target’s current HP, clamps at zero, and requests a health bar update. Again, that may wait.

You can see how this produces the visible order players expect. The attack animation appears, then the HP bar moves, then the message or fainting response appears. The script encodes that order explicitly. If all you had were a single damage function, you would have to bury the presentation sequence inside it, or build some second structure later. FireRed’s approach keeps the sequence as data-like script flow and the implementation as reusable command handlers.

Now let’s ask a harder question. Where does paralysis chance belong? Is it part of damage calculation? No. Is it part of HP update? No. It is a later effect command. The move hits. Damage is applied. Then the effect command checks whether this move has a secondary effect, whether the random chance succeeds, whether the target can receive the status, whether the target already has a major status, and whether type or ability-like restrictions apply in the full engine. If yes, it sets the target status and queues the message.

The exact FireRed implementation details are richer than our clone, but the source organization supports this mental model: pokefirered/src/battle_script_commands.c chunk_c405e26d69cb40ac is identified in the research packet as script command effects. That matters because “effects” are not merely descriptive fields on moves. They are procedural branches and state mutations executed by command handlers.

Check for understanding. Suppose Spark Tap misses. Should the paralysis command run? No. How do we ensure it does not run? We do not put a boolean check in every later command saying “if missed then return.” That would spread control-flow policy everywhere. Instead, the accuracy command jumps around the hit path to a miss path. That is the bytecode advantage. The instruction pointer encodes the path.

Now suppose Spark Tap hits but deals zero damage because of an immunity. Should paralysis run? In many battle systems, that depends on the generation and effect semantics. I am not asking you to memorize the rule right now. I am asking you to identify where the policy belongs. It belongs either in the type/effect result state that later commands consult, or in the script branch that chooses whether the secondary effect command is reached. That is an architecture answer. The exact rule is data and command policy.

Let’s connect this to side timers and status structures, because this is where students often flatten everything into one status field. A major status like paralysis is one kind of persistent condition. A side timer like Reflect, Light Screen, or a protection-like effect is not the same thing. The include focus on pokefirered/include/battle.h chunk_a08e84ff1272f826 explicitly points us toward disable, protect, special status structures, and related battle state. In a minimal clone, you may start with target.status. But by the end of the week, you should be thinking in multiple state buckets: per-battler major status, per-battler volatile status, side conditions, turn-local flags, and script-local staging values.

Here is a concrete design exercise. I’ll say it aloud as if I am reviewing your lab architecture.

You have a map loader. It reads a map id, loads a layout, places object events, and sets the player position. You have an overworld script executor. It can show text, set flags, give items, and start a battle. You have party data. It stores creatures and moves. You have battle turns. They select actions and then execute scripts. You have item use. It can mutate party or battle state, depending on context. You have save persistence. It serializes durable state such as party, flags, bag, location, and maybe play time. Week 14’s battle script interpreter sits inside that larger chain.

So when a trainer gives the player a Potion before a battle, the overworld script changes the bag. When the player uses that Potion in battle, the item command path changes battle-visible HP and queues a message. When the battle ends, the party’s HP state persists back into the party model. When the player saves, that party state goes into save data. The battle script does not own all persistence. It mutates runtime battle and party state through controlled boundaries. This is the same course-wide trace: data record to runtime globals to callback, task, script, or command to visible behavior to persistence.

Let me use a board cue. Draw a vertical line. On the left, write durable world. On the right, write transient battle. Durable world includes save flags, map id, party, bag. Transient battle includes current move, attacker, target, staged damage, turn flags, animation wait, script pointer. Some data crosses the boundary. Party HP crosses. Consumed items cross. Experience and money cross. But the script pointer does not cross. The animation queue does not cross. The staged damage value does not cross. If your save file contains “current script command 37 of Spark Tap,” you have probably made the wrong persistence boundary for this course project.

Now, there are games that support saving mid-battle. That is a different design. FireRed’s ordinary save model is not teaching us that today. Our clone objective is to design minimal versions with sane boundaries, not maximum feature coverage.

Let’s return to command parameters. A bytecode command becomes expressive when it can carry operands. Accuracy check may carry a jump pointer. Print message may carry a message id. Set status may carry a status enum. Play animation may carry a move animation id or use current move implicitly. Jump may carry an absolute script pointer or relative offset. Call may push a return location, if the engine supports script subroutines. End may signal the move script is complete.

In C source, this often looks less romantic than “virtual machine.” It may look like functions reading bytes from a pointer and incrementing that pointer manually. That is still a VM pattern. Do not reserve the word bytecode only for languages with a stack, heap, and verifier. In embedded game engines, a bytecode can be a compact command stream interpreted by C functions. The sophistication lies in the contract: each opcode has a fixed meaning, a fixed parameter layout, and predictable state effects.

The battle_script_commands.c flow chunk, pokefirered/src/battle_script_commands.c chunk_98989865d85f275c, is the one to keep in mind when you are thinking about jumps, waits, and command progression. Flow is not decoration. Flow is what makes combat behavior composable. Without flow, every move effect degenerates into a pile of if statements.

Let’s do a small verbal trace. I will play interpreter, and you tell me what state changes.

Program counter at accuracy check. Read opcode. Read miss label address. RNG says hit. Program counter continues.

Program counter at damage calculation. Read opcode. Compute damage. Store stagedDamage equals 17. Program counter continues.

Program counter at animation. Queue current move animation. Set wait mode to animation. Program counter may already point to next command, but interpreter pauses.

Next frame. Animation not done. No command executes.

Later frame. Animation done. Program counter at HP update. Subtract 17 from target HP. Queue health bar update. Pause.

Later. HP bar done. Program counter at secondary effect. RNG succeeds. Target status is none. Set target status paralysis. Queue message.

Then faint check. Target HP is greater than zero, so no jump to faint sequence. End.

That is the whole architecture in miniature. And notice, nothing in that trace required the map loader, the save system, or the party serializer. But the turn began because an overworld or trainer battle setup placed party data into battle resources, and the result will eventually be reflected back. That is why week 14 is not an isolated battle-engine week. It is where the earlier systems start to pay rent.

Now I want to address one subtle bug class. If command handlers directly print messages, directly animate, and directly mutate HP in one synchronous call, your tests become brittle. A better minimal clone is to have command handlers append events: MessageEvent, AnimationEvent, HpChangeEvent, StatusEvent. Then your runner can process or assert those events. FireRed itself has hardware-era controller and animation constraints, so the exact event abstraction will differ, but the principle maps well for your project: script commands should coordinate visible behavior through hooks, not through random direct UI calls scattered throughout move logic. The week context explicitly includes animation and message hooks, so include them in the lab even if they are simple arrays.

Here is the testing angle. The recent quiz and homework have already checked whether you can trace systems from source files and state variables to behavior. So for week 14, I am going to expect more than “my battle prints a damage number.” A good test says: given fixed RNG and Spark Tap at 100 accuracy, the interpreter executes accuracy, damage, animation, HP update, status attempt, faint check, end, and emits events in that order. Another test says: given accuracy zero, the interpreter jumps to miss message and does not emit HP update or status events. Another says: given target already poisoned, paralysis attempt emits no new paralysis status. Another says: given target HP less than damage, HP clamps to zero and faint path runs.

Check for understanding. Why fixed RNG? Because the script interpreter is sequencing state changes, and we need deterministic tests to distinguish a logic bug from a random branch. This is the same reason Pokemon Showdown is useful as a secondary comparison in the course. Its data and simulator style often support deterministic regression tests around move behavior. But we are still modeling the FireRed-style bytecode architecture here, not replacing it with Showdown’s callback model.

Let’s tie in move text briefly. Pokemon Showdown’s pokemon-showdown/data/text/moves.ts chunk_1620277751dc01f4 and chunk_92a6298340525a85 show rich move descriptions and messages for modern effects. Those chunks remind us that player-facing combat behavior has a language layer: descriptions, start messages, damage messages, and effect messages. FireRed has its own battle message machinery. In our clone, a message id is enough. The important point is separation. The battle command decides that a message should be shown. The message system decides what text that id maps to. The renderer decides how it appears. If you hard-code English strings inside every command handler, you will finish the lab faster but lose the architectural lesson.

Now, I’ll make the connection to item use, because item use looks like it should be separate from battle scripts, but it pressures the same design. A Potion in battle is also a scripted sequence: check item validity, choose target, apply HP recovery, update HP bar, print message, consume item, return to turn flow. It may not use the exact same battle script bytecode path as a move, depending on implementation, but architecturally it is the same kind of problem. It is a combat procedure that mutates shared state and schedules visible feedback. If your minimal clone has ItemEffect functions, have them emit the same kinds of events as battle commands. That gives you one battle presentation pipeline.

In a more advanced clone, you might unify move scripts and item scripts under one interpreter. That is an interesting design, but not required. FireRed has many specialized systems. The lesson is not “everything must be one VM.” The lesson is “when behavior must be data-driven, interruptible, and visible over time, a compact command interpreter is a powerful fit.”

Let’s pause for a self-check. If I ask you, “What is the difference between move data and battle script?” your answer should be: move data says what the move is, with fields like power, accuracy, type, effect, target, and flags. Battle script says what procedure is performed to execute a move or effect: checks, calculations, messages, animations, branches, status application, faint checks, cleanup. If I ask, “What is the difference between staged damage and HP?” staged damage is temporary execution state; HP is party or battle battler state that may persist after battle. If I ask, “What makes the script bytecode?” it is the compact opcode stream plus interpreter dispatch plus command parameters and instruction pointer control.

Let me add one more concrete clone design. Your script representation can be an array of small objects at first, even though FireRed uses compact script data. For example, you can represent commands as records in your host language: { op: ACCURACY, miss: 8 }, { op: DAMAGE }, { op: ANIM }, and so on. That is acceptable for learning. But name the architecture honestly. If each record is interpreted by a dispatcher and advances a program counter, you have preserved the bytecode model conceptually. Later you can pack it into bytes if you want to study memory layout.

For command dispatch, avoid making each command call the next command recursively. Use a loop that advances while no wait is requested. Recursion hides the program counter and makes waits awkward. The interpreter should be able to say, “I executed three commands this tick and stopped because animation is pending.” That maps well to the callback and task style we have carried forward since week 1.

And here is the bridge to tomorrow’s lecture, without ending today’s. Once you accept that scripts are mutating shared battle state, damage and status become less like isolated formulas and more like coordinated state transitions. Damage calculation writes a value. Type calculation modifies flags. Status commands inspect major status, special status, side timers, and current move effect. End-turn cleanup decrements timers and applies residual damage. That is why the second lecture this week is “Damage and Status Through Shared State.” Today we are building the interpreter lens; next we will use it to reason about the values flowing through that interpreter.

Before we move into a deeper source-shaped dispatch model, I want you to hold onto a practical design rule for the lab: every battle command should have a narrow contract. Accuracy command decides hit path. Damage command computes staged damage. HP update command applies staged damage and queues HP feedback. Status command applies or rejects status. Message command queues text. Faint check command branches. Cleanup command clears turn-local state. If a command is doing five of those at once, your interpreter is becoming a disguised monolith.

Now, take a breath and look back at the course arc. We began with the runtime shell: main callbacks, tasks, per-frame dispatch. We moved through maps and scripts: data records becoming world behavior. We moved into party data and battle setup: durable creatures becoming battle participants. Last week we focused on battle main state, chosen actions, and turn order. Today we are inside the move execution layer. The same pattern keeps repeating at smaller scales. A table selects behavior. A pointer or id chooses the next routine. Shared state carries context. A callback or interpreter advances in small steps. Visible behavior emerges from those steps.

That repetition is not accidental. It is the architecture of this kind of RPG. And as we continue, I want you to stop seeing “battle scripts” as a quirky implementation detail. They are the combat version of the broader course thesis: data-driven behavior executed by small state machines, tied together through explicit runtime state.

Good. Now let’s sharpen that into an implementation model you could actually build.

If I were asking you to clone only the architectural idea, not FireRed’s full behavior, I would not start by writing “use Thunderbolt” as one giant function. That is the tempting beginner design. You write a function called ExecuteMove, it checks accuracy, computes damage, applies paralysis, prints a message, checks fainting, and returns. That works for three moves. It collapses under fifty. It absolutely collapses under hundreds, because now every exception wants to interrupt the sequence.

So, on the board, I’m going to write a tiny combat bytecode stream:

accuracy check, jump if missed, damage calc, apply damage, maybe status, message, faint check, end.

That is the mental model. Each command is small. Each command knows how to read and write shared battle state. Each command either advances the script pointer or redirects it. That script pointer is the combat equivalent of the map script pointer we studied earlier, but with battle-specific globals and battler-specific context.

In FireRed, the source focus for today is exactly that layer: pokefirered/src/battle_script_commands.c, especially chunk_9779e386ddb42cb0 for battle script commands, chunk_c405e26d69cb40ac for command effects, and chunk_98989865d85f275c for battle script flow. The reason those chunks matter architecturally is not that every command is individually glamorous. Most are not. The important fact is that move behavior is decomposed into interpreter steps. The battle engine can pause for animation, pause for text, branch on accuracy, branch on fainting, and resume later without needing one enormous call stack.

Check your understanding here: why is “pause and resume” so valuable in a handheld battle engine? Not because C cannot call functions. It can. The reason is that the visible game is staged over frames. A message box has to finish. An animation has to play. A controller may have to report back. A health bar may have to drain over time. The script interpreter gives the engine a place to say, “I know what comes next, but not this frame.”

Now connect that to our lab. Your minimal interpreter does not need FireRed’s full command set. It needs enough commands to express the shape of move execution. I would give it a current user, a current target, a current move record, a damage field, a status field, and a program counter. Then I would define opcodes such as CHECK_ACCURACY, CALC_DAMAGE, APPLY_DAMAGE, TRY_STATUS, MESSAGE, FAINT_CHECK, JUMP, and END. The core dispatch loop reads one byte or enum, calls the handler for that opcode, and lets the handler mutate state. If MESSAGE needs to wait for the UI, it returns “blocked.” If APPLY_DAMAGE needs to animate HP loss, it returns “blocked.” If CHECK_ACCURACY fails, it rewrites the program counter to a miss label.

Here is the concrete example. Suppose Ember is represented as data: power 40, type Fire, accuracy 100, effect burn with some chance. Your move executor does not hard-code Ember. It loads Ember into current move state. The selected battle script says, roughly, check hit, calculate damage, apply damage, try secondary effect, print result, check faint, end. When TRY_STATUS runs, it looks at current move effect, target status, maybe ability or side condition, and random chance. That is the difference between a data-driven battle engine and a pile of move-specific functions.

And notice how this matches pokefirered/src/battle_util.c, chunk_9335e5727aa0657d. That chunk’s include surface shows battle utility code depending on move constants, item constants, weather constants, abilities, hold effects, battle move effects, battle animation, battle message, and battle controllers. That is a very dense architectural clue. Move execution is not isolated arithmetic. It is a broker among data domains: move data, item state, ability rules, animation hooks, controller callbacks, and messages. Your clone can be minimal, but it should preserve that shape.

Let me say that again in clone-design terms. Minimal does not mean “write everything in one function.” Minimal means “keep the same boundaries with fewer cases.” If FireRed has hundreds of commands and effects, your clone might have eight. If FireRed has rich party structs, your clone might have a Creature with hp, maxHp, attack, defense, speed, status, moves, item. If FireRed has complex save blocks, your clone might serialize party, bag, map id, player position, flags, and money. But the architecture should still have boundaries: map loading loads world state; script execution mutates flags and starts battles; party data persists creatures; battle turns consume actions; item use goes through inventory and party or battler state; save persistence captures durable state.

Now, why am I bringing map loading and save persistence into a battle-script lecture? Because week 14 is not a detached combat trivia week. It is the point where the whole course compresses. Earlier we said a creature RPG is a set of cooperating state machines. Battle scripts prove the idea at the most granular level. The map runtime uses data to place you in a world. The event script VM uses commands to change game state. The battle main callback coordinates battlers and actions. The battle script interpreter uses commands to make one move visibly happen. Same pattern, different scale.

Let’s put a vertical stack on the board. At the top: save file. Under that: overworld map. Under that: event script. Under that: battle setup. Under that: battle turn. Under that: battle script command. Now beside each layer I’ll write “data, state, dispatcher.” Save has serialized data, loaded state, and save/load routines. Map has headers and layouts, map objects, and callbacks. Event scripts have bytecode-like commands, event state, and an interpreter. Battle has party and battler data, global battle state, and turn dispatch. Battle scripts have command bytes, current move state, and command dispatch.

That is the course thesis in one diagram.

Now, one subtle point for advanced learners: the current move is not just a move id. In a serious engine, “current move” becomes a working transaction. At minimum, you have the move’s base data, the user, the target, the chosen target, the actual target, calculated type, power, accuracy, flags, damage, secondary effect, and temporary script branches. Some of those fields are copied from data tables. Some are derived. Some are overwritten by effects. This is why battle scripts like shared state. They avoid passing a twenty-argument context object through every command.

But shared state is dangerous if you do not discipline it. FireRed leans into globals because of the platform and the engine style. In a modern clone, you might package that as a BattleContext struct. Do not mistake the packaging for the architecture. Whether the data lives in globals or a context object, the interpreter needs a common mutable workspace. The handler for accuracy must leave behind the result that flow control uses. The handler for damage calculation must leave damage for the HP animation and faint check. The handler for status must see status timers and side conditions.

This is where pokefirered/include/battle.h, chunk_a08e84ff1272f826, is useful as a subsystem map, and I am invoking that from our prior foundation rather than re-teaching it. The disable, protect, and special-status structures tell you that battle behavior is spread across persistent per-battler state, volatile per-turn state, side state, and script-local state. If your clone has only a single “status” field, it can represent poison or sleep, but it cannot yet represent Protect, flinch, encore-like locks, disabled moves, side barriers, or multi-turn effects cleanly. For the lab, that is fine, but you should know exactly what you are omitting.

Let’s do a check for understanding. Imagine your minimal clone has a move called Guard. It prevents damage from the next incoming attack this turn. Where should that live? Not in the move table alone. The move table says Guard applies a protect-like effect. The battle script command applies it. The per-battler or side state stores it. The incoming damage script checks it. End-turn cleanup clears or ages it. If you put all of that inside the Guard function, then every damaging move has to remember Guard manually, and your architecture has already started to rot.

Now let’s compare, briefly, to Pokemon Showdown, because we have used it as a secondary comparison, not as our primary source. The packet points us to pokemon-showdown/data/moves.ts, chunk_d4dc7d8b20aa96e6, where move data can include callbacks like onTry, onTryHit, onHit, boosts, target, type, and category. That architecture is more callback-rich and JavaScript-native. FireRed’s battle scripts instead push much of the visible sequencing into command dispatch. Both solve the same problem: move data must connect to executable behavior. Showdown does it with data objects containing hooks. FireRed does it with tables, globals, command scripts, and C handlers. For this course, FireRed remains the authority because we are studying the GBA-style architecture, but the comparison helps you see that there is no such thing as “just a move table” once effects become interesting.

Now I want to walk through a minimal turn, slowly, at the bytecode level.

Player chooses Ember. Opponent chooses Tackle. We already know, from last week, that chosen actions and turn-order arrays determine who acts first, so I will not re-teach that. Suppose the player acts first. The battle main state selects the action executor for “use move.” That executor initializes the current move context: attacker is player battler, target is opponent battler, move id is Ember, script pointer points to the standard damaging-move script. Then the script interpreter begins.

First command: print attack message. It may enqueue text, then yield. Next frame, message finishes. Interpreter resumes. Second command: check accuracy. Accuracy succeeds, so script pointer advances. Third command: calculate damage. It reads level, attack, defense, move power, type modifiers, random factor, and any active conditions your clone supports. It writes damage into context. Fourth command: play animation. It yields until complete. Fifth command: apply damage. It subtracts HP gradually or immediately, depending on your UI sophistication. Sixth command: try burn. It checks move effect and target status. Seventh command: faint check. If HP is zero, branch into faint script. Otherwise continue. Final command: end move.

That is not merely a list of operations. It is a serialized control-flow representation of combat. The interpreter can splice in sub-scripts. It can branch to a miss script. It can branch to immunity. It can call common routines. It can pause for presentation without losing its place. That is why I am comfortable calling it combat bytecode.

Now, board cue: I’m drawing two columns. Left column says “direct function.” Right column says “scripted command stream.” Under direct function, we have fast to write, easy to debug for one move, hard to interrupt, hard to share presentation flow, hard to compose exceptions. Under scripted stream, we have more initial machinery, but common sequencing, reusable branches, UI-friendly pauses, and data-driven variation. If you are building a weekend prototype, the left column might be fine. If you are building a FireRed-scale battle engine, the right column wins.

Let’s bring items into this, because item use is one of the objectives for the minimal clone. An item is not a move, but architecturally it is similar: data chooses an effect; effect mutates shared state; UI must display staged results; persistence must reflect the change afterward. A Potion in battle should consume inventory, choose a party member or battler, calculate healing, clamp to max HP, display a message, update HP, and then return to turn flow. You can implement this with the same philosophy. You do not necessarily need the same opcode interpreter for items, but you should reuse the same state boundaries. Inventory owns counts. Party owns durable HP. Battle owns battler projections and turn legality. UI owns messages and animations. Save persistence later records the durable result.

A common bug in student clones is to heal only the battler copy and forget the party copy, or to heal the party copy and forget the active battler display. FireRed’s real code has many specific mechanisms around party and battler synchronization, but the architectural lesson is general. Durable creature data and active battler state are related but not identical. Battle scripts mostly operate on active battle state. Save persistence cares about the durable state after the battle or after item use. Your minimal clone should make that synchronization explicit.

Now pause and ask yourself: if a creature faints in battle, when does that become save data? In most creature RPGs, HP and status are durable enough that after battle you still have fainted or damaged party members. So fainting must eventually update party data. But faint animation and faint message are battle presentation. If you serialize during the faint animation halfway through the command stream, you may capture an awkward intermediate state. This is why real engines have disciplined transition points. Scripts mutate working state; end-of-action or end-of-battle routines commit the meaningful durable changes.

This leads to a clean minimal save model. Save should not store “currently executing command 17 of Ember” unless your game supports saving mid-battle, and that is a much harder problem. For the lab clone, save map id, player coordinates, event flags, party creatures, inventory, and maybe story variables. Battle can be reconstructed from party and encounter state when entering battle, but not necessarily saved mid-script. That decision is architectural scope control.

Now let’s connect to map loading. In your minimal clone, map loading might read a map id, load a tile layout, place object events, and attach scripts to triggers. When the player steps on a trainer line-of-sight trigger, the event script starts dialogue, locks movement, loads trainer party data, and requests battle setup. Battle setup creates battler state from party data. Battle turn selection fills chosen actions. Turn order selects who acts. Battle script bytecode executes the selected move. End battle returns a result to the event script. Event script sets defeated-trainer flag. Save persistence records that flag.

That one chain is the whole course in miniature. Map data to event script to battle setup to turn state to move script to result flag to save. If you can implement that minimal path, you understand more architecture than someone who has memorized a hundred move effects but cannot explain where state lives.

Let me give you a more precise implementation sketch in spoken form. You would define GameState with currentMapId, player position, flags, party, bag, and maybe activeBattle nullable. You would define MapDefinition with layout id, warps, objects, triggers, and scripts. You would define ScriptVM with a script pointer, locals if needed, and commands such as message, set flag, give item, start battle, wait. You would define BattleState with battlers, turn order, chosen actions, current move context, side conditions, and battle script VM. You would define BattleScriptCommand handlers. You would define SaveData as a serializable subset of GameState, deliberately excluding transient VM details unless you choose to support resume.

That is the minimal clone version of the FireRed architecture. It is smaller, but it respects the same separation of durable data, runtime state, and staged interpreters.

Now, quick check for understanding. Suppose the player uses an Antidote outside battle. Which systems participate? Inventory, party data, UI message, save persistence. Battle state does not. Suppose the player uses an Antidote inside battle. Which systems participate? Inventory, active battler or party member, battle action flow, message and animation hooks, and later persistence. Same item data, different execution context. That distinction is exactly why “item effect” should not be a random function that directly pokes everything. It needs a context.

Let’s return to battle scripts specifically and talk about branching. Branching is the soul of this design. A damaging move is not linear in practice. It might miss. It might fail because there is no target. It might be blocked by Protect. It might do zero damage because of immunity. It might break a substitute-like condition. It might trigger recoil. It might faint the target. It might faint the user. It might trigger an ability or item. If you encode every possibility as nested if statements inside every move, you get duplicated sequencing. If you encode common checks as script commands with common branches, you get shared behavior.

The FireRed source focus in pokefirered/src/battle_script_commands.c, chunk_98989865d85f275c, is relevant here because battle script flow is not just “run next command.” It is “advance, branch, call, return, and coordinate with other battle subsystems.” The command effects chunk, chunk_c405e26d69cb40ac, is the companion idea: individual commands are where abstract script steps become concrete state mutations. Keep those two categories separate in your head. Flow commands change where we are going. Effect commands change the battle world.

That distinction will matter in your homework this week. When you design your minimal battle script interpreter, I want you to identify which commands are flow and which are effects. JUMP_IF_MISSED is flow. APPLY_DAMAGE is effect. MESSAGE is partly effect and partly presentation scheduling. FAINT_CHECK is a test plus flow. END_TURN_CLEANUP is effect, often with repeated timer logic. That classification keeps your implementation readable.

Let me close this segment with the synthesis I want you to carry into the next part of lecture. Battle scripts are not a decorative layer over combat. They are the mechanism that lets combat remain data-driven while still producing staged, interruptible, highly conditional visible behavior. They sit at the intersection of move data, battler state, party durability, item and ability rules, messages, animations, and turn flow. In the FireRed material, that intersection is visible across pokefirered/src/battle_script_commands.c in the command and flow chunks, pokefirered/src/battle_util.c chunk_9335e5727aa0657d in the move-support dependency surface, and pokefirered/include/battle.h chunk_a08e84ff1272f826 in the battle-state structures we have already used as a map. In your clone, you are not trying to reproduce every exception. You are trying to reproduce the architectural pressure: data selects behavior, an interpreter advances behavior, shared state carries context, and presentation unfolds over time. Next, we will use that model to reason about damage and status as shared-state transformations rather than isolated formulas.

So, picking up from that, when I say “damage and status are shared-state transformations,” I mean something very specific. I do not mean that damage is vague, or that status is just whatever happens to be in a global variable. I mean that the battle script does not own the whole truth. The script advances through commands, but the meaning of those commands is distributed across current move data, battler structs, side conditions, disable and protect state, special status flags, party HP, controller messages, and the turn scheduler we covered last time.

Let me put that on the board. Left side: “script pointer.” Middle: “shared battle state.” Right side: “visible battle event.” Now under shared battle state I am writing HP, status, move result flags, side timers, current move, attacker, target, and message buffer. That middle column is the important one. If you build your clone so that each script command directly prints text, directly subtracts HP, directly decides fainting, and directly moves to the next battler with no common state, you may get a demo working, but you will not get the architecture. The architecture is that commands cooperate through a shared state model.

This is why the FireRed source is such a good study object here. The relevant source focus for today is not one neat “damage.c” file that explains everything in isolation. The battle script command work is in pokefirered/src/battle_script_commands.c, especially chunk_9779e386ddb42cb0 for command dispatch, chunk_c405e26d69cb40ac for command effects, and chunk_98989865d85f275c for script flow. But the move support and battle utilities reach outward through pokefirered/src/battle_util.c chunk_9335e5727aa0657d, which includes battle, battle scripts, battle message, battle animation, battle controllers, constants for moves, items, weather, abilities, hold effects, and battle move effects. That include surface is evidence of the point: move execution is not a pure formula. It is a coordination problem.

Now, let us make that concrete. Suppose our minimal clone has a move called Ember. I choose Ember because it is simple enough to reason about, but it still has the key properties: accuracy, damage, optional secondary burn, message output, animation output, faint check, and end-turn cleanup. In a naive implementation, you might write a function called useEmber, and inside it you might say, check hit, calculate damage, subtract HP, maybe burn, print messages, then return. That is fine for a toy battle if you never need interruption, reuse, or special cases. But FireRed’s architecture pressures us toward a different shape. We define move data that says Ember has a power, type, accuracy, effect chance, and effect code. Then the battle action selects the current move and current target. Then a script runs commands such as accuracy check, attack animation, damage calculation, damage application, secondary effect, message, faint check, and return.

Notice the separation. The move is not the script. The script is not the whole battle. The battler is not the party record. But they are connected by shared state.

Check for understanding here: if the script command “try damage” runs, where should it get the move’s base power? Not from a hardcoded Ember branch. It should get it from the current move data. Where should it get the attacker’s attack or special attack? From the current attacker’s battle state, which itself was initialized from party data. Where should it store the result? Not only in the defender’s HP. It also needs to update move result flags, damage variables, and possibly message context, because later commands need to know whether the move missed, whether damage was zero, whether a substitute or protection effect changed the outcome, or whether the target fainted.

And this is where advanced students often underestimate the design. You already know how to write the damage formula. That is not the hard part anymore. The hard part is designing the small number of shared variables and flags that allow many commands to communicate without making every command know every other command.

Let me draw a very small clone version on the board. We have a BattleState object. Inside it, battlers array. Party array, or references to party members. Current attacker index. Current target index. Current move id. Current script pointer. Damage accumulator. Result flags. Pending message id. Pending animation id. Then maybe a side state object for reflect, light screen, spikes, safeguard, and timers. For our lab this week, we do not need every side condition. But we do need the category. We need to feel why include/battle.h matters. The packet points us to pokefirered/include/battle.h chunk_a08e84ff1272f826 for disable, protect, and special status structures. Those are not random details. They are the memory shapes that let a turn remember things like “this battler used Protect,” “this battler is locked into a move,” “this battler has a temporary condition,” or “this side has a timer running.”

Now compare that with a modern simulator style briefly, because you have seen Pokemon Showdown in earlier homework as a secondary comparison. In pokemon-showdown/data/moves.ts chunk_d4dc7d8b20aa96e6, move data can include callbacks like onTry, onTryHit, onHit, boosts, damageCallback, and so on. That is a very different implementation style from FireRed’s bytecode-like battle scripts, but architecturally it is solving a related problem: move data triggers staged behavior through hooks that interact with shared battle state. Do not collapse those into the same architecture. FireRed’s lesson for us is the interpreter and command stream. Showdown’s comparison helps you see the same design pressure in a callback data model.

So, in our clone, I want you to think in terms of minimum viable bytecode. The command set can be tiny. Accuracy. Damage. Apply status. Message. Animation. Faint check. End. Maybe jump if miss. Maybe jump if status failed. That is enough. If your interpreter can run those commands from a list, and each command mutates BattleState in a controlled way, you have captured the architectural idea.

Let us walk an example slowly. The player’s Charmander uses Ember against a wild Caterpie. The turn-order array, which we discussed last lecture, has already selected Charmander’s action first. I am not going to re-teach battler actions or turn order today; use that as prior foundation. The battle main callback advances the battle state and eventually enters move execution. Now the engine sets current attacker to player battler, current target to enemy battler, current move to Ember, and current script pointer to the standard attack script or a move-effect-specific script. The interpreter reads the next command. Accuracy command.

What does accuracy command do? It reads the current move’s accuracy. It reads attacker and target modifiers if our clone has them. It rolls randomness. If the move misses, it sets a result flag and changes the script pointer to the miss branch. That branch might queue a message, “Charmander’s attack missed,” and then end the move. If it hits, the script pointer advances.

Next command: animation. In FireRed, animation and message hooks are part of the visible staging of battle. The research packet’s source focus explicitly includes animation and message hooks, and pokefirered/src/battle_util.c chunk_9335e5727aa0657d shows this dependency surface by including battle_anim.h, battle_scripts.h, and battle_message.h together. That does not mean every utility function is an animation function. It means the move support layer is compiled in a world where animation, scripts, and messages are neighboring systems. In your clone, queueAnimation(EMBER) is enough. Do not build a renderer first. Build the timing contract.

Next command: damage calculation. It computes damage and stores it in state.damage. Here is the subtle part: it may not subtract HP immediately, depending on how you model the command set. Some engines combine calculate and apply. Others separate them. FireRed has a rich command structure, and we are simplifying, but the architecture benefits from keeping “calculate damage” and “apply damage” conceptually separate. Why? Because other commands may need to inspect the computed value, clamp it, modify it, display it, or trigger replacement behavior. In a small clone, separation also makes tests cleaner. You can test that damage calculation returns 8, then test that applying 8 to a 20 HP target leaves 12.

Next command: apply damage. This is where party durability becomes visible. The battler’s active HP changes, but eventually that HP must be reflected in the party creature record. Remember the overall course objective today: we are not only designing battle turns. We are connecting map loading, script execution, party data, battle turns, item use, and save persistence into one minimal clone. The battle is not an isolated arcade mode. If Caterpie takes damage and later the battle ends, the party and save systems must have a consistent idea of who is fainted, who has HP, and what status persists. That is why the party data model matters even during battle script interpretation.

Now a secondary effect command runs. Ember has a chance to burn. That command checks whether damage happened, whether the target already has a major status, whether the target is immune in whatever simplified rule set we choose, then either applies burn or skips. Again, status is shared state. The command should not only print “Caterpie was burned.” It should set the status field, set a message context, maybe set a move result flag, and then let the message command display the appropriate text.

Here is a useful design test. If I remove the message renderer from your clone, can the battle logic still run? It should. If I remove the animation renderer, can the battle logic still run? It should. If I remove the status field, can the message renderer correctly know Caterpie is burned? No, because the message renderer should not be the source of truth. That separation is one of the reasons battle scripts scale.

Let me pause for a check. Imagine a script command named CmdApplyBurn. It directly searches the UI labels on screen, changes the target’s name color, prints “burned,” subtracts end-turn burn damage, and marks the save file dirty. What is wrong with that command? The issue is not that those outcomes are unrelated. They are related. The issue is that the command has crossed too many boundaries. Applying burn should mutate battle state and perhaps queue presentation. End-turn burn damage belongs to end-turn cleanup. Save persistence belongs after battle state is committed back to party and game state. UI color belongs to rendering. The command’s job is narrower than the visible event.

Now, connect this backward to map loading and overworld scripts, because week 14 is not floating by itself. Earlier in the course, when we traced data table to callback to visible behavior, we saw that the map loader does not permanently own the player’s whole story. It loads a map header, layout, objects, warps, scripts, and state. Then the overworld runtime and event script system interpret conditions and advance gameplay. The battle script interpreter is a sibling idea in a more constrained domain. It is a small VM for combat events, just as overworld scripting is a VM for field events. The difference is that battle scripts operate over a dense, volatile state space: HP, speed, accuracy, status, protection, current move, move effects, fainting, party updates, and controller synchronization.

So if I ask you in homework_w14 to design minimal clone versions of map loading and battle script execution, I am not asking for two unrelated demos. I am asking you to show the same architectural pattern in two places. Map loading takes static map data and produces a runtime scene. Battle script execution takes static command data and produces a runtime combat sequence. Both need an interpreter or dispatcher. Both need shared state. Both need persistence boundaries. Both need presentation hooks.

Let us bring item use into the same model. Suppose during battle the player uses a Potion. In the turn system, that is an action chosen by a battler. We covered chosen actions and battler globals already, so we will just use that foundation. The item action does not use the normal damage script, but it still benefits from script-like staging. Check item validity. Consume item from bag. Heal target party member. Queue message. Update HP bar. Check whether the battle can continue. Return to turn flow.

If you hardcode all of that inside the menu callback, you create a boundary problem. The bag UI should choose an item. It should not be the only system that knows how HP restoration affects battler state. In a minimal clone, you can implement useItem(itemId, targetPartyIndex, battleState), but be disciplined about which state it updates. The bag count belongs to inventory. The HP belongs to party data and active battler state. The message belongs to battle presentation. The save file should persist the inventory and party result after the battle or after the field item use completes. That is the kind of cross-system thinking expected at this point in the course.

Now I want to make the bytecode analogy precise, but not mystical. “Bytecode” here means a compact command representation interpreted by an engine. It does not have to literally be Java bytecode, and in your clone it does not even have to be bytes. You may represent commands as enum objects in Python, JavaScript, C, or whatever your lab language uses. The architectural qualities are what matter: commands are data; dispatch selects handlers; handlers read and write a shared state; control flow can branch; execution can pause for animation or message completion; then resume.

That pause-and-resume point is easy to miss. FireRed is a frame-based game. It cannot just run every battle consequence instantly and block the system until the whole turn is done. The visible battle experience is staged across frames. Messages wait for acknowledgement or timing. Animations play. HP bars drain. Fainting animations happen. The battle script pointer is what lets the engine say, “I am partway through this move; resume here after the current presentation step finishes.” That is why battle scripts are more than a convenient list of effects. They are a way to serialize combat logic into a form that can be advanced incrementally.

In your minimal interpreter, model this explicitly. A command handler can return DONE, WAITING, or JUMP. DONE means advance to the next command. WAITING means keep the script pointer where it is or advance only after the animation subsystem signals completion. JUMP means set the pointer to another command index. That alone will make your clone feel much more like the real architecture than a single useMove function.

Let us do a board trace. I am writing script index zero: check accuracy. Index one: play animation. Index two: calculate damage. Index three: apply damage. Index four: print damage message or update HP bar. Index five: try burn. Index six: print burn message if applied. Index seven: faint check. Index eight: end.

Now I run it. At index zero, hit succeeds. State.resultFlags has HIT. At index one, animation is queued, handler returns WAITING. The main battle update function exits for the frame. Next frame, animation still running, so interpreter does not proceed. Later, animation completes. Interpreter resumes at index two. Damage is calculated. Index three applies it. Index four queues HP bar drain and returns WAITING. Resume. Index five tries burn. Maybe it fails the random chance. It sets no status and maybe sets a flag saying secondary effect did not occur. Index six sees no burn message needed and advances. Index seven checks HP. If HP is zero, jump to faint script. Otherwise index eight ends.

That is the architecture in miniature.

And now, a sanity question: where does end-turn poison damage belong in that script? Not inside Ember. It belongs in end-turn cleanup, which is itself scriptable or at least staged. That cleanup walks battlers, checks statuses and timers, applies residual damage or decrements counters, queues messages, checks fainting, and returns to action selection if the battle continues. This is where the structures in pokefirered/include/battle.h chunk_a08e84ff1272f826 matter again. Disable, protect, and special status are not just move decorations. They are turn-memory and multi-turn-state mechanisms. If your clone has only HP and a status enum, it can handle the first layer. If it adds side timers and temporary battler flags, it starts to resemble the real problem.

Let me also point out the assessment expectation. The midterm and homework_w13 already checked whether you can identify files, structs, functions, constants, data tables, and state variables that implement major systems. So in week 14, I am raising the bar. It is no longer enough to say, “battle scripts are in battle_script_commands.c.” You need to say what role that file plays in a distributed system. You need to connect pokefirered/src/battle_script_commands.c chunk_9779e386ddb42cb0 to dispatch, chunk_c405e26d69cb40ac to effects, chunk_98989865d85f275c to flow, pokefirered/src/battle_util.c chunk_9335e5727aa0657d to the move-support dependency surface, and pokefirered/include/battle.h chunk_a08e84ff1272f826 to the state structures that commands operate on.

That is the difference between source familiarity and architecture understanding.

Before we move on, let me recap the thread. A battle turn starts from a selected action. That action installs current attacker, target, and move context. The battle script interpreter then advances through command data. Each command reads and writes shared battle state. Presentation is queued through message and animation hooks, not fused into the rule itself. Damage and status are not isolated formulas; they are transformations of shared state that later commands and later systems observe. Party data receives the durable consequences. Save persistence eventually records the durable game state, but not every animation twitch.

Now, keep that model warm, because next we are going to use it to inspect the two places where clones usually become brittle: first, when damage calculation starts needing exceptions, and second, when status effects start lasting across turns, switches, fainting, item use, and save boundaries.

So, let’s press into that first brittle point: exceptions in damage. This is where a clone project often starts clean, then slowly turns into a pile of special cases. At first, you say, “A move has power, attacker has attack, defender has defense, calculate damage.” Fine. Then you add critical hits. Then type effectiveness. Then abilities. Then items. Then weather. Then Protect. Then moves that do fixed damage. Then moves that use the attacker’s current HP. Then moves that faint the user. And suddenly your nice function called calculateDamage is no longer a formula. It is a negotiation among many pieces of battle state.

That is exactly why FireRed’s battle script model is worth studying as bytecode, not just as a list of effects. The command stream gives the engine a place to say, “Now check accuracy. Now calculate damage. Now apply that damage. Now print the result. Now check fainting. Now move to the next scripted consequence.” The source focus for today, pokefirered/src/battle_script_commands.c, especially chunk_9779e386ddb42cb0 for command dispatch and chunk_c405e26d69cb40ac for command effects, is important because it shows this separation between sequencing and rule mutation. The rule engine is not one giant if statement that directly performs a whole move from beginning to end. It is closer to a little interpreter moving through combat instructions.

Now, board cue here. I’m going to write a deliberately minimal version of the model. Not code yet, just the mental shape. On the left: script pointer. Under that: current attacker, current target, current move. Under that: temporary damage, hit marker, status marker, message buffer. On the right: durable party data. HP, species, moves, PP, status, held item. And between them, I’m drawing arrows labeled “read,” “stage,” and “commit.”

That distinction matters. A command may read durable party data, like current HP or current status. A command may stage temporary data, like calculated damage. Another command may commit that staged data by subtracting HP. Another command may observe the committed result and branch to a fainting script. If you collapse all of that into one function, your clone can still work for Tackle. But it becomes brittle when you introduce a move whose damage depends on the target’s current HP, or a move whose message depends on whether damage was zero, or a move whose status effect only applies if the move actually hit.

Let’s use a concrete example. Suppose our clone implements Ember. Minimal Ember script: accuracy check, damage calculation, damage application, burn chance, message, faint check, end. That sounds simple. But notice the condition hidden in “burn chance.” Burn chance should not be a totally independent command that runs no matter what. It should be downstream of hit success and often downstream of damage or substitute logic, depending on the generation and rule set you are imitating. So the bytecode design wants a shared “move result” state. Did the move miss? Was it blocked? Did it deal damage? Is the target still present? Does the target already have a major status? Is the target immune to burn? The burn command reads that shared state and decides whether it is allowed to mutate the target.

This is also why pokefirered/include/battle.h chunk_a08e84ff1272f826 is not just a pile of declarations. We used battle.h earlier as a subsystem map, and I do not want to reteach that, but today we use it in a narrower way. The disable, protect, and special status structures tell you that battle state has multiple lifetimes. Some state belongs to the party creature and can survive outside the current turn. Some state belongs to the battler slot and may change when switching. Some state belongs only to a turn, like protection flags or current move flags. When a script command runs, it must know which lifetime it is touching.

Check for understanding here. If I ask, “Where should the fact that the target flinched live?” your answer should not be automatic. You have to ask, “Flinched for how long, and observed by what?” If the flinch only prevents action later in the same turn, it is probably not durable party data. It is a per-battler or per-turn status marker. But poison, by contrast, has to persist beyond the move animation, beyond the current command stream, often beyond the battle, and eventually into save data. Different lifetime, different storage.

Now let’s connect that to the workshop you’re going to do. Your minimal battle script interpreter should have commands for accuracy, damage, status, message, faint check, and end-turn cleanup. That list is small on purpose, but it already forces the architecture. Accuracy does not reduce HP. Damage calculation does not necessarily print the message. Status application does not necessarily end the turn. Faint check does not decide the original move’s base power. Each command should have a small verb, and the interpreter should supply the continuity.

If I were sketching this clone for the lab, I would not begin with thirty moves. I would begin with four. Tackle: ordinary damage. Ember: damage plus possible status. Recover: no damage, self-heal, message, maybe failure if already full HP. Protect: turn-local side effect that changes how later incoming moves resolve. Those four moves force you to handle the essential categories without drowning in content. Then, once those pass, add a weird move. Something like fixed damage, or self-damage, or a move that fails under a condition. The point of the weird move is not flavor. It is an architecture test.

And this is where Pokemon Showdown is useful as a secondary comparison, not as our primary architecture. In pokemon-showdown/data/moves.ts chunk_d4dc7d8b20aa96e6, you can see modern move data carrying callbacks such as damageCallback or onTryHit for moves with special behavior. That is a very different implementation style from FireRed’s script bytecode, but it is solving the same pressure: ordinary declarative fields are not enough for every move. Some moves need executable hooks. Showdown tends to express those hooks in data objects and callback functions. FireRed expresses many of these battle consequences through script commands, command handlers, globals, and supporting utility functions. The comparison is valuable because it tells us the pressure is real across architectures. The solution differs because the runtime environment differs.

Now, let’s pause and name a trap. A lot of clone implementations create a Move class with an execute method, and then subclass it for every special move. That can work for a small prototype, but it often smuggles sequencing into inheritance. Tackle.execute does the whole Tackle sequence. Ember.execute does the whole Ember sequence. Recover.execute does the whole Recover sequence. Then you add common messaging, common accuracy rules, common faint checks, and common protection logic, and you start copying or creating a fragile superclass. The FireRed-inspired script approach says, instead, let moves select or point at scripts, and let scripts reuse command vocabulary. You still need special commands and special cases, but they are located in an interpreter framework rather than scattered through one method per move.

Board cue again. I’m writing two columns. Column one says “move-owned execution.” Column two says “script-owned sequencing.” Under move-owned execution: each move decides all steps. Under script-owned sequencing: move data selects parameters, script commands perform staged operations. For an advanced learner, the big question is not “Which one is objectively correct?” The question is “Where do exceptions accumulate, and can I test them?” If exceptions accumulate in hundreds of move subclasses, your tests become fragmented. If exceptions accumulate in a command interpreter with explicit shared state, you can build command-level tests and scenario-level tests.

Now let’s trace one turn in our minimal clone, using the architecture vocabulary from the last two weeks without re-opening all of it. Earlier, we established that the battle main callback coordinates the battle loop, chosen actions by battler, and turn-order arrays. Today, assume that part is already done. The selected battler’s action becomes “use move.” That action installs the current attacker, target, and move. Then it sets the script pointer to the move’s effect script. The script interpreter runs until it reaches a wait point, a message point, an animation point, or an end marker, depending on how detailed your clone is.

In FireRed, pokefirered/src/battle_script_commands.c chunk_98989865d85f275c is one of the relevant source chunks for battle script flow. The architectural point to take from that source focus is that flow itself is a first-class concern. Commands do not merely mutate numbers; they also advance, branch, pause, and resume the script. That makes the battle script interpreter more like an event VM than like a plain damage function. It is bytecode because the command pointer matters. It is combat bytecode because the commands are domain-specific: damage, status, messages, animation hooks, faint handling, protection, and so on.

Let’s make that less abstract. Imagine the command pointer is at accuracy. Accuracy command rolls or evaluates hit success. If the attack misses, the command does not need to know how every possible move ends. It can branch to a miss script or set a miss flag and advance to a common miss handler. The message command can then print “The attack missed.” The damage command is skipped. The burn command is skipped. Faint check is skipped or observes no damage. The script eventually ends and the turn loop continues. That is script flow doing real work.

Now suppose the attack hits. The script pointer advances to damage calculation. That command consults current move data, current attacker, current target, type data, stat stages, random variation, critical state, and field conditions. Some of that support sits outside the script command file. That is why pokefirered/src/battle_util.c chunk_9335e5727aa0657d matters today. The include list in that chunk touches battle, battle animation, scripts, messages, controllers, moves, items, weather, abilities, Pokemon constants, and hold effects. That include pattern is not a proof of behavior by itself, but it is strong architectural evidence that battle utility code sits at the junction of move support, battle state, and presentation-facing systems. In your clone, you should feel the same pressure: the damage command will not stay mathematically pure forever. It will need utility support.

Here is the check I want you to run mentally. If your damage function only accepts attacker attack, defender defense, and move power, where do you put weather? Where do you put burn’s attack reduction? Where do you put critical hits bypassing certain stat changes? Where do you put held item modifiers? If your answer is “I’ll add more parameters,” that is okay for the first five. But after twenty, what you really have is a battle context object. FireRed uses shared globals and structures because of its C and GBA context. A modern clone might use an explicit BattleContext or TurnContext. But the idea is the same: damage is a command operating inside a shared combat context.

Now I want to talk about status timers, because they are the second brittleness point. Status is hard not because “poison subtracts HP” is complicated. Status is hard because status has timing. When does poison tick? At end of turn. Does it tick if the Pokemon fainted earlier? No, or at least it must be checked carefully. Does it persist after switching? Major poison does, volatile confusion usually does not in the same way. Does sleep decrement before action or after action? That depends on the rule set. Does a status message play if an animation is still running? Presentation coordination matters. Does the status survive save persistence? Major status generally does, but turn-local statuses do not.

That is why status should not be treated as a string on a creature that every system casually edits. You need structured status state. At minimum, in the lab, I would expect majorStatus, volatileStatuses, sideConditions, and turnFlags. You can keep them simple, but separate them. Major status belongs close to party creature data. Volatile status belongs to battler state or battle-only creature state. Side conditions belong to the side, not to a single battler. Turn flags are cleared during end-turn cleanup. And now you can see why the lab explicitly includes end-turn cleanup as a command or phase. Without cleanup, your Protect flag lasts forever. Without cleanup, your “moved this turn” marker leaks. Without cleanup, a temporary damage flag may influence the next move.

Let’s do another concrete example. Our clone has Protect. Turn one, Squirtle uses Protect. We set a protect flag on Squirtle’s battler state. Charmander uses Ember into Squirtle. The accuracy command may still run, or protection may be checked before accuracy depending on your design, but at some point the script sees the target is protected and branches to a protected message. No damage. No burn. Then end-turn cleanup clears the protect flag or updates protect’s success counter. Turn two, if Squirtle does not use Protect, Ember should not still be blocked. If it is blocked, your state lifetime is wrong.

This is a perfect place to bring back our earlier course-wide mantra: data table to runtime globals to callback, task, or script to visible behavior. We are not re-teaching the whole stack, but we are using it. The move data chooses what is possible. The current battle state decides what is currently true. The script interpreter sequences commands. The message and animation hooks make consequences visible. The party model receives durable changes. Save persistence records the long-lived result later. That is the full path. If your clone skips one of those links, it may still demo, but it will be hard to extend.

Now, a quick comparison to Showdown again. In pokemon-showdown/data/text/moves.ts chunk_1620277751dc01f4, the move text records descriptions for effects like redirection and screen removal. In pokemon-showdown/data/text/moves.ts chunk_92a6298340525a85, the text describes end-of-turn damage over multiple turns for binding-style effects. We are not using those chunks as FireRed implementation evidence. We are using them as examples of how battle effects must be explainable over timing windows: until end of turn, before damage, after successful hit, for four turns, at end of each turn. Your engine has to represent those timing phrases somewhere. FireRed’s battle script and state structures are one way. Showdown’s event callbacks and condition data are another.

So if you are building the minimal clone version, do not ask first, “How do I implement every move?” Ask, “What timing windows do I support?” I would start with these: before move, accuracy resolution, damage resolution, after hit, faint check, end of turn, switch-in, and battle end. That is already enough to model a surprising amount of behavior. You can add more later. But if you do not name the timing windows, status logic will hide inside random methods.

Let me check the room. Suppose a move says, “If this attack does not miss, remove the target side’s screen before damage is calculated.” Where does that belong? Not after damage. Not in generic end-turn cleanup. It belongs in a hook or script command between hit confirmation and damage calculation. That exact kind of phrase appears in modern move descriptions, and the research packet’s Showdown text chunk_1620277751dc01f4 includes a move description where effects end before damage is calculated. Even though that is not FireRed-specific behavior, it is a useful stress test for your architecture. Can your script put a side-condition removal command before damage? If yes, your bytecode model is doing its job.

Now, item use. We have not centered item use today, but one of the course objectives for this capstone stretch is to design minimal clone versions of map loading, script execution, party data, battle turns, item use, and save persistence. Item use crosses the same boundary as battle scripts. A potion in the overworld is inventory plus party data plus UI plus save persistence. A potion in battle is chosen action plus turn order plus item effect plus message plus party HP mutation. The item effect should not be hardwired only into bag UI. It should be an effect routine that can be called from the context where the item is legal. That is the same architectural lesson: separate effect semantics from presentation and entry point.

For a minimal clone, implement Potion as an item effect that takes a target party member and a context. In overworld context, it updates party HP and queues an overworld message. In battle context, it consumes the battler’s action, updates party HP, queues a battle message, and returns control to the battle loop. Same durable mutation, different execution shell. If your Potion code directly manipulates battle turn order when used from the bag menu, you have fused contexts too tightly. If your battle item code cannot update party data, it is too isolated. The clean design is a shared item effect with context-specific wrappers.

Save persistence then becomes much easier to reason about. Save does not record “the Potion sparkle animation played.” Save records inventory count, party HP, major status, location, flags, and other durable state. It may record enough battle state if saving mid-battle is supported, but many games simply do not allow that. In our FireRed-inspired clone, keep the boundary clear. Map loading reconstructs the world from map data and saved player position. Script execution reads flags and variables. Party data holds creatures. Battle turns create temporary combat state. Item use mutates party or inventory through allowed contexts. Save persistence serializes durable state, not transient interpreter scratch space.

This is also how week 14 connects to the overall course plan. Weeks 1 through 4 gave us the runtime shell and data pipeline. Weeks 5 through 8 gave us maps, scripts, and progression. Weeks 9 through 11 gave us party, inventory, UI, and persistence. Weeks 12 through 16 are the battle engine architecture. Today’s battle script interpreter is not a separate curiosity. It is the battle system’s version of the same pattern we saw in overworld scripts: compact commands, shared state, explicit sequencing, and visible behavior produced through callbacks and message systems. The difference is that battle scripts run in a much more numerically dense and timing-sensitive environment.

Let me recap this segment before we move forward. Damage becomes brittle when you pretend it is only a formula. It is better understood as a staged operation inside a battle context. Status becomes brittle when you store every condition in one undifferentiated field. It is better understood by lifetime and timing window. Battle scripts help because they make sequencing explicit: check, branch, calculate, apply, announce, clean up. FireRed’s source focus in pokefirered/src/battle_script_commands.c chunks chunk_9779e386ddb42cb0, chunk_c405e26d69cb40ac, and chunk_98989865d85f275c supports that command-dispatch and flow-centered view, while pokefirered/src/battle_util.c chunk_9335e5727aa0657d reminds us that move support naturally reaches across items, weather, abilities, messages, animation, and battle constants.

And now the next move is to get very practical. We are going to turn this into a minimal interpreter design that you could actually implement in the lab: a command enum, a script pointer, a battle context, a small command dispatch table, and a set of tests that prove Tackle, Ember, Protect, and Potion do not leak state across turns or contexts.

So, on the board, I would write the smallest useful version like this: battle context contains attacker, target, current move, damage, status flags, side timers, script pointer, and pending message. That is the core. Not the whole FireRed engine, not all animations, not every ability, not every edge case. Just enough shared state for commands to cooperate without each command needing to know the entire turn system.

Now notice the design pressure here. If your damage command directly prints the message, triggers the animation, applies fainting, and advances the turn, then you have not written bytecode. You have written a large function with a decorative opcode in front of it. The value of the script model is that each command does one staged operation. Accuracy decides whether the move continues. Damage calculates and applies HP loss. Status tries to attach a condition. Message exposes player-visible text. Faint check branches to the fainting path. End-turn cleanup drains timers, clears temporary protection, and returns control to the turn scheduler.

This is exactly why the FireRed source focus matters. The battle script command material in pokefirered/src/battle_script_commands.c chunks chunk_9779e386ddb42cb0, chunk_c405e26d69cb40ac, and chunk_98989865d85f275c is not just a bag of move effects. It is evidence of a flow-centered battle design, where the current move, battler globals, script pointer, and command behavior together produce combat. And pokefirered/src/battle_util.c chunk_9335e5727aa0657d reminds us why the interpreter cannot be isolated from the rest of the engine: move support touches items, random numbers, weather, abilities, text, animation, battle constants, and controllers.

Let’s make that concrete. Suppose your minimal script for Ember is: accuracy check, play animation, calculate damage, apply damage, try burn, print result message, faint check, end. The burn command should not know how the turn began. It should read the current move effect, target status, type or immunity rules if your clone supports them, and random chance. If burn succeeds, it writes to the target’s status field and queues a message. If burn fails, it advances. That’s the bytecode discipline.

Now compare that to Potion. Potion is not a move, but it still wants the same architectural respect. Item use should be a staged operation too: validate item, validate target, compute heal amount, clamp HP to max HP, consume item, print message, return to battle flow. In a small clone, you might use a separate item script interpreter or simply reuse the battle command model with item-specific commands. The important part is not whether the opcode table is literally shared. The important part is that both moves and items are sequenced operations over shared battle state.

Quick check for understanding: if Tackle misses, which commands after the accuracy command should still run? Not damage. Not hit effects. Usually not the normal damage message. But the script still has to reach a coherent end state, so it must branch to a miss message and then return control cleanly. That clean return is the difference between a failed move and a corrupted turn.

And now let’s connect this back to the full clone architecture, because week 14 is not floating by itself. Earlier in the course, we decomposed the RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. Your minimal clone should preserve those boundaries even if each system is tiny. Map loading gives you a current map header or map record, object positions, and a way to trigger encounters or scripts. Script execution gives you a small VM for world events: show text, set flag, give item, start battle. Party data gives you creatures with species, level, HP, moves, status, and maybe held item. Battle turns consume party data and run move or item scripts. Item use touches inventory and party or battle state. Save persistence serializes the long-lived parts: map location, flags, inventory, party, and progress variables.

The key distinction is lifetime. Map loader state is current-session spatial state. World script flags are progression state. Party data persists. Battle context is mostly temporary. Current move data is extremely temporary. End-turn timers sit in between: they persist across several actions, but not across the whole save file unless you allow saving mid-battle. Once you sort data by lifetime, many design decisions become easier.

So for the lab, I would expect your minimal interpreter to have a command enum with names like accuracy, damage, status, message, branch, faint check, and end-turn cleanup. Your script pointer should advance by command size, not by wishful thinking. Your dispatch function should be boring: fetch opcode, call handler, handler either advances the pointer or assigns a new pointer. The moment your handler secretly starts an unrelated subsystem, pause and ask whether you are smuggling turn scheduling into a bytecode command.

One useful test is Tackle versus a normal target. The script should hit, compute damage, reduce HP, print the damage or hit message, check fainting, and end. Another is Ember with forced burn chance, where you assert that HP changes and status changes happen in separate stages. Another is Protect, where the protect status must block damage this turn and then clear at cleanup. That connects directly to the protect and special-status structures we saw in pokefirered/include/battle.h chunk_a08e84ff1272f826: temporary battle conditions deserve explicit storage because they have timing rules. And Potion should prove that item use can modify HP without pretending to be a damaging move.

Let me say that again because it is the architecture lesson of the day: combat bytecode is not about making battles more mysterious. It is about making battle sequencing inspectable. When a player sees “Ember missed,” “Bulbasaur was burned,” or “Charmander protected itself,” those messages are the visible surface of a command stream mutating shared state in a disciplined order.

As we close today, place week 14 inside the course map. Weeks 1 through 3 gave us the runtime shell, callbacks, tasks, and generated data mindset. The overworld weeks showed map records and script-driven progression. The creature-data weeks gave us parties and moves as structured records. The recent battle weeks gave us battlers, chosen actions, turn-order arrays, and the battle main callback. Today’s lecture adds the missing inner layer: once a turn chooses a move, the move itself is executed as a small program.

That is why battle scripts are combat bytecode. They let a data-driven RPG express move behavior as ordered commands rather than as one giant special-case function. FireRed’s implementation is more complicated than our clone will be, but the architectural lesson scales down cleanly: use shared context, keep command handlers staged, branch explicitly, separate temporary from persistent state, and test visible battle behavior by tracing data table to globals to script command to player-facing result.

Next time, we will stay in this same machinery, but we will press harder on damage and status through shared state. Today we cared most about sequencing. Next time we care about what exactly gets stored, when it is legal to mutate it, and how status, side timers, abilities, weather, and messages cooperate without turning the battle engine into an untestable knot.

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