# FireRed Battle AI as Scripted Predicates

Lecture ID: lecture_w16_d01
Week 16, Day 1

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

## Transcript
All right, let’s pick up from last week’s battle engine material, but with a slightly different lens today. We are not re-opening the whole battle script dispatcher. We already did that work: bytecode-like commands, shared battle globals, current move data, effects routed through command handlers, messages and animations attached to visible consequences. Today I want you to take that mental model and turn it sideways.

Instead of asking, “How does the battle engine execute a move?”, ask, “How does the trainer AI make a decision before the move is chosen?” That is the architectural shift for week 16. FireRed’s battle AI is not a large, modern behavior tree object graph. It is not a planner in the sense you might know from game AI textbooks. It is much closer to a scripted predicate machine: a compact command stream where individual commands ask questions about battle state, party state, move state, and status state, and then advance or redirect an AI script pointer.

Let me say that again because it is the core phrase for today: FireRed Battle AI as scripted predicates. The AI script is not primarily “doing battle.” The battle engine does battle. The AI script is inspecting battle state and steering scoring logic through branches.

Now, on the board, I’m going to write three terms: state, predicate, pointer. Those are our anchors. State is the current world of the battle: battlers, parties, status bits, selected moves, available moves, HP, side conditions, and so on. Predicate is a question over that state: is any party member asleep, is this battler poisoned, does this move satisfy some condition, can this trainer switch into something useful? Pointer is the control mechanism: if the question is true, move the AI script pointer to one location; if false, continue or branch somewhere else.

That last piece matters because it connects directly to the source focus for today. In `pokefirered/src/battle_ai_script_commands.c`, especially the week’s first focus chunk, `chunk_0a05fcecbd58029f`, the AI command source is organized around command handlers that read arguments and manipulate AI control flow. In the second focus chunk, `chunk_34a415cc38858fdc`, we are especially interested in party scanning, status predicates, and branches through `sAIScriptPtr`. The architectural claim is not just “there is some AI code.” The claim is that FireRed encodes parts of trainer reasoning as small script-level tests that can redirect a script pointer.

If you remember our battle script lecture, we treated the battle script pointer as a kind of instruction cursor for visible battle resolution. Today’s `sAIScriptPtr` is a cousin of that idea, but with a different purpose. It is not there to show “Pikachu used Thunderbolt” or apply the full damage pipeline. It is there so the AI can walk through a decision script and say, effectively, “if this condition holds, go over there; otherwise keep evaluating.” Same family of architecture, different behavioral surface.

Let’s make that concrete. Suppose the AI is considering whether to use a move that cures or benefits from some status condition. A party-aware predicate might need to ask, “Does the opponent’s party contain any Pokémon with a certain status?” Or, from the trainer’s perspective, “Do I have any party members with a particular status?” The implementation cannot answer that question by staring only at the active battler. It has to scan party slots.

So, on the board, I’ll draw a six-slot party array. Slot zero, slot one, slot two, slot three, slot four, slot five. Beside it I’ll draw a little status bitfield: sleep, poison, burn, paralysis, freeze, and so on. The AI command is not asking a philosophical question about strategy. It is walking a concrete party data structure, checking whether a slot is valid, checking whether the creature is present and not an egg or fainted when that distinction matters, reading status, and deciding whether the predicate succeeds. If it succeeds, the command adjusts `sAIScriptPtr` to the branch target encoded in the script. If it fails, the pointer advances normally.

This is why I want you to stop thinking of the FireRed trainer AI as a single opaque “intelligence.” It is a composition of small evaluators. Some are move-aware. Some are battler-aware. Today’s special emphasis is that some are party-aware. The difference is not cosmetic. Once the AI can scan party slots, it is no longer only reacting to the currently active monster. It can make decisions based on the bench, which means the architecture has crossed from local battle state into the broader creature data model.

And notice how cleanly this ties back to the course objective. We have spent the semester decomposing the creature-collector RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. Battle AI sounds like it lives entirely inside “battle,” but the moment it checks the party, it depends on creature data representation. If a trainer was loaded from trainer tables, and those party members were instantiated into battle-side party structures, then the AI predicate is downstream of trainer data. If the result of the battle later routes back to the overworld, then these AI choices also sit inside the larger loop from encounter setup to battle resolution to overworld state. This is why week 16 is not an isolated appendix. It is a capstone-style week where the battle engine touches the rest of the architecture.

Let’s recall just enough of the prior path to orient ourselves. From `pokefirered/src/battle_setup.c`, `chunk_494d132a59422c85`, we saw the battle setup layer include overworld, script, event data, Pokémon, battle transition, and battle headers. That include list is not trivia. It shows that entering battle is a cross-system operation. The battle is not born in a vacuum. It is prepared by the field and event systems, configured by encounter or trainer data, and then handed to the battle runtime. From `pokefirered/src/battle_util.c`, `chunk_9335e5727aa0657d`, we saw the util layer include battle AI script command headers alongside battle scripts, moves, abilities, items, weather, and Pokémon constants. Again, not trivia. AI is embedded in the same shared world of constants and state that the rest of the battle engine uses.

Now, let me pause for a check. If I say, “the AI command is party-aware,” what should that imply architecturally? It should imply that the command cannot be implemented only as a function of the current move. It needs access to party storage, slot iteration, and status extraction. It also implies that the AI script language has grown beyond a simple sequence of scoring operations. It can branch based on state outside the active battler. If that makes sense, you are in the right place.

Let’s now walk through a simplified version of the lab you will do later this week. I am going to call it `if_status_in_party`, not because I want you to memorize an exact function name from the source, but because that phrase captures the architecture. Imagine an AI script command with three conceptual operands: which side or party to inspect, which status mask to test, and where to jump if the condition is true. The handler begins at `sAIScriptPtr`, reads the operands, resolves the relevant party, loops through party indices, checks each Pokémon’s status, and then either assigns `sAIScriptPtr` to the branch destination or advances past the command arguments.

Board cue: I’m writing this as pseudo-control flow.

Read command arguments. Select party. For each slot, skip invalid entries. Read status. If status matches mask, branch. If no slot matches, continue.

That is the whole shape. The sophistication is not in the loop itself. The sophistication is in where this loop sits. It is inside a command interpreter. That interpreter lets designers or engine programmers encode decision paths as data-like scripts rather than as one giant monolithic C decision tree. Now, to be fair, this is still C command flow. FireRed is not giving us a modern external scripting language with reflection and hot reload. But relative to hard-coded one-off AI decisions, a command stream with branchable predicates is a genuine data-control hybrid.

This is where the comparison to Pokémon Showdown becomes useful, but only as a contrast model. FireRed remains our primary architecture source. Showdown is useful because it shows a different extension style. In `pokemon-showdown/data/abilities.ts`, `chunk_d5bdb17c16d6f1a4`, we see ability definitions expressed as data objects with callback hooks such as `onDamagingHit` and `onModifyMove`. The ability is not a bytecode command that manually moves an AI script pointer. Instead, the simulator engine exposes hook points, and the ability object supplies functions that run at those hook points.

That distinction is worth sitting with. FireRed’s AI command says, “I am currently interpreting an AI script. I will test a condition and move the instruction pointer.” Showdown’s ability hook says, “The engine has reached a semantic event, such as modifying a move or responding to damaging hit, and this data object has a callback for that event.” Both are extension systems. Both connect data to behavior. But they divide responsibility differently.

Let’s use an example. In Showdown, an ability like Infiltrator can be represented with an `onModifyMove` hook that mutates the move object so it infiltrates barriers; that is visible in the abilities source around `pokemon-showdown/data/abilities.ts`, `chunk_8a03d04894b7e127`, which includes an `onModifyMove` example. The engine owns the timing: when moves are being modified, it asks relevant effects whether they have a hook. The ability owns the local behavior: set this property, adjust that value, respond to this event.

In FireRed’s AI script model, by contrast, the AI command stream owns the timing of its own reasoning. If the script says, “check this status condition now,” the command handler checks it now. If the condition is true, branch now. That is pointer control. It is procedural in a very literal sense: the AI script proceeds step by step, and the command functions are the verbs.

Now, someone might ask, “Isn’t Showdown also procedural because the callback is a function?” Yes, inside the callback, absolutely. But at the architectural boundary, it is event-hooked data. The ability data declares functions keyed by engine events. FireRed’s AI command source is a command interpreter over a script pointer. That is the comparison I want you to be able to make on the quiz: not “old C bad, modern TypeScript good,” because that is too shallow and frankly not useful. The comparison is: pointer-driven command flow versus event-indexed callback hooks.

Let’s connect that to testing and clone design. In homework week 14, you designed minimal clone versions of map loading, script execution, party data, battle turns, item use, and save persistence. In homework week 15, you used Showdown as a secondary comparison for battle data hooks, Dex/mods, simulator turns, and regression testing without replacing FireRed as the main architecture source. So now, as advanced learners, I expect a more precise answer when I ask how you would implement simplified AI.

A weaker answer is: “I would make the enemy choose randomly unless a move is super effective.” That is a gameplay heuristic, not an architecture. A stronger answer is: “I would implement a small AI script interpreter with commands like compare HP, check status in party, adjust move score, and branch. The interpreter would maintain a script pointer, and commands would read compact operands and either advance or branch.” That answer borrows the relevant FireRed shape.

A different but also strong answer is: “For an engine modeled after Showdown, I would expose semantic hooks like `onBeforeMove`, `onModifyMove`, or `onDamagingHit`, and let ability or item data register behavior at those points.” That answer borrows the relevant Showdown shape. The key is not to mix them lazily. Do not say “I’ll make callbacks” when what you actually need is branchable decision script execution. Do not say “I’ll make scripts” when what you need is a clean event hook model for modular abilities.

Let’s do a quick comprehension check. Suppose I give you an ability that increases a move’s base power under a condition. Which model does that naturally resemble: FireRed AI predicate script or Showdown ability hook? It resembles the Showdown hook model, because the engine reaches a move modification phase and asks effects to modify base power. The packet points us to `pokemon-showdown/data/abilities.ts`, `chunk_b4a38d21b0114ee8`, as a source area for residual and base-power hook examples. Now suppose I give you a trainer decision rule: “if any healthy party member is poisoned, prefer a move or item response.” Which model does that naturally resemble? That resembles FireRed’s party-aware AI predicates, because the decision command scans party slots and branches through an AI script pointer.

Good. Now let’s deepen the FireRed side.

The important design pressure in a GBA-era C codebase is that the engine wants compact representations and predictable control. You do not allocate elaborate AI objects every turn. You do not build a heavyweight runtime planner with dynamically composed lambdas. You keep a stable shared battle state, use global or file-scope interpreter variables, and run small command functions. `sAIScriptPtr` is a symptom of that style. It is not just a variable. It is the interpreter’s current location in the AI program.

When a command branches, it is changing the future of evaluation. That means predicates are not passive. They control which later scoring or decision commands run. So, if a predicate checks party status and jumps past a block, it can suppress a whole group of evaluations. If it jumps into a block, it can activate specialized logic. That gives FireRed’s AI scripts a compact way to express conditional behavior without making every condition a separate C-level trainer policy.

Think of it as a decision grammar. The vocabulary is the command set. The sentence is the AI script. The punctuation is pointer movement. And the meaning is a sequence of score changes, comparisons, and branches that eventually influence move choice or battle action.

Now, I want to be careful here: the source packet’s focus on `pokefirered/src/battle_ai_script_commands.c`, `chunk_0a05fcecbd58029f`, and `chunk_34a415cc38858fdc`, is not asking us to pretend FireRed has a purely declarative AI. It does not. The command handlers are C functions. They perform explicit reads, loops, and assignments. In that sense, this is still command flow. But the trainer behavior is not only encoded as one giant switch statement over all possible situations. It is mediated by scripts, branch targets, and predicates.

