# Showdown Ability Hooks as a Comparison Model

Lecture ID: lecture_w16_d02
Week 16, Day 2

## Objectives
- Trace FireRed gameplay from data records through globals, callbacks, tasks, scripts, structs, and concrete source functions.
- Connect week 16 material to the overall course plan.

## Transcript
All right, let’s pick up exactly where we left off last time. In the previous lecture, we treated FireRed’s battle AI as a little predicate machine. Not a thinking creature, not a planning system in the modern AI sense, but a compact script interpreter whose power comes from pointer movement, party-aware checks, and careful branching. Today we are going to hold that model next to Pokemon Showdown’s ability hook model and ask a more architectural question: what changes when extension behavior is represented as data-attached callbacks instead of as command flow inside a C interpreter?

I want you to keep one phrase in mind for the first hour: “where does the engine invite custom behavior?” That is the comparison. FireRed invites custom behavior in places like battle scripts, AI scripts, move effect handlers, command tables, global state checks, and controller callbacks. Showdown, at least in the source examples we are using today, often invites custom behavior by letting data records contain named hook functions. Those hooks say, in effect, “when the engine reaches this kind of moment, call me.”

Now, that sounds like a modern improvement, and in many ways it is more flexible. But I do not want you to flatten this into “old C bad, TypeScript hooks good.” That would be too shallow for week 16. FireRed’s style is also highly intentional for its platform. It is predictable. It is memory-conscious. It is built around global battle state, script pointers, fixed structs, and command dispatch that can survive on the Game Boy Advance. Showdown’s style is built for a server simulator: more expressive data objects, richer runtime dispatch, more introspection, easier patching across generations and mods. Different constraints, different architecture.

Let me put the first comparison on the board. I’m drawing two columns. On the left: FireRed AI command flow. On the right: Showdown ability hook flow. Under FireRed I’m writing: “script bytecode or command list, interpreter, sAIScriptPtr, globals, party scan, branch.” Under Showdown I’m writing: “ability data record, hook name, engine event, callback arguments, mutate battle object or return modifier.” The point is not that these systems do the same job. The point is that both are extension mechanisms. They both answer the question: how do we add battle behavior without rewriting the entire battle loop?

Let’s ground that immediately in sources. Our FireRed anchor for this week is pokefirered/src/battle_ai_script_commands.c, especially chunk_0a05fcecbd58029f and chunk_34a415cc38858fdc. Those chunks are important because they show AI behavior not as a free-form planner, but as commands that inspect battle state and then adjust the AI script pointer, sAIScriptPtr. That pointer movement is the architectural lever. If a condition is true, the interpreter jumps to one region of the AI script. If false, it continues or branches elsewhere. In other words, FireRed’s AI behavior is expressed as command flow over shared battle state.

Now compare that with pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4. In that chunk, we see ability records with hook names like onDamagingHit and onModifyMove. For example, Stamina has an onDamagingHit hook that boosts Defense when the Pokemon is damaged. Stance Change has an onModifyMove hook that can change Aegislash’s form depending on the move being used. The engine does not branch through an AI script pointer to discover that behavior. The behavior is attached to the ability data object, and the simulator calls it at a named event point.

So, check for understanding before we go deeper: if I ask, “Where is the branch represented in FireRed AI?” the answer is not “inside an ability object.” It is represented by the AI script pointer and the command implementation that changes it. If I ask, “Where is the extension point represented in Showdown ability data?” the answer is “inside the data record, by the presence of a hook like onDamagingHit, onModifyMove, or another event callback.” Same general goal, different location of control.

Now let’s connect this to the course-wide architecture. Since week 1, we have been tracing gameplay from data records through globals, callbacks, tasks, scripts, structs, and concrete source functions. That trace matters here because “ability hook” can sound abstract until you place it in the runtime path. In FireRed, a gameplay fact usually starts in a table or constant, gets loaded or referenced through battle structs and globals, and then is interpreted by a command or function in the battle engine. In Showdown, a gameplay fact can live as an object property and include both passive data, like rating or flags, and executable data, like hook functions. That is a major architectural contrast.

Think back to our earlier callback work around AgbMain, SetMainCallback2, task allocation, and RunTasks. We are not re-teaching that today, but it matters as background. FireRed’s whole runtime shell is organized around controlled dispatch: main callback slots, per-frame task dispatch, and module-specific script interpreters. Battle AI fits into that style. It is another controlled dispatch system. It does not give arbitrary objects a chance to speak at every event. Instead, the battle engine reaches known command handlers and known state checks.

Pokemon Showdown, by contrast, is much more comfortable with the idea that a data object can contain behavior. In pokemon-showdown/data/abilities.ts, chunk_8a03d04894b7e127, for instance, we see ability records such as Infiltrator and Innards Out using hooks like onModifyMove and onDamagingHit. Infiltrator modifies the move object so the move can pass through certain protections. Innards Out reacts to damaging hits and can produce retaliation-style damage behavior. The key architectural observation is not the competitive effect. The key observation is that the ability record itself participates in battle event handling.

Let’s do a concrete example slowly. Suppose we want an AI predicate in FireRed that asks, “Does any Pokemon in my party have a particular status?” This is close to the lab task for this week, where you rebuild a simplified if_status_in_party command. In the FireRed style, this is not naturally an ability hook. It is a script command. The command reads parameters from the AI script, determines whose party to scan, loops over party slots, checks a status field or derived condition, and then moves sAIScriptPtr based on whether the predicate succeeds. The behavior is procedural and interpreter-centered.

I’m writing the shape on the board, not exact source code. “Read condition. Select party. For slot from zero to party count. Skip invalid or fainted depending on command semantics. Check status. If match, jump. Otherwise continue.” That shape is exactly the kind of thing supported by pokefirered/src/battle_ai_script_commands.c, chunk_34a415cc38858fdc, which the research packet identifies with party scanning, status predicates, and sAIScriptPtr branching. What matters is that the party data is not calling back into the AI. The AI command is inspecting party data.

Now rewrite the same design instinct in a Showdown-like style. You might define an ability with an onDamagingHit hook. When the holder takes damage, the engine calls the hook with arguments like damage, target, source, and effect or move. The callback decides what to do. It might boost a stat, alter a volatile condition, add a message, or modify a move in progress. Stamina in pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4, is a clean small example: on a damaging hit, the ability’s hook requests a Defense boost. The ability is not scanning an AI script. The battle event system is scanning, or at least consulting, active effects that have relevant hooks.

So ask yourself: who owns the loop? In the FireRed AI command, the command owns the loop over party slots. In Showdown’s ability event model, the engine owns the loop over effects and calls the hooks whose names match the event. That is one of today’s central distinctions. Ownership of iteration is architecture.

Let me say that again because it is a compact exam-worthy sentence: ownership of iteration is architecture. If an AI command loops over the party, then party-awareness is embedded in the command flow. If an event dispatcher loops over active effects, then extensibility is embedded in the event model. The gameplay result can look similar to a player, but the implementation has a different center of gravity.

Now, a quick warning. You may be tempted to say, “Showdown is data-driven and FireRed is not.” Be careful. FireRed is absolutely data-driven in many parts of its design. We have spent the whole course proving that through map headers, trainer tables, wild encounter data, species data, move tables, item effects, and scripts. The distinction today is narrower. Showdown’s ability records are data objects that may contain executable hook functions. FireRed’s data records usually do not contain function closures in the same way. FireRed tends to connect data to behavior through tables, constants, command IDs, script bytes, C functions, and global state.

That distinction appears even in the includes and dependencies around FireRed battle utilities. The research packet points us to pokefirered/src/battle_util.c, chunk_9335e5727aa0657d, which shows the battle utility source including battle headers, constants for moves, items, weather, abilities, Pokemon, hold effects, and battle move effects. That include cluster is a good architectural clue. FireRed centralizes a lot of battle knowledge into C modules that understand many categories of constants and shared structures. It is not that abilities do not exist as data. It is that the engine’s C command flow and utility functions are the place where those constants become behavior.

Now I want to connect this to the previous lecture’s AI script pointer control. Last time, we looked at the idea that sAIScriptPtr is not just a variable. It is the AI interpreter’s sense of “what question comes next?” When a command modifies sAIScriptPtr, it is effectively choosing the next predicate or action. So if an AI command says, “if there is a party member with status X, jump to address Y,” then that command is not merely returning true or false. It is shaping the future path of evaluation.

In Showdown, the equivalent control often does not look like “jump to address Y.” It looks like “the hook runs at a particular event point, and its return value or mutation changes the event’s outcome.” For onModifyMove, the hook may mutate the move object before the move resolves. For onDamagingHit, the hook may trigger a boost or damage response after a damaging hit. For residual or base-power hooks, which the packet associates with pokemon-showdown/data/abilities.ts, chunk_b4a38d21b0114ee8, the ability record contributes at a specific stage in the battle timeline. That is not pointer control. That is event-stage control.

Let’s pause and test the model with a hypothetical. Imagine we want an ability called “Iron Nerve” that increases Defense after the holder is hit by a physical move. In Showdown style, we would probably put an onDamagingHit hook on the ability. The hook receives enough context to inspect the source effect or move category, then calls a boost operation if the move was physical. The behavior lives with the ability data. In FireRed style, depending on where we want this to happen, we would likely add a check in the damage or hit-effect flow, or add an ability-specific case in a battle utility function, or route through an existing battle script command. The behavior does not naturally live as a function inside an ability record.

Now, which one is better? Wrong question. The right question is: better for what constraints? Showdown wants many generations, mods, tests, and quick rule changes. It benefits from attaching behavior to data and letting an event engine discover relevant hooks. FireRed wants to run in a small memory environment, with predictable static code, known data layouts, and tight control over timing, animation, controller communication, and script execution. It benefits from command flow, global state, and explicit C functions.

Here is another check for understanding. If I show you a FireRed AI command that branches sAIScriptPtr after scanning the opponent’s party, what extension style is that? Yes, command-centered predicate flow. If I show you a Showdown ability object with onModifyMove that edits move.basePower or move.type, what extension style is that? Yes, data-attached event hook flow. And if I ask, “Which one uses data?” the correct advanced answer is: both use data, but they bind behavior to data differently.

Let’s bring in a second Showdown example. In pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4, Stance Change uses onModifyMovePriority and onModifyMove. That priority field matters. Once you have many hooks responding to the same event, ordering becomes part of the architecture. You need a system for deciding whose modification happens first. FireRed also has ordering problems, but they often appear as explicit command order, battle script order, speed order, priority values in move data, or hand-coded function sequence. Showdown exposes some of that ordering at the hook metadata level.

That is an important design pattern. Hook systems make extension easy, but they create ordering questions. Command systems make order explicit, but they can make extension invasive. Again, not good versus bad. Tradeoff versus tradeoff.

Now look back at the lab. You are going to implement both sides in simplified form. First, rebuild a simplified if_status_in_party AI command. That forces you to think like FireRed: data is in party structs, battle state identifies sides and battlers, command parameters say what to check, and sAIScriptPtr encodes control. Second, implement one Showdown-style callback ability. That forces you to think like Showdown: the engine reaches an event point, locates relevant hooks, passes contextual arguments, and lets the ability mutate or respond. The purpose of the lab is not to produce a perfect simulator. It is to make the two extension styles physically obvious in your own code.

