# Move Learning as a Menu State Machine

Lecture ID: lecture_w11_d02
Week 11, Day 2

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

## Transcript
All right, let’s pick up exactly where we left off last time. Last lecture, we treated the party summary screen as a mostly read-oriented view over party data. That distinction matters today. The summary screen is allowed to inspect a party slot, render species, stats, moves, ribbons, held item, met location, and so on, but the core discipline was this: reading party data is not the same architectural act as owning permission to mutate it. Today we move from that read-side discipline into a write-side flow, but a very controlled one. The move relearner is a menu state machine whose whole purpose is to delay mutation until the user has crossed the right confirmation boundary.

So, if you are keeping a mental map of the course, we are still in the Creature and Party Data Model module, but we are now at the point where UI is not just a display layer. It is a procedural gatekeeper. It asks questions, records choices, branches through states, calls into Pokemon data helpers, and eventually mutates move slots. That is a much more dangerous operation than drawing a summary page, because now the menu can change the creature.

Let me say the thesis for today plainly. Move learning in FireRed is not “open a menu, pick a move, assign it.” That would be the toy version. In the real architecture, move learning is a staged interaction among script entry points, callback setup, task-driven frame updates, list menu state, global variables such as script vars, Pokemon structs accessed through helper APIs, and a deliberate replacement flow for full movesets. The player experiences this as a familiar dialogue: “Which move should be forgotten?” But the engine experiences it as a state machine that must preserve intent across frames.

I’m going to put that phrase on the board: preserve intent across frames. That is one of the big lessons from FireRed’s UI code. On the Game Boy Advance, a menu does not block the whole program in the way a simple command-line prompt might. You do not call “ask the user a question” and wait inside one function until they answer. Instead, the engine returns to the main loop every frame. The current callback runs. Tasks run. Input is sampled. Sprites animate. Windows draw. Then next frame, the same state machine continues where it left off.

That means intent has to live somewhere. If the player picked a party Pokemon, then picked a relearnable move, then answered yes, then chose an old move to forget, those choices cannot merely live on the C stack of one function call. They have to be stored in task data, globals, script variables, or some other state container that survives frame boundaries.

Now, in the source packet for today, the central file is pokefirered/src/learn_move.c, especially chunk_af3598df6f092723 and chunk_e820ee15ae8caa6d. The first chunk is useful because it explicitly frames the feature as the “Move relearner state machine.” That is not me imposing a computer science abstraction after the fact. The code’s own comments describe the flow in those terms. It names CB2_MoveRelearner_Init, then describes MoveRelearnerStateMachine beginning at MENU_STATE_FADE_TO_BLACK. It also mentions listMenuScrollPos for right and left buttons and listMenuScrollRow for up and down buttons. Those details are small, but they are architecturally revealing. The system is not just storing “which move did the player pick?” It is storing the cursor position and scroll row separately enough that the menu can feel stable as the player navigates.

Pause there for a second. If I ask, “Why does scroll state matter in an architecture lecture?” the answer is not “because menus are hard.” The answer is that scroll state is part of the continuity contract between frames. If the list is longer than the visible rows, then the current selected item and the current visible window over the list are two related but distinct pieces of state. If you lose either one, the player-visible behavior breaks. The cursor jumps. The list redraws in the wrong place. A selection confirms the wrong move. So even though scroll position feels like a UI detail, it is actually part of the correctness boundary for mutation.

Let’s connect this to the previous lecture. In pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, the include list itself shows the summary screen living at a junction of several systems: pokemon.h, pokemon_summary_screen.h, task.h, menu_helpers.h, link.h, overworld.h, strings.h, constants/items.h, data.h, item.h, constants/party_menu.h, battle_main.h, constants/moves.h, and others. We used that as evidence that the summary screen is not isolated from the rest of the game. But its dominant behavior, for our purposes, was reading and presenting. Today, learn_move.c has a different center of gravity. It also includes task.h, script.h, data.h, menu.h, list_menu.h, event_data.h, pokemon_summary_screen.h, strings.h, constants/moves.h, and so forth, according to pokefirered/src/learn_move.c chunk_af3598df6f092723. That include neighborhood tells you something. The move relearner sits between the script system, the menu system, the task system, and the Pokemon data model.

Now let’s do a concrete example. Suppose the player talks to the Move Relearner NPC, chooses a Charizard in the party, and wants it to relearn Flamethrower. I’m not making a claim here about that exact species table entry; I’m using a familiar move as a placeholder for the flow. The important architecture is independent of the specific move. The NPC script starts the interaction and hands off to a special UI flow. The game has to know which party slot is being considered. That is our party slot ownership idea from Monday. The UI must not accidentally operate on “some Charizard-like object.” It must operate on the selected party slot, which corresponds to a concrete Pokemon struct in the party array.

Then the move relearner initializes. In the source comments from pokefirered/src/learn_move.c chunk_af3598df6f092723, the initializer is CB2_MoveRelearner_Init. That name tells us two things. First, this is a callback-two style screen flow, so it belongs to the main callback architecture we have been tracing since week one. Second, it is initialization, not the whole interaction. The initializer sets up state, creates menu scroll tracking, prepares windows and list structures, and then control passes to a per-frame state machine.

Board cue: I’m drawing three boxes now. The first box is “Script entry.” The second is “CB2 init and menu setup.” The third is “Per-frame state machine.” Under the third box I’m writing “input, text, confirmation, mutation.” The reason I’m drawing it this way is to prevent a common mistake in reading this code. Students often look for one function that “does move relearning.” But in FireRed, the feature is distributed across a sequence of callbacks and state cases. The function names are less important than the handoff pattern.

Now, after initialization, the menu needs to present a list of possible moves. It must allow the player to scroll. It must respond to A and B. It must know whether the user chose an actual move or cancelled. That is the selection phase. But selecting a move still should not immediately overwrite a move slot. Why? Because the Pokemon may already have four moves. And in Pokemon, four moves is not just a UI convention. It is a data model constraint. We covered move slots and PP earlier, so I will not re-teach that representation, but keep the contract in mind: the Pokemon has a bounded set of move slots, and each move slot has associated PP state. Replacing a move is not a cosmetic update. It changes battle behavior, summary display, and saved party state.

This is where chunk_e820ee15ae8caa6d becomes central. That chunk of pokefirered/src/learn_move.c describes the teaching and replacement states. It says that in MENU_STATE_TEACH_MOVE_CONFIRM, the state machine waits for the player to confirm. If cancelled, it goes to MENU_STATE_SETUP_BATTLE_MODE. If confirmed and the Pokemon has an empty move slot, VAR_0x8004 is set to TRUE and the flow goes to MENU_STATE_PRINT_TEXT_THEN_FANFARE. If confirmed and the Pokemon does not have an empty move slot, it goes to MENU_STATE_PRINT_TRYING_TO_LEARN_PROMPT. That branching is the architectural heart of today’s lecture.

Notice the difference between the two paths. Empty slot means the mutation can be straightforward: insert the move, report success, fanfare. Full moveset means the system must ask another question before mutation. The player has confirmed “teach this move,” but has not yet confirmed “delete that old move.” Those are separate permissions. A sloppy implementation would collapse them and risk deleting a move after only one confirmation. FireRed’s state machine keeps them separate.

Let me check understanding here. If the player chooses a new move for a Pokemon that already has four moves, what is the next responsibility of the system? Not “teach it anyway.” Not “overwrite the first move.” Not “open the summary screen and hope the player figures it out.” The next responsibility is to enter a replacement dialogue: explain that the Pokemon is trying to learn the move, ask whether to forget an old move, and only if the player confirms, ask which old move to forget. That sequence is visible in chunk_e820ee15ae8caa6d, where the comments name MENU_STATE_PRINT_TRYING_TO_LEARN_PROMPT, MENU_STATE_WAIT_FOR_TRYING_TO_LEARN, MENU_STATE_CONFIRM_DELETE_OLD_MOVE, and MENU_STATE_PRINT_WHICH_MOVE_PROMPT.

That is why I call this a menu state machine rather than a menu screen. A screen is a visual thing. A state machine is an interaction contract. The same screen can host multiple states: printing text, waiting for text, waiting for yes/no input, building a move list, waiting for move selection, playing fanfare, returning to the script. The player sees a smooth sequence of boxes and prompts. The code sees a series of states with narrowly defined exit conditions.

Now, one thing advanced learners should watch for is how FireRed uses variables to communicate completion back to scripts. In chunk_e820ee15ae8caa6d, the comment says VAR_0x8004 is set to TRUE in the confirmed empty-slot path. We have seen these script variables before. They are part of the event scripting interface, not a local UI-only detail. When a special function or menu flow completes, the script often needs a simple result: success, failure, cancelled, selected index, selected party slot. VAR_0x8004 is one of those bridge points between C gameplay systems and the script VM.

So, if we trace the data path at a course level, it looks like this. The species and learnset records define what could be learned. The party Pokemon struct defines what this individual currently knows. The NPC script starts the relearner interaction and supplies or depends on script variables. The callback initialization sets up the UI runtime. The task or callback state machine processes input over many frames. The menu states either cancel, teach directly into an empty slot, or ask for a replacement. The final confirmed branch mutates the party Pokemon’s move data. Then the script layer resumes with a result variable.

That full trace is exactly the course objective: data records through globals, callbacks, tasks, scripts, structs, and concrete source functions. We are not merely naming subsystems anymore. By week 11, you should be able to say where each piece sits in the call chain and why it cannot be replaced by a single generic “menu manager.”

Let’s compare briefly with the summary screen again, because the contrast is important for your lab. In the lab, you are implementing a party summary screen model that reads creature data without mutating it, and then adding a move-learning flow that mutates move slots only after confirmation. That is not busywork. It is training you to separate read models from write commands. The summary screen model can ask, “What species is this? What level? What moves? What PP? What held item?” It should not casually say, “While I am here, I will normalize or repair the move list.” The move learning flow, by contrast, is allowed to mutate, but only after it has collected the required user intent.

Here is the board version of that principle. I’m writing “read path equals projection” on the left and “write path equals confirmed transition” on the right. Projection means you take existing data and shape it for display. Confirmed transition means the data moves from one valid state to another valid state because a user action authorized it. The summary screen is mostly projection. The move relearner is confirmed transition.

And this also helps explain why FireRed code can feel verbose if you read it without the hardware and engine context. You may think, “Why so many states? Why not just a few nested if statements?” But nested if statements inside one blocking procedure assume the program can wait in place. FireRed cannot structure most gameplay UI that way. It has to fit into the callback-and-task runtime we studied earlier. The comments in learn_move.c are effectively documenting an asynchronous interaction in C.

Let’s now zoom into the replacement branch. The player says, “Yes, teach the new move.” The Pokemon has no empty slot. The state machine prints a prompt that the Pokemon is trying to learn the move. Then it waits. That wait matters. Text printing in these games is itself a stateful process. The engine may draw characters gradually, wait for A or B, and then advance. So a state like MENU_STATE_WAIT_FOR_TRYING_TO_LEARN is not decorative. It prevents the next decision prompt from being evaluated before the prior message has finished being displayed and acknowledged.