That hybrid is one of the most important architectural lessons in the whole course. FireRed repeatedly uses data-shaped structures to steer C-shaped execution. Map headers steer overworld loading. Event scripts steer NPC and story logic. Trainer and encounter tables steer battle setup. Battle scripts steer visible combat resolution. AI scripts steer trainer decision evaluation. The same pattern appears at different scales: data table, runtime globals, callback or interpreter, visible behavior.

Let me put that course-wide pattern on the board one more time: data selects structure, runtime state gives context, interpreter or callback advances behavior, and the player sees an outcome.

For today’s topic, the data is the AI script command stream. The runtime state is the battle and party state. The interpreter is the AI command dispatcher and its command handlers. The visible outcome is not immediate animation, usually. It is the opponent’s selected move or action, which then flows into the battle turn machinery we already studied.

That delayed visibility is important. AI code often has an indirect user interface. The player does not see “AI predicate number 12 returned true.” The player sees the trainer switch, use a move, avoid a bad choice, or sometimes make a choice that feels surprisingly human for such a compact engine. Underneath that behavior is not magic. It is state tests and control flow.

Now, let’s compare this to the overworld for a moment. In the overworld script system, a predicate might check a flag: has the player beaten this gym, has this item been collected, is this variable equal to a value? If true, branch to one script label; if false, branch elsewhere. In battle AI, the predicate checks battle-state facts instead: status, HP, move viability, party composition. The interpreter pattern is familiar, but the domain vocabulary changes. This is exactly why the course keeps emphasizing decomposition. Once you recognize the interpreter-and-state pattern, you can move across subsystems without confusing them.

But advanced understanding means also seeing the limits. A party-aware status predicate depends on correct party state. If party data is stale, if fainted slots are treated incorrectly, if eggs are not filtered where they should be, or if status bits are interpreted with the wrong mask, the AI’s “reasoning” is wrong. There is no separate intelligence layer to rescue it. The predicate is only as good as the state contract underneath it.

That is why headers and constants matter. Earlier in the course, we framed headers as cross-module contracts. Here, battle AI command code relies on battle, Pokémon, move, ability, item, and status constants being consistent across modules. The packet’s `battle_util.c` excerpt, `chunk_9335e5727aa0657d`, shows this density of dependencies: battle AI script commands sit beside constants for moves, items, weather, abilities, Pokémon, hold effects, and battle move effects. When you see that include cluster, read it as architectural evidence. AI evaluation lives at a junction of many battle facts.

Let’s run a concrete scenario. Imagine an opposing trainer has a party with three remaining Pokémon. The active one is healthy, but a benched party member is badly poisoned. The AI script reaches a command equivalent to “if status in party includes poison, branch to special handling.” The command selects the trainer party, loops through slots, reads status for each valid Pokémon, and finds the poisoned one. The predicate succeeds. `sAIScriptPtr` is assigned to the branch target. The next commands might raise the score of a healing move, lower the score of a risky switch, or consider some other response, depending on how the script is authored.

Notice the separation: the predicate itself does not need to know the entire strategy. It only answers the question and redirects control. Later commands perform scoring or additional checks. This is compositional. Small commands can be combined into larger decision behavior.

Now, if you were implementing the lab version, I would expect you to keep that separation. Do not write `if_status_in_party_and_choose_best_move_and_print_debug_and_update_ui`. That is a bad command boundary. The command should inspect, branch, and leave action selection to the surrounding AI flow. In a minimal clone, you might store scripts as arrays of opcodes and operands. Your interpreter might have a `pc`, a party array, and a move score table. Your status command reads `partySelector`, `statusMask`, and `jumpTarget`. It loops. It sets `pc`. That is enough to teach the architecture without copying FireRed line for line.

Let me ask another check-for-understanding question. Why is `sAIScriptPtr` more than an implementation detail? Because it exposes the control model. It tells us that AI behavior is being interpreted as a stream, and that commands can redirect future interpretation. If all AI were written as direct nested C conditionals in one function, we would not need a script pointer in the same way. The pointer is evidence of a command language.

And this is where the Showdown contrast sharpens. In Showdown’s ability data, an ability does not usually say, “advance my ability script pointer by five bytes.” It says, in effect, “when this event occurs, call this function.” The packet points to `pokemon-showdown/data/abilities.ts`, `chunk_6372bce3e311c10b`, for the data-and-engine split note. That split is precisely what we care about. The engine owns the event lifecycle. Ability data owns hook implementations. In FireRed AI scripts, the script stream owns a local decision lifecycle, while C handlers implement each command’s semantics.

Both approaches are viable. Both are data-driven in different senses. FireRed’s AI scripts are data-driven through compact command streams. Showdown’s abilities are data-driven through structured effect objects containing hook functions. One emphasizes interpreter pointer control. The other emphasizes event dispatch over named hooks.

Now, as we move through week 16, keep the larger course arc in view. We began with the runtime shell: `AgbMain`, main callbacks, task dispatch, and per-frame execution. We moved through maps and events, where headers and scripts coordinate the overworld. We studied creature data and party storage, because battles need concrete Pokémon objects, not just species names. We studied battle setup from trainer and wild encounter inputs. We studied move execution, damage, status, and outcomes. Today, trainer AI sits near the top of that stack, but it reaches downward into all of it. It reads party state built from creature data. It uses constants shared with move and status systems. Its decisions feed into the same battle turn machinery. And after the battle ends, the result still resolves back to the world.

So the best way to understand FireRed battle AI is not to isolate it as “the AI module.” Understand it as an interpreter sitting inside the battle system, reading state produced by other systems, and influencing choices that the battle engine will later execute.

Let’s pause there for the first segment. When we continue, we will get more precise about the anatomy of an AI command handler: how operands are read, how party slots are scanned, how status masks become predicates, and how a branch target changes the rest of the decision path.

Good. So let’s come back in and put the command handler under the microscope.

Imagine the AI interpreter is standing at a little cursor in memory. That cursor is the AI script pointer. It is not thinking in the human sense. It is not planning with a search tree. It is not simulating every future turn. It is reading one command, consuming that command’s operands, answering one narrow question, and then either falling through to the next command or jumping to another address in the script. That is the core mental model.

And I want you to notice how different that is from the way we often talk about game AI at a high level. We say, “the trainer decided to switch,” or “the AI knew I had a poisoned Pokémon,” or “the boss trainer is smarter.” Architecturally, FireRed gets much of that behavior from a compact predicate language. A command asks, in effect, “is some condition true?” If true, move the script pointer over here. If false, continue over there. The intelligence emerges from a sequence of these little tests.

Put on the board, please: command equals opcode plus operands plus pointer effect.

That pointer effect matters. If you only remember the condition check, you miss the interpreter shape. In the FireRed AI command source, the command handlers in pokefirered/src/battle_ai_script_commands.c, especially the source focus chunks chunk_0a05fcecbd58029f and chunk_34a415cc38858fdc, are organized around this pattern: read bytes or words from the AI script stream, inspect battle or party state, then update sAIScriptPtr so the next AI command comes from either the following instruction or a branch target. That is the architecture claim, and it is more important than memorizing one individual condition.

Let’s make this concrete with a simplified version of the lab command you’ll rebuild: if_status_in_party. Suppose the AI is considering whether to use a move that becomes more valuable when the opponent has a party member with a major status condition, or suppose the AI wants to evaluate whether its own party already has a sleeping or poisoned member. The command needs several pieces of information. First, which side’s party should be scanned? The AI side? The opposing side? Second, which status mask are we checking? Sleep? Poison? Burn? Paralysis? Maybe any major status? Third, where should the script jump if the predicate succeeds?

That gives us the interpreter anatomy. Operand one tells us the target party relation. Operand two gives us the status mask. Operand three gives us the branch destination. Then the C handler translates that tiny bytecode-level request into runtime reads against the party data.

Now pause on that phrase: party data. This is where week 16 connects backward to the whole course. The AI command is not storing its own version of the trainer’s team. It reaches into the same party model that comes from creature data, battle setup, and battle runtime state. Earlier, when we traced encounter setup and trainer battle setup, we talked about the battle being assembled from trainer tables, wild encounter data, and party structures. We are not re-teaching that today, but we are using it. The AI predicate is meaningful only because those earlier systems have already populated the party slots with species, HP, status, level, moves, items, and so on. That is why the research packet’s broader architecture note frames FireRed as cooperating state machines plus data tables rather than isolated modules.

Let’s do a board trace. I’ll write six boxes: slot zero, slot one, slot two, slot three, slot four, slot five. Those are the party slots. Under each box, write three fields: species, HP, status. In the real engine there is more, obviously, but these three are enough for the predicate.

Now imagine the command says, “branch if the AI side has a poisoned party member.” The handler starts scanning. Slot zero: is this a valid Pokémon? Is it an egg? Is HP nonzero if the predicate cares about active usability? What is the status field? It takes that status value and applies a mask. If status AND poison mask is nonzero, the predicate is true. Then the handler assigns sAIScriptPtr to the branch target. If the scan reaches the end without a match, it advances past the operands and continues.

That little AND operation is doing a lot of design work. Status is represented as bits, and the predicate does not need a complex object model to ask about it. It needs a mask. This is exactly why low-level C battle engines often feel terse but powerful. The script language can expose a family of questions without inventing a new function for every English sentence. “Does anyone have this status?” becomes “scan these slots and test this mask.”

Check for understanding here. If the status field is 0b0100 and the mask is 0b1100, does the predicate pass? Yes, because the bitwise AND is nonzero. If the status field is 0b0010 and the mask is 0b1100, it fails. The script command is not asking whether the status equals the mask exactly; it is asking whether the relevant bit or bits are present. That distinction is a classic source of bugs in your clone implementations. Equality gives you “exactly this status encoding.” A mask gives you “contains this category of status.”

Now let’s connect that to party-aware AI. A move-choice AI that only looks at the active battlers is tactically shallow. It can ask, “What is in front of me?” but not “What resources exist behind that active battler?” FireRed’s party-aware predicates give the AI a limited but important ability to reason across party slots. Again, not with a modern planner, but with scripted tests. Does my party include something affected by a status? Does the opponent’s party contain a target matching some condition? Is there a possible switch? The command flow lets a designer encode rules like, “if this broader party condition exists, adjust scoring or choose a different script path.”

That is the advanced point for today: the sophistication is not located only in the C condition. It is in the partnership between the C predicate and the script arrangement. The C predicate gives the AI vocabulary. The script composes that vocabulary into behavior.

Let me say that another way. The command handler is like a built-in question. The AI script is the interview. A single question, “is there status in the party?”, does not define a personality. But a sequence of questions, ordered carefully, with different branches and score changes, can make one trainer conserve resources, another exploit status, and another behave more recklessly.

When the source focus points us to pokefirered/src/battle_ai_script_commands.c chunk_34a415cc38858fdc for party scanning, status predicates, and sAIScriptPtr branching, that is the pattern to extract. Don’t only read it as “here is one function.” Read it as “here is the interpreter boundary where bytecode operands become C-level access to party state.” And when chunk_0a05fcecbd58029f is grouped with battle AI command source, it is part of that same command vocabulary: a file full of small handlers that make the AI script language possible.

Now, why use sAIScriptPtr directly? Why not return an enum like BRANCH_TAKEN or FALL_THROUGH and let some generic dispatcher handle it? You could design it that way. In fact, in a cleaner modern interpreter, you might. But FireRed’s C code commonly lets handlers directly mutate shared interpreter state. That style is consistent with the GBA-era constraints and the surrounding engine. The handler knows how many bytes its operands occupy. It knows where the branch address lives in the stream. It can advance the pointer or replace it with the decoded target. This is compact, explicit, and close to the memory layout.

The cost is that control flow is distributed. To understand one AI command, you must inspect how it reads its operands and how it updates sAIScriptPtr. There is no single abstract syntax tree telling you the whole story. The script pointer is the story.

