# Menus as Task-Backed State Machines

Lecture ID: lecture_w19_d01
Week 19, Day 1

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

## Transcript
Good afternoon. Today we are moving from “menus as screens” to “menus as controlled state machines that are backed by the same task system as the rest of the runtime.” That distinction matters. A beginner looks at the move relearner and sees a list, some text boxes, a yes-or-no prompt, and then a changed move slot. An advanced engine programmer should see a resumable process: enter through a callback, allocate or reuse task state, wait across frames, branch on input, read party data, write party data only at a narrow commit point, and return to the previous runtime context without corrupting the caller.

Let me put the continuity on the board first. In weeks 17 and 18, we treated items as data and dispatch. We talked about item records, pockets, quantities, bag windows, and the separation between field-use and battle-use effects. I am not going to re-teach that today. Instead, I want you to carry it forward as a premise. The bag was already a UI facade over inventory and item-effect rules. Today we apply the same architecture lens to party menus, summary screens, and move replacement. The menu is not merely a visual layer. It is a temporary runtime mode that coordinates reads and writes against important game state.

So, write this phrase down: “A menu is a frame-resumable transaction coordinator.” That sounds formal, so let’s unpack it carefully.

Frame-resumable means the menu cannot block the machine the way a command-line program might block on input. The Game Boy Advance is still drawing, polling input, playing audio, running callbacks, and stepping tasks. If the player is staring at a yes-or-no box for ten seconds, the program is not sitting inside one giant while loop waiting for A or B. It is returning to the main loop each frame, and on each frame the active callback and active tasks are given a chance to advance. We have already seen that shell in the course. The runtime has main callback slots, and FireRed’s task system gives modules a way to register small stateful functions. The source packet points us again to pokefirered/src/task.c, chunk_cb0bb91a147bfb59, for CreateTask, and pokefirered/src/task.c, chunk_3ffebc0341225936, for RunTasks. Those two source anchors are not decorative. They are the reason a menu can feel modal to the player while still being incremental to the engine.

Transaction coordinator means the menu is guarding a write. In today’s source focus, the write is usually “teach this Pokémon a move,” “replace this old move,” or “return without changing anything.” The menu has to collect intent from the player before it mutates party data. It has to survive cancellation at several points. It has to display summary information without accidentally becoming the owner of that information. And it has to know where to return afterward.

Now, let’s connect that to the overall course plan. Since week 1, our thesis has been that a creature RPG is not one system. It is a stack of cooperating systems: runtime shell, maps, scripts, party data, battle, inventory, UI, save, and link features. By week 19, you are expected to trace a visible behavior across these layers. If I ask, “What happens when the move relearner teaches Thunderbolt over Growl?” I do not want the answer “the move changes.” I want you to say, “A script or NPC interaction enters a menu callback, the menu creates task-backed state, list and prompt states collect the player’s decision, the summary and party data are read for display and validation, and only after confirmation does the code write into the Pokémon’s move slot, then return through the configured callback or script continuation.” That is the level we need now.

Look at the evidence in pokefirered/src/learn_move.c, chunk_af3598df6f092723. The research packet explicitly calls out the “Move relearner state machine” comment and the transition from CB2_MoveRelearner_Init into MoveRelearnerStateMachine. The important point is not just that there is a function with “state machine” in the name. The important point is that the file documents the menu as a sequence of named states: fade, setup, list behavior, prompt behavior, confirmation behavior, and return behavior. FireRed is telling us, in source structure, that this menu is not an accidental pile of if statements. It is an explicit workflow.

Now slide your attention to pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d. This chunk supports the confirmation and replacement flow. It describes the teach-move confirmation state, the branch if the player cancels, the branch if the Pokémon has an empty move slot, and the branch if there is no empty slot and the player must choose an old move. That is the transaction boundary. If there is an empty slot, the write can be simple. If there is no empty slot, the menu needs additional consent. It cannot just pick a move. It cannot write early and hope the player confirms later. It must wait.

Let’s do a concrete example. Suppose our minimal clone has a party array with six creatures. Each creature has species, level, current HP, and four move slots. We also have a move relearner NPC. The player talks to the NPC, the script checks payment or an unlock flag, and then asks the player to choose one creature and one relearnable move. We are not rebuilding FireRed completely. We are building the minimal clone version that preserves the architecture.

On the board, I would draw a vertical stack. At the bottom: party data. Above it: script execution. Above that: menu task. Above that: visual windows and input. The player sees the top. The dangerous write is at the bottom.

The script layer should not directly replace a move. The script layer should launch the menu and provide context: which Pokémon, which candidate move, and where to return. The menu task should own the interaction state: setup list, wait for selected move, ask “Teach this move?”, if canceled return, if confirmed check for an empty move slot, if empty write, if full ask “Delete an old move?”, if canceled return to teaching prompt or quit depending on design, if confirmed select old move, ask final confirmation, then write exactly once. That “write exactly once” phrase is essential. In UI-driven data writes, repeated frames are dangerous. If your state remains in “write data” for several frames, and your code performs the mutation every frame, you can duplicate side effects, play fanfares repeatedly, consume items repeatedly, or advance script variables incorrectly. So the write state should commit and immediately advance to a post-write state.

Check for understanding here. If I pause on the state called “confirm deletion,” what data has changed so far? The answer should be: nothing about the Pokémon’s move list has changed yet. We may have temporary fields such as selected new move, selected old move index, cursor position, and prior callback. But the party data should still be intact. If a B press at that point cannot return to a clean previous state, your state machine is leaking intent into permanent data.

That is why summary screens are such a useful comparison. The summary screen reads. The packet points us to pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, where the includes alone already show the summary screen sitting at a crossroads: pokemon, task, menu helpers, item constants, moves, battle constants, party menu, and more. The summary screen is allowed to inspect a Pokémon’s data, format it, render it, and move between pages. But when it is being used as a summary screen, its default posture is read-oriented. It is not the same as the move replacement flow, where the menu eventually performs a write. So we should distinguish “menu reads” from “menu commits.” Both use callbacks and tasks. Both feel modal. But only one is a transaction.

Let’s tie this back to CreateTask and RunTasks. In a GBA-style runtime, the current main callback decides the broad mode: overworld, battle, menu, title screen, and so on. Within a mode, tasks represent smaller concurrent or resumable activities. A task has an ID, function pointer, and local data slots. CreateTask gives you a task entry. RunTasks calls active task functions each frame. When we design our clone, we do not need to copy FireRed’s exact memory layout, but we do need to preserve the principle: a menu interaction is not a blocking function call; it is a resumable state machine stepped by the engine.

Now, imagine a student implementation like this. The player selects “Teach move,” and the code immediately does creature.moves[0] = newMove, then displays “One, two, and... poof!” and then later asks which move should be forgotten. That is backwards. The visible sequence might be patched visually, but the architecture is corrupt. If the player cancels, you need to reconstruct the old move. If the game saves, crashes, or transitions during the wrong frame, you have inconsistent data. You have converted a controlled transaction into speculative mutation. In a serious engine, the right fix is not “store a backup and restore if canceled,” unless there is a strong reason. The right fix is to delay the write until the state machine reaches the commit state.

This same pattern applies outside move learning. For map loading, we do not want half-loaded maps to become visible as real gameplay state. A minimal clone map loader might have states like unload old objects, load map header, load layout, spawn objects, position camera, run map scripts, fade in, resume player control. For script execution, we might have states like fetch opcode, execute immediate opcode, wait for message box, wait for movement, branch on variable, call menu, resume after menu. For battle turns, we might have states like choose actions, validate actions, order actions, execute move, apply damage, process fainting, check victory, return to overworld or continue. For item use, based on what we covered in the previous lectures, we might have select item, choose target if required, validate target, preview or confirm, apply effect, update quantity, refresh UI, return. For save persistence, we might have collect save data, serialize, write sector or file, verify, mark success, return. The surface features are different, but the architecture repeats: hold temporary state, wait across frames, branch on player input or engine results, commit permanent data at a controlled point.

Notice how this increases the sophistication of your week 19 lab. The lab is not “make a menu.” You are building a move-replacement menu with explicit states for list setup, confirm teach, select old move, confirm deletion, write data, and return callback. I want those states to be visible in your code. Not necessarily as a giant enum if your language has a better pattern, but they must be inspectable. If I cannot tell where cancellation is legal, where the permanent write happens, and where control returns, then the architecture is too implicit.

Let me give you a minimal clone model in spoken pseudocode. I would have a MenuTask with fields: state, partyIndex, newMoveId, oldMoveIndex, cursor, returnCallback, resultCode, and maybe messageTimer. The state begins at SetupList. SetupList populates the move list and then advances to WaitForMoveSelection. WaitForMoveSelection reads input. A cancels or B returns to the caller with no mutation. A on a move stores newMoveId and advances to ConfirmTeach. ConfirmTeach displays “Teach this move?” and waits. If no, it returns to WaitForMoveSelection or exits, depending on your design. If yes, it checks for an empty slot. If there is one, it stores oldMoveIndex as that empty slot and advances to CommitWrite. If there is no empty slot, it advances to PromptDeleteOldMove. PromptDeleteOldMove asks whether to forget an old move. If the player declines, it returns to ConfirmTeach or exits. If the player accepts, SelectOldMove lets the player choose one of the four current moves. Then FinalConfirm asks, “Forget this move and learn the new one?” Only after that does CommitWrite mutate party[partyIndex].moves[oldMoveIndex]. Then PostWriteFanFare or Message displays the result, and ReturnCallback restores the previous mode.

The exact names do not matter. The guarantee matters. Every state has a narrow responsibility. Every branch is explicit. Every permanent write is delayed until all required confirmation has been collected.

Let’s check understanding with a sharper question. Why is “select old move” not enough confirmation by itself? After all, the player moved the cursor onto Tackle and pressed A. Why ask again? Architecturally, the second confirmation protects a destructive overwrite. From a UX perspective, it protects against misclicks. From an engine perspective, it gives you a clean final gate before the commit state. And from a testing perspective, it gives you a state boundary: before final confirm, party data unchanged; after commit, exactly one slot changed.

This is also where the course’s earlier data-table tracing stays relevant. In prior weeks, we traced data records through globals, callbacks, tasks, scripts, structs, and visible behavior. Here, the selected move may originate from a learnset or relearner list. The party Pokémon is a structured record. The UI windows display derived text and icons. But the player-visible behavior emerges only when those data sources are bound through the task-backed menu process. If you only understand the learnset table, you cannot explain cancellation. If you only understand the renderer, you cannot explain save correctness. If you only understand the party struct, you cannot explain how the player gets from an NPC interaction to a move slot write.

