# Move Replacement Without Corrupting Party Data

Lecture ID: lecture_w19_d02
Week 19, Day 2

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

## Transcript
All right, let’s pick up exactly where we left off last time. We are still in week 19, so our focus is not “what is a move,” and it is not “what is a party.” You already know those layers. Our focus today is narrower and more dangerous: how a menu-driven feature writes into live party data without corrupting the creature record, confusing the UI, or leaving the game in a half-finished state.

The concrete feature is move replacement. In FireRed terms, think of the move relearner flow. The player selects a Pokémon, chooses a move to relearn, confirms the choice, possibly chooses an old move to forget, confirms deletion, and then the game writes the move into the Pokémon’s move slots. That sounds simple if you say it in one sentence. But architecturally it crosses several fault lines: UI state, task scheduling, party data, move metadata, text prompts, cancellation paths, and return callbacks.

So the theme for today is this: a safe move replacement system is not just a write to an array. It is a state machine that earns the right to write.

Let me say that again, because it is the center of the lecture. A safe move replacement system is not just a write to an array. It is a state machine that earns the right to write.

Now, on the board, I’m going to write a short chain. Not code yet, just the flow of responsibility.

Player intent, menu state, confirmation branch, replacement selection, deletion confirmation, party data write, return callback.

That chain is the bridge between last lecture and today’s lecture. Last time, when we discussed menus as task-backed state machines, we looked at the fact that FireRed does not treat a menu as a blocking function call that just waits until the user is done. Instead, menus cooperate with the frame loop. The task system holds per-frame state, and the current main callback drives work forward. We grounded that in the task implementation itself: `pokefirered/src/task.c`, `chunk_cb0bb91a147bfb59`, for `CreateTask`, and `pokefirered/src/task.c`, `chunk_3ffebc0341225936`, for `RunTasks`. Those chunks matter again today because move replacement is not an isolated algorithm. It lives inside that same per-frame dispatch world.

If you try to implement move replacement as a single immediate function, something like “teachMoveNow,” you will be tempted to do too much too early. You will ask, “Did the user select the move?” yes. “Does the Pokémon have an empty slot?” maybe. “If not, which move should be deleted?” unclear. “Did the user confirm deletion?” not yet. And if the user presses B halfway through, now you need to unwind. That is where a naive implementation starts corrupting state, or at least creating inconsistent user-facing behavior.

FireRed’s answer is to stretch the operation across states. The source packet points us to `pokefirered/src/learn_move.c`, especially `chunk_af3598df6f092723`, which explicitly frames the move relearner as a state machine. Notice the language in that chunk: it names `CB2_MoveRelearner_Init`, it talks about list menu scroll positions and rows, and then it enumerates `MoveRelearnerStateMachine` states. That is not incidental commentary. It tells us the feature is being understood as a sequence of UI states, not as one monolithic write operation.

And then the second key excerpt, `pokefirered/src/learn_move.c`, `chunk_e820ee15ae8caa6d`, gives us the specific danger zone for today. It describes the teach move confirmation and replacement flow. It says, in effect, wait for the player to confirm; if cancelled, go back to setup; if confirmed and the Pokémon has an empty move slot, set a script variable and proceed to success text and fanfare; if confirmed and the Pokémon does not have an empty slot, print the “trying to learn” prompt and move toward the old-move deletion path.

That conditional branch is the architecture lesson. The same player action, “teach this move,” has two different write strategies depending on the existing party data. If there is an empty move slot, the write is direct and low-risk. If all move slots are occupied, the system must collect more intent before mutating the record.

Let’s make this concrete. Suppose the player has a Charmeleon with four moves: Scratch, Ember, Metal Claw, and Smokescreen. The move relearner offers Dragon Rage. The player selects Dragon Rage and says yes. At this moment, the game does not yet know which of the four existing moves should disappear. If the implementation overwrites slot zero by default, you have destroyed Scratch without permission. If it opens the old-move selector but has already written Dragon Rage into temporary party state, then cancellation becomes complicated. If the player backs out, do you restore the old move? Where did you store it? Did the summary screen already read the new value? Did the script variable already report success?

So the correct design is to defer the irreversible write until the player has made all necessary decisions. In the full-slot case, confirmation to learn is not enough. It is only confirmation to continue.

Check your understanding here. If a Pokémon has an empty move slot, what information is still missing after the player confirms the new move? Nothing essential. The destination can be the empty slot. But if the Pokémon has four moves, what information is missing? The old move to delete. And after the player selects an old move, what information is still missing? Whether they truly confirm deletion. That is why FireRed’s flow needs separate states for “confirm teach,” “select old move,” and “confirm deletion.”

Now, we should be precise about the data boundary. The party Pokémon is not merely a UI object. It is durable game state. The summary screen can read from it, battle can later read from it, save logic can persist it, scripts can observe outcomes, and other menus can return to it. That is why this lecture title says “without corrupting party data,” not merely “how to build a move menu.”

The summary screen source is relevant here because it demonstrates that party and move data are reused across UI surfaces. The packet points us to `pokefirered/src/pokemon_summary_screen.c`, `chunk_e8e17a42bbf17d46`. The excerpt is mostly include structure, but that structure is still evidence. It pulls in `pokemon.h`, `pokemon_summary_screen.h`, `task.h`, `menu_helpers.h`, `party_menu.h`, `battle_main.h`, and `constants/moves.h`. In other words, the summary screen is not a decorative page. It sits at a crossroads between party data, task-driven UI, menu helpers, and battle-relevant move constants. If move replacement writes bad data, the summary screen is one of the places where the bug becomes visible.

Let me draw another board diagram. On the left I’ll write “source of truth.” Under that: party Pokémon struct. On the right I’ll write “views.” Under that: summary screen, party menu, battle, scripts, save. Then between them I’ll write “controlled mutation.” That phrase matters. We want many readers, but very few writers, and the writers need to be disciplined.

This connects all the way back to the larger course plan. In week 1, we decomposed a creature-collector RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. At the time, that may have felt like a map of boxes. By week 19, we are studying the joints between the boxes. Move replacement is exactly one of those joints. It begins in an NPC or menu context, uses UI callbacks and tasks, reads move lists and Pokémon data, possibly updates a script variable, writes to party data, plays fanfare, and returns control to the previous context.

That is why this is advanced material. The hard part is not knowing that a Pokémon has four move slots. The hard part is making sure that every subsystem agrees about when those slots are allowed to change.

Now, let’s compare briefly with Pokémon Showdown, because our course uses Showdown as a secondary comparison, especially for battle data hooks, Dex and mods, simulator turns, and regression testing. But I want to be very clear: Showdown is not replacing FireRed as our architecture source today. FireRed remains the primary system because the topic is a GBA menu writing to party data through task-backed UI states. Showdown helps us ask a different question: how does a modern simulator isolate battle data and test behavior?

The research packet reminds us that Showdown’s high-signal modules include `pokemon-showdown/data/learnsets.ts`, several `pokemon-showdown/data/mods/.../learnsets.ts` files, `pokemon-showdown/sim`, and `pokemon-showdown/test`. That is useful because Showdown’s design makes data variation explicit through Dex and mods. A generation mod can alter learnsets or move behavior without pretending that there is one universal ruleset. We also have an example chunk from `pokemon-showdown/data/mods/gen2stadium2/scripts.ts`, `chunk_3480e574205d5d0f`, where damage calculation routes through move fields and callbacks such as damage callbacks, fixed damage, and immunity checks. That chunk is not about FireRed’s move relearner. It is evidence for the comparison point: Showdown tends to model battle behavior as data plus hooks inside a simulator, whereas FireRed’s move replacement menu is a task-driven UI procedure that eventually mutates a party record.

So if I ask, “Where would I regression test this in Showdown style?” I might think in terms of deterministic simulator tests: construct a battle, load a species, assert that a move behaves correctly under a mod. The packet points us toward `pokemon-showdown/test`, including `TESTS.md`, `assert.js`, and `common.js`, as recurring test infrastructure. But if I ask, “Where is the dangerous write in FireRed?” I am back in `learn_move.c`, with UI states and party mutation. The comparison is useful only if we preserve that distinction.

Let’s do a concrete failure scenario. Imagine you are building the lab version of this feature. The lab asks you to build a move-replacement menu with explicit states for list setup, confirm teach, select old move, confirm deletion, write data, and return callback. Suppose your implementation has states zero through five. State zero builds the list. State one handles selecting the new move. State two asks, “Teach this move?” State three opens the old move list. State four asks, “Forget this old move?” State five writes.

Now suppose the player selects Dragon Rage, confirms teaching, selects Ember as the move to forget, and then cancels at the deletion confirmation prompt. What should the party data contain?

It should still contain Scratch, Ember, Metal Claw, and Smokescreen. Dragon Rage should not be in the party record. Maybe it is still stored in a temporary variable as the candidate move, depending on how your menu is implemented, but it should not be committed to the Pokémon’s actual move slot.

That is the simplest test of whether you understand today’s lecture. Cancellation must be boring. It must leave the durable data unchanged.

Let’s add a more subtle version. The Pokémon has three moves and one empty slot. The player selects Dragon Rage and confirms. In that case, the write can happen without an old-move selector. But even here, you should be careful about when the UI reports success. FireRed’s flow, as summarized in `pokefirered/src/learn_move.c`, `chunk_e820ee15ae8caa6d`, distinguishes the empty-slot path from the full-slot path and moves toward printed text and fanfare after the relevant success condition. That sequencing matters because the visible story and the data mutation should agree. You do not want the game to play the fanfare while the party data remains unchanged, and you do not want the data to change while the player-facing text says the operation was cancelled.

This is where the script variable mentioned in the excerpt becomes interesting. The chunk says that if confirmed and the Pokémon had an empty move slot, it sets `VAR_0x8004` to true and proceeds. We are not going to spend today re-teaching the scripting system, but remember from earlier weeks that scripts and UI often communicate through variables and flags. Here, that variable is part of the return contract. The UI operation is not just a visual menu. It reports an outcome back to the surrounding script or caller.

So we now have three layers of correctness. First, local UI correctness: the right prompts appear, buttons do the right thing, cancel goes back to the right state. Second, data correctness: the Pokémon’s move slots are changed only at the correct commit point. Third, caller correctness: the surrounding script or callback receives the right success or failure result.

Pause there and test yourself. If a bug causes the new move to appear in the summary screen but the script variable says failure, which layer is inconsistent? The data layer and caller contract disagree. If the script says success and the fanfare plays, but the summary screen still shows the old four moves, which layer failed? The UI and caller contract advanced as though the write happened, but the data mutation failed or targeted the wrong record. If pressing B at the deletion confirmation removes the move anyway, what failed? The cancellation branch allowed a commit.