Now let’s compare, but gently, with Pokémon Showdown. We are not replacing FireRed with Showdown. The course has been clear about that. FireRed is our primary architecture source. Showdown is useful because it gives us a contrasting model of extension.

In Showdown’s abilities file, pokemon-showdown/data/abilities.ts chunk_d5bdb17c16d6f1a4 and the related chunk_8a03d04894b7e127 show ability definitions with named hooks like onDamagingHit and onModifyMove. In that model, an ability is often a data object with callback properties. The battle engine reaches event points and asks, “does any active effect have a hook for this event?” If yes, it calls the hook. For example, an ability can modify a move as it is being prepared, or react when damaging hit occurs. That is not the same extension style as FireRed’s AI script command flow.

So put two columns on the board. Left column: FireRed AI predicate command. Right column: Showdown ability hook.

Under FireRed, write: opcode-driven, pointer mutation, bytecode operands, C command handler, branch target. Under Showdown, write: event-driven, callback property, data object, engine hook point, effect mutation.

Both systems are extensible, but they extend different surfaces. FireRed’s AI commands extend the questions the AI script can ask. Showdown’s ability hooks extend the battle events that an ability can respond to. One is about decision control flow. The other is about effect behavior at named lifecycle points.

This contrast helps you avoid a common architectural mistake in your homework. Some of you, when asked to rebuild a FireRed-style command, immediately design a Showdown-style callback object. That is not wrong in the abstract, but it is not the same architecture. If the lab asks for if_status_in_party, I expect to see script pointer behavior. I expect operands. I expect a branch destination. I expect the command to participate in an interpreter. If instead you write an ability object with an onStatusCheck callback, you may have built a useful modern system, but you have not modeled FireRed’s command flow.

Conversely, when you implement the Showdown-style callback ability in the second half of the lab, I do not want you to force it through a fake bytecode interpreter. Showdown’s ability examples, including onModifyMove in pokemon-showdown/data/abilities.ts chunk_8a03d04894b7e127 and the secondary hook examples in chunk_b4a38d21b0114ee8, are about data plus callbacks. The ability declares behavior at hook names the engine recognizes. That is the split. Data names the effect; engine event flow invokes it.

Let’s take a concrete comparison. Suppose we want a behavior like, “when this creature is hit by a damaging move, punish the attacker.” In a FireRed AI predicate world, that is not naturally an AI command. The AI might ask, “should I avoid hitting that target?” But the actual punishment when damage occurs belongs elsewhere in the battle engine. In Showdown, an ability can expose onDamagingHit, and the engine can call it at the moment damage lands. The hook is attached to the ability’s data definition. The research packet specifically points us to pokemon-showdown/data/abilities.ts chunk_d5bdb17c16d6f1a4 for secondary ability hooks such as onDamagingHit and onModifyMove, which is exactly the contrast we need.

Now invert the example. Suppose we want the trainer AI to branch if any party member is asleep. In Showdown style, you might be tempted to create an event hook for “onAIScanningParty.” But in FireRed’s architecture, this is naturally an AI command. The script is executing. The command scans party slots. The script pointer branches. That is a decision-language extension, not a battle-effect hook.

Check for understanding again. If I ask, “which system is more data-driven?” what is the careful answer? Not simply “Showdown.” FireRed is deeply data-driven too, but the data is often tables, constants, bytecode scripts, and binary layouts feeding C command dispatch. Showdown’s data is higher-level TypeScript objects with callback functions embedded as properties. So the difference is not data versus no data. The difference is what kind of data and where executable behavior lives.

That is why the packet’s note about pokemon-showdown/data/abilities.ts chunk_6372bce3e311c10b as a secondary data/engine split note is useful. Showdown keeps a visible split between effect declarations and the engine that invokes those declarations. FireRed keeps a visible split between script bytes and C handlers, but the handlers are closer to raw command flow and global battle state. Both are separations. They just cut the architecture at different joints.

Now let’s return to FireRed and walk through branch mechanics more slowly.

In many of these command handlers, the script pointer points at the opcode. The dispatcher has identified which function handles that opcode. The handler then knows the operand layout. Maybe byte one after the opcode is a battler selector. Maybe the next bytes encode a value. Maybe a four-byte pointer follows. The handler reads those operands from the script stream. Then it performs its test.

If the predicate fails, the handler must skip over the entire command. That means sAIScriptPtr becomes old pointer plus instruction length. If the predicate succeeds, sAIScriptPtr becomes the branch target. So every conditional command has two possible next instructions. That is bytecode control flow. It is the same broad idea we saw with battle scripts as combat bytecode, but we are applying it to AI decision scripts instead of move effects and messages. I am not going to re-teach battle script dispatch here; use it as prior foundation. The important carry-forward is that both systems use compact command streams and command handlers, but for different purposes.

Here is a small pseudo-board example, spoken rather than written in C. Instruction at address 100: if status in AI party, poison mask, jump to 220. Instruction at address 108: lower score by 2. Instruction at address 110: continue. Address 220: raise score by 3.

Now execute it. At address 100, the handler scans party slots. If no poisoned party member exists, pointer becomes 108, and the AI lowers the score. If a poisoned party member exists, pointer becomes 220, and the AI raises the score. Same command, two possible futures. If you chain ten commands like that, you get a decision tree.

This is also why branch target bugs are painful. If you decode the branch address incorrectly, your predicate may be perfectly correct but the AI behaves nonsensically. It is like answering the right yes-or-no question and then walking into the wrong room. In your simplified implementation, keep branch targets symbolic if you want easier debugging, but still preserve the semantic model: predicate true changes the next command location.

Now, party scanning has another subtlety: whose party and which slots count? In a single battle, the opponent and player each have a party, and active battlers correspond to party indexes. In doubles, the mapping becomes more complicated. FireRed has to maintain battler IDs, party indexes, sides, and battle type flags. We have seen battle type flags and controller routing before, so I will not expand that fully today. But for an AI predicate, the implementation must choose the correct party array and then iterate over valid party slots. A sloppy clone that simply scans “all creatures in battle” is not modeling the same concept. Party-aware means reserve members matter, not just active battlers.

And this is where the runtime shell and save architecture quietly sit in the background. The party was loaded from save-backed player state or constructed from trainer data. The battle setup code, represented in the packet by pokefirered/src/battle_setup.c chunk_494d132a59422c85, includes many of the cross-module dependencies that prepare battle context: task, overworld, item, script, event data, Pokémon, battle transition, and battle constants all meet around battle entry. That chunk is not the AI command itself, but it supports the larger claim that battle state is not born inside AI. It is assembled by the game’s broader runtime and then consumed by battle subsystems.

Similarly, pokefirered/src/battle_util.c chunk_9335e5727aa0657d shows how battle utility code includes constants and interfaces for items, moves, abilities, weather, battle scripts, controllers, and the AI script command header. Again, that is architectural evidence. The AI predicate is not isolated. It sits among shared constants and shared battle state.

Let’s make this practical for the lab. When you rebuild if_status_in_party, I want three layers.

First, build a tiny script representation. It can be an array of command objects if you are working in a high-level language, or bytes if you want the closer model. But it must have a program counter or script pointer.

Second, build a party model with six slots. Each slot should have at least species or existence, HP, and status. Use a status mask, not a string comparison. If you want names for readability, map them to bits.

Third, build the command handler. It reads the selector, the mask, and the branch target. It scans the selected party. If any valid slot matches, set the pointer to the branch target. Otherwise, advance to the next instruction.

Then, and this is important, test both pointer outcomes. A lot of students test only the condition result. They assert that the function returns true. But a FireRed-style command handler is not merely a pure predicate. Its observable output is pointer movement. So your tests should say: with a poisoned member, program counter becomes branch address; without one, program counter becomes next instruction. That is how you respect the architecture.

Now we will add one more layer of sophistication. In a scoring AI, branch destinations often lead to score adjustments rather than direct move selection. The AI may consider one move at a time, run scripts that increase or decrease its desirability, and then compare scores. So if_status_in_party might not mean, “use this move immediately.” It may mean, “if this party condition holds, enter a block of script that changes the move’s score.” That distinction matters because beginners often imagine AI predicates as final decisions. In these engines, predicates usually contribute to evaluation.

Think of it like a legal argument. One fact does not decide the case. It changes the weight. The AI script accumulates many small reasons for and against a move. Party status is one reason.

Check for understanding: if an AI command branches to a block that subtracts 5 from the current move score, did the command “choose not to use the move”? Not directly. It influenced the evaluation. Another later rule might add 8. The move might still be selected. So we have to be precise: the predicate controls script flow; the script flow may affect scoring; the final move choice comes later through the battle AI selection process.

Now let’s bring this back to the course objective of decomposing the complete RPG. At week 16, you should be able to place this one command in the whole machine.

The runtime shell gives us frames, callbacks, and task execution. We covered AgbMain, SetMainCallback2, and RunTasks earlier. The overworld and event scripting decide when a battle starts. Map and trainer data help determine what kind of battle it is. Creature data and party structures provide the actors. The battle setup code creates the battle context. The battle engine runs turns. The AI script interpreter evaluates moves for an AI-controlled battler. Inventory and items may affect available actions. UI displays the outcome to the player. Save data persists party state before and after. Link systems complicate controller ownership and synchronization when multiplayer is involved.

The AI command we are studying today reaches into that stack at one narrow point. It reads party state and changes AI control flow. But understanding it properly requires knowing the stack exists.

That is the maturity jump from the midterm to now. On the midterm, you had to decompose the whole RPG and trace data through globals, callbacks, tasks, scripts, structs, and functions. By week 16, I am no longer satisfied with “this function checks status.” The advanced answer is, “this command handler extends the AI script language with a party-state predicate; it reads shared battle and party data using status masks; it mutates sAIScriptPtr to produce bytecode-level branching; and its result eventually influences move scoring within the battle turn machinery.” That is the level of sentence you should be able to produce.

Now, before we move into the next segment, I want to flag a design tension. FireRed’s approach is efficient and compact. It is also harder to inspect with ordinary data tools because behavior is split between script bytes and C handlers. Showdown’s hook approach is more declarative at the ability-definition level. You can open pokemon-showdown/data/abilities.ts and see an ability object with named hooks like onModifyMove or onDamagingHit, as in chunks chunk_d5bdb17c16d6f1a4 and chunk_8a03d04894b7e127. But that readability comes with a different engine contract: the simulator must define and consistently call all those hook points.

So neither model is universally “better.” If you are building a GBA-style ROM engine, a compact command interpreter is natural. If you are building a server-side simulator with many generations and mods, data objects with hooks are natural. The architecture follows the constraints.

Take thirty seconds and ask yourself: in your own project, where would you put a new behavior? If the behavior changes how an AI evaluates a move, it probably belongs in the AI decision language. If it changes what happens when damage lands, it belongs in the battle effect event flow. If it changes what data exists on a creature, it belongs in the creature model or species data. That placement discipline is one of the main skills this course is trying to build.

All right. In the next stretch, we are going to zoom further into operand decoding and branch layout. We will look at how a command can feel like a simple if statement at the script level while still requiring careful C-side agreement about byte widths, party selectors, masks, and pointer advancement. That agreement between script authoring and command implementation is where many engine bugs hide, and it is where your simplified lab version needs to be especially explicit.

Now, when I say operand decoding, I want you to hear something quite concrete. I do not mean, “the AI has a clever idea.” I mean the C function receives a pointer into a byte stream, reads one byte as a selector, maybe reads four bytes as a pointer, maybe reads a two-byte mask, and then decides whether sAIScriptPtr should advance past the command or jump somewhere else. That is the level of machinery we are talking about. The intelligence is not floating above the implementation. The intelligence is encoded in these small agreements between the script format and the command handlers.