Now I want to bring in battle_setup.c briefly, because it shows this pattern at another boundary. The packet cites pokefirered/src/battle_setup.c, chunk_494d132a59422c85, with includes that connect task, overworld, item, Pokémon, load_save, scripts, event data, field control, battle, and battle transition. Again, do not overread an include list as a full proof of behavior. But as architecture evidence, it supports a claim we have made repeatedly: FireRed’s runtime features are not isolated screens. They are crossings between systems. A battle setup has to carry overworld context into battle context. A move relearner has to carry script or menu context into party-data mutation context. A save operation has to carry current global state into persistent representation. The design problem is always the same: what state is temporary, what state is permanent, who owns the transition, and how do we resume?

For an advanced clone, I would ask you to define this as an interface contract. A menu task should not know everything about the whole game. It should receive a small context object. It should call a small number of services: read party member, get relearnable moves, check empty slot, write move, play sound, print message, return to caller. If your menu task reaches into map loading internals, battle turn queues, save sectors, and inventory pockets directly, it becomes impossible to reason about. FireRed’s C architecture often uses globals because that is idiomatic and practical for the target platform, but the conceptual boundaries are still visible in how callbacks, tasks, and modules divide responsibility.

Let’s pause and do a recap before we go deeper. A menu in this part of the course is not just an image on the screen. It is a mode or submode of the runtime. It advances over frames through callbacks and tasks. It reads structured game data for display. It collects player intent through explicit states. It delays permanent writes until a commit point. And it returns control to the caller through a known continuation, whether that caller is an overworld script, a party menu, or another screen.

The mistake I want you to avoid this week is building a menu that visually works but architecturally lies. A visually working menu can still mutate too early, lose its return callback, double-apply writes, fail to handle cancel branches, or read stale party data after a different system changes it. Those are the bugs that show up when you add saving, item use, battle transitions, or link constraints. At week 19, we are not impressed by a list that moves up and down. We are interested in whether the list participates correctly in the runtime.

In the next stretch, we are going to take the move relearner flow and turn it into a state diagram that you can directly implement in the lab. We will mark which states are pure display, which states read input, which states read party data, and which single state is allowed to write party data. Then we will compare that to summary screen reads, because the summary screen is the clean contrast case: rich UI, lots of data access, but normally no destructive commit.

All right, so let’s draw that contrast carefully. I’m going to put two boxes on the board. The left box is “summary screen.” The right box is “move relearner.” Both look like menus to the player. Both show party data. Both have pages, prompts, input handling, text windows, and return callbacks. But architecturally, they do not have the same danger profile.

The summary screen is mostly a reader. It may be visually elaborate, and it may navigate through a lot of information, but its normal job is to present the current Pokémon record: species, level, moves, stats, markings, held item, ribbons or origin data depending on the generation and screen. That is why the source evidence matters. In pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, notice the inclusion pattern: pokemon.h, pokemon_summary_screen.h, task.h, menu helpers, link, overworld, constants for items, moves, battle, party menu, and so on. That tells us this screen sits at a crossroads of many systems, but crossroad does not automatically mean commit point. It needs access to shared data. It needs to render shared data. It needs to respect context, like battle or trade or link constraints. But most of the time, it should not be the place where move slots are rewritten.

Now the move relearner is different. The move relearner is a menu that culminates in a mutation of party data. And that means its state machine has to be more conservative than the summary screen. In pokefirered/src/learn_move.c, chunk_af3598df6f092723, the comments explicitly frame this as a move relearner state machine. That is not just a helpful comment; it is a design clue. The code is not pretending that this is one big function called “show menu and teach move.” It is a sequence of states, and each state is responsible for a narrow piece of the interaction.

Let me say that in a slightly sharper way. A task-backed menu is not a menu with a task attached. It is a task whose current state happens to be expressed through menus, text, cursor position, sound effects, fades, and callbacks. The task is the owner of the flow. The windows are views. The cursor is view state. The party data is model state. The callback is the runtime handoff. If you mix those together, the code may still appear to work during the happy path, but it becomes very hard to answer basic questions like, “What happens if the player cancels here?” or “Has the move already been written?” or “Can this callback return into a state that still assumes an old cursor selection?”

Let’s make this concrete. Suppose we are building the lab version. A player talks to an NPC who offers to reteach a move. The script chooses a party member, then opens a move list. The list shows recoverable moves, maybe Tackle, Growl, Water Gun, Bite, whatever is appropriate for our small clone. The player selects Water Gun. Then the menu asks, “Teach Water Gun?” If the Pokémon has an empty move slot, the system can write Water Gun directly into that empty slot. If there are already four moves, the system must ask which old move to forget. Then it must confirm deletion. Then, and only then, it writes the new move.

Here is the important part. In your clone, I want only one state that writes to the Pokémon’s move array. One state. Not the list selection state. Not the yes/no prompt state. Not the old move selection state. Not the text-printing state. One commit state. If you remember nothing else from this portion, remember that user intent should be accumulated across several states, but destructive mutation should happen in one obvious state.

I’m going to write this on the board as a linear flow first, and then we’ll add branches. Setup list. Wait for move selection. Confirm teach. If empty slot exists, commit. If no empty slot, print “trying to learn.” Confirm delete old move. Select old move. Confirm deletion. Commit. Print success. Return callback.

Now, that flow is intentionally boring. Boring is good here. Boring means we can audit it. When the player presses B from the first list, the flow exits without writing. When the player says no at “Teach this move?”, the flow exits or returns to setup without writing. When the player says no at “Forget an old move?”, the flow prints a stop-teaching message without writing. When the player backs out of old move selection, the flow returns to the confirmation branch without writing. Only after the final confirmation do we enter commit.

This is exactly the shape described in pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d. The excerpt describes a teach move confirmation state, a branch if cancelled, a branch if confirmed with an empty move slot, and another branch if the Pokémon does not have an empty slot. Then it continues into prompts like trying to learn, confirm delete old move, and which move to forget. You should read that chunk as a map of control-flow obligations. It is not merely UI text ordering. It is a guardrail against corrupting party data.

Let’s pause for a check. If I ask you, “Why not write the new move as soon as the player selects it from the relearner list?” the answer is not simply “because the player might cancel.” That is true, but incomplete. The deeper answer is that selecting a candidate move creates provisional intent, not final consent. The selected move ID can be stored as pending state. It can be displayed. It can be used in text. But until the system knows whether there is an empty slot, whether an old move must be replaced, and whether the player confirmed that replacement, the party record should remain unchanged.

That distinction between pending state and committed state is one of the central architectural moves of this week.

Now let’s connect this back to the task system, because this is where the FireRed architecture gives us a useful mental model. Earlier in the course, we studied task allocation and per-frame dispatch. In pokefirered/src/task.c, chunk_cb0bb91a147bfb59, CreateTask gives us the mechanism for creating a small runtime object with a function and task data. In pokefirered/src/task.c, chunk_3ffebc0341225936, RunTasks is the per-frame dispatcher. That means your menu state machine is not usually blocking in the normal desktop sense. It is not sitting in a while loop waiting for the user. Instead, each frame it runs a little bit, checks whether text is done, checks whether input happened, checks whether a fade completed, checks whether a list menu produced a selection, then updates the task’s state field.

So when you implement this in the lab, do not write the move relearner as a single long function that calls “wait for input” and then continues. That is the wrong shape for this runtime. Write it as a task function with explicit states. Something like state zero, build the list. State one, wait for selection. State two, show confirm prompt. State three, wait for yes or no. State four, decide empty slot or replacement branch. And so on. You can name the states with an enum, and you should, because unnamed numeric states become unreadable almost immediately.

Let’s put a small clone example on the board. Our party creature struct has a species, level, current HP, four move slots, and maybe PP values. The move relearner task stores the party index, the pending new move, the selected old move slot, the cursor position, and the return callback. At list setup, it reads the species and level, builds a list of eligible moves, and initializes cursor state. That state reads party data, but does not write party data. At selection, it records pendingMove. Still no party write. At confirm teach, it asks for consent. Still no party write. At empty-slot check, it may compute the target slot. Still no party write, unless you deliberately combine the check with commit, which I do not recommend for the lab. At commit, it writes party[partyIndex].moves[targetSlot] = pendingMove, refreshes PP if your clone models PP, marks the save dirty if your clone has such a flag, then advances to success text.

Now, someone might ask, “Professor, FireRed sometimes writes variables like VAR_0x8004 during these flows. Is that party mutation?” Good question. No, not in the sense we care about here. Script variables, result variables, menu state fields, and pending selections are coordination data. They matter, but they are not the same as rewriting the Pokémon’s move list. In the chunk_e820ee15ae8caa6d excerpt, the comments mention setting VAR_0x8004 to TRUE in one branch. That is a script-visible result. It is part of returning information to the surrounding script machinery. But the dangerous mutation we are isolating is the modification of the Pokémon record itself.

This is why advanced architecture work requires vocabulary. If we call everything “state,” we lose precision. There is UI state, like cursor index and scroll row. There is task state, like which phase of the state machine we are in. There is pending intent, like selected new move and selected old slot. There is script state, like variables and flags. There is model state, like the actual party record. And there is persistence state, meaning what will later be saved. Bugs often happen when a programmer treats one layer as if it were another.

Let’s compare that to item use, but only briefly, because we covered the bag and item dispatch recently. In the item lectures, we already established that item data records, bag pockets, quantities, field-use versus battle-use dispatch, and healing behavior form the foundation. Today’s point is not to re-teach that. Today’s point is that an item-use menu has the same architectural risk as the move relearner when it can mutate party data. A Potion is not just a menu selection. It is a provisional item selection, then a target selection, then validity checks, then a commit that changes HP and decreases quantity. If you decrement the item quantity before confirming the target, or heal the Pokémon before the final validity check, you create the same class of bug as teaching a move too early.

So the general rule is broader than moves. Menus are often consent-gathering machines around model mutations. They collect intent through input states, they validate that intent against current data, and then they commit exactly once.

Now let’s widen the lens to the overall course plan. Back in the runtime shell weeks, we talked about AgbMain, main callbacks, and SetMainCallback2 as the big outer rhythm of the program. Then we moved into maps, event scripts, creature data, battle, inventory, UI, save, and link. At the time, those may have looked like separate systems. Week 19 is where they start looking like one architecture. A move relearner menu touches scripts because an NPC or field event usually launches it. It touches party data because it reads and writes moves. It touches UI because it builds windows and lists. It touches tasks because the interaction unfolds over frames. It touches callbacks because it must return to the right runtime context. It touches save persistence because the party mutation must eventually survive. And it may touch battle or link restrictions indirectly, because some menus are disallowed or altered depending on where the player is.

That is why a minimal clone has to include map loading, script execution, party data, battle turns, item use, and save persistence even if each system is tiny. If you build only a menu prototype, you can fake everything. But if you build a creature RPG architecture, the menu has to live inside the runtime.

Let’s walk through a minimal vertical slice. The player loads a map. The map has an NPC object with a script. The script says, in our simplified language, “open move relearner for party slot zero.” The script engine pauses while the menu owns the main interaction. The menu task opens, reads party[0], lists eligible moves, gets confirmation, and commits one move replacement. It then returns a result to the script, maybe success or cancel. The script resumes and prints different text depending on the result. The overworld returns control to the player. Later, the save system serializes the party, including the new move. If the player enters battle, the battle turn system reads the updated move list.