Now, because this course has already covered item data, bag pockets, quantities, item-use dispatch, and field-use versus battle-use items, I want to make a careful comparison without re-teaching those systems. In the bag, we saw a UI facade over inventory data. The player sees pockets and item lists, but the real work is controlled mutation of quantities and effects routed through dispatch. Move replacement is similar in that the menu is not the data. But it is also more personal and more structurally constrained. Removing a Potion from the bag decrements a quantity. Replacing Ember on a Charmeleon changes the future behavior of that specific party member. It affects battle choices, summary display, and saved identity.

That difference changes the risk profile. Inventory mutation is often about counts and item IDs. Move replacement is about editing part of a creature’s learned capability set. You should feel more cautious around the commit point.

Let’s bring the task system back in. The reason FireRed can safely stage this interaction is that each frame can ask, “What state am I in, and what input or animation completion am I waiting for?” `CreateTask` from `pokefirered/src/task.c`, `chunk_cb0bb91a147bfb59`, gives a feature a task slot and task data. `RunTasks`, from `chunk_3ffebc0341225936`, dispatches active tasks per frame. The move relearner can therefore be structured as a continuing conversation with the player rather than as a blocking function.

When you build your lab version, I want you to avoid hiding this structure behind vague names. Do not name a state `STATE_DO_THING`. Name it after the contract it represents. `STATE_SETUP_LIST`. `STATE_CONFIRM_TEACH`. `STATE_SELECT_OLD_MOVE`. `STATE_CONFIRM_DELETE`. `STATE_WRITE_MOVE`. `STATE_RETURN`. The name should tell you whether durable party data is allowed to change in that state. Most states should not write. They should collect intent, render text, update cursors, or branch.

Here is the rule I would put in a code review: only the commit state writes the party move slot, and every path into the commit state must have enough information to make that write unambiguous.

For the empty-slot path, enough information means selected Pokémon, selected new move, and destination empty slot. For the replacement path, enough information means selected Pokémon, selected new move, selected old move slot, and confirmed deletion. If any of those are missing, you are not ready to write.

Let’s imagine a student implementation. They store `selectedMove` globally, `selectedPokemonIndex` globally, and `oldMoveIndex` in task data. They enter the deletion confirmation prompt. The player cancels. The code returns to the new-move list, but it forgets to clear `oldMoveIndex`. Later the player chooses a different new move and confirms. If the code accidentally reuses the stale `oldMoveIndex`, it may replace the wrong slot.

This is a classic state machine bug. The issue is not that global variables are always evil, or that task data is always perfect. The issue is that each state must define which pieces of data are meaningful. A stale selection from a previous branch should not silently become valid input to a later commit.

So when we say “without corrupting party data,” we are also talking about temporary data hygiene. Temporary intent must be either valid for the current state or ignored. A clean implementation makes invalid states hard to reach and stale values hard to use.

Now, look again at the FireRed source framing. `pokefirered/src/learn_move.c`, `chunk_af3598df6f092723`, presents the move relearner state machine overview, and `chunk_e820ee15ae8caa6d` walks through confirmation and replacement. Those chunks are valuable because they show the architecture at the level we care about this week. They are not just showing a list of moves. They are showing a UI-mediated transaction.

That word, transaction, is useful, though we should not over-modernize the GBA code. In database terms, a transaction is a unit of work that should either complete correctly or not take effect. FireRed is not using a database transaction system here. But architecturally, the menu flow behaves transactionally. The player’s intent is staged. The operation can be cancelled. The durable write is delayed. The caller is notified. The UI returns.

This is also why the summary screen matters as a reader. In `pokefirered/src/pokemon_summary_screen.c`, `chunk_e8e17a42bbf17d46`, the include graph tells us the summary screen depends on Pokémon data, task and menu helpers, party menu concepts, battle constants, and move constants. When you open the summary after relearning a move, you are not looking at a separate copy that belongs to the relearner. You are seeing another UI surface interpret the same underlying party data. That is exactly what we want. But it means the writer has to be correct.

Let’s do one more check for understanding before we move deeper. I’ll describe three possible implementation strategies, and I want you to decide which one is safest.

Strategy one: immediately overwrite the first move slot when the player selects a new move, then restore it if they cancel later.

Strategy two: store the new move as a candidate, collect all needed confirmation, and write only after the final confirmation state.

Strategy three: write the new move into the summary screen display buffer, then later synchronize the party data if the player confirms.

For this architecture, strategy two is the safe one. Strategy one can work only if your restoration path is perfect, and restoration paths are exactly where cancellation bugs hide. Strategy three confuses presentation with source of truth. Strategy two respects the state machine: candidate data first, durable mutation later.

Now, we are going to keep FireRed as the main source, but I want to use Showdown one more time as a contrast. In Showdown, battle behavior often lives in a simulator that can be driven by tests. The packet’s mention of `pokemon-showdown/sim/DEX.md`, `pokemon-showdown/sim/README.md`, and `pokemon-showdown/test` points to a world where you can express rules, load data through a Dex, and run deterministic assertions. That is excellent for regression testing battle outcomes and data hooks. FireRed’s move relearner, however, sits closer to user interaction and persistent party state. A good FireRed-inspired test would not only ask “does this move exist in the learnset?” It would ask, “after this exact sequence of menu inputs, which move slots changed, which did not, and what callback or script result was produced?”

That is the level of testing sophistication I expect from you now. Earlier in the course, it was enough to identify the relevant files and structs. That was checked in week 18. By week 19, you need to trace behavior across state transitions. You should be able to say, “This branch cannot write yet,” or “This cancel path must preserve the party record,” or “This return callback must observe failure.” That is the difference between recognizing architecture and reasoning with it.

So, as we move into the next part of the lecture, keep this working model in mind. A move replacement menu has readers, candidates, confirmations, commit points, and return contracts. The danger is not the assignment statement by itself. The danger is reaching that assignment statement from the wrong state, with stale intent, or before the player has actually committed to the replacement.

Now, let’s put that model under pressure.

Imagine the player has selected a Pokémon at the move relearner. The menu has shown a list of eligible moves. The player highlights Flamethrower, presses A, and the Pokémon already knows four moves. At that moment, nothing about the party record should change. Let me say that again, because this is the central discipline of today’s lecture: selecting a candidate move is not the same as teaching it. The candidate is intent. The party struct is committed state.

On the board, I’m going to draw two columns. Left column: UI intent. Right column: party data. Under UI intent I’ll write selected move, selected row, scroll position, prompt state, confirmation result. Under party data I’ll write move slot zero through three, PP fields, maybe PP bonuses depending on the write helper involved, and the species or personality data that lets the menu know which Pokémon we are modifying. The key architectural rule is that most states in this flow are allowed to mutate the left column only. Very few states are allowed to mutate the right column.

This is exactly why the move relearner is a useful week 19 example. It is not just a menu. It is a menu that makes a persistent creature-data edit after several reversible UI decisions. In pokefirered/src/learn_move.c, chunk_af3598df6f092723, the source itself frames the feature as a “Move relearner state machine,” and it places the initialization callback, list menu scroll tasks, and named menu states into one behavioral sequence. That is important evidence. The code is not organized as a single blocking procedure where we ask a question, wait, ask another question, wait, and then write. It is organized around callback-driven and task-driven progress, which means every frame must be safe.

Now, quick check for understanding. If the player has merely highlighted Flamethrower in the relearn list, which column changes? UI intent. Good. If the player confirms, “Teach Flamethrower?” but the Pokémon has an empty move slot, when can the party data change? Only after the confirmation branch establishes that the player really chose to teach the move and the state machine reaches the write path for the empty slot. If the Pokémon has no empty slots, what has to happen first? We need a second decision: which old move is being forgotten, and then usually a confirmation around that deletion. That second decision is where many corruptions happen in student implementations.

Let’s make the bug concrete. Suppose you implement your lab menu with a variable called pendingMove and a variable called oldMoveIndex. The player chooses Flamethrower. You set pendingMove to Flamethrower. Then the player is taken to the old move selection screen. They highlight Tackle. You set oldMoveIndex to zero. Then the player presses B. What should the party data contain after that cancel? The original four moves. No Flamethrower. No missing Tackle. The oldMoveIndex can be stale after cancellation if you want, though I would prefer resetting it, but the actual party record must not be affected.

Here is the subtle version. The player highlights Tackle, presses A, reaches “Forget Tackle?” and then presses B. That cancel is different from the earlier cancel. Earlier cancel means “I do not want to choose an old move.” Later cancel means “I chose an old move, but I am not committing to deleting it.” In both cases, the party data is preserved, but the next state may differ. One cancel may return to the candidate move list. Another may return to the “which move?” prompt. FireRed’s state names make this kind of distinction visible. In pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d, the flow separates teach confirmation, trying-to-learn prompt, confirm-delete-old-move, which-move prompt, stop-teaching text, and return handling. That separation is not cosmetic. It protects the commit point.

So, if you are building the workshop version, I want you to resist the temptation to treat “replacement” as one function named replaceMove. That function may exist internally, but architecturally it is only the final commit operation. The player-facing system is a state machine whose states are closer to list setup, candidate selection, teach confirmation, old move selection, deletion confirmation, data write, result message, and callback return. Notice how that list mirrors the lab prompt for this week. The lab is not asking you to build a menu because menus are fun. It is asking you to model reversible intent separately from persistent writes.

Let’s connect this to the summary screen for a moment. The summary screen mostly reads. In pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, the file includes Pokémon data, moves constants, party menu definitions, battle references, menu helpers, and task infrastructure. For our purposes today, the important architectural point is not every individual function in that file. It is that the summary screen sits near many systems but is primarily a viewer and navigator. It can show moves. It can respond to menu input. It can transition back. But when we compare it to move replacement, the write risk is much lower unless a mode explicitly allows mutation. This is the distinction between a read surface and an edit surface.

Pause there. Why do I care so much about read surfaces versus edit surfaces? Because advanced architecture is often about assigning danger to the right boundary. A summary screen that reads move names can be called from many contexts: party menu, battle-adjacent views, trade-adjacent views, maybe help or inspection flows. A move replacement screen cannot be casually reused unless its return contract and commit behavior are precise. It is editing persistent party data. That means the callback back to the overworld or party flow must know whether the operation succeeded, failed, or was canceled.

This is where the global script variables from prior weeks matter, but I will not reteach them. You have already seen item-use dispatch and field-use behavior. Think of those as prior foundation. A field item use may set a variable or flag so the script can continue differently. A move relearner has a similar contract problem. After the UI finishes, some caller needs to know whether a move was actually taught. In chunk_e820ee15ae8caa6d, the excerpt explicitly says that if the move can be taught into an empty slot, VAR_0x8004 is set to TRUE before going to the fanfare text path. That is not merely a random variable assignment. It is part of the return contract between a UI state machine and the script or caller that initiated it.