So, picture the board for a moment. I am going to draw a short byte sequence. First box: command id. Second box: battler selector or party selector. Third and fourth boxes: status mask, if the command needs a 16-bit value. Then several boxes for a branch address. And underneath that I am writing sAIScriptPtr. That pointer is our cursor. It is the AI script’s program counter. Every command has one responsibility beyond its actual predicate: it must leave that cursor in the correct place.

That sounds mundane, but it is architecturally important. In a normal C if statement, the compiler owns the instruction layout. Here, FireRed’s AI command function is effectively participating in interpreter design. If the predicate is false, advance over the operands. If the predicate is true, load a new script address into sAIScriptPtr. The source focus for today, pokefirered/src/battle_ai_script_commands.c chunk_0a05fcecbd58029f and chunk_34a415cc38858fdc, is valuable precisely because it shows this style: command functions are not just asking questions about battle state; they are controlling the script pointer that determines what question or score adjustment comes next.

Let’s make that concrete. Suppose we have a simplified AI command called if_status_in_party. At the script authoring level, you want it to feel like this: “If anyone in my party is poisoned, branch to this alternate scoring logic.” Maybe the trainer has a move that interacts with poison. Maybe the trainer is considering whether switching, healing, or using a support move makes sense. Script-level, that is a sentence. Implementation-level, it becomes a small interpreter contract.

The command needs to know whose party to scan. Is it the attacker’s party? The target’s party? The player side? The opponent side? Then it needs to know what status mask to test. Are we testing poison only? Any major status? Sleep and paralysis? Then it needs a destination pointer. If the condition succeeds, the AI jumps. If it fails, it continues.

Now pause there. Check your understanding. If I write this as a C helper function that returns true or false, have I implemented the AI command? Not quite. I have implemented the predicate. The command also has to decode operands and update sAIScriptPtr. That is the distinction I want you to keep clear. Predicate logic answers, “Is the condition true?” Command logic answers, “Given this encoded command in the AI script stream, where does execution continue?”

This is one reason the FireRed style feels different from the battle script command dispatch we studied earlier. We already covered the larger battle script system, current move data, damage effects, status timers, and message or animation hooks, so I am not going to re-teach that machinery. But I do want to connect the ideas. Both systems are bytecode-like command flows. Both depend on globals and command handlers. Both turn small encoded instructions into visible battle behavior. The AI version, though, is not primarily causing animations or damage. It is evaluating and steering decision logic before the chosen action becomes part of the battle’s main resolution.

Let me write a possible simplified layout on the board in words. Command: if_status_in_party. Operand one: party side. Operand two: status mask. Operand three: branch pointer. Now, if I am the lab implementer, I need to decide byte widths. Is party side one byte? Is status mask four bytes because FireRed status flags may occupy a word? Is the branch pointer read as a native pointer in the decompilation context, or as an offset in my simplified VM? Those choices cannot be left implicit. If your script encoder thinks the mask is two bytes and your command handler reads four, the next “branch pointer” is no longer the branch pointer. It is garbage assembled from the wrong bytes. That is not a balance bug. That is an interpreter desynchronization bug.

And once the cursor is desynchronized, the symptoms are often misleading. The AI may choose a bizarre move three commands later. It may appear that the status predicate is wrong, when the real fault is that the command advanced by the wrong number of bytes. This is why I keep using the word agreement. Bytecode systems are agreements. The script data, the macros or constants that author it, and the C command handler must all agree.

Now let’s tie this to party scanning. A party-aware predicate is different from a battler-aware predicate. If I ask, “Is the current target poisoned?” I can inspect the battler state for the active target. If I ask, “Does any party member have a status?” I am crossing from active battle state into party data. That matters because the active battler is not the whole team. Party slots include inactive creatures, fainted creatures, sometimes empty slots, and context-specific eligibility rules.

So, when pokefirered/src/battle_ai_script_commands.c chunk_34a415cc38858fdc is identified in the course packet as party scanning, status predicates, and sAIScriptPtr branching, the architectural point is not merely “there is a loop.” The point is that AI decision logic can reach outward into the trainer’s party model and use that as part of move evaluation. That connects today’s topic back to the creature data and party model weeks. The party is not just a menu listing. It is shared strategic state. It influences switching, healing, move desirability, and risk assessment.

Let’s do an example slowly. Imagine the opponent is considering a move that cures the user’s party, or perhaps a move whose value changes if an ally is statused. In a simplified creature RPG, we could say, “If any party member has sleep, add 20 points to this move.” FireRed’s exact command set and scoring choices are source-specific, but the pattern is what matters: the script asks a party-wide question, and the branch selects a scoring path.

At the board, I would write:

Start at sAIScriptPtr.
Read command id.
Read side selector.
Read status mask.
Read branch destination.
Loop party slots.
For each valid slot, read status.
Apply mask.
If any match, set sAIScriptPtr to branch destination.
Otherwise, advance sAIScriptPtr to next command.

Now, I want you to notice three hidden design decisions in that little outline. First, “valid slot” is a policy. Does an egg count? Does a fainted creature count? Does an empty slot count? In a FireRed-style party, those questions cannot be hand-waved because party structs are real memory and commands are not protected by a high-level collection abstraction. Second, “read status” is a data contract. The predicate must know where status lives and what bit mask means. Third, “any match” is a quantifier. We are not asking for the lead creature’s status. We are asking whether there exists at least one party member satisfying the condition.

That last point is worth emphasizing. A lot of AI commands are tiny quantified statements. “If any move has this property.” “If the target is faster.” “If no party member can switch safely.” “If this side has a status.” Once you see the AI command language as a set of scripted predicates, the scripts become less mysterious. They are not a neural model. They are not search in the chess-engine sense. They are a compact decision language built out of questions the engine knows how to answer.

Here is a check for understanding. Suppose the predicate finds a matching party member in slot three. Should the command keep scanning slot four, five, and six? In a pure “if any” command, no, it can stop. The existence proof is complete. But suppose your command is not just branching; suppose it is computing a score based on how many party members match. Then it must continue and count. That difference is part of command semantics. The script author must know whether the command means “if any” or “how many.” If the name suggests one but the implementation does the other, your AI language becomes hard to reason about.

Now let’s contrast this with Showdown, but only as a comparison model. We are not replacing FireRed as our architecture source. We are using Showdown to sharpen the distinction between data hook modeling and C command flow. In pokemon-showdown/data/abilities.ts chunk_d5bdb17c16d6f1a4, the research packet points us to secondary ability hooks such as onDamagingHit and onModifyMove. In chunk_8a03d04894b7e127, we see examples like Infiltrator using onModifyMove to alter move behavior, and Innards Out using onDamagingHit after damage occurs. That is a very different extension surface.

In the Showdown style, an ability object can say, “When this event occurs, run this callback.” The engine has event phases, and ability data supplies functions for those phases. In the FireRed AI command style, a script command says, “At this point in this AI byte stream, ask this predicate and branch.” One is event-hooked data behavior. The other is explicit interpreter control flow.

Let me say that again in a more practical way. If you add a Showdown-style ability callback, you are usually attaching behavior to an event name. onModifyMove changes a move during move modification. onDamagingHit reacts when damaging hit logic happens. In FireRed AI scripting, if you add an AI predicate command, you are extending the AI scripting language itself. You must define the opcode, operand layout, command handler, pointer advancement, and intended script usage.

So if a student asks, “Which style is better?” the answer is, “Better for what?” Showdown’s style is excellent for a simulator where effects can be represented as data objects with event callbacks. It supports modding and testability very naturally. The FireRed C command flow is closer to a constrained embedded engine, where command tables, globals, and byte streams keep runtime behavior compact and predictable for the target hardware and toolchain. That is why this comparison belongs in week 16, after you have already traced tables, callbacks, tasks, scripts, structs, and source functions across the course. Earlier in the course, this would have looked like a syntax difference. Now you should see it as an architectural difference.

Let’s bring the full RPG decomposition back in for a moment. Runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems are not separate planets. Battle AI touches the creature model through party slots. It touches battle state through active battlers, statuses, moves, items, and side conditions. It touches trainer identity through party ownership. It may indirectly affect inventory-like decisions when trainers choose items in battle contexts. It must preserve save consistency because battle outcomes eventually resolve back to the world, which we covered in the prior outcome lecture. And, in link contexts, determinism and shared state become even more delicate, although today we are focusing on AI rather than link battle synchronization.

That is the course-level reason this lecture matters. AI is not a decorative layer. It is a consumer of nearly every data model you have built so far. A weak architecture will let AI code rummage through everything incoherently. A disciplined architecture gives the AI specific predicates and helper functions that expose battle-relevant facts without turning every command into a custom pile of unrelated reads.

Now, let’s return to operand decoding, because this is where your lab will either be solid or slippery. Suppose your simplified command format uses one byte for opcode, one byte for side selector, four bytes for status mask, and two bytes for branch offset. In your interpreter, you might write something like, “selector is script[ip plus one], mask is read_u32 at ip plus two, destination is read_u16 at ip plus six.” Then total command length is eight bytes. If the condition fails, ip increases by eight. If true, ip becomes destination.

That is a perfectly reasonable simplified design. It is not necessarily FireRed’s exact byte layout, and it does not need to be for the lab. What it does need is explicitness. You should be able to write the layout beside the decoder and prove that every byte is accounted for. If you cannot explain why the false path advances by eight bytes, your implementation is probably relying on vibes rather than a contract.

And yes, I am being strict about that because advanced learners should stop treating interpreters as magical while loops. A script VM is a set of invariants. The instruction pointer points at the next opcode. Each handler either advances by exactly its encoded length or branches to a valid target. Each operand reader consumes the width the authoring side emitted. Each command’s side effects are documented well enough that scripts can be composed. These invariants are what make a bytecode engine debuggable.

Let’s do another check. If your if_status_in_party command branches on success, where should sAIScriptPtr point after a failed predicate? It should point to the next command after all operands, not merely one byte after the opcode. That is the mistake I expect to see in some first drafts. Advancing only past the opcode means the interpreter will treat the side selector or the first byte of the status mask as the next command id. From there, every later conclusion is untrustworthy.

Now, what about branch targets? In a native C decompilation context, you may see macros and pointer reads that operate in terms of script pointers. In a teaching VM, you might choose numeric offsets instead. Either can work. But each has different safety properties. A pointer-style branch resembles the original C architecture more closely, but it is harder to serialize cleanly in a small student project. An offset-style branch is easier to inspect and test, but it abstracts away some of the decompiled engine flavor. For the lab, I would rather you choose the representation you can test thoroughly. The point is not to cosplay the exact binary format. The point is to understand the command contract.

Now, let me connect this to pokemon-showdown/data/abilities.ts chunk_b4a38d21b0114ee8 and chunk_6372bce3e311c10b, which the packet identifies as residual, base-power, and data-engine split examples. In those Showdown chunks, the recurring architectural shape is that data entries carry hooks that the simulator calls at named phases. That means the extension’s location is determined by the event phase. A base power hook belongs where base power is being calculated. A residual hook belongs during residual processing. The ability data does not manually advance an instruction pointer. It participates in a host engine’s event protocol.

So, here is the comparison I want on your mental board. FireRed AI command: branch-driven, script pointer, byte operands, C predicate handler. Showdown ability hook: event-driven, named callback, object data, engine phase. Both are extensibility mechanisms. Both require contracts. But the contracts live in different places. FireRed’s contract is instruction layout plus shared battle state. Showdown’s contract is hook signature plus simulator event ordering.

This also explains why direct translation between the two styles can be awkward. If you take a Showdown ability like one using onModifyMove and try to “port” it into FireRed AI scripts, you are probably putting it in the wrong place. An ability that modifies a move during execution is not an AI predicate. It belongs in battle effect resolution or move modification logic. Conversely, if you take a FireRed AI predicate that asks whether a party member is statused and try to represent it as a Showdown ability hook, you may be creating an event callback for something that is really just decision-time information. The timing is part of the design.