Notice what happened there. The move menu is not an isolated assignment. It is a bridge between script execution, party data, UI, and later battle behavior. This is exactly the kind of data-to-runtime-to-visible-behavior tracing you were assessed on in the recent homework and quiz. At this point in the course, I am going to expect you to be able to say not only “the move changed,” but “which runtime system initiated the change, which state committed it, which data structure was modified, which callback resumed afterward, and which later system observes the result.”

Let me give you a failure case, because failure cases make the architecture easier to see. Suppose the player opens the move relearner and selects Thunderbolt. Your code immediately writes Thunderbolt into move slot three, replacing Quick Attack. Then it asks, “Forget Quick Attack?” The player presses no. Now what? You can try to undo the write. But what if PP was also changed? What if a derived display was refreshed? What if a script variable was set? What if autosave or a save-dirty flag was triggered? What if the summary screen opens during the flow and reads the modified data? You have converted a simple cancel branch into a rollback problem.

The FireRed-style state machine avoids needing rollback by delaying mutation. It keeps the old party data valid until final confirmation. That is almost always simpler than speculative write plus rollback.

Now, another check for understanding. If the summary screen reads the Pokémon’s moves, and the move relearner writes the Pokémon’s moves, why don’t we just let the summary screen share the same edit code? Think about it for a second. The reason is ownership of intent. The summary screen may display moves. It may even be launched from contexts where move-related actions are available. But display code should not casually own destructive domain operations. You can share low-level helpers for reading move names, formatting PP, or drawing move types. You can share validation helpers. But the state machine that gathers consent and performs the write should have a clear owner.

That separation also helps with save persistence. The save system should not care that the move was taught by a relearner, by leveling up, by a TM, or by a scripted event. It should serialize the resulting party state. But each of those mutation paths must respect its own rules before changing the model. Persistence records the truth after the commit; it should not be asked to interpret half-finished UI intent.

Let’s bring battle turns into this for a moment. A battle turn system usually reads moves at the point where it builds the player’s choices or validates selected actions. If party data has been corrupted by a menu, the battle system will faithfully use the corrupted data. That is why we do not treat UI bugs as cosmetic in this architecture. A bad menu commit can become a battle bug. A bad item target can become a save bug. A bad script result can become a progression bug. The systems are separate, but the data connects them.

And this is also where Pokémon Showdown is useful only as a secondary comparison, as we have said before. Showdown is strong for battle data hooks, learnsets, simulator turns, and regression tests, but FireRed is our architectural authority for this course. In this week’s topic, the primary evidence is FireRed’s task and menu source, especially pokefirered/src/learn_move.c chunks_af3598df6f092723 and_e820ee15ae8caa6d, plus pokefirered/src/task.c chunks_cb0bb91a147bfb59 and_3ffebc0341225936. Showdown can help us think about deterministic battle consequences after a move exists, but it does not replace the FireRed menu runtime model.

Now, for the lab, I want you to resist the temptation to start with graphics. Start with state names. If your state names are good, the UI becomes easier to attach. If your state names are vague, the UI will hide the confusion for a while and then punish you when cancel branches appear. Use names like setupMoveList, waitMoveSelection, confirmTeach, checkMoveCapacity, printTryingToLearn, confirmForgetMove, selectMoveToForget, confirmDeleteMove, commitMoveReplacement, printLearnedMove, and returnToScript. You do not need these exact names, but you need this level of specificity.

Then identify reads and writes. Setup reads party data. Summary-style display reads party data. Old move selection reads current move slots. Confirm prompts read pending selections. Commit writes party data. Return writes script result or calls the return callback. Saving later reads party data and writes persistent storage. Battle later reads party data and creates legal move choices. That is the flow.

Let’s do a quick verbal trace. Player presses A on the NPC. Map object script begins. Script calls a special command to open the relearner. Main callback changes, or the relevant menu task is created, depending on the simplified runtime you implement. The task initializes list data. RunTasks dispatches it once per frame. The player selects a move. The task stores pendingMove. The task shows confirmation. The player confirms. The task sees no empty slot. The task shows the “trying to learn” style prompt. The player agrees to forget an old move. The task opens a four-move selection list. The player selects slot two. The task asks for final confirmation. The player confirms. The task enters commitMoveReplacement, writes exactly one slot, updates any associated PP, sets the result, and advances. Text prints. The task cleans up windows and returns control. The script resumes. Later systems observe the changed model.

If you can trace that without hand-waving, you understand the architecture. If you cannot, the bug is probably not in your syntax. It is probably in your ownership model.

Let me close this segment with a small recap before we move into the implementation diagram. The summary screen is our contrast case: rich reading, broad dependencies, normally no destructive commit, supported by pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46. The move relearner is our mutation case: staged prompts, confirm and cancel branches, and a final write, supported by pokefirered/src/learn_move.c, chunks_af3598df6f092723 and_e820ee15ae8caa6d. The task system gives us the per-frame machinery, supported by pokefirered/src/task.c, chunks_cb0bb91a147bfb59 and_3ffebc0341225936. And the clone lesson is this: menus are not decorative wrappers over data changes. In this architecture, menus are state machines that coordinate player intent, runtime context, model validity, and eventual persistence.

So, next, I’m going to turn the board diagram sideways, because a vertical list makes the flow look too inevitable. We need to see the cancellation edges. The cancellation edges are where your design is tested.

So, turning the diagram sideways, put the player’s current visible menu state in the center. Not at the top. In the center. Now draw the happy path to the right: choose move, confirm, choose old move if necessary, confirm deletion, write new move, return. That is the path everyone remembers. Now draw the cancellation paths to the left: cancel from the teach confirmation, cancel from the delete-old-move prompt, cancel from old-move selection, back out to the move list, and eventually return without writing.

That sideways shape is closer to the real architecture. The important question is not, “What screen comes next?” The important question is, “At which state is mutation still forbidden?” In the move relearner, the source comments in pokefirered/src/learn_move.c, chunk_af3598df6f092723, already frame this as a move relearner state machine, and chunk_e820ee15ae8caa6d gives us the confirmation and replacement flow: confirm teaching, check for an empty move slot, otherwise ask about forgetting an old move, then only later perform the write. That ordering is not cosmetic. That ordering protects party data.

Let’s make that concrete. Suppose I have a Charizard with four moves, and I open the move relearner menu to relearn Wing Attack. In a sloppy clone implementation, the moment I highlight Wing Attack, some code might be tempted to store “pendingMove equals Wing Attack” inside the Pokémon object itself. Or worse, it might overwrite move slot zero immediately and assume later screens will finish the process. That feels convenient for about ten minutes of development, and then the first cancel branch corrupts the party. The user presses B at “Stop trying to learn Wing Attack?” and suddenly their first move is gone, or PP does not match, or the summary screen displays one thing while battle reads another.

So, on the board, I’m going to mark every state before the final commit as “intent only.” Intent can live in task data. Intent can live in a small menu context structure. Intent can live in temporary globals if you are matching a GBA-style architecture. But intent is not the party model. That is the line.

Now, check your understanding for a second. If the player chooses a new move, confirms that they want to teach it, chooses an old move, and then cancels at the final deletion prompt, should the Pokémon’s move list have changed? No. Not partially, not invisibly, not in a pending slot inside the Pokémon. The model should still be exactly what it was when the menu opened. The only thing that changed was the menu’s temporary state.

That is why this week belongs after our item and bag work. In weeks 17 and 18, we treated items as data records and the bag as a UI facade over inventory quantities and item-use dispatch. We are not re-teaching pockets or quantities today. Instead, we are using that foundation to raise the stakes. The bag taught us that a menu can display data and route actions. The move relearner teaches us that a menu can stage a destructive edit and still be reversible until the commit state.

Now look at the task system underneath. In pokefirered/src/task.c, chunk_cb0bb91a147bfb59 supports the CreateTask side of the model, and chunk_3ffebc0341225936 supports RunTasks as the per-frame dispatcher. The task is not just a callback pointer with a cute name. It is the mechanism that lets a menu pause between human decisions. One frame draws a prompt. Many frames may pass while the player thinks. Then the task sees input, advances state, and maybe schedules the next visual change. That is fundamentally different from a normal function call that expects to finish immediately.

For a minimal clone, I would make this explicit. I would not write a function called relearnMove that runs from top to bottom. I would write a RelearnMoveTask with fields like state, selectedNewMove, selectedOldSlot, scrollPosition, returnCallback, and result. The state values might be listSetup, listInput, confirmTeach, chooseOldMove, confirmForget, commitWrite, fanfare, and exit. Do you hear the nouns there? Those are not visual effects. Those are decision boundaries. A good clone state enum names the moments where the user can still change their mind.

Now, let me connect this to the broader architecture, because week 19 is not an isolated UI week. The whole course has been building toward this exact kind of reasoning. A creature-collector RPG is not one giant loop called playGame. It is a shell of cooperating state machines: map loading, script execution, party mutation, battle turns, item dispatch, save persistence, and menus. The advanced move is to understand which state machine owns the current decision.

Take map loading. In a minimal clone, a map load should not directly run every NPC script, spawn a battle, heal the party, and save the game. It should load map metadata, layout reference, collision, object events, and the initial callback or mode needed for the overworld. The research packet’s course synthesis points us back to FireRed’s overworld and map material, including pokefirered/src/overworld.c, chunk_7ca719013, and pokefirered/data map and layout records as recurring sources. The architectural lesson is ownership. Map loading prepares the world state. It does not pretend to own every event that can happen inside that map.

Now put script execution next to it. The script VM owns story sequencing: show message, set flag, check variable, give item, start battle, call special function, return control. We saw last week how item use can be blocked by script variables and flags, but we do not need to re-teach that item-use material. The connection today is that script execution and menus often hand control back and forth. A script might open the move relearner. The move relearner might return a result. The script might continue based on that result. If your clone does not define that handoff clearly, you end up with menu code secretly advancing story flags or script code secretly editing menu selection state.

And that is the same disease in a different body.

Party data is the next owner. Party data should be boring. I mean that as praise. The party model should store species, level, experience, moves, PP, stats, status, held item, friendship, personality-derived fields, and whatever your clone includes at its chosen fidelity. It should expose controlled reads and controlled writes. The summary screen is a beautiful contrast case here. The packet points us to pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, with its broad dependencies on Pokémon data, menu helpers, item constants, moves, battle constants, and party menu material. That screen reads a lot. It has to. It presents identity, stats, moves, ribbons or origin-like information depending on version scope, and other contextual data. But a summary screen is normally not the commit point for move replacement. It is a read-heavy consumer, not the owner of arbitrary mutation.

So when you build the lab this week, I want you to be strict with yourselves. If your summary panel component has a method called replaceMove, that is a smell. It may call a shared party API after the correct task state, but the summary panel itself should not be deciding that the player has crossed the final confirmation boundary.