Let me write that on the board: “Return value is part of the architecture.” Not just function return value. In a Game Boy Advance codebase, that return value may be a script variable, a global state field, or a task data slot. The important thing is the caller observes the correct result. If the player cancels, the return result cannot look like success. If the player confirms but the write fails, it cannot look like success. If the player teaches into an empty slot, it should not force the old-move deletion path. Those are architecture-level invariants.

Now let’s use Pokémon Showdown as our comparison point, but carefully. Showdown is not our primary architecture source for this course. FireRed is. FireRed is the constrained, callback-driven, party-data-owning game architecture we are tracing. Showdown is useful because it gives us a different expression of the same idea: data definitions are separated from runtime battle behavior, and tests can lock down outcomes. In the research packet, the recurring Showdown modules include pokemon-showdown/data, pokemon-showdown/sim, and pokemon-showdown/test. Those are the comparison domains: Dex and mods, simulator turns, and regression testing.

For example, in Showdown, you often look at data and hooks through the Dex and mod system. A move can have callbacks or generation-specific behavior. The packet points us to pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, where damage calculation logic checks things like immunities, OHKO moves, damage callbacks, level-based damage, and fixed move damage. The details are battle simulation details, not move-relearner menu details. But architecturally, notice the shared lesson: the system does not treat “move” as only a number in a slot. A move becomes behavior when it is interpreted by the correct runtime context.

In FireRed’s move replacement menu, we are not resolving damage. We are deciding whether a move ID should enter a creature’s persistent move slots. In Showdown’s simulator, a move ID or move object is interpreted during a battle turn by sim logic, with hooks and generation mods shaping behavior. The comparison helps because it prevents a shallow mental model. A move is not merely data. A move is data plus the runtime that consumes it. In week 19, the consuming runtime is a menu state machine. In battle architecture, the consuming runtime is the battle engine or simulator turn executor.

Here is a concrete comparison. In FireRed, if I corrupt a party Pokémon’s move array during cancellation, the visible symptom may appear later in battle, summary, link trade, or save reload. The menu bug escapes its original screen. In Showdown, if a modded move callback is wrong, the visible symptom appears during a simulated battle turn, and a regression test can often isolate the scenario. That is why the research packet flags pokemon-showdown/test as a recurring module around assertions, battles, moves, species, and common test helpers. Again, we are not replacing FireRed with Showdown. We are borrowing a testing mindset: construct a narrow scenario, perform the turn or UI sequence, assert the resulting state.

Now, what would a FireRed-style regression test look like conceptually, even if your lab harness is simpler than the original engine? We would create a party member with four known moves. Let’s say Tackle, Growl, Ember, and Smokescreen. We would enter the move-replacement menu with Flamethrower as the candidate. We would simulate the player confirming the teach prompt, selecting Tackle as the old move, canceling at the final deletion confirmation, and then returning. The assertion is that the party member still has Tackle, Growl, Ember, and Smokescreen, and the result flag says not taught. Then we run the success path: same setup, but confirm deletion. Now the assertion is Flamethrower, Growl, Ember, Smokescreen, with the success flag set.

Let’s check that. What are the two things we assert? Party data and return result. Exactly. Do not only assert the visible message. A visible message can be right while the underlying data is wrong. Do not only assert the data. The data can be preserved while the caller receives the wrong success flag. Advanced UI mutation tests need both.

This is also why RunTasks matters from the earlier lecture. In pokefirered/src/task.c, chunk_3ffebc0341225936, RunTasks is the per-frame dispatcher we have been using as the conceptual foundation for these UI flows. A task-backed state machine does not get to hide behind a single call stack. Its state lives across frames. That means stale task data is a real risk. If task data says “selected old move index equals zero” after a cancel, and later a different candidate move reuses the same task without resetting that field, the wrong move may be overwritten. That is not a theoretical concern. That is the exact class of bug you should be hunting for in your lab implementation.

The counterpart is task creation. In pokefirered/src/task.c, chunk_cb0bb91a147bfb59, CreateTask is part of the infrastructure that allocates task state and gives the system a task ID. We covered task IDs before, so I will not repeat the mechanics. Today’s point is narrower: once you allocate a task to own a menu flow, that task owns temporary intent. It should not write permanent party data until the state machine reaches the commit state. And when the flow returns, that task’s temporary intent should not leak into the next invocation.

Let me give you a failure pattern I see often in advanced student code. The menu draws the old moves directly from the party struct, which is fine. But when the player selects the old move, the code immediately writes the new move into that slot, then asks, “Forget this move?” The student thinks, “If they cancel, I’ll just write the old move back.” That sounds workable until you include PP, PP Ups, move ordering, animations, sound fanfare, script result flags, and interruption paths. If any one of those restoration steps is incomplete, you have created corruption. The safer architecture is to preserve old data until confirmation. The old move selection should store an index, not perform replacement.

The phrase I want you to remember is “commit late.” Read early, preview freely, commit late. This is the same discipline you saw with bag item use, but now it is more delicate because the thing being edited is a multi-field creature record rather than a pocket quantity. A bag quantity decrement must happen at the correct point. A move replacement must update the correct slot and any associated move metadata at the correct point. Same architectural instinct, higher stakes.

Now, let’s connect week 19 to the whole course plan. In the first part of the course, we decomposed the RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. At that stage, you were learning the map of the building. By week 18, you were asked to identify files, structs, functions, constants, data tables, and state variables implementing major systems. That was a source-navigation milestone. Week 19 is different. This week asks you to reason through transitions. You are no longer just pointing at pokefirered/src/learn_move.c and saying, “That is where the move relearner lives.” You are saying, “This state reads candidate data. This state waits for input. This branch can cancel. This branch sets a success variable. This later state performs the write.”

That shift is the bridge into later architecture work. Save systems care about committed state. Link systems care about synchronized state. Battle systems care about move slots being valid before execution. Event scripts care about return flags. So a move replacement menu may look like a small UI topic, but it is actually a rehearsal for the whole runtime discipline of the game. Any time a player-facing interface edits durable game state, the same questions return. Who owns temporary intent? Who owns durable data? When is the commit point? What does cancellation mean? What does the caller observe?

Let’s do a quick oral exercise. I will describe a state, and you tell me whether it may write party data. “Displaying the list of relearnable moves.” No. “Moving the cursor up and down in that list.” No. “Pressing A on a candidate move.” Usually no, not yet; it records intent and asks for confirmation. “Confirming the teach prompt when there is an empty move slot.” Yes, this can enter the write path. “Confirming the teach prompt when there are no empty move slots.” Not the replacement write yet; it moves toward old-move selection. “Selecting an old move.” No, selection alone records an index. “Confirming deletion of the selected old move.” Yes, that is the commit branch. “Printing ‘Stop trying to teach the move?’” No, that is still prompt flow. “Returning to the caller after cancellation.” No write, but it must report cancellation correctly.

That exercise is simple, but it is the backbone of safe implementation.

Now I want to add one more layer: source of truth for move eligibility. The move relearner list is not supposed to invent moves. It presents candidates derived from species and learnset rules. In FireRed, this is tied into the game’s data tables and helper logic. In Showdown, the comparison would be learnsets in pokemon-showdown/data/learnsets.ts and generation-specific mod learnsets such as pokemon-showdown/data/mods/gen9dlc1/learnsets.ts, both called out in the research packet as high-signal recurring sources. I am mentioning those not because we are implementing Showdown learnsets today, but because they illustrate a modern data-driven contrast: Showdown’s Dex/mod layering lets different rule environments interpret species and moves differently. FireRed’s move relearner is tied to the game’s compiled data and menu flow. Same broad concept, different runtime architecture.

So if someone asks, “Could we port Showdown’s learnset model into a FireRed-style engine?” the architectural answer is, “Only if we also decide how the menu state machine queries that model, how the battle engine consumes the resulting move IDs, how save data stores them, and how tests cover the compatibility boundary.” Data cannot be dropped into a game architecture as if it were inert. It has callers. It has invariants. It has storage constraints.

Let’s return to the board. I’m drawing a horizontal line now. Above the line: reversible UI states. Below the line: irreversible or durable effects. Above the line we have list navigation, prompt text, cursor movement, selected candidate, selected old slot. Below the line we have SetMonData or the equivalent write helper, PP initialization, success flag, fanfare path, return callback. The design goal is to cross that line once, deliberately, from a state whose only job is to commit.

And, yes, in a real codebase the line may not look as clean as my board drawing. Older C code often combines rendering, input, and state mutation more tightly than we would in a modern application. But the conceptual line still exists. You can still inspect a branch and ask, “Is this before or after commitment?” That question is how you read this code like an architect instead of like a tourist.

Before we move on, one final check. Why is it not enough to inspect the final assignment that writes the move slot? Because corruption may be caused by reaching that assignment from the wrong state, with stale selected data, after a cancel path, or with a result flag that lies to the caller. Exactly. The assignment is the visible blade, but the state machine is the hand that moves it.

In the next segment, we are going to walk the replacement flow as if we were implementing it from scratch in the lab: first the data we need to carry in task state, then the exact confirm and cancel branches, then the tests that prove the party record survives every non-commit path.

So, let’s build this as if we were at the lab bench now, not merely reading the finished FireRed implementation. I want you to imagine we have a party Pokémon in memory, a selected new move from the relearner’s list, a visible menu cursor, and a nervous player who may press A, B, A, B, change their mind three times, and then walk away. Our design problem is not “how do we assign a move.” That is a one-line operation. Our design problem is “how do we make sure that the only path to assignment is the path where the player has consciously committed, and every other path leaves the party record exactly as it was.”

Put that on the board: the write is small, the permission to write is large.

Now, what task state do we need to carry? We need the party index, because the menu is not editing an abstract species; it is editing a concrete party member. We need the candidate move, because the list item the player selected must survive through the confirmation prompt. We need the selected old move slot only after the player is forced into replacement. We need enough scroll and cursor state to return to a stable menu when the player cancels. And we need an outcome signal, because the caller often needs to know whether the operation actually taught a move or merely visited the menu.

That last point is easy to underrate. In FireRed’s move relearner flow, the source comments in pokefirered/src/learn_move.c, chunk_af3598df6f092723, explicitly frame this as a MoveRelearnerStateMachine rather than a direct edit routine. The machine begins with setup states, list menu states, input states, and later teaching states. Then chunk_e820ee15ae8caa6d gives us the teach confirmation and replacement flow: confirm the new move, detect whether there is an empty move slot, otherwise print the “trying to learn” prompt, ask whether to delete an old move, select the old move, confirm forgetting, and only then perform the replacement. That sequence is the architecture. The text boxes and menus are visible, but the real invariant is that the party data write sits downstream from several state commitments.