I’ll sketch the simplified lab architecture verbally. For the FireRed-like side, your mini interpreter has an array of command bytes or command objects. One command is “if_status_in_party.” It contains a side selector, a status mask, and a jump target. Your interpreter has a program counter. When it reaches the command, it scans the selected party array. If any creature matches the status, it sets the program counter to the jump target. Otherwise it increments normally. That is FireRed-shaped, even if your code is in a modern language.

For the Showdown-like side, your mini engine has a creature with an ability object. That object may have a function named onDamagingHit. During damage resolution, the engine calls all relevant onDamagingHit hooks. The hook receives damage, target, source, and move. If the ability is Stamina-like, it increments target.defenseStage. That is Showdown-shaped, even if your implementation is tiny.

Now let’s connect this to battle AI in a more subtle way. Showdown ability hooks are not mainly an AI mechanism. They are battle rule hooks. FireRed’s battle_ai_script_commands.c is mainly AI logic. So why are we comparing them? Because they show two different answers to a more general engine problem: how do you let local rule fragments influence a larger battle process? In FireRed AI, local rule fragments are commands in a script language. In Showdown abilities, local rule fragments are named callbacks attached to data records. Both are ways to avoid writing one enormous monolithic “decide everything” function.

This is where the week 16 material ties back to the whole course plan. Weeks 12 through 16 have been our battle engine architecture module. We started from battle setup and data entry points, moved through move data, damage, status, battle script command dispatch, encounter-to-battle routing, outcome resolution, and now AI predicates and comparison with Showdown hooks. That arc is intentional. At the beginning, battle looked like a single feature. By now, you should see it as a stack: encounter data or trainer data initiates a battle; global battle structs hold runtime state; scripts and command dispatch coordinate effects and messages; AI scripts inspect state and branch; results are written back to world state. Showdown helps us see alternate designs, but FireRed remains the spine of the course.

Let me make the trace explicit. Player-visible behavior: the opponent makes a smarter-looking decision because it notices a status somewhere in the party. Underneath that, the AI command source in pokefirered/src/battle_ai_script_commands.c, chunk_34a415cc38858fdc, represents party-aware predicates and branching through sAIScriptPtr. That command reads shared battle and party state. The state itself exists because earlier systems loaded trainer or wild encounter data, initialized battle globals, and entered battle callbacks. When the battle ends, result storage and routing return us to the overworld. So even this small AI predicate is connected to the entire course architecture.

For Showdown, player-visible behavior: Stamina boosts Defense after taking a hit. Underneath that, pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4, attaches onDamagingHit to an ability record. The battle simulator reaches the damaging-hit event and calls the hook. The hook changes battle state through simulator APIs. If we were looking at tests, we would expect deterministic assertions around the resulting boost. Different stack, same trace discipline: data record, runtime state, event dispatch, visible behavior.

Notice, too, how the arguments tell you what the engine thinks the hook is allowed to know. In Showdown examples, hooks like onDamagingHit receive damage, target, source, and effect or move. That is a contract. It says: at this event point, the ability may reason about who was hit, who caused it, how much damage occurred, and what effect caused the hit. In FireRed AI command functions, the contract is different. The command often has access to global battle variables, the AI script stream, battler identities, and party arrays. This is a looser global-state style, but it is also extremely direct.

I want you to hear the design implication: hook arguments are a form of API design. Global battle state is also a form of API design, but a much broader one. If you give a hook only move and target, you constrain behavior and make it easier to reason about that hook. If you give a C command access to broad globals, the command can be efficient and powerful, but the coupling is wider. That is a serious architectural tradeoff.

Let’s do one more board cue before we stop this first segment. I’m drawing a horizontal timeline of a turn. Early decision, move selection, move modification, hit check, damage, damaging-hit aftermath, residual effects, end of turn. Now I’m placing FireRed AI predicates near early decision, because the AI commands help decide what the opponent will do. I’m placing Showdown onModifyMove around move modification. I’m placing onDamagingHit just after damage application. I’m placing residual hooks near end of turn, matching the packet’s note that pokemon-showdown/data/abilities.ts, chunk_b4a38d21b0114ee8, contains residual and base-power hook examples. This timeline matters because hooks are not just functions. They are functions located in time.

So when you read an ability hook, ask two questions. First, what state can it see? Second, when in the battle timeline does it run? Those two answers determine most of its architectural meaning. A hook that runs before damage and can modify base power is very different from a hook that runs after damage and boosts a stat. A predicate that runs during AI decision-making is very different from a command that runs during battle animation or message display.

Let’s recap the first big idea before we move into the next part. FireRed’s battle AI commands are interpreter-driven predicates over shared battle and party state. Their control power comes from script pointer movement, especially sAIScriptPtr branching, as emphasized by pokefirered/src/battle_ai_script_commands.c, chunk_0a05fcecbd58029f and chunk_34a415cc38858fdc. Showdown’s abilities are data records with named event hooks, as shown in pokemon-showdown/data/abilities.ts chunks d5bdb17c16d6f1a4, 8a03d04894b7e127, b4a38d21b0114ee8, and the broader data-engine split noted in chunk_6372bce3e311c10b. The comparison is not old versus new. It is command flow versus event hook flow, global interpreter control versus data-attached callbacks, explicit branch sequencing versus event-stage participation.

In the next segment, we are going to take that comparison down one level. We will look more carefully at what “party-aware” means in FireRed, why scanning party slots is not a trivial helper detail, and how a Showdown-style hook system would model the same kind of information if we tried to move it out of command flow and into data hooks.

All right, so let’s slow down on the phrase “party-aware,” because it sounds almost too simple. It sounds like we are saying, “the AI checks the party.” Fine. It loops over six slots. Fine. But in FireRed architecture, that small idea is sitting at the intersection of several systems we have been tracing since the beginning of the course: persistent creature records, battle-local projections of those records, global battle state, interpreter commands, and branch control.

So, when I say the AI is party-aware, I do not merely mean it has access to an array. I mean the AI command can ask a question whose answer depends on creatures that are not currently acting on screen, and then it can use that answer to redirect the AI script. That is the important step. A party scan becomes strategy only when its result affects command flow.

Let me put that on the board as a chain. Persistent party data, then battle setup, then active battler state, then AI script command, then predicate result, then script pointer branch, then move preference. That is the architecture. Not just “read party.” Read party, interpret condition, mutate the interpreter’s next instruction.

This is exactly why pokefirered/src/battle_ai_script_commands.c matters for us this week. In the earlier chunk, pokefirered/src/battle_ai_script_commands.c, chunk_0a05fcecbd58029f, the high-value idea is AI command execution as interpreter-driven control. In the later chunk, pokefirered/src/battle_ai_script_commands.c, chunk_34a415cc38858fdc, the focus is party scanning, status predicates, and branch movement through sAIScriptPtr. Those details are not decoration around the AI. They are the AI’s programming model.

Now, here is the mental trap I want you to avoid. Do not think of an AI script as a list of “smart behaviors” in the abstract. Think of it as a small program written in a restricted command language. The AI engine is not asking, “What would a good trainer do?” in a human sense. It is executing commands that ask particular questions. Is the target statused? Is this move useful? Does a party member satisfy some predicate? If yes, advance to one address; if no, continue or jump somewhere else. That is a very different design from a table of independent scoring hooks.

Let’s use a concrete example. Suppose we rebuild the simplified if_status_in_party command from the workshop. We will imagine the command takes three conceptual operands: whose party to inspect, which status mask to test, and where to branch if the condition is true. In a FireRed-like C command, the function reads operands from the AI script stream, resolves which party array to inspect, loops over party slots, extracts each creature’s status field, compares that field with the mask, and, if any living relevant party member matches, it assigns sAIScriptPtr to the branch target. If none match, it increments past the command and continues.

Notice how much is hiding there. “Whose party” requires knowledge of battle side. “Party slot” requires knowledge that a party is not the same thing as the currently active battler. “Status” requires knowledge of the Pokemon struct representation, not only the battle-visible gBattleMons projection. “Branch target” requires knowledge of the AI script interpreter’s program counter. So this command touches data model, battle context, and interpreter control all in one place.

Let me say that another way, because it is exam-relevant and design-relevant. In FireRed, party-aware AI predicates are not pure data annotations. They are C functions that participate in an interpreter. Their output is not an event object. Their output is control flow.

Check for understanding here. If I ask, “Where does the intelligence live?” a shallow answer is “in the AI script.” A better answer is “in the combination of the script bytecode, the C command implementations, and the shared battle and party state those commands inspect.” The script by itself cannot scan a party. The C command by itself does not know why that branch matters. The global state by itself is just state. The behavior emerges when the interpreter ties them together.

Now compare that with the Showdown ability model. In pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4, the Stamina example gives us an ability record with an onDamagingHit hook that boosts Defense after damage. In that same chunk, Stance Change uses onModifyMove to inspect the move and attacker state, then changes form under specific conditions. In pokemon-showdown/data/abilities.ts, chunk_8a03d04894b7e127, Infiltrator uses onModifyMove to mark the move as infiltrating, and Innards Out uses onDamagingHit to react when the target has no HP left. The shape is different. The ability is not an instruction in a script stream. It is a data record that registers interest in an engine event.

So on the board, under FireRed, I would write “command asks question, then moves script pointer.” Under Showdown, I would write “engine reaches event stage, then invokes attached hooks.” That is the comparison model.

Now let’s imagine moving our simplified if_status_in_party concept into Showdown style. We have to be careful, because this is a modeling exercise, not a claim that Showdown has exactly the same command. If we wanted an ability that cared whether an ally in the party had a status condition, we would not naturally implement it as “read operands from an AI script and branch.” Instead, we would probably attach a callback to some event stage, perhaps before move selection, on switch-in, onModifyMove, or onBasePower, depending on what visible effect we wanted. The hook would ask, “Does this Pokemon’s side have a reserve Pokemon with this status?” and then it would modify some event-local object: the move, the base power, the decision, the volatile state, or maybe the message log.

That distinction is enormous. In FireRed, the party scan can decide what the AI script does next. In Showdown style, the party scan more naturally decides how a currently occurring event is modified. Command flow versus event participation.

Let’s make the example specific. Suppose a trainer AI wants to prefer Heal Bell if any party member is statused. In FireRed-style AI scripting, you can imagine a sequence like this: if no party member has status, skip the Heal Bell encouragement block; otherwise continue into commands that raise the score for Heal Bell. Again, I am describing the architecture, not quoting an exact script listing. The key operation is that the predicate controls whether the script visits the scoring instructions.

In a Showdown-style ability system, if an ability wanted to strengthen a move when the side contains a statused ally, you might write a data hook such as onBasePower or onModifyMove. The engine gets to the move-modification stage, gathers relevant hooks, calls the ability’s callback, and that callback inspects side state and modifies the current move calculation. The hook does not normally say, “jump to another instruction in an AI program.” It says, “at this event stage, adjust this event.”