And this is where I want you to be precise in the homework. Do not write, “FireRed uses scripts and Showdown uses data.” That is too shallow. FireRed uses many data tables, and Showdown has plenty of imperative callback code. A better sentence is: FireRed’s battle AI expresses decision logic through interpreted script commands that manipulate sAIScriptPtr, while Showdown commonly expresses effect behavior through data objects containing callbacks invoked by simulator events. That sentence has architectural content.

Let’s walk through a lab-grade implementation verbally. You define an enum or constants for AI_IF_STATUS_IN_PARTY. You define party selectors, perhaps SELF_PARTY and FOE_PARTY. You define status flags, such as STATUS_POISON, STATUS_SLEEP, STATUS_PARALYSIS, depending on your simplified model. Your script is an array of bytes or instruction objects. If it is byte-based, you write read_u8, read_u16, and read_u32 helpers. Then your command handler reads the selector, mask, and destination. It calls a helper, maybe party_has_status(party, mask). That helper loops over slots and returns true if any non-empty creature has status bits matching the mask. Then the command handler branches or advances.

Notice the layering. The handler decodes. The helper answers the party question. The interpreter owns dispatch. The script owns ordering. That is a clean decomposition. If you instead put byte decoding inside party_has_status, you have mixed VM concerns with creature model concerns. If you make the party loop directly mutate the instruction pointer, you have made the predicate harder to reuse and test. Keep those responsibilities separate unless the original codebase gives you a strong reason not to.

Now, of course, FireRed C code is not always arranged with the neat abstractions we would design in a modern teaching engine. It is a production handheld game codebase with constraints, history, macros, globals, and performance considerations. The goal is not to pretend it is a textbook architecture. The goal is to learn from what it actually does. For example, the broader battle code includes many headers and constants that reveal cross-module dependency, as seen in pokefirered/src/battle_util.c chunk_9335e5727aa0657d. That file’s include list alone reminds us that battle utilities sit at the intersection of items, moves, abilities, weather, battle scripts, animation, controllers, and constants. AI predicates live in that same ecosystem. They are small functions, but they are not isolated.

Similarly, pokefirered/src/battle_setup.c chunk_494d132a59422c85 sits closer to transition and setup concerns, connecting battle entry to overworld, scripts, event data, battle transition, and related systems. I mention that because AI evaluation occurs inside a battle that had to be constructed from world context. The trainer party did not appear from nowhere. It came through setup paths that connect map events, trainer data, wild encounter or trainer encounter conditions, and battle initialization. By week 16, you should be able to follow that chain without needing me to re-teach battle setup line by line.

Let me ask another check question. Why might an AI party-status predicate need to ignore empty party slots? Because party arrays are fixed capacity, but fixed capacity is not the same as valid creature count. If you scan all six slots and treat default memory as a real creature, you may detect a status that is not semantically present, or you may read fields from an invalid entry. In a polished engine, helper functions or validity checks guard this. In a student lab, you must make that guard explicit.

Another question. Why might it matter whether you scan the attacker’s party or the target’s party? Because the strategic meaning flips. “My party has a poisoned member” might make a healing or support action attractive. “The opponent’s party has a poisoned member” might make stalling or switching attractive. Same status mask, different side selector, different strategic interpretation. Operand decoding gives the script language the ability to reuse one command for both questions.

Now, imagine debugging. The AI is supposed to branch when any party member is paralyzed, but it never branches. You inspect the party data and see paralysis is present. Where do you look? First, verify the mask. Maybe paralysis is bit 3 and the script emitted bit 2. Second, verify the selector. Maybe you scanned the wrong party. Third, verify validity filtering. Maybe the paralyzed creature is fainted and your helper intentionally excludes fainted slots. Fourth, verify pointer decoding. Maybe the command is reading the mask at the wrong byte offset. Fifth, verify the branch target. Maybe the branch occurs, but it jumps to logic that does not visibly change the score. That debugging order follows the architecture.

This is also why source-grounded study matters. If you only read high-level descriptions of “battle AI,” you might imagine one giant scoring function. But the FireRed source focus for today shows command-level control flow: AI script pointer control, party-aware predicates, status checks across party slots, and branching. Those are concrete implementation facts from pokefirered/src/battle_ai_script_commands.c chunk_0a05fcecbd58029f and chunk_34a415cc38858fdc. The system is understandable because it is decomposable into commands.

All right, let’s pause and recap this stretch before we move further into command composition. An AI predicate command is not just a boolean. It is a decoded instruction with cursor behavior. Party-aware predicates cross from active battler state into party data, so they need slot validity, side selection, masks, and clear quantifier semantics. sAIScriptPtr is the control-flow hinge: branch on success, advance on failure, and never lose byte alignment. Showdown’s ability hooks give us a useful contrast, because hooks attach behavior to named engine events rather than to an explicit AI script pointer. That comparison should help you decide where a mechanic belongs when you design your clone or analyze an existing game.

Now, in the next part of the lecture, we will build on this by asking how these individual predicates compose into a move-scoring personality. One command can ask a question. A sequence of commands can produce something that feels like preference, caution, aggression, or opportunism. And the trick, as always in this course, is to connect the visible behavior back to the actual data path and control path that produced it.

So let us take that phrase, “move-scoring personality,” and make it precise. I do not mean personality in the vague design-document sense, where we say, “this trainer is aggressive,” and then never connect that sentence to code. I mean personality as an emergent property of repeated predicate-controlled score edits. The AI looks at a candidate move, asks a sequence of scripted questions, and each answer changes either the script pointer or the score attached to that move. Enough of those small changes, applied consistently across moves and battle states, becomes behavior the player can recognize.

Now, on the board, I am going to write a very small schematic. Not real source syntax, just the shape. Candidate move. Read command. Evaluate predicate. If predicate true, jump or modify score. If false, advance. Repeat. End with score. Select among scores. That is the loop we are mentally tracing.

The important point is that the AI is not choosing directly from a philosophical rule like “be clever.” It is evaluating a move through many local questions. Is the user low on HP? Is the target already poisoned? Is there a party member who can exploit this status? Is this move useless because the status already exists? Is this a double battle context? Does the move’s effect line up with the current opponent? Each command is small. The behavior is large because the command sequence is ordered.

This is why the source focus for today matters. In pokefirered/src/battle_ai_script_commands.c, especially the command-control material identified in chunk_0a05fcecbd58029f, we are looking at a script interpreter that treats AI behavior as bytecode-like command flow. And in the party scanning and status predicate material identified in pokefirered/src/battle_ai_script_commands.c chunk_34a415cc38858fdc, the AI’s local question is no longer only “what is true of the active battler?” It can ask “what is true somewhere in the party?” That one shift widens the AI’s world model.

Let’s make that concrete. Imagine the enemy trainer is evaluating Toxic. A naive evaluator might say, “Toxic is good if the target is not already poisoned.” That is already a predicate. But a party-aware evaluator can ask a richer question. “Does my side already have a party member with a status condition that affects whether this plan is useful?” Or, depending on the exact command, “does the opposing party include any member with this status?” The exact side and slot details matter, and that was the point we ended on last time: side selection, masks, slot validity, and quantifier semantics. If those are wrong, the AI’s apparent intelligence becomes noise.

Now pause there. Check for understanding. If an AI command scans six party slots and returns true as soon as one living party member matches a condition, what quantifier is that? Right, existential. It is an “any” predicate. “Is there at least one?” Not “are all?” Not “which one is best?” Just “does one exist?” And if the script branches on that, the whole downstream scoring path changes.

This is also where you should be careful as advanced students not to overread the architecture. A party-aware predicate does not automatically mean the AI is planning three turns ahead. It means the script author has access to a broader fact. Planning only exists if the command sequence uses that fact to shape later choices. In other words, a predicate is a sensor. A scoring command is an actuator. The script path wires sensors to actuators.

Let me put a sample flow on the board verbally. Suppose move slot two is Thunder Wave. The score starts at a neutral value. The script asks: target already has major status? If yes, reduce score or skip improvement. Target is immune? reduce score strongly. User is near fainting and needs immediate damage? reduce score. Party contains a slower attacker who benefits from paralysis support? increase score. Opponent has a fast sweeper pattern? increase score. Some of those checks are in vanilla form, some are the sort of checks you would build in the lab, but the architectural lesson is the same. The AI never says “support mode activated” as a large object-oriented strategy. It accumulates local evidence.

And notice how this connects backward through the course without forcing us to re-teach old material. In week one and week two, we kept tracing data table to runtime global to callback or task to visible behavior. Here the same discipline applies inside battle. Move data tells us what a move is. Battle state tells us what is currently true. AI script commands test that state. sAIScriptPtr chooses the next instruction. Move scores become visible behavior when the trainer selects a move. The player sees “that trainer keeps paralyzing my fast attackers,” but the engine sees predicate, branch, score, selection.

Now compare that with Showdown, but keep FireRed as the main object. In Pokemon Showdown’s data/abilities.ts chunk_d5bdb17c16d6f1a4, the ability examples include event hooks such as onDamagingHit and onModifyMove. In chunk_8a03d04894b7e127, the excerpt includes Imposter using a start-of-presence style behavior and Infiltrator modifying a move through onModifyMove. These are not AI scripts. They are engine event callbacks attached to data records. The contrast is useful because Showdown makes many mechanics feel like named reactions to engine events, while FireRed’s AI command source makes AI decision logic feel like a tiny scripted control-flow language.

So if I ask, “Where does this behavior belong?” your answer should depend on what kind of behavior it is. If the behavior is a property of an ability whenever a battle event occurs, Showdown’s hook style is a natural model. If the behavior is a trainer’s conditional preference while choosing among moves, FireRed’s AI script model is the better mental fit. One is event reaction. The other is decision-time evaluation.

Let’s do a concrete comparison. Say we want a creature ability called Brash, where damaging moves used by this creature gain power when the creature is below one-third HP. In a Showdown-like model, you would expect an ability data record with a hook that modifies move power or modifies the move before damage. That fits the style shown in pokemon-showdown/data/abilities.ts chunk_b4a38d21b0114ee8, where residual and base-power hook examples demonstrate behavior living in data records but being invoked by the battle engine. The ability is not asking, “Should I choose this move?” It is saying, “When the engine reaches the relevant event, alter the calculation.”

Now suppose instead we want an enemy trainer to prefer high-risk finishing moves when below one-third HP. That is not an ability hook. That is move choice. In FireRed terms, you would express it as decision logic: check user HP threshold, check move category or effect, check target HP, adjust the candidate move score, branch around irrelevant scoring blocks. That belongs in AI command flow. Same visible drama, different architectural home.

This distinction will matter in your homework for week sixteen. If you put every mechanic into a hook system, your clone may become flexible but lose the inspectable script control that makes FireRed’s trainer AI teachable. If you put every mechanic into script commands, your clone may become explicit but tedious, because universal reactive mechanics will have to be manually threaded through decision code. Mature architecture is not “use hooks” or “use scripts.” It is choosing the extension surface that matches the timing of the question.

Timing is the word I want you to underline. Not literally, unless you take beautiful notes, but mentally underline it. AI script predicates run during decision time. Ability hooks run during battle event resolution. Battle scripts, which we studied earlier, run during move execution and effect presentation. Overworld scripts run during field interaction. Tasks run per frame. Main callbacks choose the current high-level runtime mode. The entire course is a study of what runs when.

Let’s return to FireRed’s AI. The score for a move is not just a mathematical number. It is a compressed trace of all the branch decisions that have happened while evaluating that move. That means order matters. If an early command jumps over a block, later score changes do not happen. If a predicate is too broad, it may route many moves through a path intended for only one tactical case. If a predicate is too narrow, the score improvement never appears, and the trainer looks dull or random.

Here is a useful debugging habit. When an AI choice surprises you, do not begin by asking, “Why is the AI dumb?” Begin with three questions. What move was being evaluated? Which script pointer path did it take? Which score edits did that path apply? Those questions map directly to the architecture. You are not guessing at intention. You are reconstructing execution.