Let me make this concrete. Suppose our party Pokémon knows Tackle, Growl, Quick Attack, and Bite. The relearner list offers Ember. The player selects Ember. At that moment, nothing about the four known moves should change. We have only captured an intention: “Ember is the candidate.” If the game asks, “Teach Ember?” and the player says no, we return to the list or leave the menu, depending on the exact branch, but we do not touch the Pokémon’s move array. If the player says yes, the system checks for an empty slot. There is no empty slot in this example. So now the flow becomes, “This Pokémon is trying to learn Ember, but cannot learn more than four moves. Delete an old move?” Again, if the player cancels here, no party data write. The candidate move is still merely a candidate. If the player confirms, we open the old move selection screen. The player highlights Growl. Still no write. Highlighting is not consent to deletion. Then the game asks whether to forget Growl. If the player cancels at that final prompt, still no write. Only after that final confirmation do we replace slot one, assuming zero-based slot one, with Ember.

Check your understanding here. How many player-facing confirmations are there in the full-slot path before the destructive write? Yes, at least three meaningful commitments: teach the candidate move, agree to consider deleting an old move, and confirm the specific old move to forget. The cursor selection is not itself the destructive action. That distinction is what protects the party.

Now, notice how this connects to Monday’s lecture on menus as task-backed state machines. We already established that FireRed’s shell uses callbacks and tasks to break interface behavior into frame-by-frame pieces. The task system is not just a scheduler convenience; it is what lets the menu wait for input, wait for text printing, wait for fanfare, or wait for another screen to return without blocking the whole game. The continuity packet points us back to pokefirered/src/task.c, chunk_cb0bb91a147bfb59 for CreateTask and chunk_3ffebc0341225936 for RunTasks. CreateTask gives the flow a place to store ongoing state; RunTasks makes that state advance once per frame. So when you see a move relearner “state machine,” do not picture a while loop that takes over the CPU until the move is learned. Picture a task whose function is repeatedly called, each time looking at its current state, the input flags, and any sub-menu results.

Let me draw a small state diagram verbally. At the left, list setup. Arrow to list input. From list input, A goes to teach confirmation, B goes to exit or prior callback. From teach confirmation, no goes back or exits without write; yes goes to either empty-slot commit or full-slot replacement. Empty slot is simple: find the first empty move slot, write candidate move, set result true, play fanfare or print success text. Full-slot replacement is longer: trying-to-learn prompt, confirm delete, choose old move, confirm forget, commit replacement, set result true, return. Every cancel arrow before commit goes to a non-writing state.

Now pause on “set result true.” In chunk_e820ee15ae8caa6d, the excerpt notes that if confirmed and the Pokémon had an empty move slot, the flow sets VAR_0x8004 to TRUE and goes to a print-text-then-fanfare state. That is important because the write and the result flag have to agree. A common student bug in the lab is to set the “learned move” flag when the player confirms the candidate move, before old-move deletion is completed. That means a caller can believe the Pokémon learned Ember even though the player later cancelled the deletion of Growl. That is not just a UI bug. That is a contract violation between the menu and the script or caller that launched it.

And this is where FireRed’s architecture gives us a pattern we should respect. The move menu is not the owner of the whole game. It is a temporary interactive subsystem invoked by a larger flow. It must return control cleanly. It must report honestly. It must leave global state in a coherent condition. That is the same lesson we have seen with item-use dispatch and with bag menus, but I am not going to re-teach those systems here. I only want you to carry forward the analogy: bag item use also separates selection from effect, effect from confirmation, and confirmation from mutation. Move replacement has a sharper edge because the target data is a party Pokémon’s moveset, and the player is deleting an existing combat capability.

Let’s bring the summary screen into the picture, because this is a good source of architecture evidence. The summary screen includes move display and move-related menu behavior, and the packet points us to pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46. The include list alone is revealing: pokemon.h, pokemon_summary_screen.h, task.h, menu helpers, party menu constants, battle constants, moves constants, dynamic placeholder text utilities. That tells us the summary screen is a reader and presenter of Pokémon data, with some controlled interaction paths, not a casual editor that can rewrite the party record whenever text changes on screen. For our week 19 theme, that distinction matters. The summary screen may show the four moves. The move relearner may eventually alter one of those moves. But display state and mutation state must be separated. If your lab implementation uses the visible list of moves as the source of truth, you have inverted the architecture.

Here is the professional version of the invariant: UI state may cache a representation of party data, but the party struct remains the authoritative record, and writes back to that record occur only in named commit states. Say that back to yourself in simpler words: menus can remember what they are showing, but they cannot become the Pokémon.

Now, let’s compare this with Pokémon Showdown, carefully, because this is where some students overcorrect. Showdown is useful as a secondary comparison, especially for battle data hooks, Dex and mods, simulator turns, and regression testing. It is not our primary architecture source for FireRed’s party menu. FireRed is an embedded GBA game with callbacks, tasks, script variables, and packed runtime structs. Showdown is a TypeScript simulator with data modules, battle objects, and testable deterministic turns. Those are different machines. But the comparison is valuable because it teaches us where mutation boundaries live.

In Showdown, move data can have hook-like behavior during simulation. The research packet’s example from pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, shows damage calculation logic asking whether a move has a damage callback, fixed damage, level damage, immunity checks, and so on. That is a battle-time hook system. The simulator asks, “Given this active move and these Pokémon, how should this move behave in this turn?” FireRed’s move relearner is asking a different question: “Should the persistent party record be changed before we return to the field script?” Those are not interchangeable. But both systems demonstrate the same broad architectural discipline: data and hooks are not random edits. They execute at designated moments inside a larger state machine.

So if you were testing Showdown, you might write a regression test that starts a battle, chooses a move, runs a turn, and asserts that the expected HP or volatile state changed. The packet flags pokemon-showdown/test as a recurring module around assertions, battles, moves, and species, and that is exactly the right mental model for simulator correctness. For FireRed move replacement, the analogous regression test is not a turn simulation. It is a menu-flow test or a structured manual test: start with a known party Pokémon, walk through every confirm and cancel branch, and inspect the move slots and return flag after each branch.

Let’s make a lab-grade test matrix out loud. Initial Pokémon knows Tackle, Growl, Quick Attack, Bite. Candidate is Ember. Test one: open the relearner list and press B. Expected moves: unchanged. Expected result flag: false. Test two: select Ember, decline “Teach Ember?” Expected moves: unchanged. Expected result flag: false. Test three: accept “Teach Ember?”, then decline deletion of an old move. Expected moves: unchanged. Expected result flag: false. Test four: accept deletion, highlight Growl, then decline “Forget Growl?” Expected moves: unchanged. Expected result flag: false. Test five: accept all prompts and forget Growl. Expected moves: Tackle, Ember, Quick Attack, Bite, or whatever exact slot was selected. Expected result flag: true. Test six: use a Pokémon with an empty fourth slot, select Ember, accept the first teach prompt. Expected moves: Ember fills the empty slot. Expected result flag: true. No old-move deletion prompt should be required.

Now, check for understanding. In which test is it acceptable for the candidate move to be stored in task data but not in the party struct? In all of the pre-commit tests. Exactly. Candidate storage is not corruption. Corruption happens when temporary intent leaks into persistent party state before commitment.

This is also why I keep using the word “commit.” It is not because FireRed has a database transaction system. It does not. It has C structs and global state and callbacks. But as architects, we can still impose a transaction-like mental model: gather intent, validate path, commit once, report result. If the player cancels, discard intent. If another menu returns, resume with explicit state. If the old move slot is invalid, reject before write. If the candidate move is invalid, never enter the confirm flow. You do not need SQL to think transactionally.

Let’s look at the order of data dependencies. First, party index must be valid. Without that, nothing else is safe. Second, candidate move must be a legal move from the generated or computed relearn list. Third, replacement slot must be in the range zero through three if the Pokémon already has four moves. Fourth, confirmation state must indicate the player committed to that specific replacement. Fifth, the write updates the move slot and any related PP information consistently. I am mentioning PP because moves are not just names. A party Pokémon’s move record has associated usage state. If your implementation replaces the move ID but leaves stale PP in a way that no local convention expects, you have produced a subtler kind of corruption. The visible menu might look correct for a second, but the next battle or summary read may expose the mismatch.

And this is where our week 19 material connects to the whole course. Back in the early weeks, we traced data table to runtime global to callback or script to visible behavior. That chain is still the course thesis. In week 17 and 18, your homework and quiz work asked you to identify files, structs, functions, constants, data tables, and state variables across major systems. Now week 19 asks you to do something harder: not just identify them, but protect their boundaries while the player is interacting with them. In a creature-collector RPG, almost every meaningful menu is a boundary-crossing tool. The bag crosses from item data into party effects. The summary screen crosses from party data into readable UI. The move relearner crosses from list selection into persistent creature mutation. Architecture is what keeps those crossings orderly.

Now let me give you a realistic implementation mistake. A student writes a function called TeachSelectedMove, and they call it as soon as the player presses A on the relearner list. Then, if the player later cancels the “forget old move” prompt, they try to restore the old move from a temporary backup. That feels plausible, but it is fragile. You have turned every cancel path into rollback logic. If you miss one path, if a callback returns unexpectedly, if the backup is stale, or if PP was modified inconsistently, you corrupt the record. The safer pattern is defer mutation. Do not write Ember into the Pokémon and then undo it. Keep Ember in task state until the commit state. The best rollback is no write.

Another mistake: using the visible cursor row as the replacement slot after the list has been redrawn or after a summary sub-screen returns. Cursor row and move slot can coincide, but they are not the same concept. The cursor is UI state. The move slot is model index. If the UI list includes a cancel row, a disabled row, a blank row, or a reordered presentation, cursor index can become an unsafe proxy. In the lab, I want you to name the replacement slot explicitly once the player chooses an old move. Do not rediscover it later from whatever happens to be highlighted.

Let’s do a quick board cue. I am writing three columns: “temporary,” “validated,” and “persistent.” Under temporary, I put candidate move, cursor row, scroll position, prompt result. Under validated, I put party index, candidate move from legal list, old move slot zero to three, final yes response. Under persistent, I put party Pokémon move slots, PP values, and result flag returned to the caller. The design goal is not to eliminate temporary state. The design goal is to prevent temporary state from masquerading as persistent state.

Now, where does Pokémon Showdown help us here? It helps us think about regression tests and controlled hook points. Showdown’s Dex and mods structure, especially in data modules such as pokemon-showdown/data/learnsets.ts and the mod learnset files flagged by the packet, is very explicit about variant data by generation or mod. The FireRed relearner is also data-driven in spirit, because the offered moves come from species and level-up learn data, but FireRed’s runtime path is embedded in C menu state. So the comparison is this: Showdown tends to make “which data applies?” highly explicit through Dex/mod selection, while FireRed makes “when does the UI write?” highly explicit through callback and task states. Both are architectural answers to the same danger: using the wrong data at the wrong time.