This is why pokemon-showdown/data/abilities.ts, chunk_b4a38d21b0114ee8, is relevant for our secondary comparison: it contains residual and base-power hook examples, which show abilities participating at specific event stages rather than as linear commands. And chunk_6372bce3e311c10b is useful because it reinforces the broader data-engine split: the data file supplies declarative records and callbacks, while the simulator engine supplies the event schedule that gives those callbacks meaning.

Now, a subtle point. A callback is still code. We should not overstate the difference by saying FireRed is “code” and Showdown is “data.” That would be false. Showdown ability entries include executable callback bodies. The distinction is not code versus no code. The distinction is where the code is attached and who controls sequencing.

In FireRed AI commands, sequencing is explicit in the script pointer. The script says, in effect, “perform this predicate; depending on the result, continue here or there.” In Showdown ability hooks, sequencing is mostly owned by the battle engine’s event system. The data record says, “when this event occurs, call me with these arguments.” The hook may return a value, mutate an object, boost a stat, or add a message, but it is not the primary owner of the whole event loop.

Let’s tie that back to our course’s recurring trace. We have repeatedly followed data table to runtime globals to callback or task to player-visible behavior. In early weeks, that meant startup and main callbacks, with AgbMain and SetMainCallback2 in pokefirered/src/main.c, and task dispatch through CreateTask and RunTasks in pokefirered/src/task.c. We are now doing the same style of trace inside battle AI. Data and scripts enter the runtime. Runtime globals represent battle and party state. An interpreter command reads that state. The command changes sAIScriptPtr. Later scoring or decision logic changes the move the opponent appears to choose. Same trace pattern, narrower subsystem.

And this is also why I do not want you to treat party scanning as a helper that can be casually moved anywhere. A party scan has a cost, an ownership question, and a semantic question. Cost: are we looping over six party slots every time a move is evaluated, every time a branch is reached, or every time an event hook fires? Ownership: does the AI own the scan, does the battle engine own it, or does the creature data model expose it as a query? Semantic question: are fainted members included? Eggs? Empty slots? Transformed active battlers? Link battle parties? Double battle partners? These are not philosophical edge cases. These are the kinds of details that distinguish a robust engine from a demo.

Let me pause for a check. If I give you this statement: “Showdown abilities are more data-driven than FireRed AI commands, so they are automatically cleaner.” What is wrong with that? The answer is that “data-driven” does not automatically mean simpler or better. It changes the extension surface. Showdown’s model is excellent when many independent effects need to subscribe to shared event stages. FireRed’s AI command model is excellent when a compact script language needs explicit branch control over scoring logic. The designs solve different problems.

Now, if you were implementing a minimal clone, which one should you choose? That depends on what you are cloning. If your goal is to reproduce FireRed-like trainer AI, a tiny bytecode or command-list interpreter is surprisingly appropriate. It lets designers or data scripts express “if this, then skip that scoring block” without writing one giant C or JavaScript function per trainer. If your goal is to reproduce a modern battle simulator with hundreds of abilities and move effects that all participate in shared timing windows, then an event hook model becomes much more attractive. You want “onModifyMove,” “onDamagingHit,” “onResidual,” and “onBasePower” style surfaces because the complexity is distributed across effects.

Look again at Stamina in pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4. The ability does not need to know the whole turn script. It says, when this Pokemon takes a damaging hit, boost Defense. That is beautifully local. But imagine writing a trainer AI strategy entirely as dozens of such local hooks. You would quickly ask, “Where is the policy? Where is the ordering of thoughts? Where does this trainer decide to prefer one move over another?” That is where FireRed’s script pointer model remains instructive.

Conversely, imagine implementing Stamina inside a FireRed-style AI command script. You could, but it would be awkward if every ability had to be woven manually into a central command flow. You would risk building a giant switchboard of special cases. Showdown avoids much of that by letting ability records carry their own event participation. The engine says, “I am at onDamagingHit,” and Stamina answers. That is the data-attached callback advantage.

So the design lesson is not “pick one forever.” The lesson is to match extension style to the axis of variation. If the variation is strategic control flow, scripts and predicates are strong. If the variation is battle effect timing, event hooks are strong.

Let me write that sentence on the board: extension style follows the axis of variation. Strategic branching wants explicit control flow. Reusable battle effects want timed hooks.

Now let’s bring this back into the week sixteen lab. When you rebuild a simplified if_status_in_party AI command, I want you to include three layers in your explanation. First, identify the state being queried. For example, party slots and status flags. Second, identify the interpreter consequence. For example, whether the script pointer jumps or advances. Third, identify the visible strategic consequence. For example, the AI increases interest in a curing move or avoids a move whose value depends on the target being statused.

Then, when you implement one Showdown-style callback ability, use the same three-layer explanation but change the middle layer. State being queried: maybe the move category, damage event, attacker species, or side condition. Hook consequence: modify the move, boost a stat, change form, alter base power, apply residual behavior. Visible consequence: the player sees a defense boost, a form change, a move penetrating screens, or a damage response. That is how you compare them without flattening them into vague “data-driven design.”

One more sophistication, because you are advanced students and week sixteen is not the time for hand-waving. Both systems rely on shared contracts. In FireRed, headers and constants make the C modules agree on what a status bit means, what a party slot is, what a battler index is, and what battle state globals are available. The research packet points us to pokefirered/src/battle_util.c, chunk_9335e5727aa0657d, where the include list itself is revealing: battle utilities pull together battle headers, battle scripts, battle messages, controllers, AI command headers, move constants, item constants, ability constants, Pokemon constants, and hold effect constants. That does not prove a particular AI command by itself, but it does show the dense contract surface around battle behavior. The AI command file is not operating in a vacuum.

In Showdown, the contract is event names, callback signatures, effect objects, Pokemon objects, move objects, and side or battle APIs. An ability entry in pokemon-showdown/data/abilities.ts can only be concise because the simulator engine promises to call onModifyMove with a move object at the right time, or onDamagingHit with damage, target, source, and effect-like context at the right time. So again, “data hook” does not mean “free-form magic.” It means the engine has created a disciplined vocabulary of extension points.

This is where your homework should become more precise than your earlier midterm answers. Back in the midterm, many of you could decompose the RPG into runtime shell, overworld maps, scripts, party data, battle, inventory, UI, save, and link systems. That was appropriate then. Now, for week sixteen, “battle system” is too broad. I expect you to say things like: this predicate reads party state rather than active battler state; this command affects sAIScriptPtr rather than damage directly; this hook participates in onModifyMove rather than owning move selection; this callback modifies an event object rather than branching an AI script.

Let’s test that with a quick verbal exercise. I will describe a behavior, and you classify the extension style. “If any reserve party member is poisoned, jump to the block that increases the score of a curing move.” That is FireRed-style AI predicate control flow. “When this Pokemon is damaged by an attack, boost its Defense by one stage.” That is Showdown-style event hook behavior, like Stamina in pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4. “Before a move resolves, mark it as bypassing certain defensive protections.” That is also a Showdown-style move modification hook, similar in shape to Infiltrator’s onModifyMove in pokemon-showdown/data/abilities.ts, chunk_8a03d04894b7e127. “If a status predicate fails, skip the next AI scoring block by assigning a new script address.” That is back to FireRed command flow, grounded in the sAIScriptPtr branching model described for pokefirered/src/battle_ai_script_commands.c, chunk_34a415cc38858fdc.

Good. Now the final point for this segment: party awareness raises the AI above the immediate turn, but it does not make it globally intelligent. It gives the AI access to a wider state window. That window includes non-active party members, so the AI can reason about team condition in a limited scripted way. But the reasoning is still bounded by the predicates the engine authors implemented and the scripts that invoke those predicates. This is an important design humility. Access to more state is not the same as flexible planning. It is only flexible if the command vocabulary and branch structure can express the plans you care about.

And with that, we are ready to go one level deeper into implementation shape. Next, I want to walk through the anatomy of that simplified party-status command as if we were writing it in a FireRed-like engine: where the operands come from, how the party pointer is selected, how the loop avoids confusing empty or invalid slots with real creatures, and exactly where the branch target enters the interpreter. Then we will contrast that with the callback registration path in the Showdown ability record, where the equivalent logic is not a branch instruction but a timed participant in the battle engine’s event sequence.

So let us put that on the board as a tiny interpreter story. I am going to write it in words first, not code, because I want you to see the control flow before you worry about syntax.

We have an AI script pointer. That pointer is not a metaphor. In the FireRed battle AI command source, the command functions are organized around advancing or replacing the current AI script pointer, and the packet specifically flags pokefirered/src/battle_ai_script_commands.c, chunk_0a05fcecbd58029f, as the battle AI command source and chunk_34a415cc38858fdc as the party-scanning, status-predicate, and sAIScriptPtr-branching region. So when I say “the command branches,” I mean the command is literally deciding whether the next interpreter step reads the next sequential bytes or starts reading from a different address.

Now, imagine the script bytecode says something like this: “if there is a party member with this status, jump over there.” In a FireRed-like implementation, the command function begins by reading operands from the script stream. First operand: whose party are we inspecting? It might be the AI battler’s own side, or the opposing side, depending on the command vocabulary. Second operand: what status mask are we testing? Poison, paralysis, sleep, burn, maybe some encoded combination. Third operand: where is the branch target? And that third operand is the crucial one. It is not a return value to a high-level planner. It is an address or offset that becomes the next value of the AI script pointer if the predicate succeeds.

Let’s slow that down. The source-level shape is: read command arguments, choose a party array, scan party slots, compute true or false, then either assign sAIScriptPtr to the branch target or advance sAIScriptPtr past the operands. That is the anatomy. Data comes in as compact script bytes. Runtime state is held in battle globals and party structs. Behavior changes because the interpreter pointer changes. That is our course thesis again, but now compressed into one command: data record, global state, callback-ish interpreter function, struct scan, branch, visible battle choice.

Now I’ll draw a six-slot party row on the board. Slot zero through slot five. If we are checking the player’s party, we are scanning one global party array. If we are checking the enemy trainer’s party, we are scanning another. In FireRed architecture, you should be hearing echoes of earlier weeks here. This is not JSON object traversal. This is not a dynamic registry of arbitrary components. It is fixed memory, fixed constants, fixed party capacity, and small functions that know which global arrays represent which side of the world. We saw the same philosophy in map headers, trainer tables, wild encounters, and save blocks. The battle AI just gives us a particularly concentrated example.

Here is the common mistake in student implementations of this lab. They loop over six party slots and check the status field, but they forget to ask whether the slot contains a real creature. That produces a very quiet bug. Empty party memory may contain a default species value, or a zeroed status value, or whatever your simplified engine uses. If your predicate is “does anyone have no status,” suddenly every empty slot looks healthy. If your predicate is “does anyone have poison,” maybe you get lucky and zero memory does not match poison. But “getting lucky” is not architecture. The actual command must distinguish between an occupied, valid party member and an empty or unusable slot.