Now battle turns. Same principle, higher pressure. Battle is full of staged intent. Player selects Fight. Player selects a move. The opponent may choose a move. Priority is calculated. Accuracy is checked. Effects resolve. Fainting may trigger switches. Items may intervene. If you mutate too early, battle becomes unreplayable and nearly impossible to debug. In FireRed, battle setup is reached through dedicated battle transition and setup code; the packet gives us pokefirered/src/battle_setup.c, chunk_494d132a59422c85, as a source for battle transition, load, event, item, Pokémon, script, and overworld connections. Even without opening every battle engine file today, we can see the architectural boundary: overworld and scripts can initiate battle, but battle owns turn resolution once entered.

Here is the clone design version. Your overworld script can say, “start trainer battle with trainer ID 12.” Battle setup can prepare battlers, background, music, and initial battle state. The battle turn state machine can collect choices, resolve order, apply effects, and return a result. But the party menu should not resolve Thunderbolt damage. The bag should not decide experience gain. The save system should not infer battle results by scanning HP. Each subsystem needs a clear commit point.

Now let’s bring item use back briefly, because it is the bridge from the last two weeks into today. An item menu has a selected item and a selected target. Some items commit immediately, like using a Potion on a valid party member. Some items open another selection screen. Some items route to field scripts. Some items are blocked in battle or blocked in the overworld. The pattern we care about today is that item-use dispatch is another task-backed state machine when a human decision is still pending. The bag is not just a bag; it is a facade over staged intent. That is exactly the same shape as move replacement, just with a different model write at the end.

Let’s do a small board exercise. I’ll write six clone systems across the top: map, script, party, battle, item, save. Under each, I want one line for “reads,” one line for “stages,” and one line for “commits.” Map reads map data and player location, stages fade and object spawn setup, commits current map context. Script reads flags and variables, stages commands and waits, commits story variables or calls into other systems. Party reads creature data, stages candidate edits through menus or battle effects, commits controlled changes like move replacement or HP changes. Battle reads party and move data, stages turn choices, commits turn results. Item reads inventory and item records, stages selected item and target, commits quantity changes and item effect results. Save reads the durable game state, stages serialization buffers, commits to storage.

Notice something: save is last. Save persistence should almost never be the first system to hear about a menu decision. If the move relearner writes directly to save memory before party mutation is coherent, your save file becomes a record of confusion. In a clone, the save system should serialize already-valid game state. It should not fix invalid game state. It should not complete half-finished menu transactions. It should not look at a menu task and say, “Ah, selectedOldSlot is two, selectedNewMove is Wing Attack, I’ll interpret that as learned.” No. The save layer persists committed model state.

Quick check. Suppose the player selects a Rare Candy, selects a party member, sees a level-up move prompt, enters a move replacement flow, then cancels learning the new move. Which systems are involved? Item menu, item-use dispatch, party model, experience or level-up logic, move-learning menu, and maybe script or field context depending on where the item was used. Which system should finally write the new move if the player accepts? The party data API, called from the correct commit state of the move-learning task. Which system should decrement the Rare Candy? That depends on your item-use transaction design, but it must be consistent: either after successful target validation and item effect start, or after the whole effect resolves, depending on your game’s intended rules. What it should not do is decrement, overwrite a move, and then let cancellation pretend none of that happened.

That is the kind of reasoning I expect at this point in the course. Earlier, in week 1, it was acceptable to say, “The game has maps, battles, menus, and saves.” By week 19, after homework and quizzes asked you to identify concrete files, structs, functions, constants, data tables, and state variables, you should be able to say, “This visible interaction is a state machine running under the task dispatcher; it reads from this model, stages intent in this context, writes only at this commit state, and returns through this callback boundary.” That is the technical sentence behind a reliable RPG menu.

Now, let’s zoom back into the move relearner lab, because this is where the abstract ownership model becomes code. Your lab state list should include list setup. Do not skip that state. List setup is where you compute the relearnable moves, initialize scroll positions, configure windows, and decide whether the list is empty. In FireRed’s source comment for the move relearner state machine, pokefirered/src/learn_move.c, chunk_af3598df6f092723, the initialization and list-related behavior are named as part of the flow, including list menu scroll positions. That detail matters because scroll state is user interface state, not creature data. If I scroll down to the tenth move, my Pokémon has not changed. If I press B, my Pokémon has not changed. If I press A on a move, my Pokémon still has not changed. We are accumulating intent.

Then comes teach confirmation. This is the first place where the player’s selection becomes a serious candidate. But it is still not a write. The text can say, in effect, “Teach this move?” and the player can say no. The source flow in pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d, distinguishes the confirm branch from the cancel branch and routes cancellation back toward setup behavior. In your clone, that means confirmTeach should not call replaceMove. It should set the next state.

Then select old move. This state exists only if the Pokémon has no empty move slot. If there is an empty slot, the commit can be simpler. But be careful with that sentence. Simpler does not mean careless. An empty slot path still needs a commit state. It may not need old move selection, but it still needs the same discipline: player confirms, state machine reaches writeData, party API writes move and PP, UI transitions to fanfare or return.

Then confirm deletion. This is the cancellation edge I care about most. The player has already selected the new move and the old move. Many novice implementations treat this as practically done. But from the player’s perspective, this is exactly the moment where cancellation must still be meaningful. “Forget this move?” is not flavor text. It is a transaction boundary.

Only after that do we write data. In a clone, I would make this boring and centralized: party.replaceMove(monIndex, oldSlot, newMoveId), or party.learnMove(monIndex, newMoveId) if empty slot rules are inside the party layer. That method should update the move ID and initialize PP according to your move data. If your clone tracks PP Ups or maximum PP modifiers, that logic belongs near the party/move data write, not scattered through the menu states. The menu knows what the player chose. The party model knows how a move slot is represented.

And then return callback. Do not underestimate return callback design. FireRed’s architecture is full of callback handoffs; earlier in the course we covered SetMainCallback2 in pokefirered/src/main.c, chunk_6cfd90f6d0136799, and today we are viewing menu tasks inside that broader callback shell. A menu that cannot return cleanly is not a reusable menu. It becomes a trapdoor. The move relearner should be callable from script context and then hand control back. A summary screen should be callable from party context and then hand control back. An item target selector should be callable from bag context and then hand control back. This is how a game with many screens remains composable.

Let me pause and ask a diagnostic question. If you are debugging a menu bug and you see that pressing B returns to the wrong screen, where should you look first? Not the party data. Not the save system. Look at the state transition table and the return callback. If the data is intact but the player lands in the wrong place, the bug is probably in control flow ownership. If the player lands in the right place but the move is already changed after canceling, the bug is probably in premature mutation. Different symptom, different layer.

Now, for the implementation diagram, I want you to imagine each menu state as a box with three possible parts: entry work, wait condition, exit decision. Entry work happens once: draw the prompt, build the list, start a fade, play a sound. Wait condition may last many frames: wait for text printer, wait for input, wait for fade complete, wait for fanfare. Exit decision chooses the next state: A goes right, B goes left, empty slot skips old move selection, full moveset enters replacement. This entry-wait-exit split is a clean way to prevent repeated initialization. If your prompt redraws sixty times per second, you probably mixed entry work with per-frame waiting.

This is where RunTasks matters. The RunTasks material in pokefirered/src/task.c, chunk_3ffebc0341225936, supports the idea that tasks are called repeatedly as part of frame dispatch. So a task function must be written as resumable logic. It cannot assume it owns the CPU until completion. It does a little work, records its state, and returns. Next frame, it resumes. That is why task data is so central. The task’s state fields are the stack frame that survives across frames.

In a desktop clone, you might not literally use a GBA task array. You might have an update method on a screen object, or a coroutine, or an event loop. Fine. The architectural obligation is the same. You need persistent state between frames, explicit transitions, and delayed mutation until confirmation. Do not get hypnotized by the exact implementation mechanism. FireRed teaches the pattern through tasks and callbacks; your clone may express it through objects, enum states, and scene managers. The pattern is the lesson.

Now I’ll weave this forward to week 20 for just a moment, because your upcoming homework needs this bridge. When we move from menu state machines into broader integration, you will be asked to trace flows across systems without losing the owner of each decision. A move replacement menu is a small but complete example of the whole course thesis. Data tables define moves. Party data stores learned moves. Menu tasks stage player intent. Text and windows make the intent visible. Callback returns restore control. Save persistence later records the committed state. If you can trace that without hand-waving, you can trace most of a creature RPG.

Let’s recap this segment before we push into the next board pass. Cancellation edges are not side paths; they are tests of whether your model mutation is correctly delayed. Summary screens, supported by pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, are read-heavy views over creature data. Move relearning, supported by pokefirered/src/learn_move.c, chunks_af3598df6f092723 and_e820ee15ae8caa6d, is a staged mutation workflow. Tasks, supported by pokefirered/src/task.c, chunks_cb0bb91a147bfb59 and_3ffebc0341225936, provide the per-frame machinery that lets those workflows wait for input without blocking the whole game. And the clone design rule is: store intent in the menu task, commit through the model API, return through an explicit callback.

Next, I want to annotate the diagram with actual failure modes, because that is usually where this material becomes permanent. We are going to take three bugs, one at a time: premature write, lost return callback, and stale selection index. Each one looks like “the menu is broken,” but each one belongs to a different architectural layer.

Good. First bug: premature write. I am going to write that phrase on the board, because this is the one that catches people who understand data structures but have not yet internalized UI as a transaction boundary.

Premature write means the model is mutated before the player has passed every cancel branch. In the move replacement workflow, the dangerous version is this: the player selects a relearnable move, the code immediately writes that move into the party creature, and only afterward asks, “Which old move should be forgotten?” That feels natural if you think of the first selection as the action. But architecturally, the first selection is not yet the action. It is intent.

Now compare that with the state outline documented in pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d. The flow distinguishes confirmation, checking whether there is an empty move slot, prompting about deleting an old move, choosing which old move, confirming the deletion, and then doing the actual data write. That separation is the whole safety property. The UI is allowed to collect intent, revise intent, cancel intent, and abandon intent. The party data should not change until the state machine reaches the commit state.

So, board cue: I am drawing two columns. Left column says “intent.” Right column says “commit.” Under intent I put selected new move, selected old slot, yes or no prompt result, return callback. Under commit I put write move slot, update PP, set script variable, play fanfare, return to field or script. The bug happens when we move “write move slot” into the left column.

Here is the concrete clone example. Suppose our minimal clone has a party creature with moves Scratch, Growl, Ember, and Smokescreen. The move relearner list offers Metal Claw. The player picks Metal Claw, then reaches the “forget which move?” screen, hesitates, presses cancel, confirms they want to stop learning, and returns to the NPC dialogue. In the correct architecture, the creature still has Scratch, Growl, Ember, Smokescreen. In the premature-write bug, Metal Claw may already be in slot zero, or worse, it may be in a temporary fifth slot that your renderer does not expect. Then the cancel path becomes a data repair path, and that is a bad smell. Cancel should usually discard intent, not reverse a committed mutation.