Then the system reaches MENU_STATE_CONFIRM_DELETE_OLD_MOVE. This is the “should we proceed into replacement?” question. If the player cancels here, chunk_e820ee15ae8caa6d says the flow goes to MENU_STATE_PRINT_STOP_TEACHING. If the player confirms, it goes to MENU_STATE_PRINT_WHICH_MOVE_PROMPT. Again, notice the careful staging. Cancelling at this point is not the same as cancelling from the first move list. The player has expressed interest in the new move but declines to delete an old one. The game should explain that teaching stopped, rather than silently falling out of the interface.

Now imagine implementing this in your own clone. A weak implementation might store only one variable: selectedMove. Then once selectedMove exists, it tries to apply it. But a robust implementation needs more state. It needs selectedPartySlot, selectedNewMove, currentMenuState, listScrollOffset, selectedListRow, maybe oldMoveCandidate, whether the Pokemon has an empty slot, and the result to report back to the calling script or scene. If you are building a modern version, maybe these live in a state object instead of task data and script vars. But the responsibilities do not disappear just because the language changes.

Let me ask a check-for-understanding question. Suppose your move-learning UI has a bug: after the user opens the replacement menu, presses down twice, backs out, then reopens replacement, the cursor starts on a different old move than expected. What kind of bug is that? It is not a species data bug. It is not a move eligibility bug. It is a UI state persistence or reset bug. The wrong state is being carried across a boundary, or the right state is not being initialized when entering a state. That is exactly why the FireRed comments mentioning listMenuScrollPos and listMenuScrollRow in chunk_af3598df6f092723 are worth our attention. Cursor and scroll state are part of correctness.

Now, we should also be precise about “mutation.” When the new move finally gets written, that operation should go through the Pokemon data interface rather than random direct memory poking wherever possible. This connects back to pokefirered/include/pokemon.h chunk_9b5ff7d0cd9f4c67 and pokefirered/src/pokemon.c chunk_9c96aedd5b8237e6 from the source focus for this week. We are not going to re-teach the Pokemon and BoxPokemon representation today, but remember the boundary: the creature data model is accessed through helper functions and constants because the internal representation has constraints. The menu should not need to know every storage detail in order to teach a move. It should know which party slot and which move index are involved, then call the proper helper flow.

This is one of the bigger software architecture lessons hiding inside an old handheld RPG. A feature can be UI-heavy and still be data-sensitive. A menu can be a state machine, a script participant, and a data mutation gateway at the same time. If you classify it as “just UI,” you miss why it has to be so careful.

Let’s place today within the broader course plan. In weeks one and two, we built the runtime shell: startup, main callbacks, task allocation, per-frame dispatch. Then we moved through maps and scripts, where NPCs and events trigger C systems. Then creature data gave us species records, party structs, move slots, PP, and helper APIs. Week 10 brought in battle data hooks and comparisons with Pokemon Showdown, but FireRed remained our main architecture source. Now, in week 11, we are joining those earlier pieces. Move relearning is not a new isolated topic; it is a convergence point. It uses the runtime shell, the script layer, the party model, the menu system, and mutation discipline.

And because your recent homework and quizzes have already asked you to identify files, structs, functions, constants, and state variables, I am going to hold us to a higher bar today. It is not enough to say, “learn_move.c handles move relearning.” That is true but shallow. A week 11 answer should say something closer to this: pokefirered/src/learn_move.c chunk_af3598df6f092723 identifies the move relearner as a state machine initialized by CB2_MoveRelearner_Init, with list menu scroll state and a transition into MoveRelearnerStateMachine. Then pokefirered/src/learn_move.c chunk_e820ee15ae8caa6d documents the confirmation and replacement states, including the empty-slot branch, VAR_0x8004 success reporting, and the full-moveset branch through trying-to-learn and delete-old-move prompts. That is an architectural answer.

Let’s do one more example before we move deeper in the next segment. Consider two Pokemon. The first knows only three moves. The second knows four. The same new move is selected for both. From a data eligibility perspective, both may be allowed to learn the move. But from a UI state machine perspective, they diverge after confirmation. The three-move Pokemon can be taught directly because the data model has capacity. The four-move Pokemon requires replacement because teaching would violate the bounded move-slot invariant. So the same selected move produces two different state paths because current creature state changes the legal transition.

That is the key phrase: current creature state changes the legal transition. In a data-driven RPG, records tell you what is possible in general, but runtime structs tell you what is legal now. Species learnsets and move records do not answer whether this individual has an empty move slot. The party Pokemon does. The menu state machine must therefore consult runtime data before choosing the next state.

So, local recap before we continue. Today’s first idea is that move learning is a controlled write flow, not just a menu. The second is that FireRed expresses this as a callback-and-state-machine interaction, with learn_move.c explicitly documenting that structure in chunk_af3598df6f092723. The third is that replacement is staged through separate confirmations, as shown in chunk_e820ee15ae8caa6d, because “teach this move” and “forget that move” are different player permissions. The fourth is that summary screens and move learning screens may both read party Pokemon data, but only one of them should cross into mutation, and only at the confirmed transition point.

In the next part of the lecture, we are going to walk the state machine more mechanically. I want to take the named states from learn_move.c, treat them like nodes in a graph, and ask what data each state must read, what input it waits for, what variables it writes, and what invariant it protects before it advances.

So let’s do that graph walk. Imagine I am drawing a row of boxes on the board. Not every box is a screen. Some boxes are visual setup, some are input waits, some are text-printing waits, and some are mutation gates. That distinction matters because, in FireRed, a player-visible “menu” is almost never just one function call that blocks until an answer comes back. It is a per-frame routine that returns to the main loop, yields control, and is called again next frame through the callback and task machinery we have been using since week one.

Now, starting from the source evidence, the key file for today is pokefirered/src/learn_move.c, chunk_af3598df6f092723. The comment there names this explicitly as the move relearner state machine, and that is useful because the code is not asking us to infer the architecture from vibes. It tells us the entry callback, CB2_MoveRelearner_Init, sets up the list menu behavior, including scroll position and scroll row handling, and then the MoveRelearnerStateMachine proceeds through named menu states. So when you are tracing this system for homework, I do not want you to say, “the move relearner opens a menu and learns a move.” That is too coarse for week eleven. I want you to say, “the callback initializes the UI resources, then a state variable drives per-frame transitions through fade, list setup, selection, confirmation, replacement, and final script return.” That is the level of architecture we are after.

Let’s put one concrete example underneath the graph. Suppose the player brings a Charmeleon to the move relearner. The creature already has four moves. It can relearn, say, Slash. The list menu is not mutating anything yet. The list is evidence, not action. It is a proposal surface. The player moves the cursor, reads move names, maybe checks PP or type if the UI exposes that, and presses A. At that exact point, we still should not overwrite a move slot. Why? Because pressing A on Slash means “I am interested in teaching Slash,” not “delete Ember immediately.” FireRed separates those permissions.

And this is where chunk_e820ee15ae8caa6d from pokefirered/src/learn_move.c becomes important. That chunk documents the teaching confirmation and replacement path. It names MENU_STATE_TEACH_MOVE_CONFIRM, then says the state waits for the player to confirm. If cancelled, it goes back toward the battle-mode setup state. If confirmed and the Pokemon has an empty move slot, VAR_0x8004 is set to TRUE and the flow proceeds toward text and fanfare. If confirmed and there is no empty move slot, the state machine prints the trying-to-learn prompt and then asks whether the player wants to delete an old move. Notice the staging. Confirmation one is about the new move. Confirmation two is about deleting an existing move. Those are not the same thing.

Check your understanding here. If I ask, “Where is the mutation allowed to happen?” the answer is not “when the move appears in the menu.” The answer is, “after the player has confirmed the proposed teaching operation, and if replacement is needed, after the player has also selected and confirmed the old move to forget.” That is the invariant. Selection is reversible. Mutation is delayed.

Now, let’s connect that back to party ownership. The move relearner is operating on a party Pokemon, not a detached copy that can be freely thrown away. We have already covered the Pokemon and BoxPokemon representation, and I am not going to reteach that today, but I do want you to keep the boundary visible. The source focus for this week includes pokefirered/include/pokemon.h, chunk_9b5ff7d0cd9f4c67, and pokefirered/src/pokemon.c, chunk_9c96aedd5b8237e6, because those are part of the interface and helper flow around creature data. The menu code should not casually poke at internal storage as though the UI owns the data layout. It should go through the established Pokemon access and mutation interface. That is how the architecture keeps UI state, party data, and stored creature representation from collapsing into one fragile blob.

Now I’m going to mark three columns on the board: reads, writes, and waits. For each state, ask those three questions. What does it read? What does it write? What is it waiting for? Early fade and setup states mostly read global UI progress and write UI resource state. The list state reads the eligible move list and cursor input, writes cursor position, and waits for A or B. The confirmation state reads the selected move and the current party creature’s move slots, writes a next-state decision, and waits for yes or no. The replacement-selection state reads the existing move slots and waits for the player to choose a move to forget. The actual teaching state writes the move slot and PP, and then the text or fanfare states wait for message completion before returning control to the script or overworld context.

That last return is easy to ignore, so let’s slow down. The move relearner is not the whole game. It is a temporary mode entered from the field-script world. We have seen this pattern repeatedly: an overworld interaction starts a specialized screen, the specialized screen takes over MainCallback2 or installs tasks, and then it hands control back when complete. The includes in pokefirered/src/learn_move.c, chunk_af3598df6f092723, show the relevant neighbors: script.h, task.h, field_fadetransition.h, overworld.h, menu.h, list_menu.h, event_data.h, and pokemon_summary_screen.h all appear in that source excerpt. That inclusion set tells a story. This is not isolated business logic. It is UI plus script integration plus task scheduling plus party data access.

Here is the architectural pattern in plain speech. The script says, “start this gameplay service.” The callback enters a modal UI world. The state machine breaks the service into frame-sized steps. The Pokemon interface supplies safe reads and writes. Event variables carry the result back to the script layer. Then the overworld resumes. This is why I keep telling you that FireRed is not one state machine. It is a stack of cooperating state machines, each with its own level of responsibility.

Let’s compare this briefly to the summary screen, because yesterday’s lecture set up the contrast. The summary screen source, pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, includes pokemon.h, pokemon_summary_screen.h, task.h, menu helpers, party menu constants, battle constants, moves, items, and other UI dependencies. That tells us the summary screen has broad read needs. It displays species, stats, moves, held item, origin information, and context-sensitive options. But broad read access is not the same as broad write authority. A summary screen can read a party Pokemon’s move list to paint it on the screen. The move relearner can read that same list, but then it also has a carefully staged path that may mutate it. So the distinction is not “which screen sees move data.” The distinction is “which transition is authorized to change move data.”