If you remember nothing else from this segment, remember this: a move replacement bug is usually not caused by ignorance of how to store an integer move ID. Everyone can store an integer. The bug is caused by letting a provisional decision become durable too early, or by letting a cancellation path report success. That is why the source evidence matters. The comments in pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d, describe cancellation branches separately from confirmation branches. They are not decorative comments. They are an architectural map of the integrity boundary.

Let me ask a final check before we move to the next part. Suppose the player selects Ember, confirms teaching, chooses to delete an old move, highlights Growl, and then cancels at the last confirmation. What should the summary screen show immediately afterward? It should show the original four moves: Tackle, Growl, Quick Attack, Bite. What should the result flag say? False. What should happen to the task’s temporary candidate move? It can be discarded as the menu exits or returns. And what should never be necessary? Repairing the party record after an early write, because we should not have written it yet.

Good. In the next stretch, we will turn that into a concrete lab design. We will name the states, assign the task fields, and walk through the exact branch table for list setup, teach confirmation, old-move selection, deletion confirmation, commit, and callback return.

All right, so let’s turn that last cancellation case into the lab design. I’m going to put the state names on the board first, because if we name the states carefully, the implementation almost writes itself.

We begin with list setup. Then teach confirmation. Then old-move selection. Then deletion confirmation. Then write data. Then return callback. That is the spine of the lab. Notice what is not in the spine: there is no “write candidate move early and maybe undo later” state. That absence is architectural, not cosmetic.

The FireRed evidence for this style is very direct. In pokefirered/src/learn_move.c, chunk_af3598df6f092723, the move relearner is explicitly described as a state machine, and it begins with callback setup and list menu preparation before the player ever commits to a data mutation. Then chunk_e820ee15ae8caa6d gives us the confirmation and replacement flow: confirm teaching, check whether there is an empty move slot, otherwise prompt that the Pokémon is trying to learn a move, ask whether to delete an old move, ask which move, confirm forgetting, and only then proceed to the replacement result. So our lab is not inventing a pattern out of nowhere. We are extracting the pattern that the production game is already showing us.

Now, let’s assign task fields. Remember from earlier in the week that tasks are small stateful records. We are not re-teaching the task system today, but we do need to carry that idea forward. CreateTask, in pokefirered/src/task.c, chunk_cb0bb91a147bfb59, gives you a task slot and an id. RunTasks, in chunk_3ffebc0341225936, calls task functions every frame. That means every value you put in the task data is a value that must remain coherent across frames, button presses, redraws, and cancellations.

So, on the board, I’ll write: task state, party index, candidate move, selected old move slot, list cursor, scroll offset, result flag, and return callback. If you need a temporary text state or fanfare delay, that can also live in the task, but it is not the conceptual core. The conceptual core is candidate versus committed data.

Let’s walk a concrete example. Charmander has Tackle, Growl, Quick Attack, and Bite. The move relearner offers Ember. The player highlights Ember. At this point, what field changes? The selected list cursor changes. Maybe the candidate move field becomes Ember if your menu caches the highlighted choice. But the Pokémon’s actual move array does not change. Say that to yourself very plainly: list navigation is not party mutation.

The player presses A. We enter teach confirmation. The prompt says, effectively, “Teach Ember?” If the player cancels here, where do we go? Back to list setup or back to the previous menu state, depending on your exact UX. What does the party contain? Still Tackle, Growl, Quick Attack, Bite. What is the result flag? False. What does the summary screen read? The old moves. And that brings in pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46. The summary screen is another consumer of Pokémon data and move constants. It is not there to validate your half-finished menu transaction. It reads the party-facing truth. If your menu leaks an early write, the summary screen exposes it.

Now the player confirms teaching Ember. We inspect the Pokémon’s move slots. If there is an empty slot, the transaction is simple. The write state can put Ember into that empty slot, set the result flag true, and return. But our lab is focused on the harder case: no empty slot. Four occupied move slots means we move to the “trying to learn” prompt, then the “delete old move?” confirmation.

Here is the important state-machine discipline. The player has confirmed interest in Ember, but still has not selected the old move to remove. So candidateMove is Ember, but selectedOldMoveSlot is invalid. Maybe you use minus one. Maybe you use four. Maybe you use a named constant. But it must be visibly invalid in your logic. If your deletion confirmation state can run while selectedOldMoveSlot is invalid, that is a bug. Advanced students should get in the habit of designing states so illegal progress is hard to express.

Now the player says yes, I am willing to delete an old move. We enter old-move selection. This is where the summary screen idea reappears in a subtle way. A move selection screen often displays current moves, power, accuracy, type, description, or PP. Those are reads. They can be rich reads. They can call helper functions. They can update windows every frame. But they are still reads. The displayed move list should come from the current party record, while the pending candidate move is shown separately as the thing being learned. Do not splice Ember visually into the actual move list unless your UI makes it unmistakably a preview.

Let’s say the player highlights Growl. The selectedOldMoveSlot becomes slot one, if we are zero-indexing. Still no mutation. The party still has Growl. Then the final prompt appears: “Forget Growl and learn Ember?” This is the last gate. If the player cancels here, you clear selectedOldMoveSlot or ignore it, return to old-move selection or stop teaching depending on your intended branch, and keep the party untouched. That was the exact case we ended on a moment ago.

Check for understanding. If the candidate move is Ember and selectedOldMoveSlot is Growl, has Growl been deleted? No. If the final confirmation window is on screen, has Ember been learned? No. If the player turns off the game during the confirmation prompt, what should the saved party record contain, assuming no save occurred inside this menu? It should contain the old four moves. The menu state can be lost; the party should not be half-mutated.

Now, let’s bring in Pokémon Showdown carefully. Showdown is useful here, but not as the primary architecture source. FireRed is our source for a GBA party menu backed by callbacks, tasks, and in-place Pokémon data. Showdown is a secondary comparison for how a different engine separates data, battle hooks, simulator turns, and tests. In the research packet, the recurring high-signal modules are pokemon-showdown/data, pokemon-showdown/sim, and pokemon-showdown/test. That is exactly the comparison we want: not “copy Showdown into FireRed,” but “observe how another mature Pokémon engine protects boundaries.”

For example, Showdown’s data and mods structure, including files such as pokemon-showdown/data/learnsets.ts and pokemon-showdown/data/mods/gen9dlc1/learnsets.ts from the packet, shows a very different representation of learnability. It is not a GBA menu task writing to a party struct. It is data used by tools, validation, and simulation. In FireRed, the move relearner menu is a UI-driven path to a concrete party data write. In Showdown, learnsets and Dex/mods answer questions like “is this species allowed to have this move in this format?” or “which generation’s rules are active?” That is a data hook and rule-modelling problem, not the same as a handheld summary screen commit problem.

But the comparison is valuable because it sharpens our boundary. Showdown’s simulator side has explicit battle objects, move objects, event hooks, and modded behavior. The packet gives pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, where move damage logic runs through an active move object, immunity checks, OHKO handling, damage callbacks, fixed damage, and other battle calculations. That tells us something important: in a simulator, hooks are often part of the turn resolution pipeline. In FireRed’s move replacement menu, the hook we care about is not a damage callback. It is a UI state transition that eventually calls a Pokémon data write. Different layer, different risk.

So if a student says, “Could we regression test this like Showdown tests battle turns?” my answer is yes, with translation. Showdown’s test module, especially pokemon-showdown/test as identified in the research packet, is valuable as a model of deterministic assertions. You set up a condition, perform a controlled action, and assert the resulting state. For our lab, the controlled action is not “turn one, use Thunderbolt.” It is “open replacement menu, choose Ember, choose Growl, cancel final confirmation.” The assertion is that the party move array remains unchanged and the result flag remains false.

Let me phrase that as a regression test scenario in spoken pseudocode. Given a Pokémon with moves Tackle, Growl, Quick Attack, Bite, and given candidate move Ember, when the user confirms teaching, chooses to replace Growl, and cancels at final confirmation, then the Pokémon’s moves equal the original four moves, candidateMove may be discarded, selectedOldMoveSlot may be reset, and resultFlag is false. That is the menu equivalent of a battle simulator regression test. It is deterministic. It names the precondition, the action path, and the postcondition.

Now, why am I spending time on this? Because week 19 is where several earlier course threads converge. From the item weeks, you already know that a menu can look like the owner of a behavior while actually being a facade over deeper systems. The Bag UI did not define what every item means. It selected, displayed, and dispatched. From the item-effect lectures, you know that field use and battle use can share item records while routing through different contexts. Now, with move replacement, the menu is not merely dispatching an effect. It is managing a small transaction that changes party data. That makes the state ordering more dangerous.

From the summary-screen work, you know that many screens are data readers. They render current state, and they may offer navigation or selection, but they are not automatically transaction managers. The move relearner borrows the visual language of move display, but it has a different responsibility: it must eventually mutate. That is why the confirmation states matter. The lab is not just “make a menu.” The lab is “make a menu whose reads and writes are separated so clearly that cancellation cannot corrupt the party.”

Let’s now make the branch table concrete. I’ll say it slowly as if I’m writing it on the board.

State list setup creates or refreshes the move list. It initializes the cursor. It displays candidate learnable moves. If the player cancels, return with false. If the player chooses a move, store candidateMove and advance to teach confirmation.

State teach confirmation displays the candidate move prompt. If the player cancels, return to list setup or exit, depending on the design. In either branch, no party write occurs. If the player confirms, inspect move slots. If an empty move slot exists, advance to write data with oldMoveSlot equal to the empty slot. If no empty slot exists, advance to the trying-to-learn prompt or directly to confirm deletion, depending on how faithfully you are modeling FireRed’s text sequence.

State confirm deletion asks whether the player wants to forget an old move. If no, enter the stop-teaching branch and return false. If yes, advance to old-move selection.

State old-move selection displays the current four moves. If the player cancels, go back to confirm deletion or stop teaching, depending on the user experience you choose. If the player chooses a slot, store selectedOldMoveSlot and advance to final deletion confirmation.

State final deletion confirmation asks whether to forget that specific move. If cancel, return to old-move selection with no write. If confirm, advance to write data.

State write data is the only state that mutates the Pokémon’s moves. It writes candidateMove into selectedOldMoveSlot or the empty slot, updates PP-related data if your implementation includes PP, sets resultFlag true, and advances to return callback.

State return callback restores the caller’s expected UI flow. In FireRed terms, this matters because callbacks and tasks are how control returns to the larger runtime shell. Again, CreateTask and RunTasks from pokefirered/src/task.c, chunks_cb0bb91a147bfb59 and_3ffebc0341225936, are the underlying mechanism that makes this sort of per-frame state progression possible.

One advanced point: the write state should be boring. If your write state contains lots of input handling, window creation, and branchy prompt logic, you waited too long to simplify. The write state should already know the party index, candidate move, target slot, and result value. It should not be asking philosophical questions about player intent. Player intent was resolved by the previous states.