Check for understanding: if I ask you, “Where should the selected new move live before final confirmation?” the answer is not “in the Pokémon.” It lives in the menu task state, or in a small menu context object owned by that task. That is exactly why the task system matters. FireRed’s task machinery, supported by pokefirered/src/task.c, chunk_cb0bb91a147bfb59 for task creation and chunk_3ffebc0341225936 for per-frame task running, gives you a place to keep temporary state while the visible menu advances frame by frame.

Second bug: lost return callback. This one is less dramatic because it often does not corrupt creature data. Instead it strands the player. The move gets taught correctly, the confirmation text appears, and then the game has nowhere coherent to go. Maybe it returns to the overworld with the wrong callback. Maybe it returns to the summary screen but the summary screen was opened from a bag item, not the party menu. Maybe it fades to black forever because the code performed the data write but did not restore the owner workflow.

Remember the pattern from earlier in the lecture: the menu is usually not the root system. It is a temporary state machine hosted by the runtime shell. FireRed’s main callback architecture lets one subsystem temporarily own the frame loop, and tasks let multiple smaller workflows advance inside that loop. So the return callback is not decorative. It is part of the contract. When you enter the summary screen or a move-learning menu, you need to know who expects control afterward.

The summary screen is a useful contrast here. In pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, the source context shows it pulling together pokemon data, menu helpers, battle constants, item constants, party menu integration, and overworld-related headers. That tells us something architectural even before we inspect every function: this screen sits at a crossroads. It can be reached from party inspection, battle-adjacent contexts, item contexts, trade-related contexts, and field contexts. A clone that hardcodes “summary screen always returns to party menu” will pass the simplest test and fail the moment a healing item, move item, or scripted NPC opens that same view.

So, on the board, I am adding a small box above the state machine: caller context. Inside it: return callback, selected party index, mode, allowed actions. Then I draw an arrow into the menu task. The task does not merely know “I am a menu.” It knows “I am a menu opened for this purpose, and when I am done, this is where I return.”

Here is the clone failure. You build a party summary screen. From the party menu, it works. From the bag, you use a TM-like item, open the creature’s move summary, teach the move, and then the UI returns to the normal party menu instead of the bag’s item-use flow. Now your bag item might not decrement, the script variable that records success might not be set, or the player might be able to reuse the item because the item-use workflow never received the result. The visible symptom is “wrong menu after teaching.” The architectural bug is “lost continuation.”

Notice how this connects to last week’s item material without re-teaching it. We already know item use is dispatched differently depending on field use, battle use, healing behavior, and key item behavior. Today’s point is that once an item dispatches into a party or move menu, the menu must return a result to the dispatcher that called it. The bag is not just a place where the player clicked something. It is the owner of a pending operation.

Third bug: stale selection index. This one is wonderfully subtle, and advanced students should pay attention because this is where many real UI bugs live. A stale selection index happens when the menu records “row 3” as the chosen thing, but the list changes before row 3 is used. In a simple static menu, row index and item identity feel equivalent. In a real RPG menu, they are not.

In the move relearner, the list may be derived from species, level, current moves, known eligibility, and maybe special-case rules. In FireRed, the move relearner state machine overview in pokefirered/src/learn_move.c, chunk_af3598df6f092723, makes clear that there is list setup, scroll handling, state transitions, and teaching flow. The list is not the party data itself. It is a view generated for the current interaction. If your clone stores only selectedListIndex and later rebuilds the list after a cancel, sort, filter, page change, or party data update, selectedListIndex might now refer to a different move.

So I want you to say this back to yourself: a UI index is not a domain identifier. For moves, the domain identifier is the move ID. For party creatures, it may be party slot plus a validation token, or a stable creature pointer if your memory model permits it, or a creature UUID in a modern clone. For items, it is item ID plus pocket or stack context. For maps, it is map group and map number, not the row in a debug list. For scripts, it is script pointer or script label, not the line currently highlighted in a debug viewer.

Concrete example. The relearn list displays Slash, Flamethrower, Fire Spin, and Smokescreen. The player highlights Fire Spin at row 2. Then your code notices the creature already knows Smokescreen and removes duplicates from the visible list before the confirmation screen redraws. If you stored “2,” row 2 may still be Fire Spin, or it may now be something else depending on the transformation. The robust version stores selectedMoveId equals Fire Spin. The index is allowed to be used for cursor rendering. It should not be the durable meaning of the choice.

Now, check for understanding: if a menu lets the player select an item from a generated list, what two things should you usually store? You store the cursor position for presentation, and you store the selected domain value for behavior. Those are different responsibilities. The cursor tells the UI where to draw the highlight. The domain value tells the model what to mutate.

Let us now widen this from move relearning to the minimal clone architecture, because week 19 is not only about this one menu. It is about how menus become the place where all the systems we have studied are forced to cooperate.

Imagine our clone engine with six major runtime systems: map loading, script execution, party data, battle turns, item use, and save persistence. We have spent the course decomposing those systems, and by week 19 I am expecting you to think of them as state machines with explicit contracts, not as blobs of gameplay code.

Map loading has a clear minimal version. A map header or map record identifies a layout, tileset, object events, warp targets, scripts, and music. When the player changes maps, the engine loads the map record, builds the visible tile layer, spawns object events, installs the field callback, and resumes player control. In the full FireRed architecture, earlier course examples from pokefirered/data maps and overworld code gave us the data-driven side of that. In today’s menu lecture, the important connection is this: a menu may suspend the map, but it should not destroy the map’s pending state unless it is explicitly transitioning away. A party menu opened from the field is an overlaying workflow, not a map reload.

Script execution also has a minimal clone version. A script VM has an instruction pointer, variables, flags, message commands, yes or no prompts, give item commands, battle commands, and calls into special functions. When an NPC offers to teach a move, the script may set up the menu and then wait for a result. That wait must not be a blocking while loop. It is a handoff to a callback or task-backed menu. When the menu finishes, it writes a result variable or calls a continuation, and script execution resumes. That is why source chunks like pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d, mention result-like behavior such as setting VAR_0x8004 in the teach flow. The detail is FireRed-specific, but the design lesson is general: UI workflows report outcomes to scripts through explicit state.

Party data is the model we are protecting today. In a minimal clone, each party creature has species, level, experience, stats, current HP, status, moves, PP, held item, friendship, and maybe metadata like original trainer. Summary screens read this data. Move replacement writes this data. Item use may write this data. Save persistence serializes this data. That means party writes must be centralized enough that every workflow respects the same invariants. If teaching a move requires setting the move ID and initializing PP, do not let three menus each do half of that by hand. Give the model an operation like TeachMoveToSlot, even if the actual clone code is small.

Battle turns are another state machine. The player selects a move, the enemy selects a move, priority is resolved, effects apply, fainting and switching are checked, and the turn either continues or exits. The reason I bring battle into a menu lecture is that battle action selection is also UI intent before commit. Selecting Flamethrower from the fight menu is not the same as damage being applied. The selected move is stored as a command. The battle engine later commits it when the turn resolver reaches that battler’s action. This is the same pattern as move relearning: collect intent, validate, commit in the owning system.

Item use, from weeks 17 and 18, now becomes easier to place. We are not going to re-teach pockets, quantities, or item effect dispatch. Instead, carry the prior rule forward: item records and bag state decide what operation is being attempted, but menus often decide the target. A Potion selected from the bag is still only intent until the player picks a party member and the healing operation succeeds. A TM-like item is still only intent until the player picks a creature, confirms the move, maybe selects an old move, confirms deletion, and the party model writes the result. The bag quantity should decrease at the correct commit boundary, not at the first highlight movement.

Save persistence is the final check on whether you designed this cleanly. A save system should serialize committed game state, not transient menu intent. If the player is halfway through a move replacement menu, your clone has a choice: either saving is disabled in that workflow, or saving records enough workflow context to resume safely. FireRed-era games generally constrain when saving is allowed, which simplifies the problem. In a modern clone with autosave, this becomes much more demanding. You must decide whether selectedNewMoveId in a menu task is saveable state or volatile UI state. Most of the time, it should be volatile.

Let me pause and ask a diagnostic question. Suppose a player opens a move relearner menu, selects a new move, reaches “forget an old move,” and then the game autosaves before final confirmation. When the player reloads, should the new move already be learned? No. Not unless the final commit happened before the autosave. If your save file contains the new move at that point, your autosave has exposed a premature write. Save systems are excellent architecture audits because they force you to identify what is real game state and what is pending UI state.

Now I want to connect this back to task-backed menus directly. FireRed’s task loop, with CreateTask in pokefirered/src/task.c, chunk_cb0bb91a147bfb59, and RunTasks in pokefirered/src/task.c, chunk_3ffebc0341225936, is a per-frame dispatcher. That means a task function should do a small amount of work, inspect input or animation completion, update its state, and return. It should not monopolize the CPU waiting for a button. It should not assume text prints instantly. It should not mutate the party just because a window appeared. Every wait becomes a state.

For the lab this week, when you build the move-replacement menu, I want the states to be boringly explicit: list setup, list navigation, confirm teach, select old move, confirm deletion, write data, return callback. If your implementation compresses these into one giant function with nested conditionals, it may still work for the happy path, but it will be harder to test the three bugs we just named. Explicit states let us put assertions at the boundaries. Before write data, assert the party moves are unchanged. In write data, assert the selected move ID and old slot are valid. After return callback, assert the caller receives success or cancel exactly once.

That “exactly once” matters. A menu result should not fire twice because A is still held on the next frame. A commit state should usually transition immediately to a wait-for-fanfare or return state after performing the write. Otherwise RunTasks will call it again next frame, and you may teach the move twice, decrement an item twice, or call the script continuation twice. This is another reason state machines are not just organization. They are protection against repeated per-frame execution.

Small recap before we move on. Premature write is a model-boundary bug. Lost return callback is a control-flow ownership bug. Stale selection index is a view-to-domain identity bug. They all present as menu problems, but they do not have the same fix. The first is fixed by delaying commit. The second is fixed by preserving caller context and returning through an explicit continuation. The third is fixed by storing domain identifiers separately from cursor positions.

And that gives us the larger week 19 thesis. Menus in a creature RPG are not decorative screens around the real game. They are transaction coordinators. They sit between data tables, party structs, item dispatchers, scripts, battles, save files, and the main callback shell. A summary screen is mostly a read-only projection, which is why pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, is such a good source anchor for read-heavy UI. A move relearner is a staged write workflow, which is why pokefirered/src/learn_move.c, chunks chunk_af3598df6f092723 and chunk_e820ee15ae8caa6d, are our anchors for mutation through explicit states. And the task system is the frame-by-frame substrate, which is why pokefirered/src/task.c, chunks chunk_cb0bb91a147bfb59 and chunk_3ffebc0341225936, keep coming back whenever we ask how the game waits without freezing.

In the next stretch, we are going to take that lab state list and turn it into a minimal implementation design. Not full FireRed, not every edge case, but enough structure that the clone can survive cancel, confirm, redraw, and return without corrupting party data.