Now suppose a student in lab implements the party summary model and the move-learning flow in the same struct because both screens need the selected party slot. That might feel efficient at first. But now ask: does the summary page need a pendingMove field? Does the read-only move display need a forgetCandidate field? Does it need a variable corresponding to “teaching succeeded”? Probably not. So, in your clone architecture, separate the shared identity from the screen-specific transaction. Shared identity is party index plus pointer or reference to the party array. Screen-specific transaction is selected new move, chosen old slot, confirmation state, result flag. This is the difference between using party slot ownership as a foundation and accidentally letting every UI become an owner.

Let’s do a concrete trace. I’ll narrate it as if we are single-stepping, but remember, on hardware this is frame-based. The player talks to the move relearner NPC. The script sets up which party member is involved and invokes the relearner screen. The main callback changes to the move relearner initialization callback. CB2_MoveRelearner_Init prepares windows, menus, and scroll tracking, as documented in pokefirered/src/learn_move.c, chunk_af3598df6f092723. The state machine fades in, builds or displays the learnable move list, and waits on input. The player highlights Slash. The list state records that selection as pending UI state. The confirm state asks, in effect, “Teach Slash?” If the player says no, the machine returns to a prior menu state without changing Charmeleon. If the player says yes, the code checks whether there is an empty move slot. If there is, the new move can be installed directly. If there is not, chunk_e820ee15ae8caa6d shows the flow proceeds to trying-to-learn text, delete-old-move confirmation, and then a prompt asking which move to forget.

Now here is the subtle point advanced students should notice. The empty-slot case and the full-moveset case are not merely two UI branches. They represent different transaction shapes. Empty slot teaching is an append-like mutation: find available slot, set move and PP, report success. Full moveset teaching is a replacement mutation: choose old slot, confirm deletion semantics, overwrite selected slot with new move and PP, report success. Those are different enough that lumping them into “learnMove(mon, move)” can hide policy. In a clean architecture, a high-level function can still exist, but the UI state machine must preserve the policy distinction before it calls the mutation function.

Let me ask a check question. If your clone has a function called teachMoveImmediately, and your menu calls it as soon as the user presses A on a move name, what bug have you created? You have collapsed proposal into commitment. You have made the list menu destructive. The player has not yet agreed to the actual teaching operation, and if the creature has four moves, the player definitely has not selected what to forget. That is exactly the kind of bug this week’s lab is meant to expose.

Another check. If your summary screen uses the same function to display moves that the move relearner uses to overwrite moves, is that automatically wrong? Not necessarily. Shared read helpers are fine. Shared mutation helpers are fine when called at the correct boundary. The problem is not shared code. The problem is shared authority without state-machine discipline.

Now, watch the role of event variables. In the source comment from chunk_e820ee15ae8caa6d, the flow mentions setting VAR_0x8004 to TRUE when the teaching operation succeeds in the empty-slot branch. The exact script variable matters because this is how the UI result can become script-visible. Remember our earlier weeks on field scripts: scripts frequently use variables and flags as the cross-boundary language between scripted progression and C subsystems. So the move relearner does not need to directly decide all future story consequences. It needs to return a compact result to the script environment. The script can then continue with dialogue, item consumption, NPC behavior, or whatever the event requires.

This is also why the state machine has so many text states. A beginner might ask, “Why not just teach the move and show one message?” Because on a GBA-style engine, text printing is itself asynchronous. You open a window, print text over time or wait for the text printer, wait for A, maybe play a fanfare, wait for sound completion, and only then advance. The state names in learn_move.c are not ornamental. MENU_STATE_PRINT_TRYING_TO_LEARN_PROMPT, MENU_STATE_WAIT_FOR_TRYING_TO_LEARN, MENU_STATE_CONFIRM_DELETE_OLD_MOVE, MENU_STATE_PRINT_WHICH_MOVE_PROMPT: these names show that text, input, and mutation are interleaved but not interchangeable.

Let’s put that another way. In a modern desktop application, you might write an async function and await user choice. In FireRed C, the await is a state number. The call stack does not sit there blocked. The main loop comes back next frame, runs tasks, calls callbacks, and the state machine asks, “Has the text finished? Has the player answered? Has the fade completed?” That is the architecture we first met with AgbMain, SetMainCallback2, task allocation, and RunTasks. Week eleven is not separate from week one. It is the same runtime shell, now applied to a specific creature-data mutation workflow.

Now I want to make the lab expectation more explicit. When you implement the party summary screen model, it should read creature data without mutating it. That means your summary model can cache display strings or derived values, but it should not be the place where moves are changed. When you add the move-learning flow, you can use the same party slot reference, but mutation should occur only after confirmation. If the Pokemon has an empty move slot, confirmation of the new move is enough. If all slots are occupied, confirmation of the new move is not enough; the state machine must collect a forget slot and confirm the replacement path. That mirrors the staged flow documented in pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d.

Let’s test that with a slightly annoying edge case. Suppose the selected new move is already known by the Pokemon. What should happen? I’m not claiming the exact FireRed branch from the excerpt we have today, so be careful in your write-up if you discuss that exact behavior. But architecturally, where would that check belong? It belongs before destructive mutation. It could belong during list construction, so the move never appears, or during confirmation, so the operation is rejected. What it should not do is overwrite a move slot and then realize the move was redundant. The invariant is still the same: validation before mutation, player permission before mutation, script result after mutation.

Another edge case: the player enters the forget-move screen and presses B. According to the flow in chunk_e820ee15ae8caa6d, cancellation routes toward stopping teaching rather than silently deleting something. Again, this preserves the player’s ability to retreat until the commit point. This is not just good UX. It is data integrity. In a game where party creatures are persistent save data, UI mistakes are not temporary. If you corrupt a party member’s moves, you have changed saved progression.

Now, I want to tie in Pokemon Showdown briefly because our course memory says to use it as a secondary comparison, not as the primary authority. Pokemon Showdown’s data modules, such as pokemon-showdown/data/mods/gen9ssb/moves.ts, chunk_f562e1983e9fdeff, express move behavior in a very different environment: a simulator with event hooks like onTryMove, onPrepareHit, and conditions. That is useful for contrasting battle effect architecture, but it is not the model we should copy for FireRed’s move relearner UI. Showdown is excellent for deterministic battle logic and data-driven move effects. FireRed’s move relearner is an overworld-to-menu-to-party-data transaction. So if you borrow a lesson from Showdown here, borrow the idea that data and behavior are separated through named hooks and structured records. Do not replace FireRed’s callback and state-machine architecture with a simulator turn hook model. Wrong layer.

Let’s return to the board graph. I’m circling the nodes where writes occur. There should be very few circles. Most nodes read or wait. This is an important design smell. In a well-structured menu state machine, many states are non-mutating because they exist to stage context, draw UI, collect input, or play feedback. Mutation should be rare and obvious. If you inspect your lab code and every state changes the Pokemon, that is a red flag. If selection, confirmation, printing, and cancellation all have side effects on the creature, debugging becomes painful because player navigation itself changes the data.

Here is a good way to audit your clone. Log the party Pokemon’s moves before entering the move-learning menu. Log them after each state transition. During fade, list navigation, selection highlight, and initial confirmation prompt, the moves should remain identical. After successful direct teaching or confirmed replacement, exactly one move-slot change should appear, plus the corresponding PP initialization. After cancellation, no move-slot change should appear. If you cannot make those statements precise, your state machine is too blurry.

And notice that this audit connects directly to the coming week twelve assessment. The midterm is cumulative, and one of the recurring skills is tracing from data records through globals, callbacks, tasks, scripts, structs, and concrete source functions. This lecture is almost a compact version of that full trace. Move data begins as species and learnset information, which we studied earlier. Party Pokemon store concrete move slots and PP, which we used as prior foundation. A script or NPC event initiates the service. A callback takes over. A menu state machine manages input and display. Pokemon access helpers read and eventually mutate party data. Event variables report the outcome. The player sees a dialogue and a changed moveset. That is the whole stack in miniature.

So, before we continue into the replacement states in more detail, pause and make sure the graph in your notes has two different kinds of arrows. One kind is a navigation arrow: fade completed, cursor moved, prompt answered, text finished. The other kind is a data-commit arrow: move installed, old move replaced, result variable set. If those arrows look the same in your diagram, annotate them now. The source in pokefirered/src/learn_move.c, especially chunks_af3598df6f092723 and_e820ee15ae8caa6d, gives you the named states. Your job as an advanced reader is to recover the responsibility of each state and the invariant it protects.

Now, let’s slow down on that word invariant, because this is where advanced architecture reading starts to matter. A beginner sees a menu and asks, “What does the A button do?” That is a reasonable first pass. But at this point in the course, you should be asking, “At what point is party memory allowed to change, and which state is responsible for preventing accidental mutation before that point?” That is the architectural question.

So, imagine the board has the replacement sequence across the top. We begin with the player selecting a move from the relearner list. That is not a mutation yet. It is a choice stored in UI state. The menu needs to remember, “This is the candidate move,” but the Pokemon’s actual four move slots should still be exactly as they were. Then the game asks for confirmation. Still no mutation. Then, if there is an empty move slot, the system can commit immediately. If there is no empty move slot, the system must enter the replacement branch, and only after the player selects an old move and confirms the replacement should the party data change.

That sequence is spelled out in the comment block and named state progression in pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d. Notice the phrasing in that chunk: the state machine distinguishes confirming the new move, printing the “trying to learn” prompt, confirming deletion of an old move, asking which move to forget, and then handling the final answer. That is not just flavor text. Those states are the transaction boundary.

Let me give you a concrete version. Suppose the selected party Pokemon is in party slot 1, and it already has four moves. The player has navigated to a relearnable move, maybe a move it had access to earlier through its level-up list, and presses A. At that moment, the game knows the desired move ID, but it should not yet overwrite move slot zero, one, two, or three. The player might back out. The player might say no at the confirmation prompt. The player might enter the “which move should be forgotten?” screen and then cancel. In all of those branches, the visible UI has changed many times, but the underlying party Pokemon has not changed. That is exactly the distinction I asked you to mark with two arrow types: navigation arrows can be reversed or abandoned; data-commit arrows are the point of no return.

Now, one subtlety here is that “point of no return” does not always mean irreversible in an absolute sense. A game could in theory undo a move replacement by saving old data somewhere. But FireRed is not building an undo stack for this menu. The practical invariant is simpler: do not call the teaching or replacement write until the player has made the final affirmative choice. The architecture keeps that discipline by splitting the interaction into states, not by relying on one giant function full of deeply nested if-statements.

Look at how that compares with the summary screen we discussed earlier. In pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, the includes alone already tell you something about the module’s role. It pulls in pokemon.h, pokemon_summary_screen.h, task.h, menu helpers, move constants, item constants, battle constants, party menu interfaces, and related UI systems. That screen is a heavy reader of Pokemon state. It needs to display species, stats, held item, moves, contest-ish or descriptive fields depending on page, and so on. But the normal summary page is not a move transaction. Its default relationship to the party slot is observational. It reads through the same broader Pokemon interface, but its architectural burden is different. It must keep display synchronized with the selected slot. The move relearner must also synchronize display, but then it must cross into mutation safely.