Now I want to connect this to the lab prompt, because the lab is not just an implementation exercise. You will rebuild a simplified if_status_in_party AI command. The simple version should still preserve the hard parts: read operands from the script stream, choose a party side, scan valid slots, apply a status mask, branch by changing the script pointer on success, and advance correctly on failure. Do not treat that as boilerplate. That is the architecture.

Imagine you implement the command like this in your clone: if any party member has sleep, jump to label. Reasonable. But then you forget to skip empty party slots. Suddenly the value in an unused slot can satisfy the predicate, depending on your memory initialization or object defaults. In a high-level language, maybe empty slots are null and you crash. In C, maybe an uninitialized or placeholder structure gives you a false positive. Either way, the visible result is strange trainer behavior. The architecture lesson is that predicates are only as good as their domain boundaries.

Another check for understanding. If the command reads a two-byte jump address, but on false you advance the script pointer by only one byte after the opcode, what happens to the next command? Yes. Byte alignment is lost. The interpreter begins reading operand bytes as opcodes, and now the bug can look unrelated to the predicate you just wrote. That is why I keep using the phrase “control-flow hinge.” sAIScriptPtr is not a passive cursor. It is the thing that preserves meaning across the script stream.

Let’s say that more sharply. In a table-driven or bytecode-driven architecture, correctness is not only about the truth value of each condition. Correctness is also about where execution resumes. A true predicate with a bad pointer is a broken command. A false predicate with a bad increment is also a broken command. Advanced students should hold both layers in mind: semantic correctness and stream correctness.

Now, how do sequences of these commands create different trainer profiles? One method is script selection. Different trainers or AI levels can point at different AI scripts, or combine different script fragments. Another method is command ordering. A cautious script may heavily penalize self-destructive moves before considering knockout potential. An aggressive script may reward immediate damage and only later check long-term drawbacks. A status-oriented script may give early positive scoring to paralysis, poison, or sleep when the target is eligible. These are design choices implemented as command flow.

This is not unlike the trainer table material from our previous battle setup weeks, but we are not going to re-teach trainer setup. Just recall the bridge: trainer data influences the battle context, battle setup brings that context into the battle engine, and then AI decision routines operate within that state. The trainer’s identity is not magic. It becomes visible through data-selected behavior and battle-time control paths. That is consistent with the battle setup source emphasis we used before, for example pokefirered/src/battle_setup.c chunk_494d132a59422c85, where battle setup sits among includes for overworld, script, event data, battle transition, and battle components. The point is that battle AI is not isolated from the rest of the RPG. It is reached through the same runtime shell and data-to-state pipeline we have been tracing all semester.

Let me give you a worked example at a slightly higher level. Suppose we have two enemy trainers with identical parties. Trainer A is a straightforward damage trainer. Trainer B is a disruptive status trainer. Same creatures, same moves, different AI scripts. On turn one, both evaluate four candidate moves: Tackle, Thunder Wave, Leer, and Quick Attack. Trainer A’s script might check damage reliability, target HP, and priority finishing potential. Thunder Wave receives only mild value because it does not deal damage. Trainer B’s script asks whether the target lacks major status, whether the target appears faster, whether the trainer’s remaining party benefits from speed control, and whether the battle is early enough for status to matter. Thunder Wave receives a larger score. To the player, Trainer B has a personality. In the code, Trainer B has different predicate-score composition.

Now, in a more modern data-hook model, you might be tempted to represent “disruptive trainer” as a callback property on the trainer object: onChooseMove, inspect battle, return move. That is valid in many engines. But FireRed’s lesson is that you can get a lot of expressive behavior from a compact command vocabulary and scripts. The benefit is that behavior can be authored as data-like control flow. The cost is that each new kind of question may require a new command implemented in C. That tradeoff is exactly what we mean by data hook modeling versus C command flow.

Showdown sits on the other side of that comparison. In pokemon-showdown/data/abilities.ts chunk_6372bce3e311c10b, the packet flags a secondary data/engine split note. That is the important phrase: data records can contain hook functions, but the engine still defines when those hooks are called and what arguments they receive. Hooks are not free-floating magic either. They depend on engine event contracts. FireRed AI commands depend on script stream contracts. Both systems are contract-heavy. They simply put the contracts in different places.

Let’s do a quick board cue. I am drawing two columns. Left column: FireRed AI command. It has opcode, operands, C handler, sAIScriptPtr, branch address, score effect. Right column: Showdown hook. It has data record, named event, callback function, engine arguments, return value or mutation. Under both columns I write “contract.” That is the shared advanced idea. When you extend either system, you are not merely adding code. You are joining a dispatch contract.

Now we should ask, how does this fit the complete creature-collector RPG decomposition objective? Because it would be easy to spend all week inside battle_ai_script_commands.c and forget the whole architecture. The complete RPG has the runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. Week sixteen lives in the battle system, but it depends on the creature data model for party slots and statuses. It depends on battle setup for bringing trainer and wild encounter contexts into battle. It depends on UI because AI choices become messages, animations, and turn results the player sees. It depends on save only indirectly, through persistent party state and progression. And link battles remind us that not every battle participant is AI-controlled, so the controller layer must distinguish human input, remote input, and AI selection.

That last point is subtle. AI is one kind of battler controller behavior. The battle engine must support multiple sources of choices. A local player chooses through UI. A link opponent’s choice arrives through communication. A trainer AI computes a choice. The move execution system downstream should not care too much where the choice came from once it has a valid selected move. That is good architecture: different input sources converge into a common battle command representation.

Now, one more check. If a predicate reads from party state during move selection, should it mutate party state? Generally, no. Why? Because decision-time queries should be side-effect-light. If evaluating Thunder Wave changes the party, and then evaluating Tackle sees a different state, your move scores are contaminated by evaluation order. There are exceptions in real engines for caching or temporary bookkeeping, but conceptually the predicate should observe. The scoring command should modify the score. The actual battle script later modifies the world if the selected move executes. Keeping those phases separate is what prevents decision logic from becoming a hidden simulator.

That is a major architectural boundary. Decision logic estimates. Execution logic commits. Showdown hooks, depending on the hook, may commit because they run inside event resolution. FireRed battle scripts commit because they are executing the move. AI scripts should mostly estimate and rank. If you blur those layers, your clone will become difficult to reason about, especially once you add tests.

Speaking of tests, remember the week fifteen homework using Showdown as a comparison for deterministic battle tests. Showdown’s test modules in pokemon-showdown/test, as the research packet notes, are high-signal examples for assert-driven battle behavior. We are not replacing FireRed with Showdown, but we can borrow the testing attitude. For your simplified AI command, a good test is not “the trainer seems smart.” A good test sets up a party with known slot statuses, runs the predicate command, and asserts the script pointer target or next offset. Then another test sets empty slots, fainted slots if your model distinguishes them, and mismatched masks. Then a scoring integration test checks that a move’s score changes only when the predicate path is taken.

Now, if you want the advanced version, log the trace. For each candidate move, record command name, operands, predicate result, old pointer, new pointer, old score, new score. That turns “AI personality” into inspectable evidence. In a teaching clone, that trace is gold. You can show a student exactly why the trainer chose Thunder Wave. In a production engine, you may disable the trace or compile it out, but during development it is the fastest way to debug scripted decision systems.

Let us now transition from individual commands to composition patterns. There are three patterns I want you listening for as we continue. First, guard pattern: reject or penalize moves that cannot help. Second, opportunity pattern: reward moves that exploit the current state. Third, setup pattern: reward moves that improve future turns. Status-in-party predicates are especially interesting because they can support setup reasoning without full search. They let the AI say, in a limited way, “given what exists elsewhere on this side, this move has more or less value.”

That is not minimax. It is not a deep planner. But it is also not random. It is a scripted heuristic system, and scripted heuristic systems have powered many good RPG battles. Their strength is designer control. Their weakness is brittleness when states combine in unanticipated ways. The solution is not to sneer at them. The solution is to understand the contracts, build focused predicates, and test the combinations that matter.

So as we move into the next segment, keep the whole chain in your head. A trainer’s apparent intention is produced by candidate move iteration, AI script command dispatch, party-aware predicates, sAIScriptPtr branching, score mutation, and final selection. Showdown gives us a contrasting language of named hooks attached to data records, useful for ability mechanics and event-time behavior. FireRed gives us the primary model for today: scripted predicates in C command flow. And now we are ready to ask how far that model can stretch before you should introduce a new command, split a script, or move behavior into a different engine layer.

Good. So let’s put a design pressure question on the board: when does an AI predicate deserve to exist as its own command?

Because that is the architectural question hiding underneath today’s code. It is easy to look at a file like pokefirered/src/battle_ai_script_commands.c, especially the command source around chunk_0a05fcecbd58029f, and treat every command as just another helper function. But that is too small a reading. In FireRed, an AI command is not merely a helper. It is an extension point in a little language. Once you add a command, you have changed what the AI script language can say.

So, board cue here: I’m writing three phrases. “Can ask,” “can branch,” “can reuse.” Those are the three tests.

If the AI merely needs a one-off calculation inside one move evaluation path, keep it in C near the scoring logic. If the AI needs to ask a reusable yes-or-no question, and the answer should control sAIScriptPtr, then it starts to look like an AI script command. And if designers, or future maintainers acting as designers, need to compose that question with other conditions, then it really starts to justify becoming part of the script vocabulary.

Now connect that to the party-aware status predicate we have been circling. The simplified if_status_in_party command from the workshop is not interesting because checking status is hard. Checking status is ordinary. It is interesting because the command changes the AI’s field of view. A move scorer looking only at attacker, target, current move, and current battle flags is a narrow tactical evaluator. A command that scans party slots gives the script strategic awareness. It lets the trainer behave as though the bench matters.

And that, by the way, is a major architectural shift. The battle engine already has party data. We have used party data since the encounter setup and trainer setup material, and battle_setup.c, chunk_494d132a59422c85, is one of the places where the transition into battle is visibly tied to overworld and battle state. But having party data somewhere in memory is not the same as exposing party facts to battle AI script logic. Architecture is mostly about that distinction: what exists, what is reachable, and what is expressible.

Let’s make this concrete. Suppose the AI has a move that cures poison for the whole party, or suppose we are simplifying and saying it has a move whose value should increase if any party member is poisoned. A naive AI asks, “Is my current battler poisoned?” That is a local predicate. The party-aware AI asks, “Does my side contain a poisoned non-fainted party member?” That second question crosses from active battler state into party roster state. In FireRed style, that can become a command whose C implementation scans the party slots, checks status fields, and then either advances the AI script pointer or branches it to an address embedded in the script stream. That is the pattern called out in pokefirered/src/battle_ai_script_commands.c, chunk_34a415cc38858fdc: party scanning, status predicates, and sAIScriptPtr branching are not separate ideas. They form one mechanism.

Check for understanding here. If I say, “This command scans the party and then jumps,” what are the two contracts? One contract is data correctness: the command must know which party to scan and how to ignore invalid or unavailable slots. The other contract is control flow correctness: the command must consume the right operands and leave sAIScriptPtr pointing to the right next instruction. If either contract fails, the bug may not look like a crash. It may look like a trainer making a mysteriously bad choice.

That is why AI script bugs are slippery. A broken overworld script often visibly stalls the player. A broken battle script may produce a wrong message, wrong animation, or wrong damage flow. But a broken AI predicate can simply lower the score of the move that should have been chosen. The user sees “the trainer was dumb,” not “the bytecode interpreter skipped two bytes incorrectly.” So the implementation standard for AI commands has to be higher than the apparent simplicity of the predicate.

Now, let me contrast two possible implementations of our simplified if_status_in_party. First version: hard-code poison. The command name is if_has_poisoned_party_member. It reads the side, loops over six slots, checks whether each Pokémon exists, checks whether it has HP, checks poison status, and branches if true. That command is very easy to write and very easy to understand. Its weakness is that every new status needs a new command or a duplicate command body.