So let’s now design the clone version. I am going to keep saying “minimal,” but I want you to hear that in the engineering sense, not the toy sense. Minimal does not mean careless. Minimal means we preserve the architectural invariants that make the real system stable.

On the board, I’m drawing six boxes in a row: map, script, party, battle, item, save. Above them I’m drawing one long bar labeled callback and task shell. That long bar matters because every one of these systems has to cooperate with the frame loop. In FireRed terms, that is why the task evidence from pokefirered/src/task.c, chunk_cb0bb91a147bfb59 for CreateTask, and pokefirered/src/task.c, chunk_3ffebc0341225936 for RunTasks, is not just a technical curiosity. It tells us the game’s basic bargain: create a small stateful worker, let the global loop call it repeatedly, and make progress one safe step at a time.

Now, for a clone, we do not need the whole GBA hardware model. We do not need VRAM character blocks today, we do not need exact window tilemaps, and we do not need decompression. But we do need the same control shape. So our clone runtime shell should have a current scene callback, a list of tasks, and a disciplined rule: input is sampled once per frame, tasks run once per frame, rendering reflects state after the update. If you violate that order, the UI begins to feel haunted because a confirm press can be consumed by the wrong state, or a cancellation can redraw the old menu after it has supposedly closed.

Let’s make that concrete. Suppose our move replacement menu has states named setup list, confirm teach, select old move, confirm deletion, write data, and return callback. If the player presses B during confirm teach, that input should move the state to return or back to the relearn list, depending on the design. It should not fall through into select old move. It should not write anything. It should not leave a half-updated label on the screen. That is precisely the kind of control safety that pokefirered/src/learn_move.c, chunk_af3598df6f092723, helps us reason about, because the move relearner is explicitly described there as a state machine with setup, menu, prompt, wait, and transition states. And chunk_e820ee15ae8caa6d is even more directly useful for mutation, because it documents the teach confirmation and replacement flow: cancel goes one way, confirm with an empty slot goes another, confirm without an empty slot proceeds to deletion prompts and old-move selection.

Pause there and check your understanding. If I ask, “Where is the move written?” the answer should not be “when the cursor lands on a new move.” It should not be “when the player opens the move list.” The write belongs in a narrow commit state after all confirmations are complete. The menu can read, preview, redraw, and ask questions for many frames, but mutation should happen at the transaction boundary.

Now let’s connect each system in our minimal clone to that principle.

For map loading, the clone needs a map registry, a current map id, a layout, an object list, and a spawn point. That is enough. We are not rebuilding every map behavior from the original engine today. But we are preserving the architecture we studied earlier in the course: data record first, runtime globals second, callback or task behavior third, visible result fourth. In the clone, loading a map should be a stateful operation: unload old objects, resolve the new map header, instantiate collision and object events, position the player, then return to overworld control. If the map loader is just one giant function, you can still make it work on a modern machine, but you lose the habit that FireRed forces on you: loading is an operation with phases. And those phases are what later allow fades, scripts, warps, object creation, and message boxes to coexist.

For script execution, we need a tiny VM. Not a full scripting language. A tiny VM. Give it an instruction pointer, a current script, a few commands, and a blocked or running state. The commands can be things like show text, give item, check flag, set flag, start battle, open menu, and wait for task. The important part is that the script must be able to yield. A script that opens a menu cannot keep charging forward and mutate the party before the player has answered. This is one of the big through-lines of weeks 17 through 19. Scripts, menus, and battles are not isolated programs. They are cooperating state machines. When an NPC script says, in effect, “Do you want to teach this move?” the script has delegated authority to a menu workflow. The menu later returns a result. Then the script resumes.

And notice how that clarifies the relationship between the move relearner and the overworld. The move relearner is not merely a screen. It is a temporary control regime. The overworld callback gives way, the menu task takes over, the party data is read and maybe written, and then control returns to a callback chosen by the caller. That is why callback discipline from the beginning of the course still matters here in week 19. SetMainCallback2 was not just startup trivia. It was our first signal that the game is organized around replacing the active mode of execution.

For party data, our clone should be stricter than a casual prototype. Define a Creature struct with species, level, experience if you want it, current HP, max HP, moves, PP, status, held item, and maybe personality or nickname if your clone needs identity. Then define a Party as a bounded array plus count. The key rule is this: menus do not own party data. Menus receive a party reference and an index, read from it for display, and write through a small number of explicit mutation functions. The summary screen evidence is useful here. In pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, the include surface alone is revealing: pokemon data, task support, menu helpers, item constants, move constants, party menu integration, battle constants, and related UI modules all converge on the summary screen. That does not mean the summary screen should casually mutate everything. It means it is a read-heavy projection sitting at a crossroads of many systems.

So in our clone, the summary screen should ask questions like: what is the creature’s species, what are its stats, what moves does it know, what item is held, what page am I on, what cursor position is active? It should not independently decide how move replacement works. If it exposes a move management action, that action should launch a separate state machine with a commit state. This is how you avoid the classic student bug where pressing cancel from the old-move menu still deletes the move because the data write was coupled to cursor movement.

Let me put a small example on the board. Imagine Bulbasaur knows Tackle, Growl, Vine Whip, and Sleep Powder. The player chooses to relearn Leech Seed. In setup list, we display eligible moves. In confirm teach, we ask whether to teach Leech Seed. If the player says no, no data changes. If the player says yes and there is no empty slot, we enter select old move. The cursor highlights Tackle. Still no data changes. The player moves the cursor to Growl. Still no data changes. The player presses A. We enter confirm deletion. Still no data changes. The player confirms, and only then do we enter write data. In write data, we replace Growl with Leech Seed, initialize PP appropriately, mark the result as success, and move to return callback. That sequence is the clone-scale version of the flow described in pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d. The important architectural idea is not the exact names of the states. It is that the names force us to separate preview from commit.

Now, battle turns. We are not doing the whole battle engine in this lab design, but we need a minimal turn loop that respects the same architecture. A battle scene should have participants, selected actions, turn order, resolution steps, messages, faint checks, and an exit result. The battle menu is another task-backed state machine. It asks for Fight, Bag, Pokemon, or Run. Fight opens a move menu. The move menu reads the active creature’s moves and PP. Selecting a move creates an action; it does not immediately subtract HP from the opponent unless your battle loop is already in the resolution phase. Again: input selection is not the same as commit.

This is where Pokemon Showdown is useful as a secondary comparison, but not as our main authority. The research packet reminds us to keep Showdown in the course as a comparison point for battle data hooks and deterministic tests, especially through modules like pokemon-showdown/test and pokemon-showdown/data, but FireRed remains our architecture source. So for this clone design, we can borrow the testing sensibility from a simulator: given these creatures, these moves, and this random seed, the battle turn should resolve predictably. But the control shell, the menu handoff, and the callback/task model are being learned from FireRed.

For item use, I am not going to re-teach bag pockets, item records, quantities, field-use versus battle-use, or healing behavior. We did that in the last two weeks. I want you to use that as assumed foundation. The week 19 addition is this: item use from a menu is a transaction with UI states. If the player selects a Potion, chooses a party member, sees “Use on Charmander?”, and then cancels, no quantity should be removed. If the selected target is invalid, no quantity should be removed. If the item succeeds, the effect and the inventory decrement need to be coordinated. The item dispatcher can decide what kind of operation this is; the menu state machine coordinates the player-facing confirmation and target selection.

Here is the clone rule I recommend: each item effect returns a result object, not just a boolean. Something like canUse, requiresTarget, message, mutation function, and maybe nextMode. The menu can ask, “Can this item be used here?” and display a message without mutating. Then after the target is selected and confirmed, the commit state calls the mutation function. That gives us the same safety pattern we used for move replacement. You should be hearing the repetition now. Read freely. Preview freely. Commit narrowly.

For save persistence, the minimal clone needs a serializable game state: current map id, player position, flags, variables, party, inventory, maybe current money, and story progress. Do not save transient menu states as your normal save file. If the player is inside a move replacement prompt, the save should not capture “cursor is on Growl in confirm deletion” unless you are intentionally building suspend-state support. FireRed’s actual save system is more complex than this, and we are not unpacking flash sectors right now. The architecture point is that persistence belongs to stable world state, not to every temporary UI state. The menu state machine is allowed to write to party data, inventory, flags, or variables. Then those stable records can later be saved.

Now let me ask you a harder check-for-understanding question. In the move relearner, suppose the player confirms replacement, the game writes the new move, and then the fanfare or success text plays. If the player mashes B during that success text, should the old move come back? No. Once the commit state has completed, cancel is no longer undo. Cancel may dismiss text or return to the overworld, but it does not reverse the transaction unless you explicitly built an undo system. That distinction matters. Before commit, B means abandon. After commit, B means advance or close. State names make that distinction visible.

And this is also why I want your lab implementations to include state constants that sound like what they do. Do not name everything state zero, state one, state two in your own code, even if you observe numeric states in a decompiled source. Use names like SETUP_LIST, CONFIRM_TEACH, SELECT_OLD_MOVE, CONFIRM_DELETE, WRITE_MOVE, SHOW_RESULT, RETURN. The compiler does not care, but future you does. More importantly, named states make it much easier to audit mutation. You can literally search for writes to party data and ask, “Do these happen only in WRITE_MOVE?” If the answer is yes, your design is inspectable.

Let’s bring this back to the course plan. In week 1, we decomposed the creature RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. At that point those were boxes on a map. In weeks 17 through 19, those boxes became interfaces. Items taught us that a bag is not the item system itself; it is a UI facade over records, quantities, and dispatch. Party UI is teaching us the same lesson from a more dangerous angle. A party screen is not the party model. A summary screen is not the mutation engine. A move relearner is not just a menu. It is a staged coordinator that has permission to write only after the player’s path through the state machine has made that write valid.

That is why this week is positioned after items. If you understand item dispatch, you already understand that a menu choice can become an effect through a table and a function pointer or equivalent dispatch mechanism. Now we are adding a stricter model of user-driven writes. Healing an item target and replacing a move are both party mutations, but move replacement has a deeper confirmation chain. It has more opportunities for cancellation. It has more UI redraws between intention and commit. So it is a better stress test for whether your architecture separates data, presentation, and transaction state.

Let’s do a small board trace across systems. The player talks to an NPC. The map object triggers a script. The script checks a condition, maybe whether the player has a compatible creature or enough currency or a required item. The script opens a menu callback. The menu creates a task. The task enters setup list. The list reads party data. The player chooses a move. The task asks for confirmation. The player confirms. The task sees whether there is an empty move slot. If there is, it writes the move. If not, it asks which old move to delete. After final confirmation, it writes party data. Then it returns a result to the script. The script displays closing text. Later, save persistence serializes the party data. That entire chain is the course thesis in miniature.