So in the simplified lab version, I would expect you to do the scan in this order. First, identify the party pointer. Second, for each slot, retrieve the species or validity marker. Third, skip empty slots. Fourth, possibly skip eggs, fainted creatures, or invalid battlers depending on the exact predicate you are modeling. Fifth, read the status condition from the creature struct. Sixth, apply the mask. Seventh, if the mask matches, set found to true and stop scanning. Then, after the scan, perform one and only one pointer decision: branch target if found, sequential advance otherwise.

Notice what I did not do. I did not let the loop itself execute strategic behavior. The loop does not pick a move. The loop does not rank switches. The loop only answers a question that the script language knows how to ask. That distinction matters because it separates the command implementation from the authored AI script. In FireRed’s model, the C function expands the predicate vocabulary, while the script composes those predicates into decision flow.

Check for understanding here. If I add a new command called “if_any_party_member_paralyzed,” where does the new power live? It lives partly in C, because now the interpreter has a new primitive operation. But it also lives in the AI script data, because until a script uses that command and provides a branch target, the new command does nothing visible. Good. That is the hybrid model: authored bytecode plus native command handlers.

Now compare that to the Showdown ability record. In Pokemon Showdown, the examples in pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4, show ability data containing named hooks such as Stamina’s onDamagingHit and Stance Change’s onModifyMove. Stamina’s hook receives the damage event context and calls a boost operation. Stance Change’s hook receives the move and attacker context and can change form before the move continues. In chunk_8a03d04894b7e127, Infiltrator’s onModifyMove marks the move as infiltrating, and Innards Out uses an onDamagingHit hook with ordering information. The architectural shape is different. The ability record is not saying, “if true, set the script pointer to address X.” It is saying, “when the battle engine reaches this named timing point, include this ability’s function in the event sequence.”

That gives us a clean comparison. FireRed AI command flow is instruction-centered. Showdown ability flow is event-centered. In FireRed, the question is, “What command is the interpreter executing right now, and does that command move the pointer?” In Showdown, the question is, “What effects are currently attached to participants, and which hooks should fire at this timing point?” Both are data-driven in a broad sense, but they are not data-driven in the same way.

Let’s make that concrete. Suppose we want a behavior like Stamina: when this creature is hit by a damaging move, raise its Defense. In a Showdown-style ability record, that is naturally a hook. The battle engine already has a damaging-hit event. The ability data says, “At onDamagingHit, run this function.” The function does not need to search an AI script for the correct branch address. It does not need to author a sequence of bytecode tests. It participates in a timing slot that the engine already defines. That is why the example in pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4, is so compact as a design artifact: the timing is encoded by the hook name, and the effect is encoded by the callback body.

Now suppose we want the FireRed AI to prefer a move if any enemy party member is poisoned. That is a different kind of behavior. We are not responding to a universal damage event. We are asking a strategic predicate during move choice. So the FireRed-like solution is to add or use a predicate command: scan the relevant party, test the status mask, branch to a script block that raises or lowers a score. The status check does not happen because the battle engine reached “onDamagingHit.” It happens because the AI interpreter reached that command in that script.

These two approaches have different failure modes. In the FireRed-style AI command, a common failure is pointer mismanagement. You read the wrong number of operand bytes, so the interpreter resumes in the middle of an argument. Or you branch to the wrong address. Or you scan the wrong party because the battler-to-side mapping is wrong. In the Showdown-style hook, the common failure is timing or event semantics. Your callback fires before the move has the property you expect, or after damage has already been finalized, or for a move category you forgot to filter. The bug surface moves from byte-level control flow to event-order correctness.

Let me pause and ask a slightly harder question. Which model is more extensible? The tempting answer is “Showdown,” because adding a new ability hook looks cleaner than writing C bytecode commands. But that answer is too simple. Showdown is more extensible along the axes its event system exposes. If there is a hook at the moment you need, the extension is elegant. If there is not, you still have to change the engine. FireRed’s AI script system is less flexible syntactically, but it is very efficient and very explicit for the constrained decision language it supports. Extensibility always means “extensible relative to which future changes?”

That is the advanced lesson for week sixteen. We are not ranking old C as bad and TypeScript callbacks as good. We are identifying the contract each engine offers to content. FireRed says, in effect, “Here is a compact command vocabulary. Author AI behavior by branching through this vocabulary, while native functions inspect battle state.” Showdown says, in effect, “Here are event names in the simulator. Attach effect records with callbacks, and the engine will call them at the right phase.” These are two different answers to the same architectural question: where do designers get to insert behavior without rewriting the whole battle loop?

Now, back to the lab. When you rebuild a simplified if_status_in_party command, I want you to deliberately preserve the FireRed flavor. Do not turn it into a general-purpose query engine too quickly. Give it a script pointer. Give it operands. Give it a fixed party size. Give it a branch target. Then implement one Showdown-style callback ability separately. For example, implement a toy Stamina: after a damaging hit, call onDamagingHit and increase a defense stage. Do not implement that by jumping a script pointer. Make it an event callback. The point of the lab is not just that both examples work. The point is that they work through different extension surfaces.

On the board, I’ll write two sentences, and these are worth copying. In the FireRed AI command, state inspection controls instruction flow. In the Showdown ability hook, engine timing controls callback execution. Those sentences are compact, but they contain most of today’s comparison.

Let’s connect this back to the course plan. In week one, we decomposed the creature RPG into runtime shell, overworld, scripts, creature data, battle, inventory, UI, save, and link systems. By week sixteen, we are no longer just naming those systems. We are tracing how a particular gameplay behavior crosses their boundaries. Earlier, when we studied SetMainCallback2 and RunTasks, the question was, “How does the game decide what subsystem runs each frame?” When we studied maps and scripts, the question was, “How do data records become player-visible world behavior?” When we studied battle damage and status, the question was, “How do shared battle globals carry consequences from one command to the next?” Today, with AI predicates and ability hooks, the question is, “Where can authored or data-attached behavior enter the decision process?”

That is why the FireRed source focus matters. pokefirered/src/battle_ai_script_commands.c, especially the cited chunk_34a415cc38858fdc, is not just a file full of conditionals. It is a boundary layer. On one side are AI scripts, compact and authored. On the other side are battle globals, party structs, status fields, and native helper logic. The command function is the translator. The Showdown abilities file is also a boundary layer, but of a different kind. pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4 and chunk_8a03d04894b7e127, shows effect records whose hook names line up with simulator events. There, the translator is the event engine that knows when to ask each ability whether it wants to modify the move, react to damage, or apply an effect.

So, as you design your clone engine, do not ask only, “Should I use scripts or callbacks?” Ask, “Which behaviors need authored branching, and which behaviors need timed reaction?” Enemy move choice benefits from authored branching because strategic predicates compose well as decision scripts. Abilities benefit from timed reaction because they often attach to lifecycle moments: before a move, during targeting, after damage, at residual cleanup. If you mix those up, your engine becomes awkward. You end up writing AI scripts that pretend to be event listeners, or event callbacks that secretly perform long strategic planning.

One last check before we move on. If a party-status command scans the bench and then changes sAIScriptPtr, is it modifying battle state directly? Usually, no. It is modifying control flow in the AI interpreter. If an ability’s onModifyMove callback changes a move flag, is it modifying interpreter flow? No. It is modifying the move object or event context while the simulator continues through its phase sequence. That difference is small in code and large in architecture.

Hold onto that distinction, because in the next part we are going to use it to evaluate student clone designs. We will look at three possible implementations of the same feature: a hard-coded C-style predicate, a bytecode AI command, and a Showdown-style event hook. Then we will ask which one is easiest to test, which one is easiest to author, which one is easiest to keep deterministic, and which one best matches the runtime shell we have been building since the first week.

All right, so let’s do that evaluation now. Suppose the feature we want is this: the opponent should prefer a safe switch if any party member is already poisoned and the active matchup is unfavorable. I am deliberately making the feature a little awkward, because real battle AI features are usually awkward. They are rarely, “if HP is low, heal.” They are more like, “if a bench creature has a status that changes the value of a future move, and if this active creature cannot pressure the target, branch to a different scoring routine.” That is the kind of feature that exposes architecture.

Now, first version. Hard-coded C-style predicate. On the board, I’m writing this as a regular function: “bool AnyPartyMemberHasStatus(struct Pokemon *party, u32 statusMask).” Then below it, “if true, adjust score or choose branch.” In a FireRed-like codebase, that is perfectly natural. You already have party data in fixed arrays. You already have status stored in creature structs. You already have constants for status bits. You can write a loop from slot zero to party size, skip eggs or fainted members if the rule requires it, read the status field, and return true when the mask matches.

The advantage is obvious. It is easy for a C programmer to inspect. It is easy to step through. It is also fast, though speed is not the main point here. The deeper advantage is that it matches the memory model. FireRed’s battle engine is full of this style: concrete structs, globals that identify active battlers, and source functions that directly ask the current runtime state questions. We saw that same include-heavy, shared-constant style in battle utility code earlier; the packet’s `pokefirered/src/battle_util.c` `chunk_9335e5727aa0657d` is a good reminder of the environment. The battle code pulls in battle headers, move constants, ability constants, item constants, controller interfaces, scripts, messages, animation definitions, and utility functions. That is not an isolated object model. It is a tightly linked C runtime where a function can know a great deal about the battle world because the battle world is compiled around shared declarations.

But the hard-coded predicate has a cost. If every trainer behavior becomes a new C function, then authoring AI becomes programming. That may be acceptable for engine developers, but it is poor for content authors. It also makes variant trainer personalities harder to express. If you want five trainers to react differently to poisoned bench members, you either add parameters, add more flags, add more functions, or push the choice into some table anyway. So this first version is clear and efficient, but it is not very flexible.

Second version: bytecode AI command. This is closer to the actual week sixteen source focus. Instead of saying, “the C function chooses the policy directly,” we say, “the AI script contains an instruction that asks a party-aware question and branches the script pointer.” In the FireRed battle AI command source, the important architectural object is not just the predicate. It is the instruction pointer. The lecture packet points us to `pokefirered/src/battle_ai_script_commands.c` `chunk_0a05fcecbd58029f` for the battle AI command source and `pokefirered/src/battle_ai_script_commands.c` `chunk_34a415cc38858fdc` for party scanning, status predicates, and `sAIScriptPtr` branching. That combination is the key. The AI command reads operands from the script, evaluates current battle or party state, and then either advances normally or rewrites where the interpreter will read next.

So on the board, I’ll write the abstract shape, not the exact C. “Read status mask.” “Read branch address.” “Scan party.” “If match, set `sAIScriptPtr` to branch address; else continue.” That is the shape of a bytecode command. Notice where the policy lives. The low-level question, “does some relevant party slot have this status,” lives in C. But the decision graph, “what should I do if the answer is yes,” lives in AI script data.

That is the compromise FireRed likes. It does not make everything data-driven in the modern scripting-language sense. It exposes selected questions as opcodes. Once an opcode exists, AI scripts can compose it with other predicates and branches. That is why we keep using the phrase “scripted predicates.” The script is not simulating the battle by itself. It is steering a compiled interpreter through prepared tests.