So if I were drawing this as a table on the board, I would put “summary screen” on the left and “move relearner” on the right. Under summary screen, I would write “read current slot, render pages, update cursor, preserve party data.” Under move relearner, I would write “read selected slot, build candidate list, render list, collect confirmation, possibly replace move, report outcome.” The shared foundation is the party Pokemon access model. The difference is whether the UI is merely inspecting data or authorizing a write.

Check your understanding here. If a bug report says, “Opening the move relearner and backing out caused one of my moves to disappear,” where would you look first? Not in species data. Not in the learnset table generation. Not in stat recalculation. You would look for a write that occurs before the final confirmation branch. You would inspect the transition out of the selected candidate state, the confirmation handling, and the replacement states in pokefirered/src/learn_move.c, especially chunk_e820ee15ae8caa6d. That is the source-level reasoning we want.

Now let’s connect this to callbacks and tasks, because this lecture is not just about moves. It is about menu state machines as an architectural pattern in FireRed. Earlier in the course, when we studied the runtime shell, we treated SetMainCallback2 as the big ownership transfer point. A system says, in effect, “For now, this screen owns the frame.” Then within that screen, tasks and local state machines divide the work across frames. The move relearner fits that pattern. The source packet points us to pokefirered/src/learn_move.c, chunk_af3598df6f092723, where the comment describes CB2_MoveRelearner_Init and the MoveRelearnerStateMachine beginning with fade and setup states. So the outer callback gives the feature a place to run every frame. The state machine gives it memory of where it is in the interaction.

That matters because a menu like this cannot usually complete in one function call. It waits for fades. It waits for text printing. It waits for button input. It waits for list menu movement. It waits for confirmation windows. A desktop program might block on a modal dialog, but a GBA game loop cannot just freeze the world in a normal blocking call and hope everything renders correctly. The work is sliced across frames. Each state does a small amount of work, then returns control to the engine. On the next frame, the callback comes back, the task or state variable is inspected again, and the machine advances if the condition is satisfied.

That is why state names like “print text then fanfare” or “wait for trying to learn” are not cosmetic. They encode frame discipline. A print state starts an operation. A wait state observes completion. A confirmation state interprets input. A commit state mutates data. If you collapse those responsibilities, you get timing bugs, double-input bugs, or mutation bugs.

Let’s do a quick spoken trace. Player talks to the move relearner NPC. The overworld script decides to launch a special interaction. The game moves away from ordinary overworld control into the relearner callback. The relearner init function prepares list scroll positions and menu resources; that is identified in the state machine description in pokefirered/src/learn_move.c, chunk_af3598df6f092723. The machine fades in, builds or displays the list of eligible moves, waits for list input, captures the chosen move, asks confirmation, and then follows either the empty-slot path or the replacement path. At the end, it sets an event variable reporting the result, so the script layer can continue with the appropriate dialogue or behavior. The player experiences this as a polite NPC conversation. Architecturally, it is data tables feeding UI state feeding party mutation feeding script-visible result state.

Now, I want to underline the event-variable part, because it is easy to miss. The move relearner is not an isolated mini-app. It is embedded in an NPC script flow. That means the menu must return an answer in a form the script system can understand. From an engine perspective, the menu does not merely teach or not teach; it reports. It reports “yes, something was learned,” or “no, nothing changed,” or whichever boolean or result convention that feature uses. The excerpt in chunk_e820ee15ae8caa6d mentions VAR_0x8004 being set to TRUE when the confirmed move can go into an empty slot. That is a very concrete clue. It tells us that the UI state machine and event script world communicate through event variables. That is a recurring FireRed pattern: specialized C code does complex work, then leaves a small result in script-visible storage.

Here is the architectural lesson. Scripts are good at sequencing story logic and NPC dialogue. C callbacks are good at real-time UI and data mutation. Globals and event variables are the bridge. If you make the script directly perform every tiny UI step, the script language becomes overburdened. If you make the C menu own the entire NPC conversation forever, the feature becomes hard to compose with the map and event system. FireRed splits the responsibilities.

Let’s pause for a check. Suppose the player cancels at “Stop trying to teach this move?” What should happen to three different things? First, the Pokemon’s move slots should remain unchanged. Second, the menu state should move toward a cleanup or return path rather than continuing to replacement. Third, the script-visible result should indicate that the teaching did not happen. If you can state those three outcomes separately, you are reading the machine correctly. If you merge them into “the player canceled,” you lose the architecture.

Now, let’s talk about the move replacement screen itself. You have seen a version of this in normal level-up learning. A Pokemon has four moves. A new move is proposed. The player is asked whether to forget an old move. The player selects one of the four old moves. There is usually an extra cancel option or a cancel path. Then the game asks for final confirmation before replacing. The reason this flow is reused conceptually is that the data model is fixed-width. We are not teaching into a dynamic list of arbitrary length. We have four move slots and associated PP data. You already know that from our previous weeks, so I am not going to re-teach the representation. What matters today is how the UI respects the fixed-width model.

The replacement menu has to present four existing moves as choices. But selection is still not mutation. Highlighting “Tackle” or “Growl” or “Quick Attack” does not forget it. Even pressing A on the old move may not be the final commit if the game asks an additional confirmation. The clean way to reason about it is: candidate new move, candidate old slot, final confirmation, write. Each piece of information becomes valid at a different state. Before selecting an old move, there is no old slot to overwrite. Before final confirmation, the selected old slot is provisional. After final confirmation, the write can happen.

If you were implementing the lab version, this is where I would expect you to have a small model object or struct-like record for the flow. It might contain the party slot, selected new move, selected old move slot, and result. The summary screen model from the workshop should read from creature data but not mutate it. Then the move-learning flow should mutate only after confirmation. That lab is not asking you to imitate FireRed line for line. It is asking you to preserve the same responsibility boundaries.

And this is also where advanced students sometimes over-engineer. They say, “Professor, should I make the whole thing a formal transaction object with rollback?” For the lab, maybe not. For the FireRed architecture, also not usually. The discipline is achieved by state ordering and by keeping writes out of early UI states. That is enough for this feature. Remember our recurring design principle for this course: match the abstraction to the pressure actually present in the source. FireRed often uses small state variables and global or task-local storage because the platform rewards predictable, compact control flow.

Let’s bring pokemon.h and pokemon.c into the conversation, but only as much as we need. The source focus includes pokefirered/include/pokemon.h, chunk_9b5ff7d0cd9f4c67, and pokefirered/src/pokemon.c, chunk_9c96aedd5b8237e6. We have already treated the Pokemon interface as the boundary around party data. Today, the relevant idea is that UI code should go through helper functions rather than casually reaching into every internal field. That does two things. It centralizes data rules, and it lets different systems, summary, party menu, battle setup, move teaching, speak a common language about a Pokemon. So when the move relearner finally commits, it should use the established Pokemon helper path. When the summary screen displays, it should use the established read path. The common API is part of how the game avoids every UI screen inventing its own interpretation of party memory.

Now imagine debugging a bad PP value after move relearning. The player says, “I relearned a move, and it appeared with zero PP.” Where do we trace? We start at the commit state, not at the list display. We ask: when the move was installed, did the code also initialize the PP slot appropriately? Did it call the right helper? Did it preserve the other three moves and PP values? Did it only modify the selected slot? Then we compare with the Pokemon helper flow in pokefirered/src/pokemon.c, chunk_9c96aedd5b8237e6, and the interface expectations exposed through pokefirered/include/pokemon.h, chunk_9b5ff7d0cd9f4c67. The exact point is not that you memorize one helper name today. The point is that writes to creature state belong at a narrow, auditable boundary.

This is a good time for a recap. So far in this part of the lecture, we have separated the move relearner into four layers. First, the data layer determines what moves are possible, based on the Pokemon and its learn history or eligible level-up moves. Second, the UI layer presents a list and collects a candidate move. Third, the replacement state machine protects confirmation and old-slot selection. Fourth, the Pokemon helper layer performs the actual mutation and reports the result back to the script world. That is the whole stack we keep tracing in this course: data records, runtime globals, callbacks, tasks, scripts, structs, and concrete source functions.

Let’s push the sophistication one notch higher. A state machine does not only model what happens. It also models what cannot happen. In a well-structured move-learning machine, the player cannot select an old move before choosing a new move. The system cannot overwrite a move before it knows which old slot is selected. The script cannot continue as though teaching succeeded before the menu reports success. The fanfare cannot play before the successful learning text has been scheduled. These impossibilities are part of the design. When you read named states in chunk_e820ee15ae8caa6d, look for those negative guarantees. Ask, “What invalid sequence is this state boundary preventing?”

This is also why the fade and setup states at the beginning are part of the same architecture, not merely polish. The initialization path in pokefirered/src/learn_move.c, chunk_af3598df6f092723, includes creating list menu scroll positions and handling fade into the menu state machine. If list state is not initialized before input is accepted, the cursor can point at nonsense. If windows are not initialized before text is printed, you get visual corruption. If the callback changes before the transition is ready, you can get a frame where old and new systems both assume ownership. These are small platform-specific details, but they reflect the same broader rule: state transitions protect invariants.

Now, check for understanding with a slightly tricky case. The Pokemon has three moves, so there is one empty slot. The player selects a relearnable move and confirms. Should the game ask which move to forget? No. There is no replacement problem. The correct path is the empty-slot commit path. The source excerpt in chunk_e820ee15ae8caa6d explicitly distinguishes the confirmed case where the Pokemon had an empty move slot from the case where it does not. That branch is not just a convenience. It prevents unnecessary UI and reduces the chance of accidental deletion. The data model allows four slots; if one is empty, the commit can fill it without asking for an old slot.

Now invert the case. The Pokemon has four moves. The player selects a new move, confirms interest, then cancels at the delete-old-move prompt. What should VAR_0x8004 say? Based on the role described in the chunk, it should not report successful teaching. The player’s party data should remain unchanged. The script should resume as a non-learning or canceled outcome. If your implementation sets the result variable as soon as the player says, “Yes, teach this move,” you have a bug. That first yes means “continue the transaction,” not “commit the transaction.”

That distinction is worth writing in your notes: intent is not commit. In UI programming, especially game UI, the player often expresses intent several times. Select this Pokemon. Select this move. Yes, I’m interested. Yes, forget that old move. Yes, that is final. Only one of those steps should trigger the write. The rest are navigation and intent-gathering.

Let me relate this to the broader week. On the previous day, we looked at party data across gameplay screens. We emphasized party slot ownership and summary screen data reads. The summary screen demonstrates how much can be built from disciplined reading. Today, the move relearner demonstrates the extra machinery required when a screen becomes a writer. That is why these two lectures sit together. They are not separate trivia topics. They are a pair: read-only party UI and mutating party UI.