And because you are advanced students, I want to be precise about one more issue: where validation lives. The UI can perform friendly validation. It can gray out a move, show “cannot learn,” or block an invalid target. But the mutation function should also validate. Why? Because UI state is not a security boundary, and even in a single-player game, it is not an integrity boundary. Bugs can call functions from unexpected states. Scripts can be edited. Tests can exercise functions directly. So TeachMoveToCreature, or whatever you call it in your clone, should check that the creature exists, the destination slot is valid, the new move id is valid, and the replacement rules are satisfied. The menu’s job is to guide the normal path. The data mutation function’s job is to preserve invariants.

Here is the mental model I want you to use in the workshop. A state machine is not just a way to avoid blocking the frame loop. It is a ledger of promises. In setup list, the promise is “I can display choices without changing data.” In confirm teach, the promise is “I can record intent without changing data.” In select old move, the promise is “I can preview replacement targets without changing data.” In confirm deletion, the promise is “I can ask one final question without changing data.” In write data, the promise changes: “I will perform exactly one validated mutation.” In return callback, the promise is “I will hand control back without re-running the mutation.” If you can state the promise of each state, you can test it.

So when you write tests for the lab, do not only test the happy path. Test cancel from confirm teach. Test cancel from confirm deletion. Test selecting an old move and then backing out. Test an empty slot path. Test a full moveset path. Test that the move list redraws after cursor movement without mutating the party. Test that the write state runs once. If your task runner calls the same state again on the next frame, and your write state appends, decrements, or replaces again, you have found a real architecture bug. The fix is usually to transition immediately after commit or to mark the transaction complete before displaying result text.

One last connection before we move into the implementation sketch. pokefirered/src/learn_move.c, chunk_af3598df6f092723, gives us the overview shape of the move relearner state machine. pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d, gives us the confirmation and replacement branch evidence. pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, reminds us that party-facing menus are connected to many modules but are often read-heavy projections. And pokefirered/src/task.c, chunks chunk_cb0bb91a147bfb59 and chunk_3ffebc0341225936, anchor the per-frame task substrate. If your clone has those four ideas, even in simplified form, it is architecturally aligned with the source we are studying.

So, in the next part of the lecture, I am going to write the actual pseudocode shape for the lab: the task struct fields, the state enum, the callback return path, and the narrow function that performs the move replacement. Keep your attention on where the data is merely read and where it is actually written, because that boundary is the difference between a menu that looks correct and a menu that is correct.

All right, eyes back to the board. I am going to draw this as a small clone architecture, not as a direct port. The goal is not to reproduce every FireRed helper, every window ID, or every sound effect. The goal is to preserve the architecture that matters: a menu is entered by a callback, it creates one or more tasks, those tasks advance through explicit states, the states read party data freely, and the write happens in one narrow, auditable place.

So I write, first, a task payload. Not the engine task object itself, but the data we care about. I’ll call it MoveReplaceTaskData. It has a party index, a selected new move, a cursor for the learnable move list, a cursor for the existing move slots, a return callback, and a state. If I were building this clone in C, I would store some of that in a task data array, because the FireRed task system is built around task IDs and per-task data. If I were building it in TypeScript or C#, I would probably make it an object. That difference does not matter architecturally. What matters is that the task owns progression, not the draw function, and not a loose collection of global booleans.

Now, next to that, I write the states. Setup list. Wait list input. Confirm teach. If empty slot exists, write move. If no empty slot exists, ask whether to delete an old move. Select old move. Confirm deletion. Write replacement. Print success. Return. That is the lab’s spine. And you should immediately recognize the evidence trail. pokefirered/src/learn_move.c, chunk_af3598df6f092723, gives us the source-level justification for treating the move relearner as a menu state machine. pokefirered/src/learn_move.c, chunk_e820ee15ae8caa6d, gives us the specific teach-confirm and old-move replacement branches. We are not guessing that this is a good style because it feels tidy. We are abstracting the shape of a real production Game Boy Advance RPG menu.

Here is the first important design boundary. Rendering the list of moves is not the same as choosing to teach a move. Moving the cursor is not the same as changing the creature. Printing “Should a move be forgotten?” is not the same as forgetting a move. Those may sound obvious when I say them slowly, but a lot of broken menu code violates exactly that boundary. It mutates data as a side effect of entering a prompt, or it deletes the old move before the confirmation prompt has completed, or it stores the replacement in the UI row rather than in the party model. Then cancel handling becomes a mess, because there is no longer a clean old world to return to.

So, for the clone, I would write a function like GetKnownMoves, which reads the four current move slots for the selected party member. I would write HasEmptyMoveSlot, which reads those same slots and returns an index or no index. I would write TeachMoveIntoSlot, which is one of the few functions allowed to write to party data. And I would write ReplaceMoveSlot, which is the other one. Notice that the menu state machine calls read helpers all the time, but it calls write helpers only after the relevant confirmation state has resolved.

Let’s make that concrete. Suppose the player brings a Charizard to a move relearner, and the selected relearnable move is Wing Attack. The creature currently has Flamethrower, Slash, Fly, and Fire Spin. There is no empty slot. The player selects Wing Attack from the list. The menu enters ConfirmTeach. If the player says no, we return to WaitListInput and no party data has changed. If the player says yes, we test for an empty slot. There is none. So we print the trying-to-learn prompt, then the delete-old-move prompt, and then move to SelectOldMove. The player hovers over Slash. Still, nothing has changed. The player cancels. Still, nothing has changed. The player chooses Slash and confirms deletion. Only then do we call ReplaceMoveSlot with partyIndex, slotIndex, and Wing Attack.

Check yourself here. Where is the first mutation? Not when Wing Attack is highlighted. Not when the yes/no box appears. Not when Slash is highlighted. The first mutation is after the second confirmation, at the write state. If you keep that in your head during the lab, the implementation becomes much easier to test.

Now, I want to connect this to the summary screen, because the contrast is useful. pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, shows a module with many includes and many connections into party, item, battle, menu, move, and region-map concerns. But for our purposes today, the summary screen is mainly a read-heavy projection. It displays what the party model already says. That does not mean it is simple. It has pages, input, windows, and callbacks. But architecturally, most of its job is to interpret existing creature data for the player. The move relearner, by contrast, eventually writes creature data. That is why the confirmation branch matters so much more.

I’m going to pause on that distinction because it is one of the big lessons of week nineteen. A menu that only reads can be wrong in presentation and still leave the game state intact. A menu that writes can corrupt the player’s save if it is wrong at the boundary. So, advanced learners, the question is not merely “does the menu work when I press A?” The question is “which states have permission to change persistent game data, and can I enumerate those states?” If you cannot enumerate them, your menu is too implicit.

Now let’s put the task system underneath it. In FireRed, pokefirered/src/task.c, chunk_cb0bb91a147bfb59, anchors the creation side with CreateTask, and pokefirered/src/task.c, chunk_3ffebc0341225936, anchors the per-frame dispatch side with RunTasks. In clone terms, CreateTask gives us a task ID and stores a function pointer or callback. RunTasks calls active tasks every frame. So the move replacement menu does not block the whole game in a while loop waiting for input. It returns every frame. It says, “my current state is WaitListInput; I will examine buttons, maybe update cursor, maybe transition, and then return.” Next frame, RunTasks calls it again.

That is the mental model I want you to use. A menu is not a long procedure. A menu is a little actor that wakes up once per frame, checks its state, does a small amount of work, and goes back to sleep.

Let me write the pseudocode verbally. The task function receives taskId. It looks up data. It switches on data.state. In SetupList, it builds the list menu entries from learnable moves, initializes cursor positions, draws the windows, and then sets state to WaitListInput. In WaitListInput, if up or down is pressed, it moves the cursor. If B is pressed, it sets state to ReturnToCaller. If A is pressed, it records the selected move and sets state to ConfirmTeach. In ConfirmTeach, it waits for the yes/no menu. If no, it goes back to WaitListInput. If yes, it checks for an empty move slot. If an empty slot exists, it records that slot and goes to WriteNewMove. If not, it goes to ConfirmDeleteOldMove. Then ConfirmDeleteOldMove either returns to the list flow or advances to SelectOldMove. SelectOldMove lets the player choose one of the current four moves. ConfirmReplacement asks for final confirmation. WriteReplacement calls the one narrow data-write function. PrintSuccess displays the result. ReturnToCaller destroys windows, destroys the task, and invokes the return callback or restores the previous main callback.

That is the whole lab in one paragraph. It sounds long, but each state is small. And that is the point. We are replacing one hard-to-reason-about menu procedure with several tiny, named moments.

Now, somebody might ask, “Professor, why have both ConfirmDeleteOldMove and ConfirmReplacement? Isn’t that redundant?” Good question. In a polished game, those prompts carry different user meanings. The first asks whether the player is willing to forget a move at all. The second confirms the specific old move. If you collapse them, you can still make a working clone, but you lose a useful cancellation point. In FireRed’s real flow, chunk_e820ee15ae8caa6d from pokefirered/src/learn_move.c gives us that confirm/cancel branching around stopping teaching, choosing which move, and completing the replacement. The exact text and fanfare are not our focus, but the staged commitment is.

Now let us widen the lens, because this week is not only about one move menu. The reason this belongs in a course on creature RPG architecture is that the same pattern appears across the whole game. The map loader is a state machine. The script engine is a state machine. Battle turns are state machines. Item use, as we saw last week, is dispatch plus state. Save persistence is staged state, too, because you do not want partial writes pretending to be complete saves.

For a minimal clone, I would not start with a giant engine. I would start with six small modules that speak to one another through clean contracts. First, map loading. A MapHeader record says which layout to load, which object events to spawn, which scripts are attached, and what music or environment flags apply. We have studied the FireRed map direction earlier in the course, especially the idea that map data records feed runtime map behavior. For a clone, you need just enough of that: a map ID, a tile grid or layout reference, a spawn position, and a list of object events. Loading the map should not run every script immediately. It should build the runtime scene and then hand control to the overworld callback or task layer.

Second, script execution. A script VM can be tiny. It needs a program counter, a current script pointer, a wait mode, and access to variables and flags. When the player talks to an NPC, you start a script. The script may show text, wait for input, give an item, check a flag, or start a battle. Again, it should not be a blocking function that monopolizes the game. It should advance step by step. If it opens a message box, it enters a wait state until the message box finishes. That is the same per-frame discipline as the move relearner.

Third, party data. A creature record needs species, level, experience if you are modeling it, current HP, max HP or stats derived from level, status, moves, PP, and maybe personality or met data if your clone needs it. For the move replacement lab, the important fields are the move slots and PP slots. If you teach a new move and forget an old one, you must decide what happens to PP. In a minimal clone, you can initialize the new move’s PP to its base PP. But the decision belongs in the party-data write helper, not in the drawing code.

Fourth, battle turns. For our clone target, a battle turn can be built as select action, choose move, choose target if necessary, resolve priority, apply damage or effect, check fainting, maybe award experience, and return to command selection or end battle. We have used Pokemon Showdown in this course as a secondary comparison, especially for deterministic battle data and test thinking, but FireRed remains the architectural source. The point today is not Showdown’s full simulator. The point is that a turn engine is another explicit state machine. The menu that chooses a move should produce an action request; the battle engine should resolve it. Do not let the move-selection UI directly subtract enemy HP.