Check for understanding here. If I write a new AI bytecode command called “if poisoned party member exists,” where is the party scanning loop? In C. Where is the trainer-specific choice of what to do after that condition? In the AI script branch target. And what state gets modified when the condition succeeds? Usually the AI script pointer, not the creature’s HP, not the field status, not the move object. That is the distinction from the last passage.

Now, third version: Showdown-style event hook. Here we do not add an AI instruction. We attach behavior to data records. Pokemon Showdown’s ability data is full of examples where an ability object declares hooks such as `onDamagingHit` or `onModifyMove`. The packet gives us `pokemon-showdown/data/abilities.ts` `chunk_d5bdb17c16d6f1a4`, where Stamina reacts through `onDamagingHit` by boosting defense, and Stance Change uses `onModifyMove` to inspect the attacker, the defender, and the move before possibly changing forme. That is not a script pointer branch. That is an event callback registered by the ability definition and invoked by the simulator at a particular phase.

Let’s make the same feature Showdown-shaped. Maybe we create an ability called “Toxic Relay.” When this Pokémon acts, if an ally in the party is poisoned, modify this move’s base power, or alter targeting, or add a secondary effect. In a Showdown-style architecture, the ability record can carry the behavior. The simulator reaches a move-modification phase, gathers applicable effects, and calls `onModifyMove` or a related hook. The ability code receives the move and participating Pokémon as arguments. It can set a property on the move object, or return a modifier, or cause a boost, depending on the hook contract.

There is an important source-backed example of this in the packet beyond abilities. `pokemon-showdown/data/mods/gen9ssb/moves.ts` `chunk_b62432a8edf466f6` shows a move definition with `onTryMove`, `onPrepareHit`, and `onModifyMove`, and inside `onModifyMove` the move’s type, accuracy, base power, category, flags, and target can be changed. You should hear that as a different grammar of extension. The data record is no longer merely selecting a compiled function or feeding constants into it. The data record contains executable callback fields that the engine knows how to call at named moments.

That gives us real advantages. It is highly local. If I want to understand Stamina, I can inspect the Stamina ability entry. If I want to understand Infiltrator, the packet’s `pokemon-showdown/data/abilities.ts` `chunk_8a03d04894b7e127` shows the ability setting move behavior through `onModifyMove`, where the move is marked as infiltrating. That is author-friendly for a simulator with many generations, mods, and exceptions. It also makes regression tests pleasant: set up a battle, give a Pokémon the ability, trigger the event, and assert the resulting state.

But it also has costs. The engine must define hook order, hook priority, event context, return conventions, and conflict resolution. If two abilities modify the same move, the architecture must say which one wins or how modifiers compose. If a hook reads party state, the simulator must expose a clean party API. And if hooks are arbitrary code, then determinism depends on the discipline of the event system and the test suite. This is why Showdown’s model is not simply “more data-driven” in a vague sense. It is data records plus an event engine plus callback contracts.

Let me pause and connect this back to our course architecture. Since week one, we have been tracing visible behavior through layers: data records, globals, callbacks, tasks, scripts, structs, and concrete functions. FireRed’s runtime shell gave us main callbacks. The task system gave us per-frame dispatch. Overworld scripts gave us a VM-like progression model. Battle scripts gave us command dispatch. Trainer and encounter data fed battle setup. Battle results flowed back to the world. Now, in week sixteen, AI script pointer control is the same course pattern at a smaller scale. A trainer’s decision is not magic. It is data and interpreter state meeting battle globals and party structs.

So when you design your clone, do not ask only, “Can I implement this feature?” Ask, “Where does this feature want to live?” If the feature is a small reusable question about runtime state, like “does any party member have status X,” it probably belongs as a C-style or engine-level predicate. If the feature is part of trainer policy, like “branch to aggressive scoring if that predicate is true,” it probably belongs in AI script or AI data. If the feature is a creature ability that should trigger whenever the simulator reaches a named phase, it probably belongs as a hook on an ability data record. Those are three different homes for behavior.

Here is a concrete comparison. Imagine a move called Venom Signal. It has 60 base power, but if any party member on the user’s side is poisoned, the AI should value it more highly, and the move itself should become 90 base power. There are two pieces here. The actual battle effect, “base power becomes 90,” is not an AI policy. It is a move or ability rule. In a Showdown-like system, I would expect that to live in a move callback, perhaps an `onBasePower` or `onModifyMove` style hook depending on the engine’s conventions. The AI scoring rule, “value it more highly when the party condition is true,” is separate. In FireRed style, that might be an AI script predicate that checks party status and branches to add score. In a hard-coded prototype, it might be a direct function call inside the scoring routine.

Now, why separate those? Because the battle rule must remain true even if a human player uses the move. The AI valuation matters only when the computer is choosing. If you collapse them, you get architecture drift. You make the AI responsible for knowing battle legality or battle math, or you make the battle effect responsible for trainer preference. That is the kind of design that passes a one-case demo and becomes painful by week four of a clone project.

Let’s do a quick comprehension check. I’m going to say three implementation statements, and I want you to classify each one silently. First: “When this command sees a poisoned party slot, it assigns a new address to `sAIScriptPtr`.” That is FireRed-style AI bytecode control flow. Second: “When this ability is hit by a damaging move, it boosts defense.” That is Showdown-style event hook behavior, and the packet’s Stamina example in `pokemon-showdown/data/abilities.ts` `chunk_d5bdb17c16d6f1a4` is exactly in that family. Third: “The battle scoring function directly loops over the party array and adds ten points.” That is a hard-coded engine predicate or scoring rule. All three can produce intelligent-looking behavior. They differ in authorship, testing, and where extension pressure accumulates.

Let’s talk testing, because at the advanced level this is where many designs reveal themselves. The hard-coded C-style predicate is easiest to unit test if you have good constructors for party state. Build a party struct array, set one member’s status, call the predicate, assert true. But if that predicate is embedded deep in turn selection, it becomes harder. You need to drive more of the engine to reach it.

The AI bytecode command is best tested with a tiny script. You construct battle state, initialize the AI interpreter pointer to a script containing the command, run one command, and assert that the pointer moved to the expected branch or advanced normally. That test is more architectural, because it validates both the predicate and the interpreter contract. It is also closer to what this week’s lab asks you to rebuild with a simplified `if_status_in_party` command.

The Showdown-style hook is tested by triggering the hook phase. Give a Pokémon the ability, define the move context, run the event or a minimal battle turn, and assert that the move object or battle state changed. That is one reason Showdown’s architecture works well for a large rules simulator: the rules are attached to named events, and tests can focus on those events. But remember, that benefit depends on the engine having a well-defined event dispatcher. You cannot just sprinkle callbacks into data records and call it architecture.

Now I want to put a boundary around an easy misconception. FireRed is not “bad” because it does not use Showdown-style hooks everywhere. FireRed is a Game Boy Advance game. It is built around static data, C code, fixed memory budgets, and predictable control flow. Its data-driven behavior is real, but it is selective. Scripts are bytecode. Tables select data. Callbacks and tasks structure the frame loop. Battle commands execute through interpreters. That is data-driven architecture under handheld constraints.

Showdown is not “better” in the abstract because it stores more behavior in TypeScript objects. It has a different target. It is a server-side simulator with a test culture, generation mods, and an enormous need to represent many mechanics without recompiling a cartridge ROM. Its ability hooks are elegant for that world. The packet’s ability chunks, including `pokemon-showdown/data/abilities.ts` `chunk_8a03d04894b7e127` and `chunk_d5bdb17c16d6f1a4`, show behavior living close to ability definitions. That is a powerful authoring model. But it moves complexity into event ordering, effect composition, and callback discipline.

So, if you are building the course clone, here is the practical rule. Use FireRed as the spine. That means keep a clear runtime shell, clear scene callbacks, clear task or update dispatch, explicit battle state, and simple scripts for authored decisions. Use Showdown as a comparison model when you need to decide whether a mechanic should be a hook attached to data. Do not import the whole Showdown architecture just because one hook looks convenient. Also do not hard-code everything in the engine just because FireRed uses C. The course goal is not imitation. It is architectural judgment.

Let’s return to the Venom Signal example and make the separation more exact. I would implement the move’s base-power change as battle rule data with a callback, if my clone already has a hook system. If my clone does not have hooks, I would implement it as a named battle effect function selected by the move’s effect id, which is closer to FireRed’s table-to-command style. Then I would implement the AI awareness as an AI predicate. If the AI is scriptable, add an `if_status_in_party` instruction. If the AI is not scriptable yet, create a small engine predicate and call it from the scoring function. But I would leave a clean seam, in the ordinary engineering sense, between “what the move does” and “how the AI values that move.”

Notice also how this connects back to trainer tables and encounter setup without re-opening those lectures. Trainer data decides who enters the battle and what party data is available. Wild encounter data decides a different source for battle setup. Battle result storage decides what returns to the overworld. In between, the battle engine owns turn execution, and the AI owns computer choice. Week sixteen sits right in that middle zone. We are no longer asking, “How did the battle start?” We are asking, “Once it has started, how does authored intelligence read the same battle state the engine is already maintaining?”

I’ll give you one more board cue before we move on. Draw three columns. Label them, in your notes, “compiled predicate,” “AI bytecode command,” and “event hook.” Under compiled predicate, write “fast, simple, engine-owned, low authoring flexibility.” Under AI bytecode command, write “script-authored branch, C-owned question, pointer control.” Under event hook, write “data-local behavior, phase-owned dispatch, callback contract.” That three-column table is the comparison model for the rest of today.

And here is the check I care about most. If I hide the implementation and only tell you, “this feature changes where the interpreter reads next,” you should immediately think script pointer, not ability hook. If I tell you, “this feature changes the move object during move modification,” you should think event hook or move-effect phase, not AI branch. If I tell you, “this feature reads a party array and returns yes or no,” you should ask who consumes the answer. The consumer determines the architecture: engine scoring, script branching, or battle-event modification.

That last sentence is worth holding onto: the consumer determines the architecture. Data alone does not answer the design question. A poisoned party slot is just state. The same state can feed AI policy, move power, ability triggers, UI display, save serialization, or post-battle cleanup. Good architecture does not make one giant “poisoned party logic” bucket. It routes the state to the correct subsystem through the correct control mechanism.

So as we go into the next segment, we are going to tighten this into implementation guidance for the lab. We will sketch a simplified `if_status_in_party` command with operands, branch behavior, and a test harness. Then we will sketch one Showdown-style callback ability and deliberately compare the testing surface. Keep the three columns visible in your notes, because every design choice in that lab should be explainable by pointing to one of those columns.

All right, keep those three columns up, because now we are going to turn the comparison into lab architecture. The point of the lab is not merely to make a branch work. You can make a branch work with almost any pile of conditionals. The point is to preserve the architectural difference between FireRed’s AI command flow and Showdown’s event-hook flow while implementing a tiny version of each.