And looking forward to the week 11 homework, this is exactly the kind of trace I will expect. Do not just say, “The move relearner teaches moves.” That is user-level description. I want the technical path. Which source file establishes the menu state machine? pokefirered/src/learn_move.c, chunks chunk_af3598df6f092723 and chunk_e820ee15ae8caa6d. Which other screen provides contrast for read-heavy party inspection? pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46. Which interface family protects Pokemon data access? pokefirered/include/pokemon.h, chunk_9b5ff7d0cd9f4c67, with helper behavior in pokefirered/src/pokemon.c, chunk_9c96aedd5b8237e6. Then explain how callbacks, tasks, scripts, event variables, and party slots cooperate.

Before we move on, I want you to do one more mental exercise. Picture the move relearner as a small database transaction, even though FireRed is obviously not using a database. The party Pokemon is the durable record. The selected new move and selected old slot are temporary transaction state. The prompts are validation and authorization steps. The final helper call is the write. VAR_0x8004 is the return code to the caller. That analogy is not perfect, but it is useful because it forces you to ask where the durable record changes.

Now, keep that transaction picture in your mind as we continue into the exact replacement branch, because the next important thing is how the menu uses ordinary player-facing text to enforce technical order. The “trying to learn” line, the “but it already knows four moves” idea, the “delete an older move?” question, and the “which move should be forgotten?” prompt are all narrative wrappers around state transitions. The player thinks they are reading dialogue. The engine is stepping through a guarded write protocol.

So, let’s stay inside that guarded write protocol for a minute. Imagine the player has a Charizard with four moves, chooses an old level-up move from the relearner list, and then reaches the familiar question: should one of the old moves be deleted? In user-interface terms, this is friendly dialogue. In architecture terms, the system has not yet modified the party record. That distinction is the whole point. The player is walking through a sequence of reversible states, and every reversible state is allowed to redraw windows, play sounds, update cursor position, and change temporary menu variables. But it is not allowed to commit the new move into the Pokémon’s move array until the confirmation path reaches the write state.

Let me put that on the board. Left side: durable party slot. Middle: transaction state. Right side: script-visible result. Under durable party slot, I’m writing the Pokémon object in gPlayerParty, though we’re not going to re-teach that representation. Under transaction state, I’m writing selected move, selected replacement slot, current menu state, and list scroll information. Under script-visible result, I’m writing VAR_0x8004. Now draw arrows only from transaction state to durable party slot at the commit moment. Do not draw arrows from every prompt. That is the mistake I want you to avoid in your own architecture diagrams.

This is why the comment block in pokefirered/src/learn_move.c, chunk_af3598df6f092723, is more than documentation trivia. It tells us the move relearner is deliberately organized around CB2_MoveRelearner_Init and MoveRelearnerStateMachine, beginning with fade and setup states, list menu state, input states, and then later confirmation states. The state machine is not just “a menu.” It is a small transactional controller. It owns what the player is currently considering, and only later delegates to the helper flow that mutates Pokémon data. When we connect this to the overall course plan, this is exactly the same pattern we have been tracing since the runtime-shell weeks: data records are passive, globals identify the current live object or UI context, callbacks and tasks pump frame-by-frame behavior, and specific concrete functions are the only places where durable state changes.

Now, a subtle point. When the player is asked which move should be forgotten, the menu displays the existing four moves. It feels like the UI has already “entered” the Pokémon’s moveset. But technically, display is a read. Selection is not mutation. Highlighting Flamethrower or Slash or Wing Attack does not delete anything. It assigns meaning to a cursor position. That cursor position becomes a candidate old slot. Only when the player confirms the replacement does the candidate become an instruction. And only when the instruction is applied through the Pokémon interface does it become durable.

Check your understanding here. If I ask, “Where does the move get replaced?”, I do not want the answer, “on the screen where the player picked the old move.” That is a user story answer, not an engine answer. The better answer is, “after the confirmation branch reaches the teaching or replacement helper, through the Pokémon data interface.” The exact source packet supports that split: pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d, describes MENU_STATE_TEACH_MOVE_CONFIRM, the empty-slot branch, the trying-to-learn prompt, the delete-old-move question, the which-move prompt, and later stop-teaching behavior. Those are not all writes. Most of them are staged control flow.

Let’s make the example concrete. Suppose the relearner offers Dragon Claw. The selected Pokémon already knows Wing Attack, Flamethrower, Slash, and Fly. The list menu state captures Dragon Claw as the proposed new move. The system asks whether to teach it. The player says yes. Because there is no empty move slot, the state machine does not write Dragon Claw immediately. Instead, it moves to the “trying to learn” narrative sequence and then to the question about deleting an old move. If the player says no at that point, the correct final result is no mutation. The proposed Dragon Claw disappears as a transaction candidate. The party slot still has Wing Attack, Flamethrower, Slash, and Fly. That is what a cancellable UI flow demands.

Now suppose the player says yes, chooses Slash, and confirms. At that moment, the old slot index becomes meaningful. It is no longer just cursor state. It is the write target. The helper flow can now set move slot two, or whichever index corresponds to Slash in that example, to Dragon Claw and update the associated PP state. I am deliberately saying “associated PP state” because, from our prior lectures, you already know move slots and PP travel together as gameplay data. We are not re-opening the packed representation lecture. But when you build your lab version, do not merely replace the move id and forget that a real move-learning flow has to leave the creature in a coherent move state.

Notice how this mirrors the summary screen from yesterday’s lecture. In pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, the include surface alone is evidence of how much context a party-facing screen touches: pokemon.h, pokemon_summary_screen.h, task.h, menu helpers, moves constants, item constants, party menu, battle constants, region map, and more. But summary screen behavior is primarily read-oriented. It gathers data from the selected party or box slot and renders pages. Move learning has a similar UI density, but a different permission profile. It is allowed to mutate party data, but only through a narrow confirmed path. That is the contrast I want you to carry into the lab: one screen is a reader with navigation; the other is a reader plus a guarded writer.

Let me pause for a quick diagnostic question. If you were designing a clone and your summary screen and your move relearner both received a direct mutable reference to the creature object, what bug class have you invited? Yes, accidental writes from read-only screens. Maybe the summary screen normalizes a move list, maybe it lazily fills a missing PP value, maybe it caches something in the creature object because that was convenient. In FireRed’s C code, the boundaries are not expressed with modern type-system immutability, but architecturally the separation still exists through convention, helper APIs, and state-machine placement. In your project, you can make that separation more explicit. Your summary model can receive a read facade. Your move-learning flow can receive a transaction object that commits only at the end.

Now look at the return path. VAR_0x8004 matters because the move relearner is not the whole game. It is entered from a script-driven overworld interaction, and the script needs to know what happened after the UI is closed. Did the player teach a move, or not? Did the transaction commit? In chunk_e820ee15ae8caa6d, the comment explicitly mentions setting VAR_0x8004 to TRUE when the confirmed teach path succeeds with an empty slot. That small event variable is the bridge from a callback-driven menu back to script-visible control flow. This is one of those places where FireRed’s architecture forces us to stop thinking in a single call stack. The NPC script starts the interaction. The game changes callback context into a menu. The menu runs over multiple frames. The player acts. The menu exits. A variable records the result. The script resumes or branches based on that result.

That is a pattern you have seen in other systems, and now you should name it fluently. Script starts. Callback owns. Task or state machine advances. Global or event variable reports. Script continues. In week one, that may have sounded like a bag of unrelated mechanisms. By week eleven, it should read as a standard crossing pattern between runtime subsystems. And for the midterm in week twelve, when you are asked to trace behavior from data records through globals, callbacks, tasks, scripts, structs, and functions, this is exactly the level of answer that earns credit. Not “the move relearner opens a menu.” That is too shallow. “The script enters a callback-based menu state machine, which stages a selected move and replacement slot, commits through Pokémon helpers only after confirmation, then reports success through VAR_0x8004.” That is the architecture answer.

Let’s do one more board pass, this time as a timeline. Frame zero: the field script triggers the relearner flow. Next: callback changes away from ordinary overworld control into the relearner initialization, named in the source packet as CB2_MoveRelearner_Init in pokefirered/src/learn_move.c, chunk_af3598df6f092723. Next: setup states build windows, list menu state, and cursor tracking. Next: input states choose a candidate move. Next: confirmation states ask whether to teach. Next: branch on empty slot. If empty, commit can be direct. If full, enter replacement dialogue. Next: select old move slot. Next: confirm replacement. Next: write. Next: set script-visible result. Next: exit back toward the caller.

That timeline also explains why the menus need so many states for what feels, to a player, like a simple sequence of questions. On the Game Boy Advance, UI is not blocking in the way a command-line prompt blocks in a beginner programming example. The game must keep updating graphics, waiting for text printers, handling fades, detecting button input, and advancing only when each visible operation is complete. So “print text” and “wait for text” often become separate states. “Ask yes/no” and “process yes/no” become separate states. “Fade out” and “run after fade” become separate states. The source packet’s learn_move.c chunks show that organization directly: named menu states break the teaching flow into small frame-safe steps.

Now, I want to connect that to error prevention. A menu state machine like this helps avoid two important classes of bugs. First, it avoids premature mutation. You do not overwrite a move just because the player entered the replacement screen. Second, it avoids ambiguous exit behavior. Every cancel path has to land somewhere: maybe back to the list, maybe to a stop-teaching prompt, maybe to final exit with failure. In chunk_e820ee15ae8caa6d, the notes mention that canceling from the delete-old-move confirmation goes to MENU_STATE_PRINT_STOP_TEACHING. That means cancel is not merely “return false.” It has its own user-visible route. The player sees the game acknowledge the canceled teaching attempt, and the engine has a clean branch that preserves the original moveset.

In your lab, I want you to model that explicitly. You do not need the exact FireRed windowing code. You do not need the exact GBA text printer. But you do need the conceptual states. For example, your model might have ChoosingMove, ConfirmTeach, PromptReplace, ChoosingOldMove, ConfirmForget, Commit, Cancelled, and Done. Those names are yours, not source names, so be careful if you cite FireRed. But the underlying reason for the names is source-grounded: FireRed’s learn_move.c chunks separate list selection, confirmation, trying-to-learn prompts, delete confirmation, old-move selection, and commit behavior.

Let’s check comprehension with a small scenario. The player selects a new move, confirms teaching, sees that the Pokémon already knows four moves, agrees to delete an old move, opens the old move list, then presses B before choosing. What should happen to the durable move list? Nothing. What should happen to the transaction state? It should either clear or route to a cancel/stop-teaching branch. What should happen to the script-visible result? It should not report successful teaching. The exact text flow may vary, but the technical invariant is non-negotiable: no confirmed replacement slot, no write.

Another scenario. The Pokémon has only three moves. The player selects a relearner move and confirms. There is no need to show the replacement selector, because no old move has to be forgotten. The empty slot is the write target. That branch is mentioned in pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d: if confirmed and the Pokémon had an empty move slot, VAR_0x8004 is set to TRUE and the flow goes to the text-and-fanfare path. So the state machine is not just linear dialogue. It is inspecting creature state and choosing a shorter or longer transaction route.