Fifth, item use. We are not going to re-teach item records, bag pockets, quantities, field-use versus battle-use, or healing item behavior, because weeks seventeen and eighteen already did that work. But we do need to weave it forward. Item use taught us the same separation: the bag UI chooses an item, the item-use dispatch decides what kind of effect it has, and the actual mutation happens in effect-specific code. A Potion is not consumed merely because the cursor touches it. It is consumed after the target is valid and the effect succeeds. That should sound familiar now. A move is not replaced merely because the cursor touches an old move. It is replaced after the confirmation path resolves.

Sixth, save persistence. This is where the menu correctness becomes durable. If the move replacement writes incorrect party data, the save system will faithfully preserve the mistake. Save code is not a moral judge. It does not know that the player meant to cancel. It writes the current model. So a clone save system needs a clear boundary: collect persistent data from party, inventory, flags, variables, map position, and maybe play time; serialize it; validate it with a checksum or version if you are being careful; and restore it on load. The menu’s responsibility is to leave the persistent model correct before save ever sees it.

Let’s do a check for understanding. Imagine a player opens the move relearner, selects a new move, chooses an old move to forget, reaches the final confirmation prompt, and then presses B. What data should change? Nothing. What UI state should change? It should return to the old-move selection or to the relevant prompt, depending on your exact design. What should the save system see if the player saves immediately afterward? The original move list. If your answer is “the new move is temporarily stored in the party but maybe undone later,” I want you to reject that for this lab. Temporary selections belong in task data. Durable changes belong in party data.

Now, let me add a bit of sophistication, because at this point in the course you should be ready for it. The task state machine and the domain model state machine are not the same machine. The menu’s state might be ConfirmReplacement. The creature’s domain state is just “has these four moves.” The save system’s state might be “dirty” or “not dirty,” or “save in progress.” The overworld’s state might be “script locked” while the menu is open. These machines interact, but they should not be collapsed into one giant enum. If you find yourself writing a global enum with values like OVERWORLD_MOVE_RELEARNER_CONFIRMING_DELETE_DURING_SCRIPT_AFTER_ITEM_USE, you have lost the architecture.

Instead, compose small machines. The overworld says, “I am paused because a menu callback owns the frame.” The task system says, “I have an active move replacement task.” The move replacement task says, “I am selecting an old move.” The party model says, “I have not changed yet.” When the final write occurs, the party model changes. Then the task returns. Then the overworld script can resume and maybe inspect VAR_0x8004 or an equivalent result variable. That kind of composition is what makes the original codebase teachable even when it is large.

Notice the source evidence again. The move relearner excerpts in pokefirered/src/learn_move.c are not isolated from the engine. The file includes script, task, menu, list menu, event data, text windows, summary screen, graphics, strings, and move constants in chunk_af3598df6f092723. That include list itself is a clue. This menu sits at a crossing point. It is a UI, but it talks to script results. It is about moves, but it must present text. It is a callback-driven screen, but it uses tasks and list menu behavior. The summary screen include list in pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, gives a similar crossing-point signal for party-facing UI. Advanced architecture work often begins by noticing these crossing points and then asking which module is allowed to own which decision.

Now I want you to imagine your lab implementation failing. This is not pessimism; this is design. The first failure mode is cursor-state leakage. You leave the old-move cursor at slot three, exit the menu, reopen it for another creature, and it still points to slot three even when that slot is empty. The fix is to initialize task-local cursor state in SetupList or SetupOldMoveSelection, not rely on stale globals. The second failure mode is cancellation corruption. You write the new move before the final confirmation, and cancel does not restore the old move’s PP correctly. The fix is to delay writes. The third failure mode is callback loss. You enter the menu from an NPC script, but when the menu exits, you return to the wrong screen or leave the script locked forever. The fix is to store and call the correct return callback, or store a script continuation result in the proper place before returning.

That last one deserves a little more time. In a callback architecture, returning from a menu is not just “close window.” It is restoring control to the right owner. Earlier in the course, when we covered AgbMain and SetMainCallback2, we saw the runtime shell that lets major screens take over the frame. This week, the menu uses that same idea at a smaller scale. It enters through a callback, uses tasks for per-frame local behavior, and eventually returns. If your clone uses a scene stack, the equivalent is push MoveRelearnerScene, then pop it. If your clone uses callbacks, the equivalent is save previous callback, set menu callback, then restore. If your clone uses coroutines, the equivalent is yield while the menu runs. The syntax changes. The ownership problem does not.

Let me place a clean version of the lab control flow on the imaginary board. The NPC script calls OpenMoveRelearner with partyIndex and returnScriptContinuation. OpenMoveRelearner sets the main callback to MoveRelearnerInit. MoveRelearnerInit loads graphics, windows, and list data, then creates a task through the clone equivalent of CreateTask. That task is inspired directly by pokefirered/src/task.c, chunk_cb0bb91a147bfb59. Each frame, the engine calls RunTasks, the idea anchored by pokefirered/src/task.c, chunk_3ffebc0341225936. The task advances through the move replacement states. When it finishes, it destroys its UI resources, writes a result flag, and returns to the script continuation. That is the system.

Now, compare that to a naive design. The NPC script calls TeachMove, which opens a menu, loops until input, directly changes party data, prints some text, and then returns. That can work in a command-line prototype, but it fights the architecture of a frame-based RPG. On the Game Boy Advance, and in most real-time game engines, you have animation, music, input polling, window drawing, palette fades, and possibly link-related timing. Blocking loops are poison unless they are carefully bounded inside very low-level engine code. For our clone, using explicit per-frame states keeps us aligned with the source architecture and with the realities of game runtime design.

Let’s bring in map loading briefly, because I promised to connect week nineteen to the course plan rather than treating menus as a side quest. In the first third of the course, we decomposed the creature RPG into runtime shell, maps, scripts, party model, battle, inventory, UI, save, and link systems. In the middle, we traced data records through globals, callbacks, tasks, scripts, structs, and visible behavior. Recently, your homework and quiz work asked you to identify files, structs, functions, constants, data tables, and state variables for major systems. Week nineteen is where that all becomes operational. You are no longer just saying, “there is a menu module.” You are saying, “this menu is a task-backed state machine that reads from party data, writes through a narrow helper, returns through a callback, and may resume a script.” That sentence is architecture.

And this is why the lab is a move replacement menu, not just a generic pause menu. A pause menu can be mostly navigation. A move replacement menu forces you to touch durable creature data. It forces you to handle confirm and cancel. It forces you to distinguish selected-new-move from written-new-move. It forces you to return to a caller. It forces you to think about what should be saved. It is small enough to build, but rich enough to expose the architecture.

Here is another check. Suppose your list menu shows five relearnable moves, and the player scrolls to the fourth. Where should that scroll row live? In task-local UI state. Suppose the player selects the fourth move, Thunder Punch. Where should Thunder Punch live before confirmation? Also in task-local state, perhaps selectedMove. Suppose the player confirms and an empty slot exists. Where does Thunder Punch live after WriteNewMove? In the party creature’s move array. Suppose the player exits and saves. Where does the save system read it from? From the party creature, not from the old menu task. If the task has been destroyed and the move still exists, you designed the ownership correctly.

Now I am going to give you a testing strategy, because advanced architecture without tests becomes performance art. For the lab, write state-transition tests at the level of the task data and party model. You do not need to render pixels to test the core behavior. Start with a creature that has an empty move slot. Initialize the task, choose a move, confirm teach, step until write, and assert that the move appears in the empty slot. Then test cancel at ConfirmTeach and assert no change. Then test a full move set, choose a replacement, cancel at the first delete prompt, and assert no change. Then choose replacement and cancel at final confirmation, assert no change. Then confirm final replacement and assert exactly one slot changed. Also assert that the other moves and their PP remain correct. You are testing the boundary we have been talking about.

If you want to be closer to the FireRed discipline, you can also test the result value. In the source excerpt, chunk_e820ee15ae8caa6d mentions setting VAR_0x8004 to true when the move is successfully taught into an empty slot, in the described confirmation flow. Your clone does not need that exact variable name unless you are imitating the script system closely. But it should have some result channel if the caller needs to know whether teaching succeeded. Maybe it returns MoveTeachResult.Taught, Cancelled, or Failed. Maybe it sets a script variable. The key is that the result channel is separate from the visual prompt text.

Let me say that again differently. Text is not state. The screen saying “1, 2, and... poof!” is not the mutation. The mutation is the write to the party data. The fanfare is not the result. The result is the value observed by the caller and the changed party model. This distinction matters in every system we study. The battle animation is not damage. The bag quantity display is not inventory. The map name popup is not map loading. The save message is not persistence. UI reports state; it should not be mistaken for state.

As we move toward the end of today’s lecture, I want to synthesize week nineteen with the broader clone project. A minimal FireRed-inspired creature RPG can be built by implementing small versions of the major systems, but those systems must communicate in the right direction. Map loading constructs the world. Script execution controls event flow. Party data stores durable creature state. Battle turns consume party and move data to produce combat outcomes. Item use dispatches from inventory selection into effect logic. Save persistence serializes the durable model. And menus sit across all of these, because menus are how the player asks to inspect or mutate the model.

The danger is to treat menus as cosmetic. They are not cosmetic. Menus are player-facing controllers over game state. The party menu chooses a creature. The summary screen projects creature data. The bag menu chooses an item and a target. The move relearner changes move data. The battle command menu creates battle actions. The save menu initiates persistence. So when FireRed builds these experiences on callbacks and tasks, it is not merely an implementation quirk. It is a way to let UI coexist with the rest of the frame-based engine.

Final recap, and then we will stop. Today, we took the source evidence from pokefirered/src/learn_move.c, especially chunk_af3598df6f092723 for the move relearner state-machine framing and chunk_e820ee15ae8caa6d for the teach and replacement branch, and we connected it to the task substrate in pokefirered/src/task.c, chunks chunk_cb0bb91a147bfb59 and chunk_3ffebc0341225936. We used pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, as the contrast case: a party-facing screen with many module connections, but often acting as a read-heavy projection. From there, we designed the lab shape: explicit menu states, task-local selection data, delayed mutation, narrow party write helpers, and a return path to the caller.

The closing idea is this. A correct menu is not one that merely displays the right options. A correct menu preserves the integrity of the game state across every confirmation, cancellation, callback transition, and save boundary. That is why this week belongs at the end of the items, menus, and party UI module. You already know how item data, bag pockets, item dispatch, and field effects work. Now you are learning how a player-facing menu can safely stand between intent and mutation. In your lab, make the state names boring, make the write functions narrow, and make cancellation prove that nothing durable changed. If you can do that for move replacement, you have the pattern you need for summary actions, item targeting, battle commands, script-driven choices, and every other serious menu in this style of RPG.

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