So, on the board, I am going to write the first shape: command byte, operands, predicate, branch. That is the FireRed side. A script command is not an object sitting in a registry waiting for an event. It is an instruction in a stream. The engine reads it because the script pointer is already moving through that stream. Then the command consumes operands, asks a question about battle state, and either advances normally or rewrites the script pointer to a target address. That is why the source focus for today is pokefirered/src/battle_ai_script_commands.c, especially chunk_0a05fcecbd58029f for the command source and chunk_34a415cc38858fdc for party scanning, status predicates, and `sAIScriptPtr` branching. Those chunks matter because they show the AI as a little predicate language, not as a set of autonomous hooks.

Now suppose our simplified command is called `if_status_in_party`. Do not begin by writing “loop over party and branch.” That is too vague. Begin by naming the operands. Which side’s party are we scanning? Which status are we testing? Where do we branch if the test succeeds? In a FireRed-like script command, those operands are not friendly TypeScript parameters. They are bytes or halfwords read from the script stream. So the lab version should make that explicit: read a side selector, read a status mask, read a branch target. Then evaluate the predicate.

Let me say that again in implementation language. The script pointer points at the next operand after the command byte. The command function reads the side operand and increments the pointer. It reads the status operand and increments the pointer. It reads the jump offset or jump pointer and increments the pointer. Then it scans the relevant party slots. If any eligible party member has the requested status bit, it assigns the script pointer to the branch target. If not, it leaves the script pointer at the fallthrough position. That last sentence is the whole control model.

Pause there. Check your understanding. If the predicate is false, should the command jump to a “false branch” automatically? In the simple FireRed-style command we are sketching, no. The false case is fallthrough. That is very important. The script author gets an if-goto shape, not a full if-else object. If they want an else path, they can arrange script bytes so the fallthrough path performs the else behavior and later jumps around the true block. That sounds primitive, but it is exactly the kind of compact control style we have been tracing since we discussed battle scripts and AI scripts. The sophistication is in the script composition, not in each command being a rich control-flow object.

Now, concretely, imagine the AI is deciding whether to use Aromatherapy, or in a more FireRed-era vocabulary, some move or item-like effect that benefits the party by clearing status. The AI wants to know, “Does my party contain a poisoned, paralyzed, burned, asleep, or frozen member?” So the script command might say: scan the AI party, use a status mask for major conditions, and if found, branch to a score-increase block. The score-increase block might add to the desirability of a cleansing move. If no status exists in party, the pointer simply falls through to the next predicate. No global “healing strategy manager” is needed. The AI’s policy is encoded as a stream of small tests.

Notice how this uses the same party data that other systems use, but with a different consumer. The creature records store status. The battle globals expose enough party and battler context for the AI command to inspect. The AI script command converts that state into pointer movement. The visible behavior is that the opponent chooses a move differently. That trace is exactly the course spine: data record, runtime globals, control mechanism, concrete function, player-visible behavior.

Let me contrast that with the Showdown side now. In Pokemon Showdown, the ability examples in pokemon-showdown/data/abilities.ts are not written as a bytecode command stream. They are data entries with named callbacks. In chunk_d5bdb17c16d6f1a4, for example, Stamina has an `onDamagingHit` hook that boosts Defense after damage, and Stance Change has an `onModifyMove` hook that can change Aegislash’s forme depending on the move. In chunk_8a03d04894b7e127, Infiltrator uses `onModifyMove` to mark a move as infiltrating, and Innards Out uses `onDamagingHit` to react when the target has no HP. Those are not AI script predicates. They are event subscribers expressed as fields on an ability data object.

So for the second lab task, when you implement a Showdown-style callback ability, do not simulate `sAIScriptPtr`. There should be no script pointer at all. Instead, make a tiny event dispatcher. The dispatcher says, “A damaging hit just occurred. Which active abilities have an `onDamagingHit` callback? Call them with damage, target, source, and move.” That is a very different extension point. The ability does not decide where the engine goes next by rewriting a pointer. It reacts at a named phase in the engine’s event sequence.

Here is the concrete ability I recommend for the lab. Implement a simplified Stamina. Give a creature an ability object with `onDamagingHit`. When the creature takes damaging hit damage, call the hook. The hook increments the target’s Defense stage by one. That is enough. You do not need the whole Showdown stat-stage system, volatile effects, message protocol, doubles targeting, or immunities. You just need the shape: ability data contains a callback; the engine invokes that callback at the damaging-hit phase; the callback mutates battle state through the engine context.

Now I am going to draw the comparison as a spoken table, so follow the columns. In the FireRed-like AI command, the trigger is “the interpreter reached this command.” In the Showdown-like ability, the trigger is “the battle engine emitted this event.” In the AI command, the input format is operands in a script stream. In the ability hook, the input format is function parameters chosen by the event contract. In the AI command, the output is primarily branch or fallthrough. In the ability hook, the output is usually a mutation to the move, target, stat stage, damage, or some related battle state. That is the heart of the contrast.

This is why I do not want you to write the lab as one generic callback system that handles both. That would erase the lesson. Yes, in a modern engine you could abstract both as “things that run functions.” But that abstraction is too shallow. The important architecture is the source of control. FireRed’s AI command runs because a script interpreter is stepping through script bytes. Showdown’s ability hook runs because the engine has reached a named semantic phase and asks registered effects whether they want to participate.

Let’s connect that to a file-level observation. The FireRed battle utility source, pokefirered/src/battle_util.c chunk_9335e5727aa0657d, shows the dense include environment of the C battle engine: battle headers, move constants, item constants, ability constants, hold effects, weather, battle scripts, AI command headers. That include pattern is a clue about the architecture. The C engine is compiled around shared global structures, constants, and helper functions. The Showdown file pokemon-showdown/data/abilities.ts, including chunks d5bdb17c16d6f1a4, b4a38d21b0114ee8, and 6372bce3e311c10b, points in another direction: data entries with embedded behavior callbacks that the simulator can collect, layer, and test by event phase. Neither is “more real.” They are real in different runtime environments.

Let me ask a diagnostic question. If you want an AI to prefer Heal Bell when a benched teammate is paralyzed, which model is more natural? FireRed’s AI predicate model is more natural. You are asking a decision-making script to inspect party state and branch into a scoring adjustment. Now, if you want an ability to increase Defense every time its owner is damaged, which model is more natural? The Showdown hook model is more natural. You are not asking a decision script to choose; you are asking the battle engine to fire a reaction at the precise moment damage lands.

That distinction will save you from a common design mistake in your clones. Students often build one giant event bus and then make the AI, status rules, item effects, move effects, UI messages, and persistence all subscribe to it. The first week that feels elegant. By the third week, everything can affect everything, and tests become vague. The historical FireRed design reminds us that separate interpreters and callbacks can be very powerful when their responsibilities are narrow. The Showdown design reminds us that named event hooks can also be powerful when the battle domain has many composable effects. The lesson is not “use old C” or “use modern TypeScript.” The lesson is to choose the control mechanism that matches the question being asked.

Now let’s outline the test harness verbally. For `if_status_in_party`, your test should not be “AI chooses the right move” as the first test. That is too high-level. Start with the command. Create a fake AI script buffer. Put the command operands in it. Create a fake party with six slots. Give slot three paralysis. Initialize the script pointer to the operands. Run the command. Assert that the pointer equals the branch target. Then repeat with no status and assert that the pointer equals the fallthrough address. That test directly matches the command’s contract.

Then add one eligibility case. Maybe fainted creatures should not count in your simplified model, or maybe empty slots should not count. Whatever rule you choose, write it down in the test. In the real FireRed source focus, chunk_34a415cc38858fdc is important because party scanning is not just “for every index.” The command has to know which side’s party is being inspected and how status predicates relate to party slots. In your lab, you can simplify, but you cannot make the scan conceptually sloppy. The point is to preserve the data path: party array, slot predicate, branch decision.

For the Stamina-like ability test, the test shape is different. Create a target with Defense stage zero and the ability object. Create a source and a move. Call the battle engine’s damaging-hit event. Assert that the target’s Defense stage is now one. Then create a target without the ability and assert no boost. Then, if your dispatcher supports multiple hooks, assert that the callback runs at the correct event and not during move selection or residual cleanup. That last test is where the Showdown comparison becomes real. In pokemon-showdown/data/abilities.ts chunk_d5bdb17c16d6f1a4, Stamina is specifically an `onDamagingHit` example; it is not a generic “onAnythingBattleRelated” function. The name of the hook is part of the engine contract.

Let’s do one more check for understanding. Suppose a student implements the Stamina-like ability by having the damage function contain this line: “if target ability is Stamina, boost Defense.” Is that Showdown-style? Not really. It may work for one ability, but the extension point is hard-coded into the damage function. A Showdown-style model would have the damage function or surrounding battle event pipeline call a generic `runEvent("DamagingHit", ...)`, and the ability data supplies `onDamagingHit`. Again, simplified is fine. Collapsed is not fine. Preserve the direction of dependency: the engine phase calls the hook registry; the individual ability does not require the engine to know its name.

Now compare that to the FireRed AI command. Suppose a student implements `if_status_in_party` by adding a C function that returns true or false, and then directly calling it from a high-level AI decision tree without any script pointer. Is that FireRed-style? Only partially. The predicate is there, but the command-flow architecture is missing. For this lab, I want the pointer movement because that is the architectural point. The script pointer is not decoration. It is the way FireRed’s AI command language composes behavior.

This is also why the prior weeks matter. When we studied `SetMainCallback2`, tasks, and `RunTasks`, we were not memorizing names. We were learning to ask, “Who owns the next step?” In the overworld, the main callback and task system own frame-to-frame progress. In scripts, the script interpreter owns progression through commands. In battle scripts, command dispatch owns battle effect sequencing. Here, in AI scripts, `sAIScriptPtr` owns the next predicate to evaluate. And in Showdown’s ability system, the battle event dispatcher owns the next hook invocation. Same question, different answer: who owns the next step?

Let me put a small example on the board. Imagine the AI has a move that cures the party. The scoring script says: if status exists in my party, jump to add eight points; otherwise continue. It then checks whether the move is otherwise weak this turn. In a bytecode-like model, that order is explicit in script layout. Predicate one, predicate two, predicate three. The AI author controls policy by arranging commands.

Now imagine a creature with an ability like Infiltrator. In pokemon-showdown/data/abilities.ts chunk_8a03d04894b7e127, the ability uses `onModifyMove` to set an infiltrating property on the move. That is not arranged inside an AI scoring script. It happens when the engine is modifying the move before resolution. The move’s later behavior changes because the hook annotated or mutated the move object. If you tried to express that as “branch to label if ability is Infiltrator,” you would be modeling the wrong thing. The ability is not making an AI decision; it is participating in move construction.

That is the sophistication I expect at this point in week 16. Earlier in the course, it would have been acceptable to say “FireRed uses scripts and Showdown uses callbacks.” Now that answer is too thin. You should be able to say which script, which pointer, which data record, which global or runtime structure, which callback phase, and which source file gives us evidence. For FireRed today, that means pokefirered/src/battle_ai_script_commands.c chunk_0a05fcecbd58029f and chunk_34a415cc38858fdc. For Showdown today, that means pokemon-showdown/data/abilities.ts chunk_d5bdb17c16d6f1a4 for `onDamagingHit` and `onModifyMove` examples, chunk_b4a38d21b0114ee8 for residual and base-power hook examples, and chunk_6372bce3e311c10b for the data-and-engine split note.