This is where pokemon.h and pokemon.c fit into the larger architecture, and I’ll keep this brief because we have already covered GetMonData-style APIs and party storage. The source focus for this week includes pokefirered/include/pokemon.h, chunk_9b5ff7d0cd9f4c67, and pokefirered/src/pokemon.c, chunk_9c96aedd5b8237e6, precisely because the menu should not know every storage detail. The menu knows it is teaching a move to a selected Pokémon. The Pokémon helper layer knows how to read and write the relevant fields coherently. That separation is what lets the same creature record participate in summary screens, battle preparation, daycare, item use, and move learning without each UI becoming a private storage implementation.

And this is also why I do not want you to describe the move relearner as “just a wrapper around SetMonData.” That would erase the hard part. The hard part is the temporal protocol. The code must preserve player intent across many frames before the final helper call. It must make cancellation reliable. It must return to the script world with a usable result. It must draw windows and menus without corrupting the creature. The helper write is important, but the state machine is what makes the write correct in context.

Now, contrast this with Pokémon Showdown for a moment, only as a secondary comparison. The research packet repeatedly flags pokemon-showdown data and simulator modules as useful laterally, especially for battle data hooks and deterministic tests, but FireRed remains the authority for this architecture course. In a Showdown-style simulator, a move’s behavior may be represented as a data object with event hooks, like the move examples in pokemon-showdown/data/mods/gen9ssb/moves.ts, chunk_f562e1983e9fdeff, where a move can define callbacks such as onTryMove, onPrepareHit, or condition behavior. That is a very different problem from FireRed’s move relearner. Showdown’s hook object explains how a move behaves during simulation. FireRed’s learn_move.c state machine explains how a move becomes installed in a party member’s move slots from an overworld UI. Both are data-driven creature RPG architecture, but they sit at different layers.

That distinction matters for your final projects. If you model move learning as “append a move to a creature object,” you have missed the FireRed lesson. If you model move effects as menu states, you have also mixed layers. Move eligibility, move installation, and move execution are separate architectural concerns. Eligibility asks, “May this creature learn this move?” Installation asks, “How does a player-authorized UI flow mutate the move slots?” Execution asks, “What does this move do in battle?” Week eleven is focused on installation from party UI flows, leaning on the earlier weeks where we studied species records, move slots, PP, and learnsets.

Let’s recap this segment before we continue. The replacement branch is a transaction. The selected new move is not enough to mutate data. The selected old slot is not enough until confirmation routes through the commit path. Most menu states are there to stage, explain, validate, wait, or cancel. The durable write belongs at the end of the confirmed path. VAR_0x8004 is the small but crucial bridge back to the script caller. And the same party Pokémon can safely appear across summary, battle-adjacent UI, and move learning because FireRed keeps using shared Pokémon interfaces rather than letting each screen invent its own storage rules.

So as we move forward, I want you to listen for state names as evidence of responsibility. When a state says print, wait, confirm, choose, or setup, ask what it is allowed to change. When a state says teach, replace, or set result, ask whether this is the commit point or merely a transition toward it. That habit is how you read old C game code without getting lost in the window code. You keep asking, “What is durable here, what is temporary here, and who needs to know the outcome when this callback gives control back?”

Now, let me put that habit onto the board in a way that is almost mechanical. I am going to write three columns: temporary UI state, party data, and script-visible result. Under temporary UI state, I am putting list position, selected move, prompt window, yes-no choice, and replacement cursor. Under party data, I am putting the actual move slots and PP slots on the Pokémon. Under script-visible result, I am putting the result variable, the thing the field script can inspect when the move relearner callback eventually returns control.

That separation is the architecture. It is not just an implementation detail. In a small student project, people often collapse all three columns into one object called, say, MoveLearnMenu, and then they wonder why backing out of a menu accidentally changes the creature, or why a script cannot tell whether the player taught the move or canceled. FireRed is old C, yes, but it is disciplined here. The UI is noisy. It has windows, palettes, list menus, button handling, fades, sound effects, and text printers. The creature record is durable. The script result is a contract with the overworld script that launched the interaction. Those are different lifetimes.

If you look at pokefirered/src/learn_move.c, chunk_af3598df6f092723, the comment block itself is unusually helpful because it labels the move relearner as a state machine. That is not me imposing a modern framework vocabulary onto it after the fact. The source is explicitly organized around CB2_MoveRelearner_Init and MoveRelearnerStateMachine, with states such as fading, setting up list menu behavior, handling input, and preparing the next prompt. The file includes script.h, task.h, event_data.h, pokemon_summary_screen.h, list_menu.h, menu.h, and constants/moves.h. Those includes are already telling you the shape of the dependency graph. This screen is not merely drawing a menu. It is crossing between the script system, the task system, menu infrastructure, party Pokémon data, and move constants.

And notice something subtle there. The move relearner is not implemented as a pure script command. The script can initiate the interaction, but the interaction is too interactive and too frame-by-frame to live comfortably inside the field script interpreter. So the script hands off to a callback-driven menu state machine. That is a course-wide pattern. We saw it in map transitions, we saw it in party menu handoffs, and now we see it again in move learning. When behavior requires per-frame input and rendering, FireRed typically leaves the script VM and enters a callback or task-managed UI mode. When the UI is finished, it writes a small result back into event data or globals, and the script resumes from there.

Let me give you a concrete example. Imagine the player talks to the move relearner NPC and chooses a Charizard in party slot zero. Again, I am not going to re-teach the Pokémon and BoxPokemon representation; that was week nine material. What matters today is ownership. The selected party slot points to durable party storage. The list of relearnable moves is derived from species and level-up move data, and the displayed cursor position is temporary menu state. Suppose the player highlights Slash. Highlighting Slash does not teach Slash. Moving the cursor over Slash does not teach Slash. Printing “Teach Slash?” does not teach Slash. Even pressing A on Slash may still not be the commit point, because the Pokémon may have a full move set and the game still has to ask which old move to forget.

That is where the second learn_move.c chunk becomes important. In pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d, the documented flow includes MENU_STATE_TEACH_MOVE_CONFIRM, then paths for cancellation, empty move slots, full move slots, trying-to-learn prompts, delete-old-move confirmation, and which-move prompts. The language in that excerpt gives us the responsibility boundaries. If the player confirms and the Pokémon has an empty move slot, the state can go toward teaching directly and set VAR_0x8004 to TRUE before the success fanfare path. But if the Pokémon does not have an empty move slot, the state machine has to enter the replacement dialogue. It is not enough to know the desired new move. The system must also know which old move, if any, the player has consented to delete.

Check your understanding here. If I ask you, “At the moment the player first chooses Slash from the relearnable move list, has the party Pokémon changed?” the answer should be no, or at least, not by architecture. That selection is intent, not mutation. If I ask, “At the moment the player confirms forgetting an old move and the state reaches the actual replacement operation, can the party Pokémon change?” now the answer is yes. That is the commit boundary. In your own lab implementation this week, that is the difference between storing pendingMoveId and calling setMoveSlot.

Let me draw the flow on the board as words, not code. Script asks for move relearner. Callback initializes menu. Menu derives candidates. Player chooses candidate. Game asks for confirmation. If there is an empty slot, teach. If there is no empty slot, ask whether to forget something. If yes, open replacement selector. Player chooses old move. Game confirms or returns. Then and only then mutate the Pokémon move slots and PP. Finally, set the result that the caller can observe, clean up the UI, and return to the overworld context.

Now, why am I emphasizing the “then and only then” so strongly? Because advanced architecture work is often about locating the mutation boundary. You can read thousands of lines of UI code and still miss the design if you do not ask where the durable object is actually written. The summary screen makes a useful contrast. In pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, we saw a broad set of dependencies: pokemon.h, pokemon_summary_screen.h, task.h, menu_helpers.h, link.h, overworld.h, constants/items.h, data.h, item.h, party_menu.h, constants/moves.h, dynamic_placeholder_text_util.h, and more. That screen reads a lot. It displays species, item, moves, battle-adjacent information, and page state. But in the ordinary summary inspection path, the screen is primarily a reader of party data. It is a view over a selected slot. The move relearner, by contrast, begins as a reader but may become a writer after confirmation.

That distinction matters for the lab. A summary screen model should read from a creature object through a stable interface, cache presentation fields if it needs to, and maintain its own page cursor. It should not casually mutate the creature because the player paged from the info tab to the moves tab. A move-learning flow, however, needs a pending transaction. It reads the current moves, presents options, asks for confirmation, and eventually commits to the same party storage the summary screen was reading from. In a clone architecture, both screens can point at the same party slot, but they should not share the same mutation permissions at every state.

Let us connect this to pokemon.h and pokemon.c without reopening the whole data model. The reason FireRed can let many screens touch party Pokémon safely is that it funnels common operations through shared Pokémon interfaces. The course packet lists pokefirered/include/pokemon.h, chunk_9b5ff7d0cd9f4c67, as the continuation of the Pokémon interface, and pokefirered/src/pokemon.c, chunk_9c96aedd5b8237e6, as Pokémon helper flow. Those modules are the bridge between high-level gameplay code and the creature’s stored fields. A screen like the summary screen should not invent an alternate decoding of creature data just because it wants to draw text. A teaching flow should not invent an alternate move-slot layout just because it wants to replace a move. They call into the shared interface. That is what keeps the course’s larger “data records through globals, callbacks, tasks, scripts, structs, and concrete functions” chain from becoming a pile of local exceptions.

Here is another way to say it. Data-driven does not mean “everything is a table.” FireRed is data-driven where it should be: species records, move constants, learnset tables, item data, map headers, object events. But the moment a player is choosing, canceling, confirming, and causing side effects, you need state machines. The table can say that Charizard may learn Slash at some level or may be eligible to relearn it under the game’s rules. The table cannot by itself decide whether this particular player, at this particular prompt, wants to forget Flamethrower. That choice belongs to runtime UI state, and the result of that choice becomes a mutation on a particular party slot.

Pause on that sentence, because it is the bridge from week ten into week eleven. In week ten, when we used Pokémon Showdown only as a secondary comparison, we talked about clean battle data hooks and deterministic tests. A modern simulator often expresses move data in rich JavaScript or TypeScript objects with event handlers. The research packet includes examples from pokemon-showdown/data and mods, and that is useful when we compare battle effect modeling. But FireRed’s move relearner is not primarily a battle simulator problem. It is a field UI and party mutation problem. So we keep FireRed as the authority here. The relevant evidence is the FireRed source path pokefirered/src/learn_move.c, especially chunks chunk_af3598df6f092723 and chunk_e820ee15ae8caa6d, not a Showdown battle effect file. That judgment about which source is authoritative is part of becoming advanced at architecture reading.