Second version: parameterize the status. The script says, in effect, “if status in party equals this mask, branch here.” Now the command can support poison, paralysis, sleep, burn, and so on. It is more reusable, but it also has a sharper operand contract. The script stream must encode the status mask correctly. The command must read that mask correctly. The branch target must be read from the correct byte position after the operand. The more expressive the command, the more dangerous an off-by-one becomes.

And that gives us a useful rule: generalized AI commands are worth it when they reduce script duplication without making the bytecode unreadable. If the command’s operands become a tiny programming language inside the tiny programming language, stop. That is a sign the behavior belongs in C scoring logic, or perhaps in a higher-level data model in a different engine.

Let’s pause and tie this to Showdown, because this is exactly where the comparison becomes productive. In Pokemon Showdown’s data/abilities.ts, the ability records can contain named hooks. The packet points us specifically to pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4, with secondary ability hooks such as onDamagingHit and onModifyMove. That style says, “Attach behavior to the data object, and let the simulator call that behavior at the appropriate event point.” FireRed’s AI script command style says, “Give a script interpreter commands that can inspect state and redirect evaluation.” Both are extensibility models, but they put the extension in different places.

So if I draw this on the board, the FireRed side is: script bytes, command dispatch, C predicate, sAIScriptPtr branch, score change, move selection. The Showdown side is: data record, named hook, simulator event, callback execution, modified move or damage or state. Neither diagram is morally superior. They optimize for different engineering worlds.

FireRed is a GBA game written in C, with memory limits, explicit globals, fixed data layouts, and engine layers that are carefully staged. A compact command interpreter makes sense there. Showdown is a server-side simulator in TypeScript, with richer object models and tests built around deterministic battle behavior. A hook-oriented data model makes sense there. The reason I keep saying “secondary contrast” is that we are not replacing FireRed’s architecture with Showdown’s. We are using Showdown as a clean mirror for the question, “Where could this behavior live if the engine had chosen a different extension style?”

Here is a concrete comparison. Imagine an ability that modifies a move before it lands. In Showdown, an ability can expose onModifyMove, and the simulator can call that hook at the move modification stage. The chunk_d5bdb17c16d6f1a4 excerpt even shows Infiltrator using onModifyMove to mark a move as infiltrating. That is data-attached behavior reacting to an engine event. In FireRed battle AI, if the trainer wants to value a move differently because it bypasses a defensive condition, that is not naturally an ability callback attached to a data object. It is more likely expressed through move data, battle state checks, and AI script commands or C score logic.

Now notice the subtlety. Showdown’s hook changes the actual move behavior. FireRed’s AI predicate often changes the AI’s evaluation of behavior. Those are not the same layer. An AI command should not usually be the place where the real battle mechanic is implemented. It should ask questions about mechanics that the battle engine already knows how to execute. If your AI predicate secretly implements a second version of the mechanic, you have created a consistency risk.

This is one of the most important advanced lessons of the week: AI should observe combat truth, not duplicate combat truth. There are exceptions, because games ship under constraints, but as an architectural principle it is strong. If move effectiveness, immunity, status availability, or ability interaction is already computed elsewhere, the AI should reuse that knowledge or query state derived from it. If the AI re-derives it with slightly different rules, you get trainers choosing moves according to a parallel battle universe.

Let me give you a simple failure case. Suppose we write an AI command that checks whether any party member has a status condition. We forget to ignore eggs, empty slots, or fainted Pokémon in our simplified engine. The command says, “Yes, someone in the party is statused.” The AI boosts a support move. But the move cannot legally affect that slot, or it is strategically irrelevant because that Pokémon has fainted. The trainer chooses the support move and loses tempo. The player calls the AI foolish. Architecturally, the predicate was too broad.

Now refine the predicate. We might ask: is there a living party member, on the AI’s side, with a curable major status, where the current move can actually address that condition? That is a better predicate, but it is more specific. This is where command granularity matters. You can create a generic if_status_in_party command and let scripts decide how to use it, or you can create a more semantic command like if_party_needs_status_heal. The generic command is reusable. The semantic command is safer. The right answer depends on how many scripts need it and how much game knowledge the script authors should carry.

For the lab, when you rebuild the simplified if_status_in_party command, I want you to feel this tradeoff directly. Do not just make the loop work. Ask what question your command is actually answering. Is it “any status anywhere”? Is it “specific status on usable party members”? Is it “status that this move can exploit”? Each version has a different place in the architecture.

Now let’s connect to party slot scanning in more operational terms. In FireRed, the AI command implementation has to bridge battler identity and party identity. The active battler is part of the battle runtime. The party array is organized by side and slot. Depending on battle type, link mode, double battle layout, or opponent ownership, mapping from battler to party may not be a trivial “use index zero” operation. We saw the broader pattern earlier in the course: runtime shell and battle setup create contexts, and later systems must respect those contexts. A party-aware predicate is another example. It cannot pretend the battle is always a simple one-on-one trainer fight unless the command contract explicitly limits it to that case.

And because this is week 16, I’m going to hold you to the advanced standard. In week 3, I would accept “loop over the party and branch.” Now I want the preconditions. Which side? Which slots? Which statuses? Which battle formats? Which invalid entries? Which command operands? Which branch semantics? What happens if no match is found? What happens if more than one match is found? Is the first match enough, or does the count matter? These questions are not ceremony. They are how we avoid turning a predicate command into an accidental policy engine.

Let’s do a board trace. I’ll speak it slowly.

The AI begins evaluating a candidate move. The script pointer is at an opcode for a party status check. The command dispatch calls the C function for that opcode. The function reads a status mask operand. It reads a branch pointer or offset, depending on the local command format. It determines which party corresponds to the AI battler. It loops over party slots. For each slot, it rejects empty or unavailable Pokémon. It reads current HP and status. It tests the status mask. If it finds a match, it assigns sAIScriptPtr to the branch target. If it does not find a match, it advances sAIScriptPtr past the operands. Then the script continues, perhaps to add to the score, subtract from the score, or skip a scoring block.

That trace is the whole architecture in miniature. Data, global battle context, interpreter control flow, predicate, branch, score. When you can narrate that path precisely, you understand the system at the level this course is aiming for.

Quick check: why do I keep naming sAIScriptPtr instead of just saying “the pointer”? Because in this code style, the pointer is the interpreter’s program counter. It is not a passive address. It is the state variable that says what the AI language will execute next. In a bytecode interpreter, control flow is data mutation. Changing the pointer is the equivalent of an if statement, a goto, or a return in a higher-level representation. If you are casual with it, you are casual with the language.

Now, I want to bring in the other Showdown chunks from the week context, because they widen the comparison. The packet identifies pokemon-showdown/data/abilities.ts, chunk_b4a38d21b0114ee8, as residual and base-power hook examples. Residual hooks and base-power hooks are a nice contrast because they show event-time specificity. A residual hook belongs near end-of-turn or recurring effect processing. A base-power hook belongs near damage calculation. Showdown can attach both as named callbacks on data records, and the engine invokes them when that phase arrives. FireRed’s AI script commands, by contrast, are not phase callbacks. They run when the AI script asks the question during candidate evaluation.

This matters for mental modeling. If an ability has an onBasePower hook, you can ask, “At what calculation stage does the simulator call this?” If an AI script has an if_status_in_party command, you ask, “At what point in this script’s evaluation does the question occur?” One is engine phase ordering. The other is script authoring order. Both can be deterministic, but they are deterministic in different dimensions.

And then chunk_6372bce3e311c10b from pokemon-showdown/data/abilities.ts is useful for the broader data and engine split note. Again, do not over-import Showdown’s structure into FireRed. But do notice the design vocabulary it gives us. Showdown often makes a data object carry hooks while the engine provides the calling convention. FireRed often makes a table or script select behavior while C command flow provides the execution. In one model, the data record says, “When this event happens, call me.” In the other, the script says, “At this point in my evaluation, ask this command.”

That distinction helps you choose designs in your own clone. If you are building a small FireRed-inspired battle AI, you may not need a full hook system. You can implement a compact script interpreter and a dozen predicates. If you are building a simulator where hundreds of abilities, items, volatile effects, and field conditions need to react to shared events, a hook registry may be cleaner. The course objective is not to memorize FireRed’s exact division forever. It is to learn how to decompose a creature-collector RPG into systems and then choose the right extension style for each system.

Let’s zoom out briefly, because this is the last week of the battle engine architecture module. We began the course with the runtime shell: startup, callbacks, per-frame task dispatch. We used AgbMain and SetMainCallback2 earlier as anchors for how FireRed gets from hardware-facing startup into game modes, and we used RunTasks as the pattern for per-frame cooperative dispatch. We then moved through overworld maps, event scripts, creature data, inventory, UI, save, encounters, battle setup, and battle execution. Today’s AI script predicate sits near the end of that long chain. But it depends on almost all of it.

A trainer’s party comes from trainer data and battle setup. The active battle context comes from the transition out of overworld or encounter flow. The candidate move list comes from creature data and current battle state. The legality and consequences of moves depend on battle scripts, move effects, status fields, and ability/item interactions. The visible outcome routes back through battle result storage and eventually back to overworld progression. So when we say “AI command,” we are standing on the whole RPG architecture, not a cute isolated interpreter.

This is why the objective says decompose the complete creature-collector RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. By week 16, decomposition alone is not enough. You must also understand the contact surfaces. The AI predicate touches party data. Party data touches save structures and battle setup. Battle setup touches overworld scripts and trainer tables. Battle result touches map progression. The architecture is not a set of boxes. It is a set of boxes with contracts between them.

Let’s return to a concrete example and make it slightly more advanced. Suppose a trainer has two candidate moves: a damaging move and a team support move that cures status. The current active Pokémon is healthy. One benched Pokémon is burned. The opponent is at low HP. Should the AI cure the bench or attack?

A simple if_status_in_party predicate says, “There is status in the party,” and increases the support move’s score. But a better AI also asks about immediate tactical urgency. Can the damaging move KO? Is the statused party member likely to return? Is the support move wasting a turn? Does the current active Pokémon risk fainting? FireRed’s script predicate model can express some of this by composing commands: check opponent HP, check move damage estimate, check party status, branch into scoring blocks. But there is a limit. The more the script tries to become a planner, the more brittle it becomes.

So here is another board phrase: predicates are not plans. Say that back to yourself. Predicates are not plans. They answer local questions that scripts can compose. A full planner models future states and expected value. FireRed’s AI scripts are closer to a heuristic scoring system. That is not a flaw. For a cartridge RPG, a heuristic scoring system is often exactly right. It is controllable, compact, and legible enough for designers and programmers to tune.

But the moment you expect it to reason like a full adversarial search engine, you will be disappointed. And if you try to force it there by adding hundreds of predicates, you will produce a fragile language with hidden interactions. The right sophistication is bounded sophistication: enough party awareness to avoid obviously bad decisions, enough status awareness to use support moves plausibly, enough branching to specialize by context, but not a full simulator inside the AI.

Now, one student usually asks at this point, “Couldn’t we just score everything in C and avoid scripts?” Yes, you could. And for a small project, that may be the correct choice. But you give up script-level composability. You also make behavioral tuning require C edits instead of data or script edits. FireRed’s AI command model gives the engine a small vocabulary of operations that can be recombined. That is valuable when you want multiple trainer personalities, move-specific preferences, or difficulty-specific behavior without rewriting the entire evaluator.

The inverse question is, “Couldn’t we make everything scriptable?” Also yes, technically. But then your script engine must expose every relevant piece of state, and the script author must understand every edge case. That is how you get a scripting language that is technically powerful but practically hostile. A healthy engine keeps primitive, safety-critical, or mechanics-heavy behavior in C, and exposes concise predicates and scoring operations to the AI script layer.