Before we move on, let’s recap the lab deliverable in plain language. Your FireRed-inspired command should prove that a party-status predicate can drive AI script pointer control. Your Showdown-inspired ability should prove that an ability callback can react to a named battle event without being hard-coded into every damage case. Your comparison paragraph should not say “both are functions.” That is true and uninteresting. It should say that the FireRed command is interpreter-driven and branch-oriented, while the Showdown ability is event-driven and mutation-oriented.

One more subtle point: testing these two designs forces different seams. For the AI command, the seam is the script pointer and the party state. You can test without rendering battle messages, calculating damage, or selecting a final move. For the ability hook, the seam is the event dispatcher and the battle state mutation. You can test without building an AI script at all. That is good architecture. Each system exposes a testable contract at the place where it actually makes decisions.

So, as you write this, resist adding clever generality. Do not create a full compiler for the AI script. Do not create all Showdown hook phases. Do not model every status. Choose a small status mask, a six-slot party, one branch target, one event name, one ability callback. But make the control flow honest. If you do that, your small lab will carry the real architectural comparison, and that comparison is exactly what ties week 16 back to the full course: a creature RPG is not one monolithic battle function. It is a network of specialized control systems, each consuming shared state through a mechanism suited to its job.

Now, push one layer deeper. The important thing is not that Showdown is “more modern” and FireRed is “older.” That is too shallow. The important thing is that each engine chooses a different place to locate extensibility.

In FireRed’s AI command path, the extension point is the script opcode plus its C implementation. The script says, in effect, “perform this predicate, and if it is true, move the script pointer over there.” The C function owns the actual walk through party memory, status masks, and branch mechanics. That is the pattern represented by pokefirered/src/battle_ai_script_commands.c chunk_34a415cc38858fdc, where the interesting behavior is not just “check a status,” but “scan party slots, decide whether any relevant creature satisfies the condition, then alter sAIScriptPtr.” The pointer movement is the control flow.

In Showdown, by contrast, the extension point is usually the data record itself. The ability object carries named hooks, and the battle engine calls those hooks at known phases. So when pokemon-showdown/data/abilities.ts chunk_d5bdb17c16d6f1a4 shows Stamina using onDamagingHit to boost Defense, or Stance Change using onModifyMove to choose a form before the move resolves, the control flow is not encoded as “jump to bytecode address A or B.” It is encoded as “the engine is currently in the damaging-hit phase or modify-move phase; call all relevant hooks for this phase.”

Let me put that on the board as two short traces. FireRed trace: AI script byte, command table, C command function, party or battle globals, predicate result, sAIScriptPtr branch, next AI decision. Showdown trace: ability data object, registered event name, battle engine phase, callback invocation, mutation or return value, continued phase resolution. Same kind of design problem: attach special rules to battle state. Very different architectural answer.

Concrete example. Suppose we want an enemy trainer to prefer a move that poisons if the player has no party member already poisoned, but avoid wasting that strategy if poison is already spread. In the FireRed style, you would likely model that as a predicate command. The AI script reaches something like “if status in party,” supplies a battler side or party selector, supplies a status mask, and supplies a jump target. The C implementation reads the party array, checks each valid slot, and either advances normally or assigns sAIScriptPtr to the branch target. The trainer’s intelligence is distributed: the script owns the strategic shape, while C owns the legal and memory-aware predicate.

Now compare that to a Showdown-style ability. Suppose we make an ability called Toxic Pressure. Every time this creature is hit by a contact move, it checks the attacker and maybe poisons it. In Showdown’s model, we do not add an AI opcode. We put a callback on the ability record, probably a damaging-hit or after-hit hook depending on timing. The engine already knows when a damaging hit has occurred, so the ability says, “When you reach this event, here is my local rule.” That is exactly the kind of split you see in pokemon-showdown/data/abilities.ts chunk_8a03d04894b7e127 with Infiltrator setting a move property through onModifyMove and Innards Out reacting through onDamagingHit. The ability is not driving the whole battle. It is contributing at a declared phase.

Check for understanding here. If I ask, “Where is the branch?” in FireRed AI, your answer should be, “In the script pointer.” If I ask, “Where is the branch?” in a Showdown ability hook, your answer should be more careful. Often there is no explicit branch in the ability data. There is a callback registered under a phase name, and the engine decides whether to call it. The ability may return something, mutate something, or do nothing, but it is not usually deciding the next instruction address of an AI script.

This is why the lab is not just programming practice. It is architecture practice. In the first half, when you rebuild simplified if_status_in_party, you are practicing a command-interpreter model. You need a tiny program counter. You need operands. You need a representation of branch destinations. You need a fake party struct or array. And most importantly, you need to make the command function boringly honest: read operands, inspect state, update instruction pointer. If your version hides the pointer update inside a high-level if statement in the test, you have lost the architectural lesson.

In the second half, when you implement one Showdown-style callback ability, you are practicing event-phase modeling. You need an object or table with a named function. You need an engine moment that says, “I am resolving onDamagingHit now,” or “I am resolving onModifyMove now.” Then you call the hook and let it mutate the move, the target, the source, or the battle log. If your version directly calls the ability from the move’s code, again, you have lost the architectural lesson. The point is not “special effect happens.” The point is where the engine allows special effects to enter.

Notice how this loops back to the whole semester. In week 1, we broke the RPG into cooperating systems. We said the overworld, party model, battle engine, script VM, UI, save system, and link behavior are not one giant function. In week 16, we are looking at the same principle at a smaller scale. Battle AI is not one giant intelligence function. Abilities are not one giant special-case switch. Each system creates carefully chosen holes where data or scripts or callbacks can affect execution.

And now I want you to hear a subtle but important distinction. FireRed is data-driven, but not always data-callback-driven. A move table, a trainer table, a wild encounter table, and an AI script are all data. But the data has different degrees of executable flavor. A wild encounter table is mostly declarative. A trainer table is partly declarative and partly routed into battle setup. A battle script is closer to bytecode. An AI script predicate sequence is executable control data. Showdown ability records are data objects that include executable callbacks. So when people say “data-driven,” do not nod vaguely. Ask, “What kind of data? Declarative table? Script bytecode? Function-valued record? Generated source? Runtime JSON? And what system consumes it?”

That question is one of the course’s main through-lines. FireRed’s architecture constantly crosses from static records into global state and then into callbacks or scripts. We saw that in the runtime shell with main callbacks and tasks. We saw it in maps with headers and object events. We saw it in battle setup with trainer and wild encounter inputs. Now we see it inside decision logic: the data record is not enough by itself. Something must interpret it.

So, board cue again: I am drawing three columns. Column one is “representation.” Column two is “interpreter.” Column three is “mutation.” For FireRed AI status-in-party, representation is AI script bytes and operands. Interpreter is the AI script command dispatcher plus the concrete command in pokefirered/src/battle_ai_script_commands.c, especially the party scanning and sAIScriptPtr branching described in chunk_34a415cc38858fdc. Mutation is not necessarily HP or status. The mutation may simply be “the AI script pointer now points somewhere else.” That is still state mutation, and it changes behavior.

For Showdown Stamina, representation is an ability record in pokemon-showdown/data/abilities.ts chunk_d5bdb17c16d6f1a4. Interpreter is the battle event system that reaches onDamagingHit. Mutation is a stat boost, Defense plus one. For Stance Change, same chunk: representation is still the ability record, interpreter is the modify-move phase, mutation is a forme change before the attack proceeds. For the examples in pokemon-showdown/data/abilities.ts chunk_b4a38d21b0114ee8, where residual or base-power-style hooks appear, the same mental model applies: the ability is attached to data, but its meaning depends on an engine phase that calls it.

Now, one advanced warning. Do not over-romanticize callback hooks. They are powerful, but they create their own complexity. In FireRed’s C command flow, you can often follow the branch by reading the current function and the script pointer. It can be dense, yes, and it is less flexible, but the control transfer is relatively explicit. In a hook system, behavior may be scattered across many records. If the engine says, “Now collect all onModifyMove handlers,” then the final move object may be the result of several independent callbacks. That is elegant when the event phases are disciplined. It becomes painful when phase ordering is unclear.

Showdown handles this by making hook names, priorities, and engine conventions central to the design. You can see that in the ability snippets where hooks are not just arbitrary function names. They are phase names: onModifyMove, onDamagingHit, residual-style hooks, base-power modification hooks. Some also carry ordering fields or priority conventions. FireRed’s equivalent discipline is not hook priority; it is command dispatch order, script pointer control, and shared battle globals.

Let me ask a diagnostic question. If two FireRed AI commands both want to influence the next decision, how do they coordinate? They coordinate by sequence. The first command either falls through or branches. The next command is whatever sAIScriptPtr now indicates. If two Showdown abilities both want to influence a move during onModifyMove, how do they coordinate? They coordinate through the event system’s ordering rules and through mutation of the shared move object. That difference matters when you debug.

A FireRed bug might look like: “The AI always jumps past the scoring command when a party member has paralysis.” You inspect operands, party slot iteration, status mask comparison, and sAIScriptPtr assignment. A Showdown bug might look like: “This move unexpectedly bypasses protection after an ability modifies it.” You inspect which onModifyMove hooks ran, in what order, and which properties they set on the move object. Same debugging discipline, different trail of evidence.

This is also why source literacy matters. The excerpted FireRed battle utility include cluster in pokefirered/src/battle_util.c chunk_9335e5727aa0657d is a reminder that battle code sits at the intersection of items, moves, abilities, controllers, scripts, messages, animations, constants, and random behavior. That does not mean every function is equally important. It means when a small command checks a status or an ability changes a move, it is touching a wide contract surface. Advanced work is knowing which contracts are relevant and which are noise.

Let us tie that to the homework. For homework_w16, I am not looking for a large framework. I am looking for evidence that you can name the contract. In the FireRed-style part, your contract is: given party state, status mask, and branch target, the command updates the script pointer correctly. In the Showdown-style part, your contract is: given an event phase and a creature with an ability, the engine invokes the hook at the correct time and the hook mutates only the intended state. Your tests should reflect those contracts.

Here is a small example of a good test shape, spoken rather than coded. For the FireRed command, create a six-slot party. Slot zero is healthy, slot one is poisoned, the rest are empty or healthy. Set the script pointer to an instruction that checks poison and branches to address 20 if found. Run one command. Assert that the script pointer is 20. Then change slot one to healthy, rerun from the same initial instruction, and assert that the pointer falls through to the next instruction. That test does not need real FireRed party structs, but it must preserve the architectural truth of party scanning and pointer movement.

For the Showdown-style ability, create a source, a target, and a move. Give the target a Stamina-like ability. Resolve a simplified damaging-hit event. Assert that the target’s Defense stage increases by one. Then resolve a non-damaging event and assert that nothing happens. If you choose an Infiltrator-style onModifyMove example from pokemon-showdown/data/abilities.ts chunk_8a03d04894b7e127, assert that the move gains an infiltrates-like property during the modify-move phase, not during damage calculation. Timing is part of the behavior.