Now let us inspect the replacement flow conceptually. Suppose the Pokémon already knows four moves. The selected new move is pending. The old move list is displayed. At this point, the system is holding two identities: newMove and oldMoveCandidate. The old move candidate changes as the cursor moves. Still no mutation. If the player presses B, the state machine must return to some earlier prompt or stop teaching, depending on the precise state. If the player presses A, the state machine may ask, “Forget this move?” Again, confirmation text is not mutation. Only after confirmation should the helper that writes the move slot be called. And when it writes, it must keep move ID and PP coherent. You do not want a move slot containing Slash with the PP of the forgotten move, unless the engine has a deliberate rule for that. The helper layer is where that coherence belongs.

This is why I want you to avoid the tempting implementation in your lab where the UI directly writes party.moves[index] = pendingMove the instant the replacement cursor lands on a row. That implementation will appear to work in the shortest happy path. Then the first cancellation test will reveal the bug. The player moves the cursor to Flamethrower, thinks better of it, presses B, and somehow Flamethrower is gone. That is not a rendering problem. That is a transaction boundary problem.

Let me ask a diagnostic question. If you were writing a unit test for your lab, what would you assert after the player opens the move replacement menu, highlights the second move, and cancels? You should assert that all four original move slots are unchanged. You should also assert that no success result has been returned to the script-level caller. If your architecture has a pendingMove field, it may still exist temporarily during the interaction, but it must not be reflected in durable party data. The test is not just about behavior; it encodes the ownership model.

Now, the callback and task pieces are worth one more pass, because this is where students sometimes flatten FireRed’s runtime into “some function calls some other function.” That is not enough. The main callback controls which high-level mode is running. A task can provide per-frame behavior inside that mode. The script system can initiate an interaction and later resume. In the move relearner, CB2_MoveRelearner_Init is the entry into the menu mode described in pokefirered/src/learn_move.c, chunk_af3598df6f092723. The state machine then advances through states as input, printing, list handling, and confirmation complete. So the flow is not a single blocking function call like teachMoveAndReturnBoolean. It is spread over frames. The result has to survive across frames, and the selected party slot has to remain identifiable across frames. That is why globals, task data, and event variables appear in these systems.

For a modern engine, you might implement this with an async function, a coroutine, a modal stack, or a scene state machine. Fine. But the responsibilities would be the same. You still need pending selection, visual state, durable party mutation, and caller result. Do not let the syntax of the old GBA engine distract you from the architecture. FireRed uses C callbacks and task arrays. Unity might use scenes and MonoBehaviours. Godot might use nodes and signals. A React-based tool might use component state and reducers. The question remains: what is temporary, what is durable, and who is waiting for the outcome?

Let me do a quick recap before we push into the next layer. The summary screen is our clean reading example. It is broad, it touches many concepts, and its include list in pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, shows that it sits near the intersection of party, menu, item, move, battle, and overworld systems. But its normal responsibility is display and navigation. The move relearner is our controlled writing example. Its state machine in pokefirered/src/learn_move.c, chunk_af3598df6f092723, handles menu setup and input. Its later teaching and replacement path in chunk_e820ee15ae8caa6d shows that the write to the Pokémon is gated behind confirmation and slot conditions. The shared Pokémon interface in pokefirered/include/pokemon.h, chunk_9b5ff7d0cd9f4c67, and helper flow in pokefirered/src/pokemon.c, chunk_9c96aedd5b8237e6, are what keep that mutation consistent with the rest of the game.

Now, let us connect this to the week eleven homework, because the assessment is no longer asking you to merely identify files. Earlier quizzes and homework checked whether you could name the systems, trace callbacks, and recognize the source modules. At this stage, I expect you to infer responsibility from structure. When you submit the move-learning portion, I am not looking only for “does the Pokémon end up with the move?” I am looking for whether your menu states make cancellation safe, whether your result variable is set at the correct time, whether your summary model reads without mutating, and whether your move replacement code uses one coherent creature-data interface rather than scattering writes through UI handlers.

If you need a mental model, use the phrase “pending intent becomes committed data.” The selected list row is pending intent. The yes-no prompt is pending intent. The replacement cursor is pending intent. The confirmed replacement operation is committed data. The script-visible success value is committed outcome. And those two committed facts are related but not identical. The Pokémon may be updated, and then the caller must be told that teaching succeeded. Or the interaction may end without mutation, and the caller must be told that teaching did not happen. That caller might branch to different dialogue, consume or not consume an item, continue or abort a script path. So the result variable is not decorative. It is part of the contract between the UI state machine and the overworld script.

One last check for understanding before we move on. Imagine a bug report says, “After I cancel learning a move, the NPC still says the move was learned.” Which column on the board would you inspect first? Not the move slot column. You would inspect the script-visible result column. That bug suggests the party data may be unchanged, but the result flag was set too early. Now imagine a different bug report says, “After I cancel at the replacement screen, one of my old moves is already gone.” That is the party data column. That suggests the mutation happened during selection rather than confirmation. Same feature, different broken boundary.

And that is the level of precision I want you to carry into the next part. We are going to keep following the same handoff, but we will zoom in on the return path: how a modal gameplay screen cleans itself up, how it restores or hands back control, and how the surrounding script interprets the result. Because the architecture is not complete when the move is written. It is complete when the game has returned to a coherent mode, the UI resources are no longer pretending to own the screen, and the script that started all this can make the next visible thing happen.

So, continuing from that handoff, look carefully at the phrase I just used: returned to a coherent mode. That is not just a polite way of saying “go back to the overworld.” In this engine, coherent mode means the current main callback is appropriate for the screen, the active task list contains tasks that make sense for that callback, temporary windows and menu resources have either been freed or are no longer referenced, and any result that the caller expects has been placed somewhere the caller actually reads.

Let me put that on the board as a runtime chain, but I will say it out loud rather than treating it as a diagram only. We begin in an overworld script. That script gives the player a service, the move relearner. The script cannot itself run a rich scrolling menu every frame, because scripts are not the per-frame UI scheduler. So the script launches or yields to a callback-driven screen. That screen uses a state machine, and in `pokefirered/src/learn_move.c`, chunk `chunk_af3598df6f092723`, the file itself frames this as the “Move relearner state machine,” beginning with `CB2_MoveRelearner_Init` and then `MoveRelearnerStateMachine`. That is already a strong architectural clue. This is not a single function that asks a question and immediately returns a value. It is a small modal application.

Now, if it is a modal application, it has a contract. The caller says, in effect, “I am giving you temporary ownership of the screen and input loop. When you are done, leave the game in a state where I can continue.” And the move relearner says, “I will tell you whether a move was learned or not, and I will mutate the party only at the confirmed point.” That contract is what we have been building toward all week.

Notice the difference between this and the summary screen. The summary screen, in `pokefirered/src/pokemon_summary_screen.c`, chunk `chunk_e8e17a42bbf17d46`, pulls together `pokemon.h`, `task.h`, `menu_helpers.h`, `party_menu.h`, `constants/moves.h`, and related UI and data headers. That tells us it is also a screen that reads party data through shared interfaces and presents it through tasks and menus. But its normal job is observational. It renders a Pokemon’s held item, stats, moves, ribbons, origin information, or whatever page you are on. It may have menu actions in certain contexts, yes, but the core summary pattern we used in lab was: read creature data, model it for the UI, and do not mutate the party as a side effect of displaying it. The move relearner is different. It is a UI state machine whose whole purpose is to eventually mutate a move slot, but only after the player has traveled through a sequence of prompts that makes the mutation legitimate.

That distinction matters for the return path. A read-only screen can often exit by restoring the previous callback and discarding its windows. A mutating modal screen has to do that and also communicate outcome. If it taught a move, the script may need to print a success message, take payment, consume an item, set a variable, branch to another dialogue, or simply continue from the next command. If it did not teach a move, the script needs a different answer. In the move relearner excerpt from `pokefirered/src/learn_move.c`, chunk `chunk_e820ee15ae8caa6d`, the state comments explicitly mention `VAR_0x8004` being set to `TRUE` when the move is confirmed and there was an empty slot. That is a beautiful small detail, because it shows the bridge between UI state and script-readable global state.

Pause there and check your understanding. If I ask, “Why not just return a C boolean from the move relearner function?” the answer is that the move relearner is not executing as a synchronous call on the same stack as the script. It is spread across frames. It is living under the main callback and task system. The original script cannot just wait inside a C call in the ordinary way, because the game still needs VBlank, input polling, tasks, rendering, window updates, fades, music, and all the other per-frame work. So the result has to survive across frames and across control ownership boundaries. That is why globals and event variables are not just sloppy old C habits here. They are part of the protocol between asynchronous gameplay components.

Now let us follow one concrete path. Suppose the player talks to the move relearner, selects a party Pokemon, chooses Thunder Wave from the relearnable move list, and the Pokemon has an empty fourth move slot. The state machine has already gone through initialization, list construction, and input handling. When the player confirms the move, the modal does not need to open the “which move should be forgotten?” submenu, because there is an empty slot. At that point the move write can happen directly through the Pokemon data interface we have already studied, and the result variable can be set. The excerpt in `learn_move.c`, chunk `chunk_e820ee15ae8caa6d`, describes this branch as confirmed, empty slot, set `VAR_0x8004` to true, then go to the text and fanfare state. So the visible behavior is: the player sees confirmation, the game records the new move, the game acknowledges it, and then the screen exits.

But here is the important advanced part: “screen exits” is not one operation. It is several operations that must occur in an order that does not leave dangling assumptions. Input must no longer be routed to the list menu. The list menu’s scroll state should no longer be the active interpretation of D-pad presses. Text windows used for prompts should not remain as if they are still current. The callback has to transition back, usually through a fade or cleanup step. And whatever script or caller is waiting must observe the result after the state machine has finalized it, not halfway through.

This is why I keep pushing you to think in state machines rather than in feature labels. “Move relearner” is a feature label. “Move list selection,” “teach confirmation,” “empty slot branch,” “replacement prompt,” “forget selection,” “mutation,” “fanfare,” and “return to caller” are states or transitions. If your clone architecture has only a `teachMove()` function, you may be able to pass a unit test that checks the final move array, but you will not be modeling FireRed’s actual gameplay architecture. The FireRed lesson is that player-visible gameplay is stitched across records, globals, callbacks, tasks, scripts, structs, and source functions. That is the course thesis in miniature.

Let me bring in the task shell for a moment, because this is where week 1 and week 11 touch. Earlier in the course, we used `pokefirered/src/main.c`, especially the `SetMainCallback2` material in chunk `chunk_6cfd90f6d0136799`, to talk about the main callback slot as the current high-level mode. We also used `pokefirered/src/task.c`, chunks `chunk_cb0bb91a147bfb59` and `chunk_3ffebc0341225936`, for `CreateTask` and `RunTasks`. Do not hear that as ancient history. The move relearner is exactly the kind of screen that justifies that architecture. A state machine can run as a task or under a callback, advancing one small step per frame, while the engine continues to do the rest of its per-frame obligations. That is how FireRed avoids pretending that a menu is a blocking desktop dialog.