Here is another check. In which state should PP be initialized for the newly learned move? In the write state. In which state should the old move description be displayed? Old-move selection or final confirmation. In which state should the summary screen become able to observe the new move? Only after write data has committed. And if the result flag is true, what must also be true? The party record must reflect a successful teaching. A true result flag with unchanged moves is just as suspicious as changed moves with a false result flag.

This is also where we connect forward to the overall course plan. We are near the end of the Items, Menus, and Party UI module, but the pattern is not going away. In later systems, especially battle and save, we will keep asking the same question: where is the authoritative state, and when does a visible interaction become a committed mutation? In battle, that question appears as turn order, volatile statuses, HP changes, move effects, and animation timing. In save, it appears as serialization boundaries. In link play, it appears as synchronization and rollback avoidance. Today’s move replacement menu is smaller, but it trains the same architectural reflex.

So for the lab, I want you to resist the easy implementation. The easy implementation is to update the Pokémon as soon as the player chooses the old move, then rely on final confirmation to keep going. That feels convenient because the UI immediately has the new state. But that convenience is a trap. The robust implementation keeps a candidate transaction in task data, renders previews from a mixture of committed data and pending choice, and commits once.

And yes, this is more ceremony than a tiny menu seems to deserve. But that ceremony is what makes the bug surface small. If a cancellation branch is wrong, it returns to the wrong state, but it does not corrupt party data. If a text box delay runs one frame too long, the party is still intact. If the player backs out at the final prompt, there is no repair operation. You have turned a dangerous UI flow into a controlled transaction.

Before we pause this stretch, let’s do one final verbal trace. The player enters the move relearner. The callback initializes the task. The task enters list setup. RunTasks calls it frame by frame. The player chooses Ember. The task stores Ember as candidateMove. The player confirms. The task sees no empty slot. The task asks whether to delete an old move. The player says yes. The task shows the current four committed moves. The player highlights Growl. The task stores slot one. The player confirms. Only now does write data replace slot one with Ember, set the result flag true, and return through the callback path. If the player cancels at any earlier confirmation, every branch returns without touching the party record.

That is the whole architectural lesson of the lab: menu states may be temporary, visual, and interruptible, but party data should change only at the commit boundary.

So, let’s widen the lens, but keep our feet planted in FireRed. Pokemon Showdown is useful here as a comparison, not because it teaches us how the Game Boy Advance code is structured, but because it makes a different architectural choice very visible. In FireRed, the move replacement flow is a UI-driven state machine guarding a party data mutation. In Showdown, much of the comparable safety comes from battle objects, Dex lookups, modded data tables, and tests around simulator behavior. Different runtime, different language, different goals, but the same architectural question keeps showing up: when is data merely being inspected or staged, and when is it actually committed?

If I were drawing this on the board, I would put two columns up. Left column: FireRed. Right column: Showdown. Under FireRed I would write callback, task, menu state, party struct, commit. Under Showdown I would write Dex, mod data, battle turn, hook, regression test. Now, please notice what I am not writing. I am not writing “Showdown replaces FireRed.” It does not. Our primary source remains FireRed, especially pokefirered/src/learn_move.c, chunk chunk_af3598df6f092723, where the move relearner is explicitly framed as a state machine, and pokefirered/src/learn_move.c, chunk chunk_e820ee15ae8caa6d, where the teach-confirm, delete-confirm, and replacement flow are laid out. Showdown helps us name some design pressure that is harder to see in the older C code because the C code is so tightly bound to callback timing, VRAM work, windows, and party memory.

Let’s make that concrete. Suppose the player wants to relearn Ember. In FireRed, Ember is not simply pushed into the Pokemon’s move list when the menu cursor lands on it. It is not written when the text box says “Teach Ember?” It is not written when the player first agrees that an old move should be forgotten. The system delays the actual mutation until the old move slot has been selected and deletion has been confirmed. That delayed write is the point. It means the menu can be noisy, animated, cancelable, and full of intermediate state without turning every intermediate state into persistent game state.

Now compare that to a battle simulator hook. In Pokemon Showdown, a move can have behavior that is calculated dynamically during battle. The research packet points us to pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk chunk_3480e574205d5d0f, where damage calculation consults move properties and callbacks such as damageCallback. That is not a move replacement menu. It is a battle calculation path. But architecturally, it asks a related question: are we reading data, deriving a temporary result, invoking a hook, or mutating long-lived state? A damage callback can compute a result for this one interaction. That does not mean the move definition in the Dex has changed. It does not mean the Pokemon permanently acquired a different move. It means the simulator has a structured place for a temporary behavior to occur during a turn.

Check your understanding here. If a FireRed menu highlight changes from Growl to Tail Whip, should the Pokemon’s party data change? No. If a Showdown damage callback returns a different damage number for a particular move interaction, should the global move table be rewritten? Also no. Those are both examples of temporary runtime decisions, even though they live in completely different architectures.

This is why I want you to be precise with the word “state.” State is not one thing. The cursor position is state. The current menu task’s state number is state. The selected candidate move is state. The party Pokemon’s move list is state. The result flag used by the script side is state. But those states do not have the same lifetime, authority, or risk. The cursor position can be discarded. The candidate move can be discarded. The party move list is player-owned save-relevant data. That last category deserves a commit boundary.

In FireRed, we have already seen the runtime shell that makes this style possible. CreateTask, in pokefirered/src/task.c, chunk chunk_cb0bb91a147bfb59, gives the game a task entry. RunTasks, in pokefirered/src/task.c, chunk chunk_3ffebc0341225936, dispatches those tasks frame by frame. Earlier in the course, that might have felt like engine plumbing. By week 19, it should feel more consequential. The task system is not just a scheduling convenience. It is one of the mechanisms that allows user interfaces to be broken into stable, resumable states. The player presses A, the task advances. The player presses B, the task retreats or exits. Text waits, fanfares wait, fades wait, and all of that can happen without pretending that the party data has changed before the player has truly committed.

Now let’s connect this to the summary screen. The summary screen is another important comparison inside FireRed itself. The packet gives us pokefirered/src/pokemon_summary_screen.c, chunk chunk_e8e17a42bbf17d46, and even from the include surface you can see how cross-cutting that screen is: pokemon data, task support, menu helpers, battle constants, move constants, party menu connections, and text utilities all meet there. But the summary screen is mostly a reader. It presents the Pokemon’s current known data. It may provide navigation and context, and in some modes it can be part of a larger workflow, but its architectural posture is different from the replacement commit. When you inspect a move on the summary page, you are not proposing a mutation. You are asking the UI to reflect the already committed model.

So if we draw a timeline for a move replacement lab, there are really three views of the same Pokemon. First, there is the committed party record before the interaction. Second, there is the staged UI state while the interaction is underway. Third, there is the committed party record after the final confirmation. A robust implementation keeps those three separable. A fragile implementation blurs them. The most common student bug in this lab is to write the new move into the first open-looking slot in the display array or the visible row data, then try to undo it if the player cancels. That is exactly backward. Cancellation should not require reconstructing the old truth. The old truth should remain untouched until commit.

Let me give you a concrete failure case. Imagine your replacement screen has a local array called visibleMoves. It is filled with the Pokemon’s current four moves: Tackle, Growl, Tail Whip, and Quick Attack. The player selects Ember as the relearned move. Your code immediately writes Ember over Growl in visibleMoves to show a preview. Then, because visibleMoves is actually a pointer or alias into the Pokemon’s real move storage, the party data has already changed. The player then presses B at “Stop trying to teach Ember?” Your UI says the teaching was canceled, but the Pokemon now knows Ember anyway. That is not a graphics bug. That is an ownership bug. Your display buffer was not a display buffer. It was the model.

A cleaner design stages the choice separately. candidateMove is Ember. replacementSlot is maybe one, maybe unset. confirmationState says whether the player has agreed. The display can render “Ember will replace Growl” without writing Ember into the Pokemon. That is just a preview. Only the write data state crosses the boundary. And in FireRed’s move relearner flow, that is the idea reflected by the confirm and replacement sequence in pokefirered/src/learn_move.c, chunk chunk_e820ee15ae8caa6d. The control path moves through prompts and branches before the data write outcome is finalized.

Now, a quick check. I’m going to ask it in a way that sounds simple, but it catches a real architectural mistake. If the player has selected Ember and selected Growl as the old move, but has not yet confirmed forgetting Growl, what should the source of truth say the Pokemon knows? It should still say Growl. The staged UI may say “about to replace Growl,” but the party record still says Growl. If your debugger inspects the party struct at that moment, Growl should still be there. If it is not, your commit boundary is too early.

Showdown gives us another useful comparison through mods and Dex data. In Showdown, different generations or formats can supply modified data while still running through a shared simulator architecture. The packet highlights pokemon-showdown/data and pokemon-showdown/data/mods as recurring modules, including learnsets in pokemon-showdown/data/learnsets.ts and generation-specific mod learnsets such as pokemon-showdown/data/mods/gen9dlc1/learnsets.ts. Again, this is not FireRed’s architecture. FireRed is not loading TypeScript mod overlays at runtime. But the comparison is valuable because Showdown separates canonical data lookup from battle execution. You ask the Dex what a species, move, or learnset means in a particular ruleset, and then the simulator operates with those resolved definitions.

In FireRed, the equivalent separation is less abstract and more static. Data tables are compiled in. Generated data and constants feed C code. The runtime navigates arrays, structs, globals, callbacks, and tasks. But the design question remains: what data is authoritative for the current rule context, and what code is merely presenting or applying it? When the move relearner offers a move, that list is not arbitrary UI decoration. It is derived from game data and the Pokemon’s identity and level history. When the player chooses one move from that list, we move from data eligibility into UI staging. When the player confirms the replacement, we move from staging into mutation.

This connects directly to the course plan. Weeks ago, when we decomposed the RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems, that decomposition may have looked like a catalog. By week 19, it should look like a set of boundaries. The move relearner touches creature data, UI, script variables, callbacks, tasks, text, and eventually save-relevant party state. It is a small feature, but it crosses many systems. That is why it is such a good advanced example. You cannot solve it by saying “make a menu.” You have to know which system owns which truth at which point in time.

Think back to our item work, but only briefly. The bag was a UI facade over pockets and quantities. Item-use dispatch separated selecting an item from applying an effect. Healing items had to validate targets, compute effects, mutate party data, and report outcomes. We are not re-teaching that today, but we are reusing the same principle. Selection is not effect. Preview is not commit. Confirmation is not always mutation. In a careful architecture, those are separate steps unless there is a strong reason to collapse them.