This is the same design lesson we saw in overworld event scripting, just in a battle-specific form. Overworld scripts do not implement the whole renderer. They request movement, messages, flags, warps, and state changes through commands. Battle AI scripts should not implement the whole battle engine. They request information and mutate scores through commands. Different domain, same architectural principle.

Let’s check understanding with a comparison question. If Showdown’s onDamagingHit hook from abilities.ts, chunk_d5bdb17c16d6f1a4, runs after a damaging hit event, is that more like an AI predicate or more like a battle script effect? It is more like an event-time battle effect. It is tied to the actual resolution of combat. It is not merely asking whether the AI should prefer a move. That distinction matters. Hooks can change what happens. AI predicates should generally change what the trainer chooses.

Now, in practice, the line can blur because AI needs to predict what will happen. If the AI wants to know whether a move will trigger some ability, it must approximate future battle behavior. That is prediction. Prediction can be wrong if it does not share logic with resolution. Advanced battle AI design often revolves around managing that mismatch. You either keep the prediction coarse and heuristic, or you create shared calculation helpers so prediction and resolution stay aligned. What you should avoid is copying large chunks of resolution logic into AI-only code and letting them drift.

This brings us to testing. For your week 16 homework and quiz, I am not interested in whether you can write a loop. I am interested in whether you can test the contract. For if_status_in_party, test no status. Test one status on the active battler. Test one status on the bench. Test fainted statused Pokémon if your rules exclude fainted slots. Test multiple statuses if your mask supports them. Test the branch-taken path and the fallthrough path. And, critically, test pointer consumption. A command that returns the right boolean but leaves the script pointer wrong is still broken.

In a modern clone, you can instrument this directly. Create a tiny script stream: predicate command, branch target, score command, end command. Run it against a fake party. Assert final score and final instruction path. That is the lab’s real purpose. You are not rebuilding FireRed for nostalgia. You are learning how to isolate an interpreter command with enough fake state to prove its behavior.

If you also implement the Showdown-style callback ability in the workshop, compare the tests. The callback ability test probably constructs a battle event or a move object, invokes the hook through the engine or a test harness, and asserts that the move or damage or state changed. That is a different testing shape. The FireRed-style command test asserts script pointer behavior plus predicate result. The Showdown-style hook test asserts event callback behavior plus state mutation. Again, different extension style, different test seam.

Let me say that in plain terms. For a script command, test the language. For a hook, test the event contract.

Now, I want to spend a moment on naming, because naming is architecture at the human layer. If you name the command if_status_in_party, that suggests a generic predicate. If you name it if_can_benefit_from_status_heal, that suggests policy. If you name it if_party_member_has_major_status, that says more about scope. Bad names cause misuse. A future script author may see if_status_in_party and use it for a move that only cures poison, forgetting that the command also matches paralysis or burn unless parameterized. Then the AI chooses a poison cure when no poisoned Pokémon exists. The code worked. The name invited the wrong use.

In C-heavy codebases, names often carry contracts that type systems do not enforce. FireRed’s style has many compact globals, constants, and command functions. That makes local clarity matter. You do not have a rich type-level DSL protecting every predicate. The script byte stream certainly does not protect you. So the command name, operand order, and nearby documentation become part of the engineering surface.

This is also why source navigation matters. The research packet points not only to the AI command file, but to battle_util.c, chunk_9335e5727aa0657d, with includes tying battle utility code to battle AI script command headers, move constants, item constants, weather constants, abilities, hold effects, and move effects. That include list is evidence of battle’s cross-cutting nature. Battle code is where many data worlds meet. When an AI command reaches into party status, it is participating in that same dense network. You should feel some caution before adding yet another dependency or duplicating another rule.

Let’s talk briefly about link systems, because they appear in the course objective and are easy to forget during AI week. Link battles complicate assumptions about control, ownership, determinism, and visibility. A cartridge battle engine that supports link behavior cannot casually let one side’s AI inspect data it should not own in a competitive context, and it must keep deterministic state synchronized where applicable. FireRed’s trainer AI is primarily for non-player opponents, but the overall architecture includes link modules. The larger lesson is that a predicate’s legitimacy depends on mode. In a wild battle or trainer battle, party-aware AI is fine. In a player-versus-player context, AI scoring may not be involved at all, or it may be limited to battle facilities or partner logic. So when you design your clone, do not make “AI can scan party” a universal global privilege. Make it part of the mode contract.

That same idea applies to save and UI. Party status is persisted in save-related creature data. It is displayed in UI. It is modified in battle. It is healed by inventory and Pokémon Center scripts. The AI reads it. One field, many systems. This is why decomposition cannot mean isolation. The systems are distinct because they have different responsibilities, not because they never touch the same data.

Let’s do one more concrete trace, this time from the whole-game perspective.

The player fights a trainer. Trainer data and party data feed battle setup. The battle runtime creates battlers and move options. During a turn, the AI evaluates a healing move. The AI script command in battle_ai_script_commands.c scans the trainer’s party for a status condition, as in the party-aware predicates highlighted by chunk_34a415cc38858fdc. It branches sAIScriptPtr into a scoring block. That block increases the move’s desirability. The final selection chooses the support move. The battle engine later resolves the move using battle mechanics, messages, animations, and state updates. At battle end, battle result storage determines how the overworld script continues. The player experiences all of that as “the trainer made a smart choice.”

That is the illusion. Architecture manufactures the illusion of intention.

And I want that sentence to land. Creature RPG AI, especially in this generation of design, is not usually deep cognition. It is layered state inspection plus scripted preference. The trainer appears intentional because the architecture has placed the right predicates near the right score mutations. If the predicates are too weak, the trainer looks random. If the predicates are too broad, the trainer looks foolish. If the predicates are too strong and opaque, the trainer may feel unfair or uncanny. Good RPG AI is often a carefully tuned middle.

Now, since this is an advanced class, let’s ask where the model stretches too far. First limit: combinatorial state. Status, weather, stat stages, held items, abilities, party composition, remaining PP, battlefield sides, and move effects can combine explosively. A predicate language can only cover selected combinations. Second limit: future planning. Scripted scoring is mostly immediate or shallow. Third limit: maintenance. Every new command increases the interpreter vocabulary and testing burden. Fourth limit: semantic drift. If AI approximations are not tied to actual battle mechanics, they fall out of sync.

When you see those limits, you have several design options. You can add a focused command. You can split scripts so different trainer classes or move categories use simpler logic. You can move behavior into shared C helpers. You can expose a new data field and let the scoring system read it. Or, in a Showdown-like architecture, you can model the behavior as an event hook on an ability, item, move, or condition. The mature answer is not always “more scripting.” The mature answer is choosing the layer where the rule has the least duplication and the clearest contract.

Let’s run that decision process for our example. “AI should prefer Aromatherapy-style team healing if multiple party members have major status.” Should that be one generic status predicate plus script scoring? Probably yes in a FireRed-like engine. The mechanic already exists elsewhere; the AI just needs to observe party status. “A new ability causes damaging moves to become a different type before hit calculation.” In a Showdown-like engine, that belongs naturally as an onModifyMove hook, like the hook style represented in abilities.ts chunk_d5bdb17c16d6f1a4. In a FireRed-like engine, it belongs in battle mechanic resolution, not in AI scripts. The AI may later query or approximate it for move choice, but the ability’s truth belongs in the battle engine.

That separation is how you keep the architecture sane. Mechanics define reality. AI evaluates reality. UI displays reality. Save persists reality. Scripts coordinate reality. Those are not absolute walls, but they are good defaults.

Let me give you a quick recap before we move into the final synthesis.

Today we treated FireRed battle AI as a scripted predicate system. We did not re-teach battle script command dispatch; we used it as prior foundation. The important piece was that AI commands form a small language whose control flow is driven by sAIScriptPtr. We looked at party-aware predicates, especially status checks across party slots, using pokefirered/src/battle_ai_script_commands.c chunks_0a05fcecbd58029f and_34a415cc38858fdc as the source focus. We contrasted that with Pokemon Showdown’s ability hook model, especially abilities.ts chunks_d5bdb17c16d6f1a4, b4a38d21b0114ee8, and 6372bce3e311c10b, where named callbacks live on data records and are invoked by simulator events. And we kept the comparison disciplined: FireRed is our primary architecture source; Showdown is the secondary model that helps us name alternatives.

Now, final check for understanding, and I want you to answer this in your notes as a sentence, not as a keyword. What is the difference between an AI predicate and an ability hook?

A good answer would be: an AI predicate is a script-invoked question used to guide move scoring or branch through AI logic, while an ability hook is event-invoked behavior attached to an ability data record that can modify battle resolution. That sentence is not just vocabulary. It tells you where to implement a feature.

Second question: why is party-aware AI architecturally riskier than active-battler AI? Because it crosses a boundary from current battler evaluation into party roster state, and that requires correct side mapping, slot filtering, battle mode assumptions, and branch control. Again, not because the loop is hard. Because the contract is wider.

And third question: what should your simplified lab command prove? It should prove both the predicate result and the interpreter behavior. Did it detect the right party status? Did it branch or fall through correctly? Did it consume exactly the operands it should consume? If your test only checks the boolean idea in your head, you have not tested the command.

Now let’s close the whole lecture by tying it back to the course architecture.

A complete creature-collector RPG is not one engine. It is a federation of small engines. The runtime shell decides which mode is active. The overworld map system gives space and object context. The event script system coordinates progression. Creature data defines species, moves, stats, status, party membership, and persistence. The inventory system changes what the player can do to those creatures. The UI makes state legible. The save system makes state durable. The link system constrains communication and determinism across machines. The battle system stages conflict, resolves mechanics, and returns consequences. Battle AI sits inside that battle system, but it depends on almost every other layer’s data contracts.

FireRed’s design, as we have studied it, repeatedly uses compact data tables, globals, callback dispatch, task dispatch, script interpreters, and C command functions. That style is not accidental. It fits the hardware, the toolchain, and the production needs of the game. Pokemon Showdown, by contrast, gives us a modern simulator’s hook-based data model, where abilities and other effects can attach callbacks such as onModifyMove, onDamagingHit, residual behavior, or base-power modification. That style fits a different environment: server execution, TypeScript objects, modded data, and regression-oriented tests.

If you remember only one design lesson from today, make it this: extension style shapes thought. A script predicate system makes you think in terms of questions, branches, and scoring blocks. A hook system makes you think in terms of events, callbacks, and effect objects. A table-driven system makes you think in records and lookups. A task/callback shell makes you think in modes and per-frame state. None of these is the universal architecture. Each one is a tool for placing behavior where it can be expressed, tested, and maintained.

So when you design your own FireRed-inspired clone, do not ask, “How do I copy every command?” Ask, “What language does this subsystem need?” The overworld needs a language for progression and interaction. Battle resolution needs a language for mechanics and effects. AI needs a language for preference and conditional choice. UI needs a language for presenting state and receiving input. Save needs a language of stable serialization. Link needs a language of synchronized messages and constraints. Good architecture is matching each subsystem to the language it actually needs.

For next time, carry forward the contrast. We are going to keep using Showdown as a comparison model, but not as a replacement authority. When you see an ability hook, ask what event owns it. When you see a FireRed AI command, ask what script question it answers. When you see a branch through sAIScriptPtr, ask what behavior became expressible because that branch exists. And when you write the lab, be strict with yourself: a command that works in the happy path is not done until its data contract and control-flow contract are both tested.

That is the synthesis for today. FireRed battle AI is not magic and it is not just a pile of if statements. It is a compact scripted predicate layer sitting on top of a much larger RPG architecture. Its power comes from exposing carefully chosen questions to a scoring script. Its danger comes from exposing questions too broadly, duplicating combat truth, or letting pointer control drift. Read it that way, and the file stops being a dense list of commands and starts looking like what it is: a small decision language for making trainers appear intentional inside a constrained, data-driven creature RPG.

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