Now, imagine a bug report. “After I learn a move, the overworld music continues, but the screen stays black.” Where do you look? Do you start with the move data table? Probably not. The data table may be fine. Do you start with `SetMonData` or move slot mutation? Maybe later, but the symptom says return path. You would inspect the states after teaching, especially fade-out, cleanup, callback restoration, and script resumption. You would ask whether the state machine reached its terminal state. You would ask whether the callback was restored correctly. You would ask whether a task remained active but no longer had valid resources. You would ask whether the fade transition completed but no state advanced afterward. That is debugging by architectural boundary, not by keyword search.

Another bug report: “The move is learned, but the NPC acts like I canceled.” That points to the result channel. The party data mutation happened, but the script-readable result did not match it. In FireRed terms, from the excerpt we have, one suspicious boundary would be `VAR_0x8004`. Did the state that writes the move also set the variable? Did a later cancel branch overwrite it? Did the script read a different variable? Did the variable get initialized to false at the start and never flipped on one of the replacement branches? This is the same system, but now the broken link is not party mutation. It is UI-to-script communication.

And a third bug report: “I cancel after choosing a move to forget, but the success fanfare still plays.” That is a state transition bug. The replacement branch should distinguish confirm from cancel. The excerpt in `learn_move.c`, chunk `chunk_e820ee15ae8caa6d`, describes a `MENU_STATE_CONFIRM_DELETE_OLD_MOVE` path where confirming moves toward the “which move” prompt and canceling moves toward stop-teaching text. That is not just flavor text. It is a specification of the permitted transitions. In an advanced code review, you would not merely ask, “Does the old move remain?” You would ask whether the fanfare, result variable, and return path also align with the chosen branch.

Let me give you a concrete clone-design version, because this is close to your week 11 homework. In your model, you might have a `Party` object, a `Creature` object with four move slots, a `SummaryScreenModel` that reads from a party index, and a `MoveLearningFlow` that has states like `ChoosingMove`, `ConfirmTeaching`, `ChoosingReplacement`, `ConfirmForgetting`, `ApplyingMutation`, `ShowingResult`, and `Done`. If you mutate the creature in `ChoosingReplacement` as soon as the cursor lands on Tackle, you have violated the FireRed pattern. If you mutate in `ApplyingMutation` after the player confirms, and then set a result flag before leaving `Done`, you are much closer. If your caller can distinguish `Learned`, `Canceled`, and perhaps `NoEligibleMoves`, you are closer still. The exact enum is yours, but the boundary discipline is FireRed’s.

Now, I want to emphasize something subtle about globals. Advanced programmers sometimes react to global variables with a reflexive grimace, and often that reflex is earned. But in this engine, there are several categories of global state. Some globals represent persistent game state, like party data or event flags. Some represent engine services, like callbacks, tasks, windows, and sprite resources. Some represent cross-component scratch communication, like script variables. The mistake is not “a global exists.” The mistake is failing to know which category the global belongs to and who is allowed to write it.

So when the move relearner sets a script variable, that is not the same kind of mutation as changing a Pokemon’s move slot. One is a return value channel. The other is durable gameplay data. They have different invariants. A return variable can be reset when entering the service. It can be overwritten by another script operation later. A party move slot must remain correct after saving, battle entry, summary display, and future move replacement. If your architecture treats those two writes as equally casual, you will produce very different bugs.

Let us connect that to the summary screen again. The summary screen reads move slots, PP, species, level, stats, and other data through the Pokemon interface we used as a foundation, including `pokemon.h` from chunk `chunk_9b5ff7d0cd9f4c67` and helper flow in `pokefirered/src/pokemon.c`, chunk `chunk_9c96aedd5b8237e6`, as identified in the week source focus. We are not re-teaching the packed representation today. The point today is ownership. When summary reads a move slot after the move relearner returns, it should see the committed state. If summary sees a half-replaced move, or if PP does not match the new move, or if the old move appears until a reload, your mutation did not pass through the right data interface or did not update the related fields consistently.

Here is the check for understanding I would put on a quiz now that you have had the labs. I would show you a simplified trace. Frame 0, script launches move relearner. Frame 1, callback initializes menu. Frame 20, player selects a move. Frame 35, player confirms teaching. Frame 36, code writes move slot. Frame 37, code sets result variable. Frame 60, fanfare finishes. Frame 70, callback restores overworld. Frame 71, script resumes and branches on result. Then I would ask: where is the earliest safe frame to branch the script on the result? Not frame 35, because the confirmation input is not the whole operation. Not frame 36, even though the party mutation has happened, because the modal still owns the screen. In FireRed’s pattern, the script should resume only after the modal has completed and returned control. The result can be written earlier, but it should be observed by the caller after the handoff is complete.

That distinction is one of the most important mental upgrades in this course. A value can be assigned before it is semantically available to another subsystem. In ordinary C, if you are thinking only about one call stack, that sounds strange. In a frame-based engine, it is normal. The game has modes, and modes own interpretation. The overworld script should not start printing the next NPC line while the move relearner’s text window is still fading out or while its list menu still owns input. The result value is not the whole return. The return is value plus restored mode.

Now let us talk about scripts as clients. The script system is not just story text. It is a gameplay orchestration layer. It can ask questions, set flags, call specials, branch, and sequence visible events. When an NPC offers move relearning, the script is acting as the client of a modal gameplay service. That means it should not know the internal cursor row of the move list. It should not know which window ID was used for the prompt. It should not know the state number for `MENU_STATE_PRINT_TRYING_TO_LEARN_PROMPT`. It should know the public result: did the player teach a move, cancel, fail eligibility, or finish? That separation is what lets `learn_move.c` contain the menu state machine while the event script contains the NPC conversation logic.

This separation also tells you what not to expose in your own project. Do not make the overworld script poll the menu cursor. Do not make the party data layer decide which dialogue line the NPC says next. Do not make the summary screen directly launch script branches based on which summary page is open, unless that is truly its contract. The boundaries are not academic neatness. They are what keep a creature RPG from becoming a knot of special cases.

At this point, I want to zoom out to the week and the module. Week 11 has not been three unrelated topics called party lifecycle, summary UI, and move learning. It has been one ownership story. Party slot ownership asks: who is this Pokemon, where is it stored, and which subsystem is allowed to modify it? Summary UI asks: how do we display party data without accidentally becoming an owner of that data? Move learning asks: when a UI must mutate party data, how does it delay that mutation until confirmation and then return a coherent result to the caller? The through-line is data authority across screens.

That is why the lab asks you to implement a party summary screen model that reads creature data without mutating it, and then add a move-learning flow that mutates move slots only after confirmation. The lab is not just practicing menus. It is testing whether you understand the difference between display models, modal state machines, and durable party writes. Your week 10 work compared FireRed with Pokemon Showdown for battle data hooks and deterministic tests, but here FireRed is the stronger architectural source. Showdown is excellent for battle simulation data and regression testing, and the research packet flags `pokemon-showdown/data/learnsets.ts` and related mod learnset files as high-signal for learnset modeling. But the move relearner screen flow, the callback handoff, the UI cleanup, and the script result channel are FireRed architecture. Do not replace that with a server simulator mental model.

Let me say that another way. Pokemon Showdown can tell you a lot about representing moves, learnsets, formats, and battle effects in a modern data-driven simulator. FireRed teaches you how a handheld RPG moves the player from an NPC script into a UI, mutates a party creature, plays feedback, and returns to the map. Both are valuable. For this lecture, the primary architecture is the FireRed modal state machine in `pokefirered/src/learn_move.c`, chunks `chunk_af3598df6f092723` and `chunk_e820ee15ae8caa6d`.

Now, suppose you are implementing this in your own engine with a scene stack rather than FireRed callbacks. The lesson still transfers. Your `MoveRelearnerScene` can be pushed on top of the overworld scene. It can own its windows and input handlers. It can carry internal state for selected move and selected replacement slot. It can receive a reference or handle to the party slot. But it should produce a result object or set a script variable only at the appropriate point, and it should pop itself only after visual cleanup. If the scene stack resumes the overworld beneath it before the result is final, you have recreated the same class of bug under a different engine style.

If you are using an ECS, same lesson. The selected Pokemon might be a component reference. The menu might be entities with UI components. The script result might be an event. The architecture changes shape, but the invariants remain. Only confirmed actions mutate durable party data. The modal owns input while it is active. The caller observes a completed result, not an in-progress cursor state. Temporary UI resources are cleaned up before control returns. That is the portable principle we want from FireRed, not just the exact C names.

Let us do one final board trace. I am drawing five columns: data record, runtime global or variable, callback or task, script, and visible behavior. In the data record column, put the Pokemon’s move slots and PP. In the runtime variable column, put selected party slot, selected relearn move, menu cursor positions, and the script result variable such as `VAR_0x8004`. In the callback or task column, put `CB2_MoveRelearner_Init` and `MoveRelearnerStateMachine`, grounded in `pokefirered/src/learn_move.c`, chunk `chunk_af3598df6f092723`. In the script column, put the NPC service that starts the move relearner and later branches on the result. In visible behavior, put the list of moves, the confirmation prompt, the replacement prompt if needed, the fanfare, and the return to dialogue or overworld.

Now follow the arrows. The script starts the modal. The callback initializes resources. The state machine reads party and learnset data to build choices. The player input changes UI state, not party state. Confirmation moves the state machine to a mutation state. The mutation writes through the Pokemon data boundary. The result variable is set. Feedback states play text or fanfare. Cleanup states release or deactivate menu ownership. The callback returns control. The script reads the result and continues. That is the whole lecture in one trace.

And now a last check for understanding before we close this part. If a summary screen displays the new move correctly after relearning, what does that prove? It proves that at least one read path sees the committed party data. It does not by itself prove that the script result was set correctly. It does not prove that cancellation paths are safe. It does not prove that UI resources were cleaned up. Conversely, if the NPC says the move was learned, what does that prove? It proves the result path likely succeeded. It does not prove the move slot was actually changed. Mature debugging separates these observations. Visible behavior is evidence, but you still map it back to the responsible boundary.

So the closing synthesis for today is this: FireRed’s move learning is not merely a move-array edit. It is a menu state machine embedded in a larger callback, task, and script architecture. It begins with data records, but it becomes gameplay only when those records are mediated through globals, UI state, player input, confirmation gates, Pokemon data accessors, event variables, feedback states, cleanup, and callback restoration. Week 11 has been about respecting ownership across those boundaries. The summary screen taught us how to read party data without taking ownership of it. The move relearner taught us how a UI can temporarily take operational control, perform a confirmed mutation, and then hand control back with a result the surrounding script can trust.

Carry that forward into the next module. When we move into broader assessment and integration, I am going to ask you to trace features at this same granularity. Not “the item heals the Pokemon,” but which item table entry, which script or menu launches it, which party slot is selected, which data accessor mutates HP or status, which feedback state runs, which callback owns the frame, and how the caller resumes. That is the level at which a creature RPG stops being a collection of screens and becomes an engine with contracts.

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