Now I want to talk about regression testing, because this is where Showdown is especially instructive. FireRed’s original codebase is not organized around modern unit tests in the way a TypeScript simulator can be. Showdown, by contrast, has a visible testing culture around simulator behavior; the research packet flags pokemon-showdown/test, including TESTS.md, assert.js, and common.js, as high-signal testing sources. We do not need to pretend FireRed has the same test harness to learn from that. We can still ask: what would a regression test for move replacement assert?

It would not merely assert that Ember appears after a successful replacement. That is the happy path, and yes, we need it. But the more important tests are cancellation tests. Cancel at teach confirmation: old move list unchanged. Confirm teaching, then cancel when asked to delete an old move: unchanged. Confirm deletion prompt, open old move selection, then back out: unchanged. Select Growl, then cancel final deletion: unchanged. Complete the full path: exactly one slot changes, PP and related move metadata are updated according to the engine’s expected rules, the result flag indicates success, and control returns to the correct callback or script continuation.

That test list is the lab in miniature. And it illustrates a broader professional habit: when a feature’s risk is accidental mutation, your tests should emphasize non-mutation paths. Students often over-test the path where the exciting thing happens and under-test the paths where nothing should happen. But “nothing happened” is a real behavior. It is especially real in menu programming. A player cancels constantly. A player changes their mind constantly. If every cancellation path is treated as an afterthought, your RPG will corrupt data through ordinary play.

Let’s do a board-work cue here. I would write this equation: UI state plus player input does not equal model mutation. Then I would add: UI state plus final confirmation plus valid target plus commit state equals model mutation. That is the pattern. It sounds formal, but it is practical. If you can identify the commit state in your code, you can audit the feature. If you cannot identify it, then the write may be smeared across cursor handlers, text handlers, and callback transitions. That is where bugs hide.

Now, how does this sophistication change what I expect from you in week 19? Earlier in the course, I was satisfied if you could point to a table, point to a callback, and narrate roughly how visible behavior emerged. That was appropriate then. The recent homework and quizzes have already checked your ability to identify FireRed files, structs, functions, constants, data tables, and state variables. So now the bar is higher. I expect you to describe the lifetime of each piece of state. I expect you to distinguish display state from staged decision state from committed game state. I expect you to say where cancellation returns and what it guarantees. And if you use Showdown as a comparison, I expect you to use it surgically: Dex and mods for data resolution, simulator hooks for turn behavior, tests for regression discipline. Not as a replacement architecture for FireRed.

Let’s walk one more example slowly, because this is the one that tends to reveal whether the idea has landed. A Charmeleon has Scratch, Growl, Smokescreen, and Rage. The move relearner offers Ember. The player chooses Ember. The game asks, in effect, “Teach Ember?” The player says yes. There is no empty slot, so the system asks whether to forget an old move. The player says yes. The four current moves are displayed. At this exact moment, the four displayed moves must come from the committed party record, not from a half-mutated replacement. The player moves the cursor from Scratch to Growl to Smokescreen. The party data still has Scratch, Growl, Smokescreen, Rage. The player chooses Smokescreen. The game asks for final confirmation. The player cancels. The party data still has Scratch, Growl, Smokescreen, Rage. Now the player starts again, chooses Ember, chooses Smokescreen, and confirms. Only then does the party record become Scratch, Growl, Ember, Rage.

If you can narrate the feature that way, you can build it. If your narration has sentences like “we replace the move so the player can decide whether to keep it,” stop. That sentence tells you the architecture is inverted. The player decides, then we replace.

And this is also why callback return paths matter. A UI feature is not finished when the data write occurs. It is finished when it returns to the correct owner with the correct result. The move relearner may be entered from a script interaction. It may need to set an event variable or result flag so the script can branch or continue. The excerpt in pokefirered/src/learn_move.c, chunk chunk_e820ee15ae8caa6d, specifically mentions setting VAR_0x8004 to true when teaching succeeds through the empty-slot path. That detail matters because UI completion is also a contract with the caller. The NPC script, field flow, or previous callback does not want a vague feeling that something happened. It needs a concrete result.

Showdown’s battle tests make a similar point in another domain. A simulator turn is not just animation or text output. It has inputs, resolved data, hooks, state transitions, and expected final state. If a damage callback in pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk chunk_3480e574205d5d0f, produces the wrong result, a good test catches the final state or event trace. For our FireRed-inspired lab, if a cancellation path mutates a move slot, a good test or debugging trace should catch the party record after each branch.

So as we move toward the end of this week’s material, connect the pieces. The menu is task-backed. The task is run frame by frame. The summary screen reminds us that not every party-facing UI is a writer. The move relearner shows us a writer that must be carefully staged. The broader course reminds us that creature RPG architecture is not a pile of screens; it is a chain of contracts between data, runtime state, user input, and persistent consequences. And Pokemon Showdown, used carefully, gives us a modern mirror: data hooks, Dex and mods, deterministic turns, and regression tests all reinforce the same discipline. Keep the source of truth stable. Stage decisions locally. Commit once. Then return a result that the caller can trust.

Now, let me put one more comparison on the board, because this is where advanced architecture students sometimes overgeneralize in the wrong direction. FireRed and Pokemon Showdown are solving related problems, but not the same problem. FireRed is an embedded, retail game architecture with persistent party records, callbacks, tasks, scripts, windows, and save-facing data. Pokemon Showdown is a simulator architecture, written for deterministic battle execution, ruleset variation, and regression testing. So when I say Showdown is useful today, I do not mean, “replace the FireRed architecture with Showdown.” I mean, “use Showdown as a secondary mirror that makes certain contracts easier to see.”

On the board, I am writing two columns. Left column: FireRed. Right column: Showdown. Under FireRed I write, “party struct, menu task, staged UI decision, write move slot.” Under Showdown I write, “Dex data, modded rule layer, simulator turn, assertion test.” These are not equivalent components, but they rhyme architecturally.

In FireRed, our evidence today is still the move relearner flow. The overview comment in pokefirered/src/learn_move.c, chunk chunk_af3598df6f092723, tells us to think about the move relearner as a state machine, not as a single function that magically teaches a move. Then the confirmation and replacement section in pokefirered/src/learn_move.c, chunk chunk_e820ee15ae8caa6d, gives us the important branch: if the Pokemon has an empty move slot, the operation can complete directly; if it does not, the player must be taken through the “trying to learn,” “confirm delete,” “which move,” and final replacement path. That is the architectural heart of today’s lecture.

Now compare that to Showdown. In a simulator, you usually do not have a party menu asking the player to delete Tackle in a persistent cartridge save. You have battle objects, move data, rule hooks, and tests that say, “given this state, when this turn runs, this effect should occur.” The research packet points us especially toward pokemon-showdown/data, pokemon-showdown/sim, and pokemon-showdown/test as recurring secondary references. So if we were testing a move’s legality, or a battle rule, or a generation-specific exception, Showdown’s Dex and mod structure is exactly the kind of modern architecture that gives us confidence. But for today’s FireRed problem, the vulnerable thing is not primarily battle damage. The vulnerable thing is a UI-driven mutation of a party record.

Let’s make that concrete. Suppose our Charmeleon knows Scratch, Growl, Ember, and Smokescreen. The move relearner offers Dragon Rage. The player selects Dragon Rage. The menu asks for confirmation. The player says yes. There is no empty slot. Now the system must ask whether to delete an old move. The player says yes. The system opens the move-selection interface. The player highlights Growl. Then the player cancels.

Pause there. What should the party record contain?

It should still contain Scratch, Growl, Ember, and Smokescreen. Not Dragon Rage. Not an empty slot. Not some half-written state where Growl is gone but Dragon Rage is not committed. This is the simplest example of why we separate decision state from committed party data. The UI can remember, “candidate new move equals Dragon Rage,” and “candidate old slot equals index one,” but the Pokemon’s actual moves should not be modified until the player completes the final confirmation path.

Check your understanding here. If a student says, “Well, we can write Dragon Rage immediately, and if the player cancels, restore the old move,” what is the problem with that design? The problem is not that it is impossible. The problem is that it increases the number of states in which the persistent record is temporarily false. You now need restoration logic for every cancel path, every fade path, every callback return, and every interruption edge. On a small menu that may seem tolerable. Across a creature RPG, that habit becomes a save corruption factory.

This is why I keep using the phrase “commit once.” It is not a slogan. It is an engineering constraint. Staging variables can be messy. Menus can fade in and out. Prompts can branch. Tasks can wait for text printers and fanfares. But the party data should cross from old valid state to new valid state at one controlled point.

Now, look at how this connects back to the summary screen. The source packet points us to pokefirered/src/pokemon_summary_screen.c, chunk chunk_e8e17a42bbf17d46. The summary screen includes party, move, item, battle, and menu-related headers, and it is absolutely party-facing UI. But for our purposes today, it is a reminder that party-facing does not automatically mean party-mutating. A summary screen reads data and presents it. A move replacement menu eventually writes data. Those are different responsibilities, and advanced code review should treat them differently.

If I were reviewing your lab implementation this week, I would not merely ask, “Does the menu look right?” I would ask, “Which states read the Pokemon? Which states stage a selection? Which exact state mutates the Pokemon? Which states return to the caller? Which states are cancel-only? Which states can play text but should not write anything?” That is the level at which menu state machines become architecture rather than UI glue.

Now let’s bring in the task system again, but only briefly, because we covered this deeply last time. FireRed’s task scheduler gives us the frame-by-frame dispatch model. The research packet identifies pokefirered/src/task.c, chunk chunk_cb0bb91a147bfb59 for CreateTask, and pokefirered/src/task.c, chunk chunk_3ffebc0341225936 for RunTasks. Those chunks matter here because the move relearner does not run as a blocking console application where the function asks every question in sequence and returns one final answer. It is distributed across frames. Each state does a little work, waits for input or animation or text, and then advances.

That frame-based structure is why intermediate correctness matters. If you mutate data too early, then the game can spend many frames in a visibly normal menu while the underlying party record is already changed. A later cancel branch then has to undo history. A better architecture lets the menu be uncertain while the saved creature data remains certain.

Now, Showdown helps us sharpen the testing vocabulary. In Showdown, the simulator culture pushes you toward deterministic scenarios: build a battle, choose actions, assert outcomes. The packet calls out pokemon-showdown/test as a recurring module around assert, Pokemon, moves, battle, species, and Showdown. Again, we are not copying its runtime into FireRed. But we can borrow the discipline. For the move relearner, our equivalent regression tests would not only test the happy path. They would test every branch where the user backs out.

So on the board, I am sketching a test matrix. I will say it aloud rather than leave it as a table. Case one: Pokemon has an empty move slot, player confirms new move, result is new move in the empty slot. Case two: Pokemon has an empty move slot, player cancels confirmation, result is no change. Case three: Pokemon has four moves, player confirms teaching, confirms deletion, selects old move, confirms replacement, result is exactly one old move replaced. Case four: Pokemon has four moves, player confirms teaching but cancels deletion, result is no change. Case five: Pokemon has four moves, player enters old-move selection and then cancels, result is no change. Case six: Pokemon has four moves, player chooses to stop learning after the warning prompt, result is no change.