Do you see the pattern? A weak test says, “The result eventually looks right.” A strong architectural test says, “The result occurs through the correct control surface.” This is the difference between cloning outcomes and understanding engines.

Now, let me make one more connection to prior battle material, briefly. When we studied damage and status, we focused on shared battle state: current move data, battler status, side timers, damage effects, and message or animation hooks. Today we are not re-teaching those. We are asking how other systems decide when to consult or mutate that state. AI predicates inspect it before choosing. Ability hooks inspect or mutate it during resolution. Battle scripts mutate it as part of effect execution. The shared state is the common substrate, but each subsystem has a different access ritual.

That phrase is worth keeping: access ritual. FireRed AI does not casually wander through all code. It enters through AI command dispatch. Overworld scripts do not casually call every map function. They enter through the script engine and special commands. Tasks do not run by magic. They are allocated and then called by the per-frame task runner. This is the course architecture in miniature. Large games stay understandable when state mutation has rituals.

And yes, sometimes those rituals are messy. FireRed has globals. It has dense C. It has command tables and macros and packed data. Showdown has many hook names and mutable objects and interactions that require regression tests. Architecture is not purity. Architecture is the set of constraints that make a large behavior space workable. FireRed’s constraints come from the GBA, C, memory layout, and a content pipeline built around static data. Showdown’s constraints come from simulator correctness, rapid rules changes, mods, and testability across generations. That is why comparing them is valuable, but only if FireRed remains our primary object and Showdown remains a contrast model.

Before we pause this section, check yourself with one final question. If I hand you a new ability effect and say, “It should make sound moves ignore substitutes,” which architecture would you reach for? In Showdown, you probably think of an onModifyMove hook that sets a property on the move, similar in spirit to Infiltrator modifying move behavior in pokemon-showdown/data/abilities.ts chunk_8a03d04894b7e127. In FireRed, you would ask a different question: “Where in the C command flow does substitute blocking happen, and what data flag or ability check is consulted there?” You would not automatically invent a callback object, because that is not the native extension surface of the engine.

So the synthesis for this part is this: FireRed’s AI script predicates and Showdown’s ability hooks both solve the problem of conditional battle behavior, but they assign responsibility differently. FireRed gives strategic control to script bytes and implements truth in C functions that read globals and move pointers. Showdown gives local rule ownership to data records with named callbacks, while the engine owns phase ordering and invocation. Your job as an advanced reader is to trace the path from representation to interpreter to mutation, not merely to recognize the feature on screen. We will keep using that three-part trace as we move from this comparison back into the week 16 wrap-up and the final architecture map of the battle engine.

Now, let’s turn that into the last architecture map for today. On the board, I’m going to write three columns: representation, dispatch, mutation. Under FireRed, representation is not “an ability object with methods.” It is an AI bytecode stream, plus party records, battler globals, move tables, and helper constants. Dispatch is the AI command interpreter advancing or redirecting `sAIScriptPtr`. Mutation is usually indirect: the AI score changes, the chosen branch changes, and later the battle engine executes the selected move through the normal battle command flow. That distinction matters. The AI command does not itself burn the opponent, heal the party, or animate the move. It answers a strategic predicate that influences what command the trainer selects.

In the FireRed source focus for this week, `pokefirered/src/battle_ai_script_commands.c` in `chunk_0a05fcecbd58029f` is our anchor for the AI command source, and `chunk_34a415cc38858fdc` is the anchor for the party scanning and status predicate pattern, especially the `sAIScriptPtr` branching behavior. So when you rebuild the simplified `if_status_in_party` command in lab, I do not want you to think of it as a generic utility function. Think of it as a small VM instruction. It consumes encoded operands from the current script position, looks outward into party state, computes a truth value, and then either jumps the script pointer or lets evaluation continue. That is a very different extension style from Showdown’s ability table.

Now look at the Showdown column. Representation is a data record in `pokemon-showdown/data/abilities.ts`. Dispatch is the battle simulator calling a named hook during a known phase. Mutation is local to the hook’s phase: modify the move, boost a stat, change form, add damage, set a flag. In `pokemon-showdown/data/abilities.ts` `chunk_d5bdb17c16d6f1a4`, for example, Stamina is represented with an `onDamagingHit` hook that boosts Defense after taking a damaging hit, and Stance Change is represented with `onModifyMove`, where the move context determines whether Aegislash should change form. That is not the same shape as “branch this AI script.” It is “when the engine reaches this event point, ask this ability whether it contributes behavior.”

Let’s do a concrete contrast. Suppose our design goal is: “A trainer should favor a curing move if one of their party members has a major status.” In FireRed style, that belongs naturally in an AI predicate. We would encode a script command that says, in effect, check the party for sleep, poison, burn, paralysis, or freeze; if the predicate is true, branch to a scoring adjustment or a move preference path. The command reads party slots. It respects fainted or empty slots according to the surrounding engine conventions. Then it manipulates `sAIScriptPtr`. The result is not the cure itself. The result is that the AI’s evaluation path changes.

Now suppose the design goal is: “When this creature is hit by a damaging move, raise its Defense.” In Showdown style, that belongs naturally in an ability hook. That is the Stamina pattern from `pokemon-showdown/data/abilities.ts` `chunk_d5bdb17c16d6f1a4`. The engine has already reached the event “damaging hit occurred.” The ability does not need to scan an AI bytecode stream. It receives the battle context and applies the effect. If you implemented that as a FireRed-style AI command, you would be putting reactive battle physics inside strategic decision code, which is the wrong ownership boundary.

Check your understanding here. If I say, “this feature changes how the trainer evaluates whether to use a move,” which side of the comparison should you reach for first? FireRed AI predicate style. If I say, “this feature changes the move object before it resolves,” which side? Showdown hook style. And if I say, “this feature changes the actual damage or stat state after a hit,” again, Showdown hook style is the clean comparison model, while FireRed would likely route through battle script commands, effect functions, or battle utility code rather than the AI predicate layer.

And this is where the course-wide trace comes back. Since week one, we have been practicing the same move: table, global, dispatcher, visible behavior. FireRed’s runtime shell gave us callbacks and tasks. The overworld gave us map headers, object events, scripts, and movement state. Creature data gave us species records, party structs, and accessors. Battle gave us move data, battler globals, side timers, status flags, script commands, and result routing. This week adds one more interpreter: the AI script interpreter. It is smaller than the full battle script engine, but architecturally it is still a command stream controlling execution through a pointer.

That is why `pokefirered/src/battle_util.c` `chunk_9335e5727aa0657d` is also useful as supporting evidence. Look at the include surface in that chunk: battle headers, battle scripts, battle messages, controllers, AI script command headers, constants for moves, items, weather, abilities, species, and hold effects. Even from the include list, you can see that FireRed’s battle implementation is not one monolithic “battle function.” It is a set of C modules sharing structured globals, constants, command interpreters, and tables. The AI code lives inside that ecosystem. It is not a separate rules engine in the Showdown sense.

Let me pause and make this precise. FireRed tends to centralize phase control in C command flow. Data tells the engine what exists: moves, species, items, trainers, parties, maps. Scripts tell the engine what path to take: overworld scripts, battle scripts, AI scripts. Globals carry the current state between small command handlers. Showdown, by contrast, often lets data records carry executable callbacks, and the simulator calls those callbacks at phase points. That is the phrase I want you to retain: FireRed uses command flow with data inputs; Showdown uses data hooks inside engine phases.

Now, that phrase is slightly simplified, and at your level I expect you to see the limits of it. FireRed does have data-driven behavior. Showdown still has a central engine. FireRed can use function pointer tables and callbacks elsewhere in the program. Showdown’s data callbacks are not free-floating; they depend on a strict simulator event order. So do not turn this into a cartoon. The serious distinction is about extension surface. If you are adding behavior, where does the existing system expect the behavior to attach? A bytecode command? A C helper? A data table field? A named event hook? A task callback? A main callback? A script command? That question is architectural, not cosmetic.

For the lab, when you implement a simplified `if_status_in_party`, I want the code review standard to be higher than “it works on my sample.” First, identify which party you are scanning. Second, define how empty slots, eggs if relevant to the model, fainted party members, and absent status are treated. Third, separate operand decoding from predicate evaluation if doing so makes your mini-engine clearer. Fourth, make the branch behavior explicit: when true, assign the script pointer to the target; when false, continue to the next instruction. That mirrors the source pattern in `pokefirered/src/battle_ai_script_commands.c` `chunk_34a415cc38858fdc`, where the important architectural move is not just scanning a party; it is scanning a party in service of script pointer control.

Then, when you implement the Showdown-style callback ability, I want you to choose a hook phase and defend it. If you model something like Infiltrator, `pokemon-showdown/data/abilities.ts` `chunk_8a03d04894b7e127` gives the simple comparison: `onModifyMove` marks the move as infiltrating. That is elegant because the behavior belongs to the move before resolution. If you model something like Stamina, use `onDamagingHit`, because the ability depends on a hit that has actually happened. If you put Stamina in `onModifyMove`, you have moved the behavior too early. If you put Infiltrator in a post-hit hook, you have moved the behavior too late. The hook name is not decoration; it is a phase contract.

Let’s connect this to assessment. Your week 16 quiz is not going to ask you to recite the names of hooks as trivia. It will ask you to trace causality. Given a status-aware AI branch, where is the represented condition, who evaluates it, and what state changes immediately? Given a Showdown ability hook, what event invokes it, what object or battler does it mutate, and what later engine phase observes that mutation? Those questions are deliberately similar to the midterm’s “trace data records through globals, callbacks, tasks, scripts, structs, and concrete source functions,” but now the battle layer is denser and the ownership boundaries are subtler.

So, final check for today: if you see `sAIScriptPtr` changing, what should that make you think? Script control flow. Not damage. Not animation. Not final move execution. Script control flow. If you see `onModifyMove`, what should that make you think? A phase hook mutating move properties before later resolution. If you see `onDamagingHit`, what should that make you think? A reactive hook after damage has connected, usually with access to damage, target, source, and effect context. Good.

The synthesis is this. Week 16 closes the battle architecture module by showing that intelligence and special rules are not magic layers above the engine. They are ordinary architecture: representations, dispatch mechanisms, and state mutations placed at carefully chosen boundaries. FireRed’s battle AI is a scripted predicate machine embedded in a C battle engine. Showdown’s abilities are data records with named callbacks embedded in a simulator event system. Both are extensible, both are rule-driven, and both can produce sophisticated battle behavior, but they ask different questions of the programmer. In FireRed, you ask, “which command stream and global state path leads to this decision?” In Showdown, you ask, “which event hook owns this rule, and what later phase consumes its mutation?” That comparison is the bridge from this week back to the whole course: a creature RPG is not one system, but a chain of interpreters, records, callbacks, scripts, structs, and shared state, all cooperating to make a turn on screen look inevitable.

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