That is not excessive testing. That is testing the actual architecture.

And notice something subtle. The test oracle is not “the text box appeared.” The test oracle is the party record. The UI is evidence that the user traveled through the path, but the invariant is data correctness. This is the same discipline we used earlier in the course when tracing from data tables through globals, callbacks, tasks, scripts, structs, and concrete source functions. We are applying it now to a narrower and more dangerous operation: a menu write into party data.

Let me give you a second example, because this one catches implementation mistakes. Suppose the Pokemon knows Pound, Disable, Confusion, and Headbutt. The player is relearning Barrier. The player selects Barrier, confirms, chooses to delete a move, highlights Disable, then moves the cursor to Confusion, then cancels. If your code wrote the selected old slot every time the cursor moved, you have already lost. Cursor movement is not semantic choice. Highlighting is not confirmation. The old move slot should become authoritative only when the confirmation path says it is authoritative, and even then it should generally remain staged until the final replacement state.

This is one of the most useful distinctions in UI architecture: focus, selection, confirmation, and commit are not the same thing. Focus is where the cursor is. Selection is what the user has picked as a candidate. Confirmation is the user approving the candidate. Commit is mutation of the underlying model. If you collapse those into one operation, the UI may still appear to work in a quick manual test, but it becomes fragile under cancellation, repeated input, and callback transitions.

Now, connect this to the week’s lab. The workshop asks you to build a move-replacement menu with explicit states for list setup, confirm teach, select old move, confirm deletion, write data, and return callback. That wording is deliberate. “Write data” is one state, not a mist spread across the whole flow. I want you to be able to point to the line or function where the party record changes. If you cannot point to it, your design is too diffuse.

Let’s return briefly to the course plan. Weeks seventeen through nineteen form our Items, Menus, and Party UI module. We began with item data records, bag pockets, quantities, bag windows, money helpers, and item-use dispatch. We then moved into field-use versus battle-use behavior and script blockers. This week, we shifted from inventory as a facade to menus as task-backed state machines. The continuity is important. Items taught us that data records do not act by themselves. Dispatch code interprets them. Menus teach us that visible UI does not mutate data by itself. State machines decide when mutation is allowed.

This is also why week nineteen sits after the earlier runtime and data-table weeks. In week one, we decomposed the RPG into runtime shell, overworld, scripts, creature data, battle, inventory, UI, save, and link systems. That could sound abstract at the beginning of the course. Now you can see the practical value. Move replacement crosses creature data, UI, tasks, scripts or callbacks, and persistence. If you do not already have the mental model of cooperating systems, this feature looks like “just a menu.” It is not just a menu. It is a controlled transaction over a player-owned creature record.

Now, one more check for understanding. Imagine you are adding a new “teach move” item in your own FireRed-like engine. The item opens a party menu, the player chooses a creature, the game opens a move list, and then maybe replaces a move. Where should you put the move-writing function? Should it live in the bag pocket code, the item data record, the summary screen, or the final teach/replacement flow?

The best answer is the final teach/replacement flow, or a small shared party-mutation helper called only from that final flow. The bag item can initiate dispatch. The party menu can select a target. The summary-style UI can show current moves. But the write should occur where all preconditions have been satisfied: target Pokemon known, new move known, old slot known if needed, confirmation complete, and return result ready. That placement gives you a clean contract.

This also gives you a way to reason about bugs. If someone reports, “My Pokemon forgot a move after I backed out,” you do not begin by staring at graphics code. You inspect the cancel branches around old-move selection and deletion confirmation. You verify whether any branch writes to the Pokemon before the final commit. You instrument the party moves before entering the menu, after each branch, and after return callback. That is a professional debugging path.

Now, to bring Showdown back in carefully: Showdown’s value is especially visible when rules vary. The packet points to pokemon-showdown/data/mods as an important area, including modded learnsets across generations. In Showdown, a mod can say, in effect, “in this generation or ruleset, these move relationships differ.” FireRed does not have that same mod layer in its retail architecture. FireRed has compiled data and game-specific rules. But the conceptual comparison is useful. Both systems need to answer, “Where does move legality come from?” and “Where does move execution behavior come from?” and “Where do we test that the answer remains stable?”

For FireRed’s move relearner, legality and menu availability are upstream of the final write. The menu should not need to rediscover the entire learnset theory at the moment of mutation. By the time the player is confirming replacement, the candidate move should already have come from a valid source. The replacement state should be narrow: write this valid move into this valid slot for this valid Pokemon. When students mix legality calculation, UI rendering, input handling, and data mutation into one function, they make it hard to test any of them.

In Showdown terms, you would not want a battle damage hook secretly rewriting the Dex. You would not want a test assertion to mutate learnset data globally and then leak into the next test. Same principle. Stable source of truth, local staged state, controlled mutation, and isolated verification.

Let’s do a small board trace from the FireRed side. We start with the caller entering the move relearner. A task or callback-backed menu initializes list state. The player navigates a list of candidate moves. Candidate move is staged. Confirmation prompt appears. If cancelled, return without touching party moves. If confirmed and an empty slot exists, write to that empty slot and set the script/result variable accordingly. If no empty slot exists, print the trying-to-learn prompt. Then ask whether to delete an old move. If no, print stop-teaching text and return unchanged. If yes, show current moves. The player selects one. Then, only after the replacement is confirmed, write the new move over the selected slot, update result state, and return.

That trace is basically the lecture in miniature.

Now, I want you to notice the phrase “set the script/result variable.” The excerpt from pokefirered/src/learn_move.c, chunk chunk_e820ee15ae8caa6d, mentions VAR_0x8004 being set when the move is successfully taught through the empty-slot path. That is another contract boundary. The caller does not need to inspect the whole menu history. The caller needs a trustworthy result: did teaching happen or not? This is the same design shape we have seen across scripts and item use. A lower-level interaction performs a controlled operation and reports a compact result to the higher-level script or callback.

So, when you design your own version, do not return five half-meaningful flags. Do not make the caller infer success from whether a text box was printed. Return a simple result that corresponds to actual mutation. If the party record changed, report success. If it did not, report cancellation or failure. Make that result boring and reliable.

Let’s also talk about save safety. FireRed’s party data is not just a local UI model. It is player progress. Any operation that modifies moves affects battles, future menus, link behavior, and eventually saved state. That is why a move replacement bug is more serious than a temporary cursor drawing bug. A cursor drawing bug vanishes when the menu closes. A corrupted move slot follows the player.

In advanced architecture, we rank bugs partly by persistence. Visual glitches are not harmless, but persistent data corruption is in a different category. The party is part of the player’s long-term identity in a creature RPG. So the code that mutates it should be small, explicit, and surrounded by branch discipline.

Now, a quick comparison to the item system we just finished. In the bag, quantity changes had to happen only when the item was actually consumed, not merely highlighted or inspected. Here, move changes happen only when the move is actually taught, not merely highlighted or previewed. Different subsystem, same architectural instinct. The UI facade can be rich. The data write should be narrow.

And this is where I want you to raise your standards for the weekly homework. The week nineteen homework should not merely show that you found the relevant files. By now, after homework and quizzes in weeks seventeen and eighteen, you should be able to identify the files, structs, functions, constants, data tables, and state variables that implement a system, then explain the runtime path. For this lecture, a strong answer says: the move relearner state machine is evidenced in pokefirered/src/learn_move.c, chunk chunk_af3598df6f092723; the replacement confirmation flow is evidenced in pokefirered/src/learn_move.c, chunk chunk_e820ee15ae8caa6d; summary-style party reading is supported by pokefirered/src/pokemon_summary_screen.c, chunk chunk_e8e17a42bbf17d46; task creation and dispatch are grounded in pokefirered/src/task.c, chunks chunk_cb0bb91a147bfb59 and chunk_3ffebc0341225936. Then, and only then, you may use Pokemon Showdown as comparison for Dex data, mods, simulator turn execution, and tests.

That order matters. FireRed first. Showdown second. We are studying FireRed creature RPG architecture, not using FireRed as a nostalgic preface to a different engine.

Now let me give you a final implementation warning before we synthesize. Be careful with helper functions that sound innocent. A function named something like “TryTeachMove” can hide a lot. Does it check for an empty slot? Does it write immediately? Does it return a slot index? Does it open UI? Does it set a result variable? Does it print text? Advanced codebases often accumulate helpers whose names no longer reveal their side effects. When you call such a helper from a state machine, you must know whether it is pure, staged, or mutating.

If you are building the lab version, I would rather see boring names with precise boundaries than clever names with hidden side effects. “FindEmptyMoveSlot” should not write. “SetMoveSlot” should write and should not print a prompt. “EnterConfirmDeleteState” should change menu state and should not mutate the Pokemon. “CommitMoveReplacement” should be the scary one, and it should be small enough to audit.

Here is a last check for understanding. I will describe three operations, and you tell me which ones may write the party record. The player moves the cursor over a candidate relearn move. No write. The player confirms that they want to try learning that move, but the Pokemon has four moves. Still no write. The player confirms deleting an old move and the final replacement state runs. Yes, that is where the write belongs. If the Pokemon had an empty move slot, then the direct successful teach branch may write there, because no destructive replacement is needed and the confirmation is complete.

That distinction is the whole point of the lecture.

So, as we close part six and the lecture as a whole, here is the synthesis I want you to carry forward. A creature RPG is full of friendly-looking screens that sit on top of serious data. The player sees menus, prompts, move names, cries, fanfares, and little arrows. The engine sees callbacks, tasks, state variables, branch results, party structs, and save-facing consequences. Good architecture keeps those layers connected without letting them blur.

The FireRed move relearner is a compact case study in that discipline. It uses a menu state machine to stage a player decision across many frames. It distinguishes reading party data from writing party data. It routes confirmation and cancellation through explicit branches. It reports success back to the caller through a compact result. And it protects the party record by making mutation a final consequence, not an incidental side effect of navigation.

Pokemon Showdown, used as a secondary comparison, reinforces the modern version of the same lesson. Dex data should be stable. Mods should isolate rule differences. Simulator turns should be deterministic. Regression tests should catch behavior changes. But FireRed gives us today’s central architecture lesson in its original form: when UI drives persistent creature data, the menu is not decoration. It is a transaction boundary.

That is the bridge into the next part of the course. We have now moved from runtime shell, maps, scripts, data tables, item dispatch, and party menus into the deeper question of how a creature RPG protects player progress while still offering flexible interactions. Week twenty will build on this by asking how these contracts behave when larger systems meet: battle entry, party state, field scripts, save data, and eventually link-facing assumptions. Keep today’s rule close: source of truth stable, decisions staged locally, one controlled commit, one trustworthy result.

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